is_connected() ) { $batcher = new WPPilot\Event_Batcher( $connection, $logger, static function () use ( $backend_client ) { // Renew daily, so an expired or revoked token heals within a day. return $backend_client->get_token_if_needed( DAY_IN_SECONDS ); } ); $activity = new WPPilot\Activity_Hook_Registry( $batcher ); $activity->register_hooks(); // Delivery for requests that could not hand their response back first. add_action( WPPilot\Event_Batcher::CRON_HOOK, array( $batcher, 'deliver_outbox' ) ); } // Register WPPilot's abilities (wppilot/*) so other AI tools can discover // them. The Abilities API (WP 6.9+) loads its category first and then the // abilities that name it; on older cores this registers nothing. $abilities->hook(); // A description the owner approved is printed even when no SEO plugin is // there to print it. ( new WPPilot\Meta_Description( new WPPilot\Seo_Detector() ) )->register(); // Lead forms: the shortcode, the submit handler and the lead store work // on every site, connected or not. Only the definition routes need the // backend's token. $forms = new WPPilot\Forms( $logger ); $forms->register(); // Comment moderation for the backend: the queue and governed status moves. $comments = new WPPilot\Comments( $logger ); // A post's own discussion settings (Allow comments / pingbacks), read and // changed per post with a re-check, for the backend. $post_discussion = new WPPilot\Post_Discussion( $logger ); // One custom field value on one post, read exactly and changed with a // compare-and-swap (and put back exactly), for the backend. $custom_field_write = new WPPilot\Custom_Field_Write( $logger, $cache_purger ); // A public page of this site, read by the site itself when the host in // front of it refuses WPPilot's own request. $public_page = new WPPilot\Public_Page( $logger ); // A notice printed while a wppilot/v1 route runs must never reach its JSON. ( new WPPilot\Rest_Output_Guard( $logger ) )->register(); add_action( 'rest_api_init', array( $rest, 'register_routes' ) ); add_action( 'rest_api_init', static function () use ( $forms, $comments, $post_discussion, $custom_field_write, $public_page, $rest ): void { $forms->register_routes( array( $rest, 'callback_token_permission_check' ) ); $comments->register_routes( array( $rest, 'callback_token_permission_check' ) ); $post_discussion->register_routes( array( $rest, 'callback_token_permission_check' ) ); $custom_field_write->register_routes( array( $rest, 'callback_token_permission_check' ) ); $public_page->register_routes( array( $rest, 'callback_token_permission_check' ) ); // Where a piece of text is stored, store by store (read-only). WPPilot\Site_Find::register_routes( array( $rest, 'callback_token_permission_check' ) ); // Every post of one type as compact rows, a page at a time (read-only). WPPilot\Content_Set::register_routes( array( $rest, 'callback_token_permission_check' ) ); } ); // Before core's _add_post_type_submenus (admin_menu, 10, hooked before any // plugin loads): the Leads post type's submenu must find this menu already // registered, or it becomes the first entry and the menu opens Leads. add_action( 'admin_menu', array( $admin, 'register_menu' ), 9 ); add_action( 'admin_enqueue_scripts', array( $admin, 'enqueue_assets' ) ); add_action( 'admin_post_wppilot_connect', array( $admin, 'handle_connect' ) ); add_action( 'admin_post_wppilot_disconnect', array( $admin, 'handle_disconnect' ) ); add_action( 'admin_post_wppilot_open', array( $admin, 'handle_open' ) ); add_filter( 'removable_query_args', array( 'WPPilot\\Admin_Page', 'removable_query_args' ) ); add_action( 'wp_ajax_wppilot_handoff', array( $admin, 'handle_handoff_ajax' ) ); // "Connect this site", on the Plugins screen and the Dashboard, until it is. if ( is_admin() ) { ( new WPPilot\First_Run_Notice( $connection ) )->register(); } }