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 '
' . esc_html__( 'Manage and review reservation requests from the Reservations admin page.', 'reservair' ) . '
'; echo ''; } 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); }