2026-06-11 19:03:29 +02:00
< ? php
2026-06-14 07:16:13 +02:00
use Reservair\Templating\Elements\RsvReservationSummaryElement ;
use Reservair\Templating\Elements\RsvReservationActionsElement ;
use Reservair\Templating\Elements\RsvResetFormButtonElement ;
2026-06-11 19:03:29 +02:00
/**
* 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 {
2026-06-14 07:16:13 +02:00
global $rsv_form_registry , $rsv_template_registry ;
2026-06-11 19:03:29 +02:00
// 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 () );
2026-06-14 07:16:13 +02:00
// 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 );
2026-06-11 19:03:29 +02:00
}
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 );
}