get( MigrationRunner::class );
$runner->migrate();
/** @var RolesRegistrar $roles Roles registrar service. */
$roles = $container->get( RolesRegistrar::class );
$roles->register();
/** @var SettingsStore $settings Settings store service. */
$settings = $container->get( SettingsStore::class );
$settings->seed_defaults();
/** @var NotificationTemplateSeeder $templates Template seeder service. */
$templates = $container->get( NotificationTemplateSeeder::class );
$templates->seed();
self::seed_automation_recipes();
self::schedule_heartbeat();
}
/**
* Apply pending schema and seed gallery recipes when this request is behind.
*
* Activation is the canonical path (docs/15 M1). Development sites often skip
* a deactivate/reactivate cycle, which left M12a recipe_key columns and the
* gallery rows missing.
*
* @since 0.1.0
*/
private static function maybe_upgrade(): void {
$container = self::container();
/** @var MigrationRunner $runner */
$runner = $container->get( MigrationRunner::class );
if ( $runner->needs_upgrade() ) {
try {
$runner->migrate();
} catch ( \Throwable ) {
// Another request holds the lock, or the schema is not ready yet.
return;
}
}
self::seed_automation_recipes();
}
/**
* Insert missing gallery recipes (idempotent per recipe_key).
*
* @since 0.1.0
*/
private static function seed_automation_recipes(): void {
$mark = 'v2:' . implode( ',', ( new RecipeCatalogue() )->keys() );
$have = get_option( 'bookline_recipe_seed', '' );
if ( is_string( $have ) && $have === $mark ) {
return;
}
try {
/** @var AutomationRecipeSeeder $seeder */
$seeder = self::container()->get( AutomationRecipeSeeder::class );
$saved = $seeder->seed();
if ( count( ( new RecipeCatalogue() )->keys() ) === $saved ) {
update_option( 'bookline_recipe_seed', $mark, false );
}
} catch ( \Throwable ) {
// Schema may still be mid-migration on this request.
return;
}
}
/**
* Deactivation: cancel scheduled actions only.
*
* Action Scheduler is not bundled yet (Milestone 8). When `as_unschedule_all_actions`
* is available we cancel the `bookline` group; otherwise this is a no-op stub.
*/
public static function deactivate(): void {
if ( function_exists( 'as_unschedule_all_actions' ) ) {
as_unschedule_all_actions( null, [], 'bookline' );
}
}
/**
* Early bootstrap — keep this light: no DB, no HTTP, no file scans.
*/
public static function on_plugins_loaded(): void {
self::register_core_services( self::container() );
self::boot_automation_registries( self::container() );
/**
* Fires once core services are registered so add-ons can register.
*
* @param Registry $registry Typed registries (docs/12 §2).
* @since 0.1.0
*/
do_action( 'bookline/register', new Registry( self::container() ) );
self::apply_automation_extension_filters( self::container() );
}
/**
* WordPress init — later feature registration.
*/
public static function on_init(): void {
self::maybe_upgrade();
add_action(
'bookline/holds/expire',
static function (): void {
/** @var ExpireHoldsJob $job */
$job = self::container()->get( ExpireHoldsJob::class );
$job->run();
}
);
add_action(
'bookline/order_intents/cleanup',
static function (): void {
/** @var IntentCleanupJob $job */
$job = self::container()->get( IntentCleanupJob::class );
$job->run();
}
);
add_action(
PaymentReconciliationJob::HOOK,
static function (): void {
/** @var PaymentReconciliationJob $job */
$job = self::container()->get( PaymentReconciliationJob::class );
$job->run();
}
);
/** @var Bus $bus */
$bus = self::container()->get( Bus::class );
/** @var BookingNotificationListener $notify_listener */
$notify_listener = self::container()->get( BookingNotificationListener::class );
$bus->subscribe( BookingCreated::class, static fn( $e ) => $notify_listener->handle( $e ) );
$bus->subscribe( BookingRescheduled::class, static fn( $e ) => $notify_listener->handle( $e ) );
$bus->subscribe( BookingCancelled::class, static fn( $e ) => $notify_listener->handle( $e ) );
$bus->subscribe( BookingStatusChanged::class, static fn( $e ) => $notify_listener->handle( $e ) );
/** @var AutomationEventSubscriber $automation_events */
$automation_events = self::container()->get( AutomationEventSubscriber::class );
$bus->subscribe( BookingCreated::class, static fn( $e ) => $automation_events->handle( $e ) );
$bus->subscribe( BookingRescheduled::class, static fn( $e ) => $automation_events->handle( $e ) );
$bus->subscribe( BookingCancelled::class, static fn( $e ) => $automation_events->handle( $e ) );
$bus->subscribe( BookingStatusChanged::class, static fn( $e ) => $automation_events->handle( $e ) );
$bus->subscribe( ReviewRequested::class, static fn( $e ) => $automation_events->handle( $e ) );
$bus->subscribe( IntentAbandoned::class, static fn( $e ) => $automation_events->handle( $e ) );
$bus->subscribe( IntentRecovered::class, static fn( $e ) => $automation_events->handle( $e ) );
$bus->subscribe( CustomerCreated::class, static fn( $e ) => $automation_events->handle( $e ) );
add_action(
AutomationExecutor::STEP_HOOK,
static function ( int $run_id ): void {
/** @var AutomationExecutor $executor */
$executor = self::container()->get( AutomationExecutor::class );
/** @var \Bookline\Domain\Automation\AutomationRunRepository $runs */
$runs = self::container()->get( \Bookline\Domain\Automation\AutomationRunRepository::class );
/** @var AutomationRepository $autos */
$autos = self::container()->get( AutomationRepository::class );
$run = $runs->find_by_id( $run_id );
if ( null === $run ) {
return;
}
$auto = $autos->find_by_id( $run->automation_id() );
if ( null === $auto ) {
return;
}
$executor->process( $run_id, $auto );
},
10,
1
);
add_action(
RelativeTimeScheduler::HOOK,
static function ( int $automation_id, int $subject_id, string $kind = 'booking' ): void {
/** @var RelativeTimeScheduler $relative */
$relative = self::container()->get( RelativeTimeScheduler::class );
$relative->fire( $automation_id, $subject_id, $kind );
},
10,
3
);
add_action(
NotificationDispatcher::SEND_HOOK,
static function ( int $log_id ): void {
/** @var NotificationDispatcher $dispatcher */
$dispatcher = self::container()->get( NotificationDispatcher::class );
$dispatcher->process_log( $log_id );
},
10,
1
);
add_action(
HealthMonitor::HEARTBEAT_HOOK,
static function (): void {
/** @var HealthMonitor $health */
$health = self::container()->get( HealthMonitor::class );
$health->beat();
}
);
add_action(
'admin_notices',
static function (): void {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
/** @var HealthMonitor $health */
$health = self::container()->get( HealthMonitor::class );
if ( ! $health->is_stale() ) {
return;
}
$snap = $health->snapshot();
$msg = is_string( $snap['fix_guidance'] ?? null ) ? $snap['fix_guidance'] : 'Bookline scheduler heartbeat is stale.';
printf(
'
',
esc_html( $msg )
);
}
);
/** @var HealthMonitor $health_boot */
$health_boot = self::container()->get( HealthMonitor::class );
$health_boot->ensure_scheduled();
/** @var PaymentReconciliationJob $payments_job */
$payments_job = self::container()->get( PaymentReconciliationJob::class );
$payments_job->ensure_scheduled();
/** @var PersonalDataRegistrar $privacy */
$privacy = self::container()->get( PersonalDataRegistrar::class );
$privacy->register();
/**
* Fires after Bookline has finished its init work.
*
* @since 0.1.0
*/
do_action( 'bookline/init' );
}
/**
* Register REST routes under bookline/v1.
*
* @since 0.1.0
*/
public static function register_rest(): void {
/** @var RouteRegistrar $registrar */
$registrar = self::container()->get( RouteRegistrar::class );
$registrar->register();
}
/**
* Register WP-CLI commands.
*
* @since 0.1.0
*/
public static function register_cli(): void {
if ( ! class_exists( 'WP_CLI' ) ) {
return;
}
\WP_CLI::add_command( 'bookline migrate', MigrateCommand::class );
\WP_CLI::add_command( 'bookline seed-demo', SeedDemoCommand::class );
\WP_CLI::add_command( 'bookline scrub-automations', ScrubAutomationsCommand::class );
\WP_CLI::add_command( 'bookline e2e-reset', E2eResetCommand::class );
}
/**
* Public alias used by CLI when services were not yet registered.
*
* @param Container $container Container.
*
* @since 0.1.0
*/
public static function register_services_for_cli( Container $container ): void {
self::register_core_services( $container );
}
/**
* Register core services on the container.
*
* @param Container $container Container.
*/
private static function register_core_services( Container $container ): void {
$container->singleton(
Container::class,
static function () use ( $container ): Container {
return $container;
}
);
$container->singleton(
Encryption::class,
static function (): Encryption {
return new Encryption();
}
);
$container->singleton(
SettingsStore::class,
static function ( Container $c ): SettingsStore {
return new SettingsStore( $c->get( Encryption::class ) );
}
);
$container->singleton(
MigrationRunner::class,
static function (): MigrationRunner {
return new MigrationRunner();
}
);
$container->singleton(
RolesRegistrar::class,
static function (): RolesRegistrar {
return new RolesRegistrar();
}
);
$container->singleton(
NotificationTemplateSeeder::class,
static function (): NotificationTemplateSeeder {
return new NotificationTemplateSeeder();
}
);
$container->singleton(
Clock::class,
static function (): Clock {
return new SystemClock();
}
);
$container->singleton(
BusyProviderRegistry::class,
static function (): BusyProviderRegistry {
$registry = new BusyProviderRegistry();
/**
* Register busy-interval providers (calendar sync, time off).
*
* @param BusyProviderRegistry $registry Registry.
*
* @since 0.30.0
*/
do_action( 'bookline/availability/register_busy', $registry );
$filtered = apply_filters( 'bookline/availability/busy_providers', $registry->all() );
if ( is_array( $filtered ) ) {
$registry->replace( $filtered );
}
return $registry;
}
);
$container->singleton(
TransactionManager::class,
static function (): TransactionManager {
return new TransactionManager();
}
);
$container->singleton(
ServiceRepository::class,
static function (): ServiceRepository {
return new WpdbServiceRepository();
}
);
$container->singleton(
ServiceDurationRepository::class,
static function (): ServiceDurationRepository {
return new WpdbServiceDurationRepository();
}
);
$container->singleton(
ServiceExtraRepository::class,
static function (): ServiceExtraRepository {
return new WpdbServiceExtraRepository();
}
);
$container->singleton(
ServiceCategoryRepository::class,
static function (): ServiceCategoryRepository {
return new WpdbServiceCategoryRepository();
}
);
$container->singleton(
AgentRepository::class,
static function (): AgentRepository {
return new WpdbAgentRepository();
}
);
$container->singleton(
WeeklyScheduleRepository::class,
static function (): WeeklyScheduleRepository {
return new WpdbWeeklyScheduleRepository();
}
);
$container->singleton(
ScheduleOverrideRepository::class,
static function (): ScheduleOverrideRepository {
return new WpdbScheduleOverrideRepository();
}
);
$container->singleton(
LocationRepository::class,
static function (): LocationRepository {
return new WpdbLocationRepository();
}
);
$container->singleton(
CustomerRepository::class,
static function (): CustomerRepository {
return new WpdbCustomerRepository();
}
);
$container->singleton(
BookingRepository::class,
static function (): BookingRepository {
return new WpdbBookingRepository();
}
);
$container->singleton(
OrderRepository::class,
static function (): OrderRepository {
return new WpdbOrderRepository();
}
);
$container->singleton(
PaymentRepository::class,
static function (): PaymentRepository {
return new WpdbPaymentRepository();
}
);
$container->singleton(
CouponRepository::class,
static function (): CouponRepository {
return new WpdbCouponRepository();
}
);
$container->singleton(
AutomationRepository::class,
static function (): AutomationRepository {
return new WpdbAutomationRepository();
}
);
$container->singleton(
AutomationRunRepository::class,
static function (): AutomationRunRepository {
return new WpdbAutomationRunRepository();
}
);
$container->singleton(
AutomationTriggerRegistry::class,
static function (): AutomationTriggerRegistry {
return new AutomationTriggerRegistry();
}
);
$container->singleton(
AutomationConditionRegistry::class,
static function (): AutomationConditionRegistry {
return new AutomationConditionRegistry();
}
);
$container->singleton(
AutomationHttpClient::class,
static function (): AutomationHttpClient {
return new WpRemoteAutomationHttp();
}
);
$container->singleton(
AutomationActionRegistry::class,
static function (): AutomationActionRegistry {
return new AutomationActionRegistry();
}
);
$container->singleton(
RecipeCatalogue::class,
static function (): RecipeCatalogue {
return new RecipeCatalogue();
}
);
$container->singleton(
AutomationRecipeSeeder::class,
static function ( Container $c ): AutomationRecipeSeeder {
return new AutomationRecipeSeeder(
$c->get( AutomationRepository::class ),
$c->get( RecipeCatalogue::class ),
$c->get( Clock::class )
);
}
);
$container->singleton(
AutomationContextFactory::class,
static function ( Container $c ): AutomationContextFactory {
return new AutomationContextFactory( $c->get( Clock::class ) );
}
);
$container->singleton(
SubjectAutomationContextRefresher::class,
static function ( Container $c ): SubjectAutomationContextRefresher {
return new SubjectAutomationContextRefresher(
$c->get( BookingRepository::class ),
$c->get( CustomerRepository::class ),
$c->get( OrderIntentRepository::class ),
$c->get( AutomationContextFactory::class )
);
}
);
$container->singleton(
AutomationExecutor::class,
static function ( Container $c ): AutomationExecutor {
self::boot_automation_registries( $c );
return new AutomationExecutor(
$c->get( AutomationRunRepository::class ),
$c->get( AutomationActionRegistry::class ),
$c->get( AutomationConditionRegistry::class ),
$c->get( Clock::class ),
$c->get( JobScheduler::class ),
$c->get( SubjectAutomationContextRefresher::class )
);
}
);
$container->singleton(
AutomationMatcher::class,
static function ( Container $c ): AutomationMatcher {
self::boot_automation_registries( $c );
$executor = $c->get( AutomationExecutor::class );
$matcher = new AutomationMatcher(
$c->get( AutomationRepository::class ),
$c->get( AutomationRunRepository::class ),
$c->get( AutomationConditionRegistry::class ),
$executor,
$c->get( Clock::class ),
static function ( \Bookline\Domain\Automation\Automation $automation, int $count ): void {
unset( $count );
if ( function_exists( 'do_action' ) ) {
do_action( 'bookline/automation/rate_cap', $automation );
}
}
);
$executor->set_reenter(
static function ( string $event, AutomationContext $ctx ) use ( $matcher ): void {
$fresh = new AutomationContext(
$ctx->trigger_event_id() . ':' . $event,
$event,
$ctx->subject_type(),
$ctx->subject_id(),
$ctx->customer_id(),
$ctx->snapshot_at(),
$ctx->fields(),
$ctx->depth(),
$ctx->ancestor_ids(),
$ctx->is_test()
);
$matcher->handle( $fresh );
}
);
return $matcher;
}
);
$container->singleton(
AutomationCancelService::class,
static function ( Container $c ): AutomationCancelService {
return new AutomationCancelService(
$c->get( AutomationRepository::class ),
$c->get( AutomationRunRepository::class ),
$c->get( AutomationExecutor::class )
);
}
);
$container->singleton(
RelativeTimeScheduler::class,
static function ( Container $c ): RelativeTimeScheduler {
return new RelativeTimeScheduler(
$c->get( AutomationRepository::class ),
$c->get( BookingRepository::class ),
$c->get( CustomerRepository::class ),
$c->get( OrderIntentRepository::class ),
$c->get( AutomationContextFactory::class ),
$c->get( AutomationMatcher::class ),
$c->get( JobScheduler::class ),
$c->get( Clock::class )
);
}
);
$container->singleton(
AutomationEventSubscriber::class,
static function ( Container $c ): AutomationEventSubscriber {
return new AutomationEventSubscriber(
$c->get( BookingRepository::class ),
$c->get( CustomerRepository::class ),
$c->get( OrderIntentRepository::class ),
$c->get( AutomationContextFactory::class ),
$c->get( AutomationMatcher::class ),
$c->get( AutomationCancelService::class ),
$c->get( RelativeTimeScheduler::class )
);
}
);
$container->singleton(
AutomationAdminService::class,
static function ( Container $c ): AutomationAdminService {
self::boot_automation_registries( $c );
return new AutomationAdminService(
$c->get( AutomationRepository::class ),
$c->get( AutomationRunRepository::class ),
$c->get( AutomationTriggerRegistry::class ),
$c->get( AutomationConditionRegistry::class ),
$c->get( AutomationActionRegistry::class ),
$c->get( RecipeCatalogue::class ),
$c->get( AutomationMatcher::class ),
$c->get( AutomationExecutor::class ),
$c->get( AutomationContextFactory::class ),
$c->get( BookingRepository::class ),
$c->get( CustomerRepository::class ),
$c->get( OrderIntentRepository::class ),
$c->get( ReminderRuleRepository::class ),
$c->get( Clock::class ),
$c->get( NamedFeatureGate::class )
);
}
);
$container->singleton(
OrderIntentRepository::class,
static function (): OrderIntentRepository {
return new WpdbOrderIntentRepository();
}
);
$container->singleton(
CartRepository::class,
static function (): CartRepository {
return new WpdbCartRepository();
}
);
$container->singleton(
CartItemRepository::class,
static function (): CartItemRepository {
return new WpdbCartItemRepository();
}
);
$container->singleton(
TransactionIntentRepository::class,
static function (): TransactionIntentRepository {
return new WpdbTransactionIntentRepository();
}
);
$container->singleton(
EntityMetaRepository::class,
static function (): EntityMetaRepository {
return new WpdbEntityMetaRepository();
}
);
$container->singleton(
OrderIntentService::class,
static function ( Container $c ): OrderIntentService {
return new OrderIntentService(
$c->get( OrderIntentRepository::class ),
$c->get( CartRepository::class ),
$c->get( CartItemRepository::class ),
$c->get( TransactionIntentRepository::class ),
$c->get( TransactionManager::class ),
$c->get( SettingsStore::class ),
$c->get( Clock::class ),
$c->get( Bus::class ),
$c->get( LiveTotalsCalculator::class )
);
}
);
$container->singleton(
IntentCleanupJob::class,
static function ( Container $c ): IntentCleanupJob {
return new IntentCleanupJob(
$c->get( OrderIntentRepository::class ),
$c->get( CartRepository::class ),
$c->get( CartItemRepository::class ),
$c->get( BookingRepository::class ),
$c->get( ReleaseHoldHandler::class ),
$c->get( SettingsStore::class ),
$c->get( Clock::class ),
$c->get( Bus::class )
);
}
);
$container->singleton(
OrderIntentPersonalDataExporter::class,
static function ( Container $c ): OrderIntentPersonalDataExporter {
return new OrderIntentPersonalDataExporter( $c->get( OrderIntentRepository::class ) );
}
);
$container->singleton(
OrderIntentPersonalDataEraser::class,
static function ( Container $c ): OrderIntentPersonalDataEraser {
return new OrderIntentPersonalDataEraser(
$c->get( OrderIntentRepository::class ),
$c->get( Clock::class )
);
}
);
$container->singleton(
PersonalDataRegistrar::class,
static function ( Container $c ): PersonalDataRegistrar {
return new PersonalDataRegistrar(
$c->get( OrderIntentPersonalDataExporter::class ),
$c->get( OrderIntentPersonalDataEraser::class )
);
}
);
$container->singleton(
Bus::class,
static function (): Bus {
return new Bus();
}
);
$container->singleton(
JobScheduler::class,
static function (): JobScheduler {
return new ActionSchedulerAdapter();
}
);
$container->singleton(
VariableRegistry::class,
static function (): VariableRegistry {
$registry = new VariableRegistry();
( new CoreVariableRegistrar() )->register( $registry );
if ( ! function_exists( 'apply_filters' ) ) {
return $registry;
}
/**
* Filter the notification variable catalogue.
*
* @param list $variables Variables.
*
* @since 0.1.0
*/
$filtered = apply_filters( 'bookline/notification/variables', $registry->all() );
if ( ! is_array( $filtered ) ) {
return $registry;
}
$out = new VariableRegistry();
foreach ( $filtered as $variable ) {
if ( $variable instanceof VariableDefinition ) {
$out->register( $variable );
}
}
return $out;
}
);
$container->singleton(
TemplateRenderer::class,
static function (): TemplateRenderer {
return new TemplateRenderer();
}
);
$container->singleton(
ThemeTemplateLoader::class,
static function (): ThemeTemplateLoader {
return new ThemeTemplateLoader();
}
);
$container->singleton(
ChannelRegistry::class,
static function ( Container $c ): ChannelRegistry {
$registry = new ChannelRegistry();
$registry->register(
new EmailChannel(
$c->get( TemplateRenderer::class ),
$c->get( SettingsStore::class ),
$c->get( ThemeTemplateLoader::class )
)
);
return $registry;
}
);
$container->singleton(
AutomationNotificationSender::class,
static function ( Container $c ): AutomationNotificationSender {
return new AutomationNotificationSender( $c->get( NotificationDispatcher::class ) );
}
);
$container->singleton(
ListNotificationLogHandler::class,
static function ( Container $c ): ListNotificationLogHandler {
return new ListNotificationLogHandler( $c->get( NotificationLogRepository::class ) );
}
);
$container->singleton(
RetryNotificationLogHandler::class,
static function ( Container $c ): RetryNotificationLogHandler {
return new RetryNotificationLogHandler(
$c->get( NotificationLogRepository::class ),
$c->get( NotificationDispatcher::class )
);
}
);
$container->singleton(
NotificationTemplateRepository::class,
static function (): NotificationTemplateRepository {
return new WpdbNotificationTemplateRepository();
}
);
$container->singleton(
NotificationLogRepository::class,
static function ( Container $c ): NotificationLogRepository {
return new WpdbNotificationLogRepository( $c->get( Clock::class ) );
}
);
$container->singleton(
ReminderRuleRepository::class,
static function (): ReminderRuleRepository {
return new WpdbReminderRuleRepository();
}
);
$container->singleton(
ActivityLogRepository::class,
static function (): ActivityLogRepository {
return new WpdbActivityLogRepository();
}
);
$container->singleton(
NotificationDispatcher::class,
static function ( Container $c ): NotificationDispatcher {
return new NotificationDispatcher(
$c->get( NotificationTemplateRepository::class ),
$c->get( NotificationLogRepository::class ),
$c->get( VariableRegistry::class ),
$c->get( ChannelRegistry::class ),
$c->get( JobScheduler::class ),
$c->get( BookingRepository::class ),
$c->get( CustomerRepository::class ),
$c->get( ServiceRepository::class ),
$c->get( AgentRepository::class ),
$c->get( LocationRepository::class ),
$c->get( SettingsStore::class ),
new IcsGenerator(),
new CalendarLinks(),
$c->get( Clock::class ),
new RetryPolicy()
);
}
);
$container->singleton(
ReminderService::class,
static function ( Container $c ): ReminderService {
return new ReminderService(
$c->get( ReminderRuleRepository::class ),
new ReminderScheduler(),
$c->get( NotificationDispatcher::class ),
$c->get( NotificationLogRepository::class ),
$c->get( JobScheduler::class ),
$c->get( Clock::class )
);
}
);
$container->singleton(
ReminderCanceller::class,
static function ( Container $c ): ReminderCanceller {
return $c->get( ReminderService::class );
}
);
$container->singleton(
HealthMonitor::class,
static function ( Container $c ): HealthMonitor {
return new HealthMonitor(
$c->get( JobScheduler::class ),
$c->get( NotificationLogRepository::class ),
$c->get( Clock::class ),
new SmtpPluginDetector()
);
}
);
$container->singleton(
BookingNotificationListener::class,
static function ( Container $c ): BookingNotificationListener {
return new BookingNotificationListener(
$c->get( NotificationDispatcher::class ),
$c->get( ReminderService::class ),
$c->get( BookingRepository::class )
);
}
);
$container->singleton(
BookingPolicy::class,
static function ( Container $c ): BookingPolicy {
/** @var SettingsStore $settings */
$settings = $c->get( SettingsStore::class );
return new BookingPolicy(
(int) $settings->get( 'booking.cancel_window_hours' ),
(int) $settings->get( 'booking.reschedule_window_hours' )
);
}
);
$container->singleton(
GuestCustomerResolver::class,
static function ( Container $c ): GuestCustomerResolver {
return new GuestCustomerResolver(
$c->get( CustomerRepository::class ),
$c->get( Clock::class )
);
}
);
$container->singleton(
CreateBookingHandler::class,
static function ( Container $c ): CreateBookingHandler {
return new CreateBookingHandler(
$c->get( BookingRepository::class ),
$c->get( OrderRepository::class ),
$c->get( ServiceRepository::class ),
$c->get( GuestCustomerResolver::class ),
$c->get( TransactionManager::class ),
$c->get( Bus::class ),
$c->get( Clock::class )
);
}
);
$container->singleton(
StripeClient::class,
static function (): StripeClient {
return new StripeHttpClient();
}
);
$container->singleton(
StripeGateway::class,
static function ( Container $c ): StripeGateway {
return new StripeGateway(
$c->get( SettingsStore::class ),
$c->get( StripeClient::class )
);
}
);
$container->singleton(
PaymentGatewayRegistry::class,
static function ( Container $c ): PaymentGatewayRegistry {
$registry = new PaymentGatewayRegistry();
$registry->register( $c->get( StripeGateway::class ) );
/**
* Register additional payment gateways.
*
* @param PaymentGatewayRegistry $registry Gateways.
*
* @since 0.1.0
*/
do_action( 'bookline/payments/register', $registry );
return $registry;
}
);
$container->singleton(
WebhookEventDeduper::class,
static function (): WebhookEventDeduper {
return new WebhookEventDeduper();
}
);
$container->singleton(
IntentSnapshotCommandFactory::class,
static function ( Container $c ): IntentSnapshotCommandFactory {
return new IntentSnapshotCommandFactory(
$c->get( AgentRepository::class )
);
}
);
$container->singleton(
CreatePaymentIntentHandler::class,
static function ( Container $c ): CreatePaymentIntentHandler {
return new CreatePaymentIntentHandler(
$c->get( OrderIntentService::class ),
$c->get( PaymentGatewayRegistry::class ),
$c->get( Clock::class )
);
}
);
$container->singleton(
ProcessPaymentWebhookHandler::class,
static function ( Container $c ): ProcessPaymentWebhookHandler {
return new ProcessPaymentWebhookHandler(
$c->get( TransactionIntentRepository::class ),
$c->get( OrderIntentService::class ),
$c->get( CreateBookingHandler::class ),
$c->get( IntentSnapshotCommandFactory::class ),
$c->get( WebhookEventDeduper::class ),
$c->get( Clock::class )
);
}
);
$container->singleton(
RefundPaymentHandler::class,
static function ( Container $c ): RefundPaymentHandler {
return new RefundPaymentHandler(
$c->get( PaymentRepository::class ),
$c->get( PaymentGatewayRegistry::class ),
$c->get( Clock::class )
);
}
);
$container->singleton(
PaymentReconciliationJob::class,
static function ( Container $c ): PaymentReconciliationJob {
return new PaymentReconciliationJob(
$c->get( PaymentRepository::class ),
$c->get( PaymentGatewayRegistry::class ),
$c->get( JobScheduler::class ),
$c->get( Clock::class )
);
}
);
$container->singleton(
CreateSoftHoldHandler::class,
static function ( Container $c ): CreateSoftHoldHandler {
return new CreateSoftHoldHandler(
$c->get( CreateBookingHandler::class ),
$c->get( SettingsStore::class )
);
}
);
$container->singleton(
RescheduleBookingHandler::class,
static function ( Container $c ): RescheduleBookingHandler {
return new RescheduleBookingHandler(
$c->get( BookingRepository::class ),
$c->get( ServiceRepository::class ),
$c->get( TransactionManager::class ),
$c->get( Bus::class ),
$c->get( Clock::class ),
$c->get( BookingPolicy::class ),
$c->get( ReminderCanceller::class )
);
}
);
$container->singleton(
AdminRescheduleBookingHandler::class,
static function ( Container $c ): AdminRescheduleBookingHandler {
return new AdminRescheduleBookingHandler(
$c->get( BookingRepository::class ),
$c->get( RescheduleBookingHandler::class )
);
}
);
$container->singleton(
AbandonedAdminService::class,
static function ( Container $c ): AbandonedAdminService {
return new AbandonedAdminService(
$c->get( OrderIntentRepository::class ),
$c->get( CartRepository::class ),
$c->get( CartItemRepository::class ),
$c->get( ServiceRepository::class ),
$c->get( NotificationLogRepository::class ),
$c->get( NotificationDispatcher::class ),
$c->get( Clock::class )
);
}
);
$container->singleton(
CancelBookingHandler::class,
static function ( Container $c ): CancelBookingHandler {
return new CancelBookingHandler(
$c->get( BookingRepository::class ),
$c->get( TransactionManager::class ),
$c->get( Bus::class ),
$c->get( Clock::class ),
$c->get( BookingPolicy::class ),
$c->get( ReminderCanceller::class )
);
}
);
$container->singleton(
ChangeBookingStatusHandler::class,
static function ( Container $c ): ChangeBookingStatusHandler {
return new ChangeBookingStatusHandler(
$c->get( BookingRepository::class ),
$c->get( TransactionManager::class ),
$c->get( Bus::class ),
$c->get( Clock::class )
);
}
);
$container->singleton(
UpdateBookingNotesHandler::class,
static function ( Container $c ): UpdateBookingNotesHandler {
return new UpdateBookingNotesHandler(
$c->get( BookingRepository::class ),
$c->get( Clock::class )
);
}
);
$container->singleton(
ReleaseHoldHandler::class,
static function ( Container $c ): ReleaseHoldHandler {
return new ReleaseHoldHandler(
$c->get( BookingRepository::class ),
$c->get( TransactionManager::class ),
$c->get( Bus::class ),
$c->get( Clock::class )
);
}
);
$container->singleton(
ExpireHoldsJob::class,
static function ( Container $c ): ExpireHoldsJob {
return new ExpireHoldsJob(
$c->get( BookingRepository::class ),
$c->get( ReleaseHoldHandler::class ),
$c->get( Clock::class )
);
}
);
$container->singleton(
ListBookingsHandler::class,
static function ( Container $c ): ListBookingsHandler {
return new ListBookingsHandler( $c->get( BookingRepository::class ) );
}
);
$container->singleton(
ListCustomersHandler::class,
static function ( Container $c ): ListCustomersHandler {
return new ListCustomersHandler(
$c->get( CustomerRepository::class ),
$c->get( BookingRepository::class )
);
}
);
$container->singleton(
GetCustomerHandler::class,
static function ( Container $c ): GetCustomerHandler {
return new GetCustomerHandler(
$c->get( CustomerRepository::class ),
$c->get( BookingRepository::class ),
$c->get( ServiceRepository::class )
);
}
);
$container->singleton(
ListServicesHandler::class,
static function ( Container $c ): ListServicesHandler {
return new ListServicesHandler(
$c->get( ServiceRepository::class ),
$c->get( ServiceDurationRepository::class )
);
}
);
$container->singleton(
ListAgentsHandler::class,
static function ( Container $c ): ListAgentsHandler {
return new ListAgentsHandler( $c->get( AgentRepository::class ) );
}
);
$container->singleton(
BookingFormRepository::class,
static function (): BookingFormRepository {
return new WpdbBookingFormRepository();
}
);
$container->singleton(
FeatureGate::class,
static function (): FeatureGate {
return new FeatureGate( CatalogueCapacity::limits() );
}
);
$container->singleton(
NamedFeatureGate::class,
static function (): NamedFeatureGate {
if ( function_exists( 'bookline_named_feature_gate' ) ) {
return bookline_named_feature_gate();
}
return new OpenNamedFeatureGate();
}
);
$container->singleton(
ListBookingFormsHandler::class,
static function ( Container $c ): ListBookingFormsHandler {
return new ListBookingFormsHandler( $c->get( BookingFormRepository::class ) );
}
);
$container->singleton(
GetBookingFormHandler::class,
static function ( Container $c ): GetBookingFormHandler {
return new GetBookingFormHandler( $c->get( BookingFormRepository::class ) );
}
);
$container->singleton(
MutateBookingFormHandler::class,
static function ( Container $c ): MutateBookingFormHandler {
return new MutateBookingFormHandler(
$c->get( BookingFormRepository::class ),
$c->get( Clock::class ),
$c->get( NamedFeatureGate::class )
);
}
);
$container->singleton(
CreatePreviewSessionHandler::class,
static function ( Container $c ): CreatePreviewSessionHandler {
return new CreatePreviewSessionHandler(
$c->get( BookingFormRepository::class ),
$c->get( BookingSessionService::class ),
$c->get( MutateBookingFormHandler::class )
);
}
);
$container->singleton(
SystemReportHandler::class,
static function ( Container $c ): SystemReportHandler {
return new SystemReportHandler( $c->get( HealthMonitor::class ) );
}
);
$container->singleton(
LiveTotalsCalculator::class,
static function ( Container $c ): LiveTotalsCalculator {
return new LiveTotalsCalculator(
$c->get( SettingsStore::class ),
$c->get( Clock::class ),
$c->get( ServiceRepository::class ),
$c->get( ServiceDurationRepository::class ),
$c->get( ServiceExtraRepository::class ),
$c->get( CouponRepository::class ),
new PriceCalculator(),
$c->get( NamedFeatureGate::class )
);
}
);
$container->singleton(
MutateCouponHandler::class,
static function ( Container $c ): MutateCouponHandler {
return new MutateCouponHandler(
$c->get( CouponRepository::class ),
$c->get( Clock::class ),
$c->get( NamedFeatureGate::class )
);
}
);
$container->singleton(
MutateServiceHandler::class,
static function ( Container $c ): MutateServiceHandler {
return new MutateServiceHandler(
$c->get( ServiceRepository::class ),
$c->get( Clock::class ),
$c->get( NamedFeatureGate::class )
);
}
);
$container->singleton(
MutateServiceDurationHandler::class,
static function ( Container $c ): MutateServiceDurationHandler {
return new MutateServiceDurationHandler(
$c->get( ServiceDurationRepository::class ),
$c->get( ServiceRepository::class )
);
}
);
$container->singleton(
MutateServiceExtraHandler::class,
static function ( Container $c ): MutateServiceExtraHandler {
return new MutateServiceExtraHandler(
$c->get( ServiceExtraRepository::class ),
$c->get( Clock::class ),
$c->get( NamedFeatureGate::class )
);
}
);
$container->singleton(
MutateAgentHandler::class,
static function ( Container $c ): MutateAgentHandler {
return new MutateAgentHandler(
$c->get( AgentRepository::class ),
$c->get( Clock::class )
);
}
);
$container->singleton(
MutateLocationHandler::class,
static function ( Container $c ): MutateLocationHandler {
return new MutateLocationHandler(
$c->get( LocationRepository::class ),
$c->get( Clock::class )
);
}
);
$container->singleton(
MutateCustomerHandler::class,
static function ( Container $c ): MutateCustomerHandler {
return new MutateCustomerHandler(
$c->get( CustomerRepository::class ),
$c->get( Clock::class ),
$c->get( Bus::class )
);
}
);
$container->singleton(
WizardService::class,
static function ( Container $c ): WizardService {
return new WizardService(
$c->get( SettingsStore::class ),
$c->get( MutateServiceHandler::class ),
$c->get( MutateServiceDurationHandler::class ),
$c->get( MutateAgentHandler::class ),
$c->get( AgentRepository::class ),
$c->get( WeeklyScheduleRepository::class ),
$c->get( MutateBookingFormHandler::class )
);
}
);
$container->singleton(
ListNotificationTemplatesHandler::class,
static function ( Container $c ): ListNotificationTemplatesHandler {
return new ListNotificationTemplatesHandler(
$c->get( NotificationTemplateRepository::class )
);
}
);
$container->singleton(
NotificationAdminService::class,
static function ( Container $c ): NotificationAdminService {
return new NotificationAdminService(
$c->get( NotificationTemplateRepository::class ),
$c->get( VariableRegistry::class ),
$c->get( TemplateRenderer::class ),
$c->get( NotificationLogRepository::class ),
$c->get( BookingRepository::class ),
$c->get( NotificationDispatcher::class ),
$c->get( Clock::class )
);
}
);
$container->singleton(
ReminderAdminService::class,
static function ( Container $c ): ReminderAdminService {
return new ReminderAdminService(
$c->get( ReminderRuleRepository::class ),
$c->get( Clock::class ),
$c->get( NamedFeatureGate::class )
);
}
);
$container->singleton(
FinancialReportsService::class,
static function ( Container $c ): FinancialReportsService {
return new FinancialReportsService(
$c->get( ListBookingsHandler::class ),
$c->get( NamedFeatureGate::class )
);
}
);
$container->singleton(
AdminSearchHandler::class,
static function ( Container $c ): AdminSearchHandler {
return new AdminSearchHandler(
$c->get( ListCustomersHandler::class ),
$c->get( ListServicesHandler::class ),
$c->get( ListAgentsHandler::class ),
$c->get( ListBookingsHandler::class ),
$c->get( SettingsStore::class ),
$c->get( Clock::class )
);
}
);
$container->singleton(
CreateAdminBookingHandler::class,
static function ( Container $c ): CreateAdminBookingHandler {
return new CreateAdminBookingHandler(
$c->get( CreateBookingHandler::class ),
$c->get( ServiceRepository::class ),
$c->get( ServiceDurationRepository::class ),
$c->get( SettingsStore::class )
);
}
);
$container->singleton(
AccessScopeFactory::class,
static function ( Container $c ): AccessScopeFactory {
return new AccessScopeFactory( $c->get( AgentRepository::class ) );
}
);
$container->singleton(
RateLimiter::class,
static function (): RateLimiter {
return new RateLimiter();
}
);
$container->singleton(
CustomerSessionService::class,
static function ( Container $c ): CustomerSessionService {
return new CustomerSessionService( $c->get( Clock::class ) );
}
);
$container->singleton(
PaymentStepRuleSet::class,
static function (): PaymentStepRuleSet {
return CoreStepBootstrap::payment_rules();
}
);
$container->singleton(
StepRegistry::class,
static function ( Container $c ): StepRegistry {
return CoreStepBootstrap::step_registry(
$c->get( PaymentStepRuleSet::class )
);
}
);
$container->singleton(
StepEngine::class,
static function ( Container $c ): StepEngine {
return new StepEngine(
$c->get( StepRegistry::class )
);
}
);
$container->singleton(
BookingFormScope::class,
static function ( Container $c ): BookingFormScope {
return new BookingFormScope(
$c->get( BookingFormRepository::class ),
$c->get( ServiceRepository::class ),
$c->get( AgentRepository::class ),
$c->get( LocationRepository::class ),
);
}
);
$container->singleton(
BookingSessionService::class,
static function ( Container $c ): BookingSessionService {
return new BookingSessionService(
$c->get( OrderIntentService::class ),
$c->get( StepEngine::class ),
$c->get( SettingsStore::class ),
$c->get( Clock::class ),
$c->get( LocationRepository::class ),
$c->get( AgentRepository::class ),
$c->get( ServiceRepository::class ),
$c->get( BookingFormRepository::class ),
$c->get( BookingFormScope::class ),
$c->get( CreateBookingHandler::class ),
$c->get( ServiceExtraRepository::class ),
$c->get( NamedFeatureGate::class )
);
}
);
$container->singleton(
PermissionGate::class,
static function ( Container $c ): PermissionGate {
return new PermissionGate(
$c->get( RateLimiter::class ),
$c->get( AccessScopeFactory::class ),
$c->get( CustomerSessionService::class )
);
}
);
$container->singleton(
PublicRestController::class,
static function ( Container $c ): PublicRestController {
return new PublicRestController(
$c->get( BookingSessionService::class ),
$c->get( OrderIntentService::class ),
$c->get( CustomerSessionService::class ),
$c->get( ServiceRepository::class ),
$c->get( ServiceCategoryRepository::class ),
$c->get( AgentRepository::class ),
$c->get( LocationRepository::class ),
$c->get( CouponRepository::class ),
$c->get( BookingRepository::class ),
$c->get( CustomerRepository::class ),
$c->get( CancelBookingHandler::class ),
$c->get( RescheduleBookingHandler::class ),
$c->get( CreatePaymentIntentHandler::class ),
$c->get( ProcessPaymentWebhookHandler::class ),
$c->get( PaymentGatewayRegistry::class ),
$c->get( BookingFormScope::class ),
$c->get( NamedFeatureGate::class ),
$c->get( BusyProviderRegistry::class ),
);
}
);
$container->singleton(
AdminRestController::class,
static function ( Container $c ): AdminRestController {
return new AdminRestController(
$c->get( PermissionGate::class ),
$c->get( ListBookingsHandler::class ),
$c->get( ChangeBookingStatusHandler::class ),
$c->get( BookingRepository::class ),
$c->get( ServiceRepository::class ),
$c->get( AgentRepository::class ),
$c->get( WeeklyScheduleRepository::class ),
$c->get( ScheduleOverrideRepository::class ),
$c->get( LocationRepository::class ),
$c->get( CustomerRepository::class ),
$c->get( CouponRepository::class ),
$c->get( SettingsStore::class ),
$c->get( Clock::class ),
$c->get( HealthMonitor::class ),
$c->get( RefundPaymentHandler::class ),
$c->get( PaymentRepository::class ),
$c->get( ListCustomersHandler::class ),
$c->get( ListServicesHandler::class ),
$c->get( ListAgentsHandler::class ),
$c->get( WizardService::class ),
$c->get( ListNotificationTemplatesHandler::class ),
$c->get( ListNotificationLogHandler::class ),
$c->get( RetryNotificationLogHandler::class ),
$c->get( MutateServiceHandler::class ),
$c->get( MutateServiceDurationHandler::class ),
$c->get( MutateServiceExtraHandler::class ),
$c->get( MutateAgentHandler::class ),
$c->get( MutateCustomerHandler::class ),
$c->get( ListBookingFormsHandler::class ),
$c->get( GetBookingFormHandler::class ),
$c->get( MutateBookingFormHandler::class ),
$c->get( CreatePreviewSessionHandler::class ),
$c->get( SystemReportHandler::class ),
$c->get( AutomationAdminService::class ),
$c->get( CancelBookingHandler::class ),
$c->get( UpdateBookingNotesHandler::class ),
$c->get( GetCustomerHandler::class ),
$c->get( NotificationAdminService::class ),
$c->get( ReminderAdminService::class ),
$c->get( ActivityLogRepository::class ),
$c->get( AdminSearchHandler::class ),
$c->get( CreateAdminBookingHandler::class ),
$c->get( AdminRescheduleBookingHandler::class ),
$c->get( AbandonedAdminService::class ),
$c->get( MutateCouponHandler::class ),
$c->get( MutateLocationHandler::class ),
$c->get( FinancialReportsService::class )
);
}
);
$container->singleton(
RouteRegistrar::class,
static function ( Container $c ): RouteRegistrar {
return new RouteRegistrar(
$c->get( PermissionGate::class ),
$c->get( PublicRestController::class ),
$c->get( AdminRestController::class )
);
}
);
}
/**
* Schedule the five-minute heartbeat when Action Scheduler is present.
*
* Stubbed until Milestone 8 ships Action Scheduler integration. When available,
* records a recurring action in the `bookline` group.
*/
private static function schedule_heartbeat(): void {
/** @var HealthMonitor $health */
$health = self::container()->get( HealthMonitor::class );
$health->ensure_scheduled();
}
/**
* Register core triggers, conditions and actions once.
*
* @param Container $container Container.
*/
private static function boot_automation_registries( Container $container ): void {
static $done = false;
if ( $done ) {
return;
}
$done = true;
/** @var AutomationTriggerRegistry $triggers */
$triggers = $container->get( AutomationTriggerRegistry::class );
/** @var AutomationConditionRegistry $conditions */
$conditions = $container->get( AutomationConditionRegistry::class );
/** @var AutomationActionRegistry $actions */
$actions = $container->get( AutomationActionRegistry::class );
( new CoreAutomationRegistrar() )->register(
$triggers,
$conditions,
$actions,
$container->get( AutomationNotificationSender::class )
);
$actions->register( new HttpIntegrationAction( $container->get( AutomationHttpClient::class ) ) );
}
/**
* Merge filter-provided triggers, conditions and actions (docs/20 §11).
*
* @param Container $container Container.
*/
private static function apply_automation_extension_filters( Container $container ): void {
if ( ! function_exists( 'apply_filters' ) ) {
return;
}
/** @var AutomationTriggerRegistry $triggers */
$triggers = $container->get( AutomationTriggerRegistry::class );
/** @var AutomationConditionRegistry $conditions */
$conditions = $container->get( AutomationConditionRegistry::class );
/** @var AutomationActionRegistry $actions */
$actions = $container->get( AutomationActionRegistry::class );
$extra_triggers = apply_filters( 'bookline/automation/triggers', $triggers->all() );
if ( is_array( $extra_triggers ) ) {
foreach ( $extra_triggers as $item ) {
if ( $item instanceof AutomationTrigger ) {
$triggers->register( $item );
}
}
}
$extra_conditions = apply_filters( 'bookline/automation/conditions', $conditions->all() );
if ( is_array( $extra_conditions ) ) {
foreach ( $extra_conditions as $item ) {
if ( $item instanceof AutomationCondition ) {
$conditions->register( $item );
}
}
}
$extra_actions = apply_filters( 'bookline/automation/actions', $actions->all() );
if ( is_array( $extra_actions ) ) {
foreach ( $extra_actions as $item ) {
if ( $item instanceof AutomationAction ) {
$actions->register( $item );
}
}
}
}
}