170 lines
6.8 KiB
PHP
170 lines
6.8 KiB
PHP
<?php
|
|
|
|
use Reservair\Templating\Elements\RsvReservationSummaryElement;
|
|
use Reservair\Templating\Elements\RsvReservationActionsElement;
|
|
use Reservair\Templating\Elements\RsvResetFormButtonElement;
|
|
use Reservair\Templating\Elements\RsvQrPaymentElement;
|
|
/**
|
|
* Plugin Name: Reservair
|
|
* Description: A reservation and booking system for WordPress. Site visitors browse available time slots and submit reservation requests via a Gutenberg block; administrators manage timetables, services, forms, and reservations from the WordPress admin panel.
|
|
* Version: 0.1.0
|
|
* Requires at least: 6.7
|
|
* Requires PHP: 7.4
|
|
* Author: Martin Slachta
|
|
* License: GPL-2.0-or-later
|
|
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
|
* Text Domain: reservair
|
|
*
|
|
* @package reservair
|
|
*/
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit; // Exit if accessed directly.
|
|
}
|
|
|
|
require_once __DIR__ . '/vendor/autoload.php';
|
|
|
|
require_once __DIR__ . '/admin.php';
|
|
|
|
register_activation_hook( __FILE__, [ 'RsvInstaller', 'install' ] );
|
|
|
|
/**
|
|
* Wires up the plugin's runtime services on `plugins_loaded` instead of at
|
|
* file-include time, so that nothing is instantiated until WordPress (and any
|
|
* plugins we might interact with) is fully loaded.
|
|
*/
|
|
function rsv_bootstrap(): void {
|
|
global $rsv_form_registry, $rsv_template_registry, $rsv_form_price_registry;
|
|
|
|
// Re-grant the custom capability after a plugin *update* (the activation hook
|
|
// only runs on activate). No-op once the stored version matches.
|
|
RsvCapabilities::ensure();
|
|
|
|
RsvEventDispatcher::init( new RsvWordPressEventBus() );
|
|
|
|
RsvGoogleCalendarListener::register();
|
|
RsvEmailListener::register();
|
|
|
|
// Shared element registry — consumed via `global $rsv_form_registry` by the
|
|
// form processor, HTML renderer, and the form-admin pages.
|
|
$rsv_form_registry = new RsvFormElementRegistry();
|
|
$rsv_form_registry->register( 'input-text', new RsvTextFieldElementHandler() );
|
|
$rsv_form_registry->register( 'button', new RsvButtonElementHandler() );
|
|
$rsv_form_registry->register( 'reservation', new RsvFormReservationElementHandler() );
|
|
$rsv_form_registry->register( 'output-reservation-summary', new RsvReservationSummaryElementHandler() );
|
|
$rsv_form_registry->register( 'output-text', new RsvOutputTextElementHandler() );
|
|
|
|
// Price calculators — extensions add per-element calculators via the action.
|
|
add_action( 'rsv-register-price-calculator', function ( RsvFormPriceCalculatorRegistry $reg ): void {
|
|
$reg->register( 'reservation', function ( RsvFormElementDefinition $def, $value ): float {
|
|
if ( ! is_array( $value ) || ! is_array( $value['timetable_reservations'] ?? null ) ) {
|
|
return 0.0;
|
|
}
|
|
|
|
$price_per_block = (float) $def->getAttr( 'price_per_block', 0 );
|
|
|
|
return $price_per_block * count( $value['timetable_reservations'] );
|
|
} );
|
|
} );
|
|
$rsv_form_price_registry = RsvFormPriceCalculatorRegistry::boot();
|
|
|
|
// Template custom-element registry. Extensions register via the action.
|
|
add_action( 'rsv-template-register-custom-elements', function ( \Reservair\Templating\RsvTemplateRegistry $reg ): void {
|
|
$reg->register( 'reservation-summary', new RsvReservationSummaryElement() );
|
|
$reg->register( 'reservation-actions', new RsvReservationActionsElement() );
|
|
$reg->register( 'reset-form-button', new RsvResetFormButtonElement() );
|
|
$reg->register( 'qr-payment', new RsvQrPaymentElement() );
|
|
} );
|
|
$rsv_template_registry = new \Reservair\Templating\RsvTemplateRegistry();
|
|
do_action( 'rsv-template-register-custom-elements', $rsv_template_registry );
|
|
}
|
|
|
|
add_action( 'plugins_loaded', 'rsv_bootstrap' );
|
|
|
|
add_action( 'admin_menu', 'rsv_admin_menu_definition' );
|
|
add_action( 'init', 'rsv_register_block' );
|
|
add_action( 'enqueue_block_assets', 'rsv_enqueue_assets' );
|
|
add_action( 'admin_enqueue_scripts', 'rsv_enqueue_admin_assets' );
|
|
add_action( 'wp_dashboard_setup', 'rsv_add_dashboard_widget' );
|
|
add_action( 'rest_api_init', 'rsv_define_rest_api' );
|
|
|
|
/**
|
|
* Registers the block using the metadata loaded from the `block.json` file.
|
|
* Behind the scenes, it registers also all assets so they can be enqueued
|
|
* through the block editor in the corresponding context.
|
|
*
|
|
* @see https://developer.wordpress.org/reference/functions/register_block_type/
|
|
*/
|
|
function rsv_register_block() {
|
|
register_block_type( __DIR__ . '/build' );
|
|
}
|
|
|
|
function rsv_add_dashboard_widget() {
|
|
wp_add_dashboard_widget(
|
|
'rsv_pending_reservations_widget',
|
|
'Pending Reservation Requests',
|
|
'rsv_pending_reservations_widget_render'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Renders the "Pending Reservation Requests" dashboard widget.
|
|
*/
|
|
function rsv_pending_reservations_widget_render(): void {
|
|
$url = admin_url( 'admin.php?page=reservations-settings' );
|
|
echo '<p>' . esc_html__( 'Manage and review reservation requests from the Reservations admin page.', 'reservair' ) . '</p>';
|
|
echo '<p><a href="' . esc_url( $url ) . '" class="button button-primary">'
|
|
. esc_html__( 'Open Reservations', 'reservair' ) . '</a></p>';
|
|
}
|
|
|
|
function rsv_google_oauth_callback($request) {
|
|
$code = sanitize_text_field($request->get_param('code'));
|
|
|
|
if (!$code) {
|
|
return new WP_REST_Response(['error' => 'No code received'], 400);
|
|
}
|
|
|
|
$service = new RsvGoogleCalendarService();
|
|
if ($service->exchange_code($code)) {
|
|
$service->register_webhook();
|
|
wp_redirect(admin_url('admin.php?page=rsv-google-calendar&connected=1'));
|
|
exit;
|
|
}
|
|
|
|
return new WP_REST_Response(['error' => 'Token exchange failed'], 500);
|
|
}
|
|
|
|
function rsv_google_calendar_webhook(WP_REST_Request $request): WP_REST_Response {
|
|
$headers = $request->get_headers();
|
|
|
|
$channel_id = $headers['x_goog_channel_id'][0] ?? '';
|
|
$resource_state = $headers['x_goog_resource_state'][0] ?? '';
|
|
|
|
if ($channel_id !== get_option('rsv_google_webhook_channel_id')) {
|
|
return new WP_REST_Response(['error' => 'Invalid channel'], 400);
|
|
}
|
|
|
|
// Google sends a 'sync' notification when the webhook is first registered.
|
|
if ($resource_state === 'sync') {
|
|
return new WP_REST_Response(['status' => 'ok'], 200);
|
|
}
|
|
|
|
$gcal = new RsvGoogleCalendarService();
|
|
$actions = $gcal->process_changes();
|
|
$service = new RsvTimetableReservationService();
|
|
|
|
foreach ($actions as $action) {
|
|
try {
|
|
if ($action['action'] === 'accept') {
|
|
$service->accept_by_reservation_id($action['reservation_id']);
|
|
} else {
|
|
$service->refuse_by_reservation_id($action['reservation_id']);
|
|
}
|
|
} catch (\Throwable $e) {
|
|
error_log('Google Calendar sync [' . $action['action'] . ' #' . $action['reservation_id'] . ']: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
return new WP_REST_Response(['status' => 'ok'], 200);
|
|
}
|