Files
Reservair/reservair.php
T
Martin Slachta 7b3d9f0ece (#3) - templating
2026-06-14 07:16:13 +02:00

153 lines
6.0 KiB
PHP

<?php
use Reservair\Templating\Elements\RsvReservationSummaryElement;
use Reservair\Templating\Elements\RsvReservationActionsElement;
use Reservair\Templating\Elements\RsvResetFormButtonElement;
/**
* 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;
// 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() );
// 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() );
} );
$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);
}