140 lines
5.2 KiB
PHP
140 lines
5.2 KiB
PHP
|
|
<?php
|
||
|
|
/**
|
||
|
|
* 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;
|
||
|
|
|
||
|
|
// 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() );
|
||
|
|
}
|
||
|
|
|
||
|
|
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);
|
||
|
|
}
|