Registered modules. */ private array $modules = []; private function __construct() {} public static function instance(): self { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } /** * Register and initialise all plugin modules. */ public function boot(): void { $this->schedule_crons(); // Admin-only modules. if ( is_admin() ) { $this->register( new AdminController() ); } // Frontend & shared modules. $this->register( new ViewTracker() ); $this->register( new ShortcodeHandler() ); $this->register( new BlockRenderer() ); // REST API endpoints — always registered (used by JS tracker too). $this->register( new TrackEndpoint() ); $this->register( new AnalyticsEndpoint() ); /** * Fires after all core PulseViews modules have been booted. * * Pro extensions or third-party add-ons should hook here to register * their own modules via Plugin::instance()->register( $module ). * * @since 1.0.0 */ do_action( 'pulseviews/booted', $this ); } /** * Register a module and call its init() method if present. * * @param object $module Module instance. */ public function register( object $module ): void { $this->modules[ get_class( $module ) ] = $module; if ( method_exists( $module, 'init' ) ) { $module->init(); } } /** * Retrieve a registered module by class name. * * @template T * @param class-string $class Fully-qualified class name. * @return T|null */ public function get( string $class ): ?object { return $this->modules[ $class ] ?? null; } // ── Private helpers ──────────────────────────────────────────────────────── private function schedule_crons(): void { if ( ! wp_next_scheduled( 'pulseviews_daily_aggregate' ) ) { wp_schedule_event( strtotime( 'tomorrow midnight' ), 'daily', 'pulseviews_daily_aggregate' ); } if ( ! wp_next_scheduled( 'pulseviews_cleanup_old_views' ) ) { wp_schedule_event( time() + WEEK_IN_SECONDS, 'weekly', 'pulseviews_cleanup_old_views' ); } // Wire cron callbacks. add_action( 'pulseviews_daily_aggregate', [ new Analytics\Aggregator(), 'run_daily' ] ); add_action( 'pulseviews_cleanup_old_views', [ new Database\ViewsRepository(), 'prune_old_rows' ] ); } }