420 lines
14 KiB
PHP
420 lines
14 KiB
PHP
|
|
<?php
|
||
|
|
/**
|
||
|
|
* Seasons admin page.
|
||
|
|
*
|
||
|
|
* Handles the admin interface for managing seasonal pricing.
|
||
|
|
*
|
||
|
|
* @package Magdev\WpBnb\Admin
|
||
|
|
*/
|
||
|
|
|
||
|
|
declare( strict_types=1 );
|
||
|
|
|
||
|
|
namespace Magdev\WpBnb\Admin;
|
||
|
|
|
||
|
|
use Magdev\WpBnb\Pricing\Season;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Seasons admin page class.
|
||
|
|
*/
|
||
|
|
final class Seasons {
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Initialize the admin page.
|
||
|
|
*
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
public static function init(): void {
|
||
|
|
add_action( 'admin_menu', array( self::class, 'register_menu' ) );
|
||
|
|
add_action( 'admin_init', array( self::class, 'handle_actions' ) );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Register the submenu page.
|
||
|
|
*
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
public static function register_menu(): void {
|
||
|
|
add_submenu_page(
|
||
|
|
'wp-bnb',
|
||
|
|
__( 'Seasons', 'wp-bnb' ),
|
||
|
|
__( 'Seasons', 'wp-bnb' ),
|
||
|
|
'manage_options',
|
||
|
|
'wp-bnb-seasons',
|
||
|
|
array( self::class, 'render_page' )
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Handle form actions.
|
||
|
|
*
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
public static function handle_actions(): void {
|
||
|
|
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Just checking page.
|
||
|
|
if ( ! isset( $_GET['page'] ) || 'wp-bnb-seasons' !== $_GET['page'] ) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Handle delete action.
|
||
|
|
if ( isset( $_GET['action'], $_GET['season_id'], $_GET['_wpnonce'] )
|
||
|
|
&& 'delete' === $_GET['action']
|
||
|
|
&& wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'delete_season' )
|
||
|
|
) {
|
||
|
|
$season_id = sanitize_text_field( wp_unslash( $_GET['season_id'] ) );
|
||
|
|
Season::delete( $season_id );
|
||
|
|
|
||
|
|
wp_safe_redirect( admin_url( 'admin.php?page=wp-bnb-seasons&deleted=1' ) );
|
||
|
|
exit;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Handle create default seasons.
|
||
|
|
if ( isset( $_GET['action'], $_GET['_wpnonce'] )
|
||
|
|
&& 'create_defaults' === $_GET['action']
|
||
|
|
&& wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'create_default_seasons' )
|
||
|
|
) {
|
||
|
|
Season::createDefaults();
|
||
|
|
|
||
|
|
wp_safe_redirect( admin_url( 'admin.php?page=wp-bnb-seasons&defaults_created=1' ) );
|
||
|
|
exit;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Handle form submission.
|
||
|
|
if ( isset( $_POST['wp_bnb_season_nonce'] )
|
||
|
|
&& wp_verify_nonce( sanitize_key( $_POST['wp_bnb_season_nonce'] ), 'save_season' )
|
||
|
|
) {
|
||
|
|
self::save_season();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Save a season from form data.
|
||
|
|
*
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
private static function save_season(): void {
|
||
|
|
$data = array(
|
||
|
|
'id' => isset( $_POST['season_id'] ) && '' !== $_POST['season_id']
|
||
|
|
? sanitize_text_field( wp_unslash( $_POST['season_id'] ) )
|
||
|
|
: wp_generate_uuid4(),
|
||
|
|
'name' => isset( $_POST['season_name'] )
|
||
|
|
? sanitize_text_field( wp_unslash( $_POST['season_name'] ) )
|
||
|
|
: '',
|
||
|
|
'start_date' => isset( $_POST['season_start_date'] )
|
||
|
|
? sanitize_text_field( wp_unslash( $_POST['season_start_date'] ) )
|
||
|
|
: '',
|
||
|
|
'end_date' => isset( $_POST['season_end_date'] )
|
||
|
|
? sanitize_text_field( wp_unslash( $_POST['season_end_date'] ) )
|
||
|
|
: '',
|
||
|
|
'modifier' => isset( $_POST['season_modifier'] )
|
||
|
|
? floatval( $_POST['season_modifier'] )
|
||
|
|
: 1.0,
|
||
|
|
'priority' => isset( $_POST['season_priority'] )
|
||
|
|
? absint( $_POST['season_priority'] )
|
||
|
|
: 0,
|
||
|
|
'active' => isset( $_POST['season_active'] ),
|
||
|
|
);
|
||
|
|
|
||
|
|
$season = new Season( $data );
|
||
|
|
Season::save( $season );
|
||
|
|
|
||
|
|
$is_edit = isset( $_POST['season_id'] ) && '' !== $_POST['season_id'];
|
||
|
|
$message = $is_edit ? 'updated' : 'created';
|
||
|
|
|
||
|
|
wp_safe_redirect( admin_url( 'admin.php?page=wp-bnb-seasons&' . $message . '=1' ) );
|
||
|
|
exit;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Render the admin page.
|
||
|
|
*
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
public static function render_page(): void {
|
||
|
|
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display only.
|
||
|
|
$action = isset( $_GET['action'] ) ? sanitize_key( $_GET['action'] ) : 'list';
|
||
|
|
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display only.
|
||
|
|
$season_id = isset( $_GET['season_id'] ) ? sanitize_text_field( wp_unslash( $_GET['season_id'] ) ) : '';
|
||
|
|
|
||
|
|
?>
|
||
|
|
<div class="wrap">
|
||
|
|
<h1 class="wp-heading-inline"><?php esc_html_e( 'Seasonal Pricing', 'wp-bnb' ); ?></h1>
|
||
|
|
|
||
|
|
<?php if ( 'list' === $action || '' === $action ) : ?>
|
||
|
|
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wp-bnb-seasons&action=add' ) ); ?>" class="page-title-action">
|
||
|
|
<?php esc_html_e( 'Add Season', 'wp-bnb' ); ?>
|
||
|
|
</a>
|
||
|
|
<?php endif; ?>
|
||
|
|
|
||
|
|
<hr class="wp-header-end">
|
||
|
|
|
||
|
|
<?php self::render_notices(); ?>
|
||
|
|
|
||
|
|
<?php
|
||
|
|
switch ( $action ) {
|
||
|
|
case 'add':
|
||
|
|
self::render_form();
|
||
|
|
break;
|
||
|
|
case 'edit':
|
||
|
|
self::render_form( $season_id );
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
self::render_list();
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
?>
|
||
|
|
</div>
|
||
|
|
<?php
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Render admin notices.
|
||
|
|
*
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
private static function render_notices(): void {
|
||
|
|
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display only.
|
||
|
|
if ( isset( $_GET['deleted'] ) ) {
|
||
|
|
echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'Season deleted successfully.', 'wp-bnb' ) . '</p></div>';
|
||
|
|
}
|
||
|
|
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display only.
|
||
|
|
if ( isset( $_GET['created'] ) ) {
|
||
|
|
echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'Season created successfully.', 'wp-bnb' ) . '</p></div>';
|
||
|
|
}
|
||
|
|
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display only.
|
||
|
|
if ( isset( $_GET['updated'] ) ) {
|
||
|
|
echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'Season updated successfully.', 'wp-bnb' ) . '</p></div>';
|
||
|
|
}
|
||
|
|
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display only.
|
||
|
|
if ( isset( $_GET['defaults_created'] ) ) {
|
||
|
|
echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'Default seasons created successfully.', 'wp-bnb' ) . '</p></div>';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Render the seasons list.
|
||
|
|
*
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
private static function render_list(): void {
|
||
|
|
$seasons = Season::all();
|
||
|
|
?>
|
||
|
|
<div class="bnb-seasons-description">
|
||
|
|
<p><?php esc_html_e( 'Seasonal pricing allows you to automatically adjust room rates based on the time of year. Define seasons with date ranges and price modifiers.', 'wp-bnb' ); ?></p>
|
||
|
|
<p><?php esc_html_e( 'Seasons with higher priority take precedence when dates overlap (e.g., Winter Holidays overrides Low Season).', 'wp-bnb' ); ?></p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<?php if ( empty( $seasons ) ) : ?>
|
||
|
|
<div class="bnb-no-seasons">
|
||
|
|
<p><?php esc_html_e( 'No seasons have been configured yet.', 'wp-bnb' ); ?></p>
|
||
|
|
<p>
|
||
|
|
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wp-bnb-seasons&action=add' ) ); ?>" class="button button-primary">
|
||
|
|
<?php esc_html_e( 'Add Your First Season', 'wp-bnb' ); ?>
|
||
|
|
</a>
|
||
|
|
<a href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin.php?page=wp-bnb-seasons&action=create_defaults' ), 'create_default_seasons' ) ); ?>" class="button">
|
||
|
|
<?php esc_html_e( 'Create Default Seasons', 'wp-bnb' ); ?>
|
||
|
|
</a>
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
<?php else : ?>
|
||
|
|
<table class="wp-list-table widefat fixed striped">
|
||
|
|
<thead>
|
||
|
|
<tr>
|
||
|
|
<th scope="col" class="column-name"><?php esc_html_e( 'Season Name', 'wp-bnb' ); ?></th>
|
||
|
|
<th scope="col" class="column-dates"><?php esc_html_e( 'Period', 'wp-bnb' ); ?></th>
|
||
|
|
<th scope="col" class="column-modifier"><?php esc_html_e( 'Price Modifier', 'wp-bnb' ); ?></th>
|
||
|
|
<th scope="col" class="column-priority"><?php esc_html_e( 'Priority', 'wp-bnb' ); ?></th>
|
||
|
|
<th scope="col" class="column-status"><?php esc_html_e( 'Status', 'wp-bnb' ); ?></th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
<?php foreach ( $seasons as $season ) : ?>
|
||
|
|
<tr>
|
||
|
|
<td class="column-name">
|
||
|
|
<strong>
|
||
|
|
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wp-bnb-seasons&action=edit&season_id=' . $season->id ) ); ?>">
|
||
|
|
<?php echo esc_html( $season->name ); ?>
|
||
|
|
</a>
|
||
|
|
</strong>
|
||
|
|
<div class="row-actions">
|
||
|
|
<span class="edit">
|
||
|
|
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wp-bnb-seasons&action=edit&season_id=' . $season->id ) ); ?>">
|
||
|
|
<?php esc_html_e( 'Edit', 'wp-bnb' ); ?>
|
||
|
|
</a> |
|
||
|
|
</span>
|
||
|
|
<span class="delete">
|
||
|
|
<a href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin.php?page=wp-bnb-seasons&action=delete&season_id=' . $season->id ), 'delete_season' ) ); ?>"
|
||
|
|
class="submitdelete"
|
||
|
|
onclick="return confirm('<?php esc_attr_e( 'Are you sure you want to delete this season?', 'wp-bnb' ); ?>');">
|
||
|
|
<?php esc_html_e( 'Delete', 'wp-bnb' ); ?>
|
||
|
|
</a>
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
</td>
|
||
|
|
<td class="column-dates">
|
||
|
|
<?php echo esc_html( self::format_date_range( $season->start_date, $season->end_date ) ); ?>
|
||
|
|
</td>
|
||
|
|
<td class="column-modifier">
|
||
|
|
<?php echo esc_html( $season->getModifierLabel() ); ?>
|
||
|
|
<?php if ( $season->modifier !== 1.0 ) : ?>
|
||
|
|
<span class="bnb-modifier-visual" style="color: <?php echo $season->modifier > 1 ? '#d63638' : '#00a32a'; ?>;">
|
||
|
|
(<?php echo esc_html( number_format( $season->modifier, 2 ) ); ?>x)
|
||
|
|
</span>
|
||
|
|
<?php endif; ?>
|
||
|
|
</td>
|
||
|
|
<td class="column-priority">
|
||
|
|
<?php echo esc_html( $season->priority ); ?>
|
||
|
|
</td>
|
||
|
|
<td class="column-status">
|
||
|
|
<?php if ( $season->active ) : ?>
|
||
|
|
<span class="bnb-status-active"><?php esc_html_e( 'Active', 'wp-bnb' ); ?></span>
|
||
|
|
<?php else : ?>
|
||
|
|
<span class="bnb-status-inactive"><?php esc_html_e( 'Inactive', 'wp-bnb' ); ?></span>
|
||
|
|
<?php endif; ?>
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
<?php endforeach; ?>
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
<?php endif; ?>
|
||
|
|
<?php
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Render the season form.
|
||
|
|
*
|
||
|
|
* @param string $season_id Season ID for editing, empty for new.
|
||
|
|
* @return void
|
||
|
|
*/
|
||
|
|
private static function render_form( string $season_id = '' ): void {
|
||
|
|
$season = $season_id ? Season::find( $season_id ) : null;
|
||
|
|
$is_edit = null !== $season;
|
||
|
|
|
||
|
|
if ( $season_id && ! $season ) {
|
||
|
|
echo '<div class="notice notice-error"><p>' . esc_html__( 'Season not found.', 'wp-bnb' ) . '</p></div>';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
?>
|
||
|
|
<form method="post" action="" class="bnb-season-form">
|
||
|
|
<?php wp_nonce_field( 'save_season', 'wp_bnb_season_nonce' ); ?>
|
||
|
|
<input type="hidden" name="season_id" value="<?php echo esc_attr( $season ? $season->id : '' ); ?>">
|
||
|
|
|
||
|
|
<table class="form-table" role="presentation">
|
||
|
|
<tr>
|
||
|
|
<th scope="row">
|
||
|
|
<label for="season_name"><?php esc_html_e( 'Season Name', 'wp-bnb' ); ?></label>
|
||
|
|
</th>
|
||
|
|
<td>
|
||
|
|
<input type="text" name="season_name" id="season_name"
|
||
|
|
value="<?php echo esc_attr( $season ? $season->name : '' ); ?>"
|
||
|
|
class="regular-text" required>
|
||
|
|
<p class="description"><?php esc_html_e( 'A descriptive name for this season (e.g., "High Season", "Winter Holidays").', 'wp-bnb' ); ?></p>
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<th scope="row">
|
||
|
|
<label for="season_start_date"><?php esc_html_e( 'Start Date', 'wp-bnb' ); ?></label>
|
||
|
|
</th>
|
||
|
|
<td>
|
||
|
|
<input type="text" name="season_start_date" id="season_start_date"
|
||
|
|
value="<?php echo esc_attr( $season ? $season->start_date : '' ); ?>"
|
||
|
|
class="small-text" placeholder="MM-DD" pattern="\d{2}-\d{2}" required>
|
||
|
|
<p class="description"><?php esc_html_e( 'Format: MM-DD (e.g., 06-15 for June 15th). Seasons repeat annually.', 'wp-bnb' ); ?></p>
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<th scope="row">
|
||
|
|
<label for="season_end_date"><?php esc_html_e( 'End Date', 'wp-bnb' ); ?></label>
|
||
|
|
</th>
|
||
|
|
<td>
|
||
|
|
<input type="text" name="season_end_date" id="season_end_date"
|
||
|
|
value="<?php echo esc_attr( $season ? $season->end_date : '' ); ?>"
|
||
|
|
class="small-text" placeholder="MM-DD" pattern="\d{2}-\d{2}" required>
|
||
|
|
<p class="description"><?php esc_html_e( 'Format: MM-DD. Seasons can span year boundaries (e.g., 12-20 to 01-06).', 'wp-bnb' ); ?></p>
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<th scope="row">
|
||
|
|
<label for="season_modifier"><?php esc_html_e( 'Price Modifier', 'wp-bnb' ); ?></label>
|
||
|
|
</th>
|
||
|
|
<td>
|
||
|
|
<input type="number" name="season_modifier" id="season_modifier"
|
||
|
|
value="<?php echo esc_attr( $season ? $season->modifier : '1.00' ); ?>"
|
||
|
|
class="small-text" min="0.1" max="3" step="0.01" required>
|
||
|
|
<p class="description">
|
||
|
|
<?php esc_html_e( 'Multiplier for base prices. Examples:', 'wp-bnb' ); ?><br>
|
||
|
|
<?php esc_html_e( '1.00 = Normal price | 1.25 = 25% increase | 0.85 = 15% discount', 'wp-bnb' ); ?>
|
||
|
|
</p>
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<th scope="row">
|
||
|
|
<label for="season_priority"><?php esc_html_e( 'Priority', 'wp-bnb' ); ?></label>
|
||
|
|
</th>
|
||
|
|
<td>
|
||
|
|
<input type="number" name="season_priority" id="season_priority"
|
||
|
|
value="<?php echo esc_attr( $season ? $season->priority : '10' ); ?>"
|
||
|
|
class="small-text" min="0" max="100">
|
||
|
|
<p class="description"><?php esc_html_e( 'Higher priority seasons take precedence when dates overlap.', 'wp-bnb' ); ?></p>
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<th scope="row"><?php esc_html_e( 'Status', 'wp-bnb' ); ?></th>
|
||
|
|
<td>
|
||
|
|
<label>
|
||
|
|
<input type="checkbox" name="season_active" value="1"
|
||
|
|
<?php checked( $season ? $season->active : true ); ?>>
|
||
|
|
<?php esc_html_e( 'Active', 'wp-bnb' ); ?>
|
||
|
|
</label>
|
||
|
|
<p class="description"><?php esc_html_e( 'Inactive seasons are ignored in price calculations.', 'wp-bnb' ); ?></p>
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
</table>
|
||
|
|
|
||
|
|
<p class="submit">
|
||
|
|
<input type="submit" name="submit" class="button button-primary"
|
||
|
|
value="<?php echo $is_edit ? esc_attr__( 'Update Season', 'wp-bnb' ) : esc_attr__( 'Add Season', 'wp-bnb' ); ?>">
|
||
|
|
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wp-bnb-seasons' ) ); ?>" class="button">
|
||
|
|
<?php esc_html_e( 'Cancel', 'wp-bnb' ); ?>
|
||
|
|
</a>
|
||
|
|
</p>
|
||
|
|
</form>
|
||
|
|
<?php
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Format a date range for display.
|
||
|
|
*
|
||
|
|
* @param string $start Start date (MM-DD).
|
||
|
|
* @param string $end End date (MM-DD).
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
private static function format_date_range( string $start, string $end ): string {
|
||
|
|
$months = array(
|
||
|
|
'01' => __( 'Jan', 'wp-bnb' ),
|
||
|
|
'02' => __( 'Feb', 'wp-bnb' ),
|
||
|
|
'03' => __( 'Mar', 'wp-bnb' ),
|
||
|
|
'04' => __( 'Apr', 'wp-bnb' ),
|
||
|
|
'05' => __( 'May', 'wp-bnb' ),
|
||
|
|
'06' => __( 'Jun', 'wp-bnb' ),
|
||
|
|
'07' => __( 'Jul', 'wp-bnb' ),
|
||
|
|
'08' => __( 'Aug', 'wp-bnb' ),
|
||
|
|
'09' => __( 'Sep', 'wp-bnb' ),
|
||
|
|
'10' => __( 'Oct', 'wp-bnb' ),
|
||
|
|
'11' => __( 'Nov', 'wp-bnb' ),
|
||
|
|
'12' => __( 'Dec', 'wp-bnb' ),
|
||
|
|
);
|
||
|
|
|
||
|
|
$format_date = function ( string $date ) use ( $months ): string {
|
||
|
|
$parts = explode( '-', $date );
|
||
|
|
if ( count( $parts ) !== 2 ) {
|
||
|
|
return $date;
|
||
|
|
}
|
||
|
|
$month = $months[ $parts[0] ] ?? $parts[0];
|
||
|
|
$day = ltrim( $parts[1], '0' );
|
||
|
|
return $month . ' ' . $day;
|
||
|
|
};
|
||
|
|
|
||
|
|
return $format_date( $start ) . ' - ' . $format_date( $end );
|
||
|
|
}
|
||
|
|
}
|