624 lines
20 KiB
PHP
624 lines
20 KiB
PHP
|
|
<?php
|
||
|
|
/**
|
||
|
|
* Service post type.
|
||
|
|
*
|
||
|
|
* Custom post type for BnB additional services.
|
||
|
|
*
|
||
|
|
* @package Magdev\WpBnb\PostTypes
|
||
|
|
*/
|
||
|
|
|
||
|
|
declare( strict_types=1 );
|
||
|
|
|
||
|
|
namespace Magdev\WpBnb\PostTypes;
|
||
|
|
|
||
|
|
use Magdev\WpBnb\Pricing\Calculator;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Service post type class.
|
||
|
|
*/
|
||
|
|
final class Service {
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Post type slug.
|
||
|
|
*
|
||
|
|
* @var string
|
||
|
|
*/
|
||
|
|
public const POST_TYPE = 'bnb_service';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Meta key prefix.
|
||
|
|
*
|
||
|
|
* @var string
|
||
|
|
*/
|
||
|
|
private const META_PREFIX = '_bnb_service_';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Initialize the post type.
|
||
|
|
*
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
public static function init(): void {
|
||
|
|
add_action( 'init', array( self::class, 'register' ) );
|
||
|
|
add_action( 'add_meta_boxes', array( self::class, 'add_meta_boxes' ) );
|
||
|
|
add_action( 'save_post_' . self::POST_TYPE, array( self::class, 'save_meta' ), 10, 2 );
|
||
|
|
add_filter( 'manage_' . self::POST_TYPE . '_posts_columns', array( self::class, 'add_columns' ) );
|
||
|
|
add_action( 'manage_' . self::POST_TYPE . '_posts_custom_column', array( self::class, 'render_column' ), 10, 2 );
|
||
|
|
add_filter( 'manage_edit-' . self::POST_TYPE . '_sortable_columns', array( self::class, 'sortable_columns' ) );
|
||
|
|
add_action( 'restrict_manage_posts', array( self::class, 'add_filters' ) );
|
||
|
|
add_action( 'pre_get_posts', array( self::class, 'filter_query' ) );
|
||
|
|
add_filter( 'enter_title_here', array( self::class, 'change_title_placeholder' ), 10, 2 );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Register the post type.
|
||
|
|
*
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
public static function register(): void {
|
||
|
|
$labels = array(
|
||
|
|
'name' => _x( 'Services', 'post type general name', 'wp-bnb' ),
|
||
|
|
'singular_name' => _x( 'Service', 'post type singular name', 'wp-bnb' ),
|
||
|
|
'menu_name' => _x( 'Services', 'admin menu', 'wp-bnb' ),
|
||
|
|
'name_admin_bar' => _x( 'Service', 'add new on admin bar', 'wp-bnb' ),
|
||
|
|
'add_new' => _x( 'Add New', 'service', 'wp-bnb' ),
|
||
|
|
'add_new_item' => __( 'Add New Service', 'wp-bnb' ),
|
||
|
|
'new_item' => __( 'New Service', 'wp-bnb' ),
|
||
|
|
'edit_item' => __( 'Edit Service', 'wp-bnb' ),
|
||
|
|
'view_item' => __( 'View Service', 'wp-bnb' ),
|
||
|
|
'all_items' => __( 'Services', 'wp-bnb' ),
|
||
|
|
'search_items' => __( 'Search Services', 'wp-bnb' ),
|
||
|
|
'parent_item_colon' => __( 'Parent Services:', 'wp-bnb' ),
|
||
|
|
'not_found' => __( 'No services found.', 'wp-bnb' ),
|
||
|
|
'not_found_in_trash' => __( 'No services found in Trash.', 'wp-bnb' ),
|
||
|
|
'archives' => __( 'Service archives', 'wp-bnb' ),
|
||
|
|
'insert_into_item' => __( 'Insert into service', 'wp-bnb' ),
|
||
|
|
'uploaded_to_this_item' => __( 'Uploaded to this service', 'wp-bnb' ),
|
||
|
|
'filter_items_list' => __( 'Filter services list', 'wp-bnb' ),
|
||
|
|
'items_list_navigation' => __( 'Services list navigation', 'wp-bnb' ),
|
||
|
|
'items_list' => __( 'Services list', 'wp-bnb' ),
|
||
|
|
);
|
||
|
|
|
||
|
|
$args = array(
|
||
|
|
'labels' => $labels,
|
||
|
|
'public' => false,
|
||
|
|
'publicly_queryable' => false,
|
||
|
|
'show_ui' => true,
|
||
|
|
'show_in_menu' => 'wp-bnb',
|
||
|
|
'query_var' => false,
|
||
|
|
'capability_type' => 'post',
|
||
|
|
'has_archive' => false,
|
||
|
|
'hierarchical' => false,
|
||
|
|
'menu_position' => null,
|
||
|
|
'menu_icon' => 'dashicons-plus-alt',
|
||
|
|
'supports' => array( 'title', 'editor', 'thumbnail' ),
|
||
|
|
'show_in_rest' => true,
|
||
|
|
'rest_base' => 'services',
|
||
|
|
'rest_controller_class' => 'WP_REST_Posts_Controller',
|
||
|
|
);
|
||
|
|
|
||
|
|
register_post_type( self::POST_TYPE, $args );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Add meta boxes.
|
||
|
|
*
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
public static function add_meta_boxes(): void {
|
||
|
|
add_meta_box(
|
||
|
|
'bnb_service_pricing',
|
||
|
|
__( 'Pricing', 'wp-bnb' ),
|
||
|
|
array( self::class, 'render_pricing_meta_box' ),
|
||
|
|
self::POST_TYPE,
|
||
|
|
'normal',
|
||
|
|
'high'
|
||
|
|
);
|
||
|
|
|
||
|
|
add_meta_box(
|
||
|
|
'bnb_service_settings',
|
||
|
|
__( 'Service Settings', 'wp-bnb' ),
|
||
|
|
array( self::class, 'render_settings_meta_box' ),
|
||
|
|
self::POST_TYPE,
|
||
|
|
'side',
|
||
|
|
'default'
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Render pricing meta box.
|
||
|
|
*
|
||
|
|
* @param \WP_Post $post Current post object.
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
public static function render_pricing_meta_box( \WP_Post $post ): void {
|
||
|
|
wp_nonce_field( 'bnb_service_meta', 'bnb_service_meta_nonce' );
|
||
|
|
|
||
|
|
$pricing_type = get_post_meta( $post->ID, self::META_PREFIX . 'pricing_type', true ) ?: 'per_booking';
|
||
|
|
$price = get_post_meta( $post->ID, self::META_PREFIX . 'price', true );
|
||
|
|
$currency = get_option( 'wp_bnb_currency', 'CHF' );
|
||
|
|
?>
|
||
|
|
<table class="form-table">
|
||
|
|
<tr>
|
||
|
|
<th scope="row">
|
||
|
|
<label for="bnb_service_pricing_type"><?php esc_html_e( 'Pricing Type', 'wp-bnb' ); ?></label>
|
||
|
|
</th>
|
||
|
|
<td>
|
||
|
|
<fieldset>
|
||
|
|
<label>
|
||
|
|
<input type="radio" name="bnb_service_pricing_type" value="included"
|
||
|
|
<?php checked( $pricing_type, 'included' ); ?>>
|
||
|
|
<?php esc_html_e( 'Included (Free)', 'wp-bnb' ); ?>
|
||
|
|
<p class="description"><?php esc_html_e( 'Service is included at no extra cost.', 'wp-bnb' ); ?></p>
|
||
|
|
</label>
|
||
|
|
<br><br>
|
||
|
|
<label>
|
||
|
|
<input type="radio" name="bnb_service_pricing_type" value="per_booking"
|
||
|
|
<?php checked( $pricing_type, 'per_booking' ); ?>>
|
||
|
|
<?php esc_html_e( 'Per Booking (One-time)', 'wp-bnb' ); ?>
|
||
|
|
<p class="description"><?php esc_html_e( 'Fixed price charged once per booking.', 'wp-bnb' ); ?></p>
|
||
|
|
</label>
|
||
|
|
<br><br>
|
||
|
|
<label>
|
||
|
|
<input type="radio" name="bnb_service_pricing_type" value="per_night"
|
||
|
|
<?php checked( $pricing_type, 'per_night' ); ?>>
|
||
|
|
<?php esc_html_e( 'Per Night', 'wp-bnb' ); ?>
|
||
|
|
<p class="description"><?php esc_html_e( 'Price multiplied by the number of nights.', 'wp-bnb' ); ?></p>
|
||
|
|
</label>
|
||
|
|
</fieldset>
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
<tr id="bnb-service-price-row" <?php echo 'included' === $pricing_type ? 'style="display:none;"' : ''; ?>>
|
||
|
|
<th scope="row">
|
||
|
|
<label for="bnb_service_price"><?php esc_html_e( 'Price', 'wp-bnb' ); ?></label>
|
||
|
|
</th>
|
||
|
|
<td>
|
||
|
|
<div class="bnb-price-input-wrapper">
|
||
|
|
<input type="number" id="bnb_service_price" name="bnb_service_price"
|
||
|
|
value="<?php echo esc_attr( $price ); ?>" class="small-text"
|
||
|
|
min="0" step="0.01">
|
||
|
|
<span class="bnb-price-unit"><?php echo esc_html( $currency ); ?></span>
|
||
|
|
<span id="bnb-service-price-suffix"></span>
|
||
|
|
</div>
|
||
|
|
<p class="description" id="bnb-service-price-description">
|
||
|
|
<?php
|
||
|
|
if ( 'per_night' === $pricing_type ) {
|
||
|
|
esc_html_e( 'This price will be charged per night of the stay.', 'wp-bnb' );
|
||
|
|
} else {
|
||
|
|
esc_html_e( 'This price will be charged once for the booking.', 'wp-bnb' );
|
||
|
|
}
|
||
|
|
?>
|
||
|
|
</p>
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
</table>
|
||
|
|
<?php
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Render settings meta box.
|
||
|
|
*
|
||
|
|
* @param \WP_Post $post Current post object.
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
public static function render_settings_meta_box( \WP_Post $post ): void {
|
||
|
|
$status = get_post_meta( $post->ID, self::META_PREFIX . 'status', true ) ?: 'active';
|
||
|
|
$sort_order = get_post_meta( $post->ID, self::META_PREFIX . 'sort_order', true ) ?: 0;
|
||
|
|
$max_qty = get_post_meta( $post->ID, self::META_PREFIX . 'max_quantity', true ) ?: 1;
|
||
|
|
?>
|
||
|
|
<p>
|
||
|
|
<label for="bnb_service_status"><strong><?php esc_html_e( 'Status', 'wp-bnb' ); ?></strong></label>
|
||
|
|
</p>
|
||
|
|
<select id="bnb_service_status" name="bnb_service_status" class="widefat">
|
||
|
|
<option value="active" <?php selected( $status, 'active' ); ?>><?php esc_html_e( 'Active', 'wp-bnb' ); ?></option>
|
||
|
|
<option value="inactive" <?php selected( $status, 'inactive' ); ?>><?php esc_html_e( 'Inactive', 'wp-bnb' ); ?></option>
|
||
|
|
</select>
|
||
|
|
<p class="description"><?php esc_html_e( 'Inactive services cannot be added to bookings.', 'wp-bnb' ); ?></p>
|
||
|
|
|
||
|
|
<hr>
|
||
|
|
|
||
|
|
<p>
|
||
|
|
<label for="bnb_service_sort_order"><strong><?php esc_html_e( 'Sort Order', 'wp-bnb' ); ?></strong></label>
|
||
|
|
</p>
|
||
|
|
<input type="number" id="bnb_service_sort_order" name="bnb_service_sort_order"
|
||
|
|
value="<?php echo esc_attr( $sort_order ); ?>" class="small-text" min="0">
|
||
|
|
<p class="description"><?php esc_html_e( 'Lower numbers appear first.', 'wp-bnb' ); ?></p>
|
||
|
|
|
||
|
|
<hr>
|
||
|
|
|
||
|
|
<p>
|
||
|
|
<label for="bnb_service_max_quantity"><strong><?php esc_html_e( 'Maximum Quantity', 'wp-bnb' ); ?></strong></label>
|
||
|
|
</p>
|
||
|
|
<input type="number" id="bnb_service_max_quantity" name="bnb_service_max_quantity"
|
||
|
|
value="<?php echo esc_attr( $max_qty ); ?>" class="small-text" min="1" max="99">
|
||
|
|
<p class="description"><?php esc_html_e( 'Maximum times this service can be added to a booking.', 'wp-bnb' ); ?></p>
|
||
|
|
<?php
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Save post meta.
|
||
|
|
*
|
||
|
|
* @param int $post_id Post ID.
|
||
|
|
* @param \WP_Post $post Post object.
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
public static function save_meta( int $post_id, \WP_Post $post ): void {
|
||
|
|
// Verify nonce.
|
||
|
|
if ( ! isset( $_POST['bnb_service_meta_nonce'] ) ||
|
||
|
|
! wp_verify_nonce( sanitize_key( $_POST['bnb_service_meta_nonce'] ), 'bnb_service_meta' ) ) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check autosave.
|
||
|
|
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check permissions.
|
||
|
|
if ( ! current_user_can( 'edit_post', $post_id ) ) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Pricing type.
|
||
|
|
$valid_pricing_types = array( 'included', 'per_booking', 'per_night' );
|
||
|
|
$pricing_type = isset( $_POST['bnb_service_pricing_type'] )
|
||
|
|
? sanitize_text_field( wp_unslash( $_POST['bnb_service_pricing_type'] ) )
|
||
|
|
: 'per_booking';
|
||
|
|
if ( in_array( $pricing_type, $valid_pricing_types, true ) ) {
|
||
|
|
update_post_meta( $post_id, self::META_PREFIX . 'pricing_type', $pricing_type );
|
||
|
|
}
|
||
|
|
|
||
|
|
// Price (not required for 'included').
|
||
|
|
if ( 'included' === $pricing_type ) {
|
||
|
|
update_post_meta( $post_id, self::META_PREFIX . 'price', 0 );
|
||
|
|
} elseif ( isset( $_POST['bnb_service_price'] ) ) {
|
||
|
|
update_post_meta( $post_id, self::META_PREFIX . 'price', floatval( $_POST['bnb_service_price'] ) );
|
||
|
|
}
|
||
|
|
|
||
|
|
// Status.
|
||
|
|
$valid_statuses = array( 'active', 'inactive' );
|
||
|
|
$status = isset( $_POST['bnb_service_status'] )
|
||
|
|
? sanitize_text_field( wp_unslash( $_POST['bnb_service_status'] ) )
|
||
|
|
: 'active';
|
||
|
|
if ( in_array( $status, $valid_statuses, true ) ) {
|
||
|
|
update_post_meta( $post_id, self::META_PREFIX . 'status', $status );
|
||
|
|
}
|
||
|
|
|
||
|
|
// Sort order.
|
||
|
|
if ( isset( $_POST['bnb_service_sort_order'] ) ) {
|
||
|
|
update_post_meta( $post_id, self::META_PREFIX . 'sort_order', absint( $_POST['bnb_service_sort_order'] ) );
|
||
|
|
}
|
||
|
|
|
||
|
|
// Max quantity.
|
||
|
|
if ( isset( $_POST['bnb_service_max_quantity'] ) ) {
|
||
|
|
$max_qty = absint( $_POST['bnb_service_max_quantity'] );
|
||
|
|
$max_qty = max( 1, min( 99, $max_qty ) );
|
||
|
|
update_post_meta( $post_id, self::META_PREFIX . 'max_quantity', $max_qty );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Add custom columns to the post list.
|
||
|
|
*
|
||
|
|
* @param array $columns Existing columns.
|
||
|
|
* @return array
|
||
|
|
*/
|
||
|
|
public static function add_columns( array $columns ): array {
|
||
|
|
$new_columns = array();
|
||
|
|
foreach ( $columns as $key => $value ) {
|
||
|
|
$new_columns[ $key ] = $value;
|
||
|
|
if ( 'title' === $key ) {
|
||
|
|
$new_columns['pricing_type'] = __( 'Pricing Type', 'wp-bnb' );
|
||
|
|
$new_columns['price'] = __( 'Price', 'wp-bnb' );
|
||
|
|
$new_columns['service_status'] = __( 'Status', 'wp-bnb' );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// Remove date column.
|
||
|
|
unset( $new_columns['date'] );
|
||
|
|
return $new_columns;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Render custom column content.
|
||
|
|
*
|
||
|
|
* @param string $column Column name.
|
||
|
|
* @param int $post_id Post ID.
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
public static function render_column( string $column, int $post_id ): void {
|
||
|
|
switch ( $column ) {
|
||
|
|
case 'pricing_type':
|
||
|
|
$pricing_type = get_post_meta( $post_id, self::META_PREFIX . 'pricing_type', true ) ?: 'per_booking';
|
||
|
|
$labels = self::get_pricing_type_labels();
|
||
|
|
$icons = array(
|
||
|
|
'included' => 'yes-alt',
|
||
|
|
'per_booking' => 'tag',
|
||
|
|
'per_night' => 'calendar-alt',
|
||
|
|
);
|
||
|
|
$colors = array(
|
||
|
|
'included' => '#00a32a',
|
||
|
|
'per_booking' => '#135e96',
|
||
|
|
'per_night' => '#dba617',
|
||
|
|
);
|
||
|
|
echo '<span class="dashicons dashicons-' . esc_attr( $icons[ $pricing_type ] ?? 'admin-generic' ) . '" style="color: ' . esc_attr( $colors[ $pricing_type ] ?? '#646970' ) . '; vertical-align: middle; margin-right: 3px;"></span>';
|
||
|
|
echo esc_html( $labels[ $pricing_type ] ?? $pricing_type );
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 'price':
|
||
|
|
$pricing_type = get_post_meta( $post_id, self::META_PREFIX . 'pricing_type', true ) ?: 'per_booking';
|
||
|
|
if ( 'included' === $pricing_type ) {
|
||
|
|
echo '<span class="bnb-service-included">' . esc_html__( 'Included', 'wp-bnb' ) . '</span>';
|
||
|
|
} else {
|
||
|
|
$price = get_post_meta( $post_id, self::META_PREFIX . 'price', true );
|
||
|
|
if ( $price ) {
|
||
|
|
echo esc_html( Calculator::formatPrice( (float) $price ) );
|
||
|
|
if ( 'per_night' === $pricing_type ) {
|
||
|
|
echo ' <small style="color: #646970;">' . esc_html__( '/ night', 'wp-bnb' ) . '</small>';
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
echo '<span class="bnb-no-price">' . esc_html__( 'Not set', 'wp-bnb' ) . '</span>';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 'service_status':
|
||
|
|
$status = get_post_meta( $post_id, self::META_PREFIX . 'status', true ) ?: 'active';
|
||
|
|
$classes = array(
|
||
|
|
'active' => 'bnb-service-status-active',
|
||
|
|
'inactive' => 'bnb-service-status-inactive',
|
||
|
|
);
|
||
|
|
$labels = array(
|
||
|
|
'active' => __( 'Active', 'wp-bnb' ),
|
||
|
|
'inactive' => __( 'Inactive', 'wp-bnb' ),
|
||
|
|
);
|
||
|
|
echo '<span class="bnb-service-status ' . esc_attr( $classes[ $status ] ?? '' ) . '">';
|
||
|
|
echo esc_html( $labels[ $status ] ?? $status );
|
||
|
|
echo '</span>';
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Add sortable columns.
|
||
|
|
*
|
||
|
|
* @param array $columns Existing sortable columns.
|
||
|
|
* @return array
|
||
|
|
*/
|
||
|
|
public static function sortable_columns( array $columns ): array {
|
||
|
|
$columns['price'] = 'price';
|
||
|
|
$columns['service_status'] = 'status';
|
||
|
|
return $columns;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Add filter dropdowns to admin list.
|
||
|
|
*
|
||
|
|
* @param string $post_type Current post type.
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
public static function add_filters( string $post_type ): void {
|
||
|
|
if ( self::POST_TYPE !== $post_type ) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Status filter.
|
||
|
|
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Filter display only.
|
||
|
|
$selected_status = isset( $_GET['service_status'] ) ? sanitize_text_field( wp_unslash( $_GET['service_status'] ) ) : '';
|
||
|
|
?>
|
||
|
|
<select name="service_status">
|
||
|
|
<option value=""><?php esc_html_e( 'All Statuses', 'wp-bnb' ); ?></option>
|
||
|
|
<option value="active" <?php selected( $selected_status, 'active' ); ?>><?php esc_html_e( 'Active', 'wp-bnb' ); ?></option>
|
||
|
|
<option value="inactive" <?php selected( $selected_status, 'inactive' ); ?>><?php esc_html_e( 'Inactive', 'wp-bnb' ); ?></option>
|
||
|
|
</select>
|
||
|
|
<?php
|
||
|
|
|
||
|
|
// Pricing type filter.
|
||
|
|
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Filter display only.
|
||
|
|
$selected_pricing = isset( $_GET['pricing_type'] ) ? sanitize_text_field( wp_unslash( $_GET['pricing_type'] ) ) : '';
|
||
|
|
$labels = self::get_pricing_type_labels();
|
||
|
|
?>
|
||
|
|
<select name="pricing_type">
|
||
|
|
<option value=""><?php esc_html_e( 'All Pricing Types', 'wp-bnb' ); ?></option>
|
||
|
|
<?php foreach ( $labels as $value => $label ) : ?>
|
||
|
|
<option value="<?php echo esc_attr( $value ); ?>" <?php selected( $selected_pricing, $value ); ?>>
|
||
|
|
<?php echo esc_html( $label ); ?>
|
||
|
|
</option>
|
||
|
|
<?php endforeach; ?>
|
||
|
|
</select>
|
||
|
|
<?php
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Filter services by status and pricing type in admin list.
|
||
|
|
*
|
||
|
|
* @param \WP_Query $query Current query.
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
public static function filter_query( \WP_Query $query ): void {
|
||
|
|
if ( ! is_admin() || ! $query->is_main_query() ) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( self::POST_TYPE !== $query->get( 'post_type' ) ) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
$meta_query = array();
|
||
|
|
|
||
|
|
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Filter query only.
|
||
|
|
if ( ! empty( $_GET['service_status'] ) ) {
|
||
|
|
$meta_query[] = array(
|
||
|
|
'key' => self::META_PREFIX . 'status',
|
||
|
|
'value' => sanitize_text_field( wp_unslash( $_GET['service_status'] ) ),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Filter query only.
|
||
|
|
if ( ! empty( $_GET['pricing_type'] ) ) {
|
||
|
|
$meta_query[] = array(
|
||
|
|
'key' => self::META_PREFIX . 'pricing_type',
|
||
|
|
'value' => sanitize_text_field( wp_unslash( $_GET['pricing_type'] ) ),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( ! empty( $meta_query ) ) {
|
||
|
|
$meta_query['relation'] = 'AND';
|
||
|
|
$query->set( 'meta_query', $meta_query );
|
||
|
|
}
|
||
|
|
|
||
|
|
// Handle sorting.
|
||
|
|
$orderby = $query->get( 'orderby' );
|
||
|
|
if ( 'price' === $orderby ) {
|
||
|
|
$query->set( 'meta_key', self::META_PREFIX . 'price' );
|
||
|
|
$query->set( 'orderby', 'meta_value_num' );
|
||
|
|
} elseif ( 'status' === $orderby ) {
|
||
|
|
$query->set( 'meta_key', self::META_PREFIX . 'status' );
|
||
|
|
$query->set( 'orderby', 'meta_value' );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Change title placeholder.
|
||
|
|
*
|
||
|
|
* @param string $placeholder Default placeholder.
|
||
|
|
* @param \WP_Post $post Current post.
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
public static function change_title_placeholder( string $placeholder, \WP_Post $post ): string {
|
||
|
|
if ( self::POST_TYPE === $post->post_type ) {
|
||
|
|
return __( 'Service name', 'wp-bnb' );
|
||
|
|
}
|
||
|
|
return $placeholder;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get pricing type labels.
|
||
|
|
*
|
||
|
|
* @return array<string, string>
|
||
|
|
*/
|
||
|
|
public static function get_pricing_type_labels(): array {
|
||
|
|
return array(
|
||
|
|
'included' => __( 'Included (Free)', 'wp-bnb' ),
|
||
|
|
'per_booking' => __( 'Per Booking', 'wp-bnb' ),
|
||
|
|
'per_night' => __( 'Per Night', 'wp-bnb' ),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get all active services.
|
||
|
|
*
|
||
|
|
* @param array $args Additional query args.
|
||
|
|
* @return array<\WP_Post>
|
||
|
|
*/
|
||
|
|
public static function get_active_services( array $args = array() ): array {
|
||
|
|
$defaults = array(
|
||
|
|
'post_type' => self::POST_TYPE,
|
||
|
|
'post_status' => 'publish',
|
||
|
|
'posts_per_page' => -1,
|
||
|
|
'meta_query' => array(
|
||
|
|
array(
|
||
|
|
'key' => self::META_PREFIX . 'status',
|
||
|
|
'value' => 'active',
|
||
|
|
),
|
||
|
|
),
|
||
|
|
'meta_key' => self::META_PREFIX . 'sort_order',
|
||
|
|
'orderby' => 'meta_value_num',
|
||
|
|
'order' => 'ASC',
|
||
|
|
);
|
||
|
|
|
||
|
|
return get_posts( array_merge( $defaults, $args ) );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get service data for a service.
|
||
|
|
*
|
||
|
|
* @param int $service_id Service post ID.
|
||
|
|
* @return array|null Service data or null if not found.
|
||
|
|
*/
|
||
|
|
public static function get_service_data( int $service_id ): ?array {
|
||
|
|
$service = get_post( $service_id );
|
||
|
|
if ( ! $service || self::POST_TYPE !== $service->post_type ) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
return array(
|
||
|
|
'id' => $service_id,
|
||
|
|
'name' => $service->post_title,
|
||
|
|
'description' => $service->post_content,
|
||
|
|
'pricing_type' => get_post_meta( $service_id, self::META_PREFIX . 'pricing_type', true ) ?: 'per_booking',
|
||
|
|
'price' => (float) get_post_meta( $service_id, self::META_PREFIX . 'price', true ),
|
||
|
|
'status' => get_post_meta( $service_id, self::META_PREFIX . 'status', true ) ?: 'active',
|
||
|
|
'sort_order' => (int) get_post_meta( $service_id, self::META_PREFIX . 'sort_order', true ),
|
||
|
|
'max_quantity' => (int) get_post_meta( $service_id, self::META_PREFIX . 'max_quantity', true ) ?: 1,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Calculate service price for a booking.
|
||
|
|
*
|
||
|
|
* @param int $service_id Service post ID.
|
||
|
|
* @param int $quantity Quantity of the service.
|
||
|
|
* @param int $nights Number of nights (for per-night pricing).
|
||
|
|
* @return float Calculated price.
|
||
|
|
*/
|
||
|
|
public static function calculate_service_price( int $service_id, int $quantity = 1, int $nights = 1 ): float {
|
||
|
|
$data = self::get_service_data( $service_id );
|
||
|
|
if ( ! $data ) {
|
||
|
|
return 0.0;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( 'included' === $data['pricing_type'] ) {
|
||
|
|
return 0.0;
|
||
|
|
}
|
||
|
|
|
||
|
|
$base_price = $data['price'];
|
||
|
|
|
||
|
|
if ( 'per_night' === $data['pricing_type'] ) {
|
||
|
|
return $base_price * $quantity * max( 1, $nights );
|
||
|
|
}
|
||
|
|
|
||
|
|
// per_booking.
|
||
|
|
return $base_price * $quantity;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get services for booking display/selection.
|
||
|
|
*
|
||
|
|
* @return array Array of services with their data.
|
||
|
|
*/
|
||
|
|
public static function get_services_for_booking(): array {
|
||
|
|
$services = self::get_active_services();
|
||
|
|
$result = array();
|
||
|
|
|
||
|
|
foreach ( $services as $service ) {
|
||
|
|
$data = self::get_service_data( $service->ID );
|
||
|
|
if ( $data ) {
|
||
|
|
$data['formatted_price'] = self::format_service_price( $data );
|
||
|
|
$result[] = $data;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return $result;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Format service price for display.
|
||
|
|
*
|
||
|
|
* @param array $service_data Service data array.
|
||
|
|
* @return string Formatted price string.
|
||
|
|
*/
|
||
|
|
public static function format_service_price( array $service_data ): string {
|
||
|
|
if ( 'included' === $service_data['pricing_type'] ) {
|
||
|
|
return __( 'Included', 'wp-bnb' );
|
||
|
|
}
|
||
|
|
|
||
|
|
$formatted = Calculator::formatPrice( $service_data['price'] );
|
||
|
|
|
||
|
|
if ( 'per_night' === $service_data['pricing_type'] ) {
|
||
|
|
/* translators: %s: Formatted price */
|
||
|
|
return sprintf( __( '%s / night', 'wp-bnb' ), $formatted );
|
||
|
|
}
|
||
|
|
|
||
|
|
return $formatted;
|
||
|
|
}
|
||
|
|
}
|