define_admin_hooks(); $this->define_public_hooks(); if ( defined( 'WP_CLI' ) && WP_CLI ) { \WP_CLI::add_command( 'webcal', \WebCalendar\WP\CLI\WebCalCommand::class ); } } /** * Register all of the hooks related to the admin area functionality * of the plugin. */ private function define_admin_hooks(): void { $settings = new \WebCalendar\WP\Admin\Settings(); $settings->init(); $holidays = new \WebCalendar\WP\Admin\Holidays(); $holidays->init(); $remote_cals = new \WebCalendar\WP\Admin\RemoteCalendars(); $remote_cals->init(); $import = new \WebCalendar\WP\Admin\Import(); $import->init(); $shared_cals = new \WebCalendar\WP\Admin\SharedCalendars(); $shared_cals->init(); $layers = new \WebCalendar\WP\Admin\Layers(); $layers->init(); $site_health = new \WebCalendar\WP\Admin\SiteHealth(); $site_health->init(); $profile_prefs = new \WebCalendar\WP\Admin\ProfilePreferences(); $profile_prefs->init(); add_action( 'rest_api_init', [ $this, 'register_rest_routes' ] ); } /** * Register REST API routes. */ public function register_rest_routes(): void { $controllers = [ new \WebCalendar\WP\REST\EventsController(), new \WebCalendar\WP\REST\UsersController(), new \WebCalendar\WP\REST\CategoriesController(), new \WebCalendar\WP\REST\RemoteCalendarsController(), new \WebCalendar\WP\REST\HolidaysController(), new \WebCalendar\WP\REST\FeedController(), new \WebCalendar\WP\REST\SharedCalendarsController(), new \WebCalendar\WP\REST\LayersController(), new \WebCalendar\WP\REST\SubmissionsController(), ]; foreach ( $controllers as $controller ) { $controller->register_routes(); } } /** * Register all of the hooks related to the public-facing functionality * of the plugin. */ private function define_public_hooks(): void { $container = Container::get_instance(); $shortcode = new \WebCalendar\WP\Shortcode(); $shortcode->register(); $submit_shortcode = new \WebCalendar\WP\SubmitEventShortcode(); $submit_shortcode->register(); $single_event = new \WebCalendar\WP\SingleEventPage(); $single_event->init(); $event_search = new \WebCalendar\WP\EventSearchIntegration(); $event_search->init(); /** @var \WebCalendar\WP\ReminderProcessor */ $reminder_processor = $container->get( 'reminder_processor' ); $reminder_processor->register(); /** @var \WebCalendar\WP\EmailQueue */ $email_queue = $container->get( 'email_queue' ); $email_queue->register(); add_action( 'webcal_sync_remote_calendars', [ $this, 'sync_remote_calendars' ] ); if ( ! wp_next_scheduled( 'webcal_sync_remote_calendars' ) ) { /** @var string $interval */ $interval = apply_filters( 'webcal_sync_interval', 'hourly' ); wp_schedule_event( time(), $interval, 'webcal_sync_remote_calendars' ); } add_action( 'wp_enqueue_scripts', [ $this, 'register_assets' ] ); add_action( 'init', [ $this, 'register_blocks' ], 10, 0 ); add_action( 'init', [ $this, 'register_sitemap_provider' ], 10, 0 ); add_action( 'init', [ $this, 'maybe_run_deferred_seeding' ], 10, 0 ); add_action( 'widgets_init', [ $this, 'register_widgets' ], 10, 0 ); // Configurable rate limit from Settings. /** @psalm-suppress HookNotFound -- Applied in Controller::check_rate_limit(). */ add_filter( 'webcal_rate_limit', function ( int $default ): int { $raw = get_option( 'webcal_rate_limit' ); return is_scalar( $raw ) && (int) $raw > 0 ? (int) $raw : $default; } ); /** @psalm-suppress HookNotFound -- Applied in Controller::check_rate_limit(). */ add_filter( 'webcal_rate_limit_period', function ( int $default ): int { $raw = get_option( 'webcal_rate_limit_period' ); return is_scalar( $raw ) && (int) $raw > 0 ? (int) $raw : $default; } ); // Query Monitor integration — expose PDO queries in QM panel \WebCalendar\WP\Integrations\QueryMonitor\QueryMonitorCollector::register(); // Elementor integration — register widgets when Elementor is active add_action( 'plugins_loaded', function (): void { if ( did_action( 'elementor/loaded' ) ) { ( new \WebCalendar\WP\Integrations\Elementor\ElementorIntegration() )->init(); } }, 20, 0 ); // Divi Builder integration — register modules when Divi is active add_action( 'plugins_loaded', function (): void { if ( defined( 'ET_CORE_VERSION' ) ) { ( new \WebCalendar\WP\Integrations\Divi\DiviIntegration() )->init(); } }, 20, 0 ); } /** * Register Gutenberg blocks. */ public function register_blocks(): void { register_block_type( WEBCALENDAR_WP_PATH . 'blocks/calendar' ); register_block_type( WEBCALENDAR_WP_PATH . 'blocks/upcoming-events' ); register_block_type( WEBCALENDAR_WP_PATH . 'blocks/submit-event' ); } /** * Register the event sitemap provider for single event pages. */ public function register_sitemap_provider(): void { if ( ! function_exists( 'wp_register_sitemap_provider' ) ) { return; } wp_register_sitemap_provider( 'webcalendar', new EventSitemapProvider() ); } /** * Register sidebar widgets. */ public function register_widgets(): void { register_widget( MiniCalendarWidget::class ); } /** * Run deferred data seeding if activation could not complete it. * * When DB_HOST cannot be reached during register_activation_hook (e.g. * socket-only MySQL with an unparseable DB_HOST), Activator sets a * webcal_pending_seed transient. We retry on the next admin request, * by which time the connection may be reachable. */ public function maybe_run_deferred_seeding(): void { if ( ! get_transient( 'webcal_pending_seed' ) ) { return; } if ( ! is_admin() ) { return; } try { Activator::seed_default_categories(); Activator::seed_holiday_calendar(); Activator::auto_subscribe_holidays(); delete_transient( 'webcal_pending_seed' ); } catch ( \Throwable $e ) { // Connection still failing; leave the transient in place to retry later. if ( defined( 'WP_DEBUG' ) && WP_DEBUG && defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Diagnostic only fires when WP_DEBUG_LOG is enabled. error_log( 'WebCalendar deferred seed retry failed: ' . $e->getMessage() ); } } } /** * Register frontend assets. */ public function register_assets(): void { $asset_file = WEBCALENDAR_WP_PATH . 'build/frontend.asset.php'; if ( ! file_exists( $asset_file ) ) { // Fallback to index.asset.php if frontend isn't built separately yet $asset_file = WEBCALENDAR_WP_PATH . 'build/index.asset.php'; } if ( ! file_exists( $asset_file ) ) { return; } $assets = require $asset_file; wp_register_script( 'webcal-frontend', WEBCALENDAR_WP_URL . 'build/frontend.js', $assets['dependencies'], $assets['version'], true ); // Load current user's calendar preferences $user_default_access = 'P'; if ( is_user_logged_in() ) { $user_repo = Container::get_instance()->get_user_repository(); $prefs = $user_repo->getPreferences( wp_get_current_user()->user_login ); foreach ( $prefs as $pref ) { if ( $pref->key() === 'default_access' ) { $user_default_access = $pref->value(); break; } } } // Build layer data for frontend color matching $layers_data = []; if ( is_user_logged_in() ) { $layer_service = Container::get_instance()->get_layer_service(); $layers = $layer_service->getLayersForUser( wp_get_current_user()->user_login ); foreach ( $layers as $layer ) { $layer_name = $layer->layerUser(); $wp_layer_user = get_user_by( 'login', $layer->layerUser() ); if ( $wp_layer_user ) { $layer_name = $wp_layer_user->display_name; } else { $resource_repo = Container::get_instance()->get_resource_repository(); $resource = $resource_repo->findByLogin( $layer->layerUser() ); if ( $resource !== null ) { $layer_name = $resource->name(); } } $layers_data[] = [ 'layerUser' => $layer->layerUser(), 'color' => $layer->color(), 'showDuplicates' => $layer->showDuplicates(), 'name' => $layer_name, ]; } } $api_settings = [ 'root' => esc_url_raw( rest_url() ), 'nonce' => wp_create_nonce( 'wp_rest' ), 'timeFormat' => get_option( 'webcal_config_TIME_FORMAT', '12' ), 'mapProvider' => get_option( 'webcal_map_provider', 'osm' ), 'googleMapsApiKey' => get_option( 'webcal_google_maps_api_key', '' ), 'defaultAccess' => $user_default_access, 'layers' => $layers_data, 'canEdit' => is_user_logged_in() && current_user_can( 'webcal_can_edit' ), 'canSubmit' => is_user_logged_in() && current_user_can( 'webcal_can_submit' ), 'adminEventsUrl' => admin_url( 'admin.php?page=webcalendar' ), 'features' => [ 'conflict_detection' => webcal_feature_available( 'conflict_detection' ), 'remote_calendars' => webcal_feature_available( 'remote_calendars' ), 'email_notifications' => webcal_feature_available( 'email_notifications' ), 'multi_user_filter' => webcal_feature_available( 'multi_user_filter' ), 'category_merge' => webcal_feature_available( 'category_merge' ), 'frontend_submission' => webcal_feature_available( 'frontend_submission' ), ], ]; // Localize for shortcode handle and block view script handles. // Blocks auto-generate handles from block.json viewScript; the // shortcode path enqueues webcal-frontend directly. $handles = [ 'webcal-frontend', 'webcalendar-main-view-script', 'webcalendar-upcoming-events-view-script', 'webcalendar-submit-event-view-script', ]; foreach ( $handles as $handle ) { wp_localize_script( $handle, 'webcalApiSettings', $api_settings ); } wp_register_style( 'webcal-frontend', WEBCALENDAR_WP_URL . 'build/frontend.css', [], $assets['version'] ); wp_register_script( 'webcal-mini-calendar', WEBCALENDAR_WP_URL . 'src/mini-calendar.js', [], $assets['version'], true ); } /** * Sync remote calendars via cron. */ public function sync_remote_calendars(): void { $container = Container::get_instance(); try { /** @var \WebCalendar\WP\RemoteCalendarService */ $service = $container->get( 'remote_calendar_service' ); $service->sync_all(); update_option( 'webcal_last_sync_time', (string) time(), false ); } catch ( \Exception $e ) { /** @var \WebCalendarDeps\Psr\Log\LoggerInterface */ $logger = $container->get( 'logger' ); $logger->error( 'Cron Error: ' . $e->getMessage() ); } } /** * Run the plugin. */ public function run(): void { $database = new \WebCalendar\WP\Database\Database(); $database->init(); } }