Documentation · Technical Documentation

Plugin Bootstrap And Lifecycle

This document describes how the ADP Car Market Hub plugin loads, what happens during activation, deactivation and uninstall, and which migrations the plugin runs as it boots.

When to use this document

Read this document if you need to:

  • Trace the order in which plugin code executes during a WordPress request.
  • Understand which actions run on activation and deactivation (capabilities, default pages, custom tables, scheduled events).
  • Plan an upgrade or troubleshoot a migration that did not apply.
  • Decide where to hook custom code so that it runs after the plugin's services are available.

Overview

The plugin is bootstrapped from the main file adp-car-market-hub.php. That file is short by design and only does work that must happen at file-load time. All feature code is loaded through a PSR-4-style autoloader and wired up by the AS24CI\Plugin singleton on the plugins_loaded action.

Constants defined at load time

adp-car-market-hub.php defines the following constants before any hooks run:

  • AS24CI_VERSION — Current plugin version (kept in sync with the plugin header and readme.txt).
  • AS24CI_PLUGIN_FILE — Absolute path to the main plugin file.
  • AS24CI_PLUGIN_DIR — Directory of the plugin (with trailing slash).
  • AS24CI_PLUGIN_URL — URL to the plugin directory, useful for asset enqueuing.

The file also calls require_once AS24CI_PLUGIN_DIR . 'src/Core/Helpers.php' to load shared procedural helpers and conditionally defines the global helpers as24ci_format() and as24ci_get_available_ai_models() if no function with the same name has been declared earlier.

Autoloader

Immediately after the constants, the file registers a small PSR-4-compatible autoloader for the AS24CI\ namespace. Class names are mapped to file names of the form class-as24ci-<lower-dashed-name>.php. The autoloader looks first in includes/ and falls back to includes/admin/ for admin-only classes.

Hook registration at load time

The main file registers the following hooks before any feature code runs:

HookCallbackPurpose
plugins_loaded (priority 1)as24ci_load_textdomainLoads the adp-car-market-hub text domain from /languages.
plugins_loaded (default priority)AS24CI\Plugin::initConstructs the singleton and registers all feature hooks.
register_activation_hookAS24CI\Plugin::activateRuns once when the plugin is activated.
register_deactivation_hookAS24CI\Plugin::deactivateRuns once when the plugin is deactivated.
cron_schedulesas24ci_add_custom_cron_intervalsRegisters the custom WP-Cron interval as24ci_every_5_minutes (300 s).
plugin_action_links_<basename>as24ci_add_plugin_action_linksAdds "License activation" and "Settings" links on the WordPress Plugins screen.
plugin_row_metaas24ci_add_plugin_row_metaAdds a "Documentation" link to the plugin's row meta.

The main file also bootstraps the Content Studio module at load time, independently of the AS24CI\Plugin router:

  • register_activation_hookAS24CI\Content_Studio_Repository::maybe_create_tables (creates the Content Studio tables on activation).
  • admin_menu (priority 30) → AS24CI\Admin_Tab_Content_Studio::register_menu.
  • plugins_loaded (priority 20) → AS24CI\Content_Studio_Admin_Worker::register_hooks.

The custom WP-Cron interval is registered unconditionally so that WordPress always knows about it, even when individual feature toggles that use it are disabled.

Bootstrap sequence (normal request)

When WordPress loads the plugin during a normal request, the following steps happen in order:

  1. The main file is included; constants, autoloader and the hooks listed above are registered.
  2. WordPress fires plugins_loaded at priority 1: as24ci_load_textdomain() runs load_plugin_textdomain() so that translations are available before any translatable string is rendered.
  3. WordPress fires plugins_loaded at the default priority: AS24CI\Plugin::init() constructs the singleton.
  4. The constructor of AS24CI\Plugin: - Instantiates the shared services: Logger, Client, Image_Importer, Vehicle_Repository, Importer, Scheduler. - Calls Vehicle_Field_Resolver::set_repository() so the central field resolver can read from the vehicles table. - Calls Vehicle_Deleter::set_repository() and Vehicle_Deleter::register_hooks() so every permanent-delete path (native WordPress, importer full sync, bulk action) goes through a single, idempotent cleanup. - Calls register_hooks() to wire all features for the current request.
  5. register_hooks() wires two admin_init callbacks (they are wired during plugins_loaded but only fire on a later admin_init in wp-admin): - AS24CI\Plugin::maybe_upgrade_caps() — ensures roles and capabilities exist for the current plugin version. - AS24CI\Plugin::maybe_upgrade() — runs option/data migrations.
  6. register_hooks() then registers each subsystem's own hooks: - Always-on: CPT, Taxonomies, Leads_CPT, Contact_Form, Templates, Assets, Ajax, Archive_Filters, Cron_Endpoint, License_Manager, License_Refresh_Signal, Webhooks, Analytics, Ai_Assistant, Data_Quality_Scanner, Pricing_Engine (with daily schedule), Financing_Calculator, Rest_Api, Locations, Seller_Profile_Fields, plus the scheduler. The private Updater is also wired here, gated to admin / WP-Cron / WP-CLI contexts. - Gated by feature options on AS24CI\Options: Schema (plus Seo_Compatibility), Sitemap, Social_Share, Pdf_Datasheet, Favorites, Compare, Export, Bulk_Actions, Search_Agent. - Admin only (is_admin() true): the AS24CI\Admin controller, Admin_Team (CMH Team), Update_Visibility and Admin_Setup_Wizard, and — when Options::FEATURE_DASHBOARD_WIDGET is enabled — the dashboard widget.
  7. WordPress proceeds with the rest of the request lifecycle. Most feature classes register additional hooks on later actions such as init (post types, taxonomies, table creation for Search Agents), rest_api_init (REST routes), wp_footer (analytics tracking pixel) or admin_init (admin-only table upgrades).

Activation

AS24CI\Plugin::activate() is registered with register_activation_hook and runs once when an administrator activates the plugin. It performs the following work, in order:

  1. Calls seed_safe_defaults(), which uses add_option() to install conservative defaults for first setup. Existing values are never overwritten. Notable defaults: - as24ci_default_currency = EUR. - as24ci_default_post_status = draft so admins can review mappings before publishing. - as24ci_max_images = 30 (a finite cap rather than unlimited). - as24ci_full_sync = 0 (hard delete remains opt-in). - Frontend-friendly features (sitemap, schema, favorites, compare, financing, dashboard widget, export, lazy loading, layout manager) default on; tracking, external, batch and risky features (analytics, REST API, AI Assistant, social share, PDF datasheet, test drive) default off.
  2. Calls add_option( Options::SETUP_FIRST_ACTIVATION_AT, gmdate('c') ) to record the first-activation timestamp used by the Setup Wizard admin notice. Because add_option() is a no-op when the value exists, reactivations preserve the original date.
  3. Calls ensure_roles_and_caps() which: - Creates the as24ci_editor role if it does not exist. - Grants the as24ci_car / as24ci_cars capability set to both as24ci_editor and administrator. - Adds the manage_as24_imports capability (the Plugin::CAP_MANAGE constant) to administrators only. This capability gates the importer, settings, tools and logs UI.
  4. Sets as24ci_caps_version to 1 so the capability migration does not run again on subsequent requests.
  5. Sets as24ci_db_version to 5 so the data migrations defined in maybe_upgrade() are skipped on a fresh install.
  6. Calls maybe_create_default_pages() to optionally create the Cars, Compare Cars and Favorites pages and store their post IDs in as24ci_page_archive_id, as24ci_page_compare_id and as24ci_page_favorites_id. The behaviour is gated by the as24ci_create_default_pages option and the as24ci_default_pages_enabled and as24ci_default_pages filters.
  7. Calls Analytics::maybe_create_table() to create the analytics table.
  8. Calls Vehicle_Repository::maybe_create_table() to create the dedicated vehicles table.
  9. Schedules the daily analytics retention cleanup (as24ci_daily_cleanup) if not already scheduled.
  10. Calls License_Manager::ensure_cron_scheduled() to schedule the daily license re-validation event (as24ci_license_refresh).
  11. Calls CPT::register_post_type() and then flush_rewrite_rules() so that the /cars archive works immediately after activation.
  12. Logs Plugin activated (v<version>). via AS24CI\Logger.

The Content Studio tables are created by a separate activation hook (Content_Studio_Repository::maybe_create_tables) registered in the main plugin file, not by Plugin::activate().

activate() deliberately does not seed default API URLs. Missing configuration is surfaced through the system status / health UI and through client errors when the importer runs.

Capability and data migrations

Two static methods are wired on admin_init (not plugins_loaded) to keep existing installations in sync with the current plugin version. Hooking on admin_init avoids forcing the pluggable authentication stack to load early on every request:

  • maybe_upgrade_caps() — Wired on admin_init. It guards against AJAX, cron and REST requests (returning early in those contexts) and uses a per-request static guard so the migration body runs at most once. It is not gated on manage_options. Compares as24ci_caps_version against the target value (1 at the time of writing) and re-runs ensure_roles_and_caps() if needed.
  • maybe_upgrade() — Wired on admin_init; returns early unless is_admin(). Compares as24ci_db_version against the target value (5) and applies any outstanding migration steps. Each step is idempotent:
  • Migration 1: marker only, no data changes.
  • Migration 2: forces the analytics-consent option (Options::ANALYTICS_REQUIRE_CONSENT) to 0. Administrators can re-enable it from the settings UI.
  • Migration 3: migrates the legacy FINANCING_PLACEMENT option to the new Layout Manager zones. The migration only writes to a Layout Manager zone if that zone has not been saved yet, so existing layouts are preserved.
  • Migration 4: re-applies seed_safe_defaults() so existing installs receive any new safe defaults without overwriting values the administrator has already set.
  • Migration 5: protects plaintext secrets at rest. Reversible secrets (Options::CLIENT_SECRET, Options::HUB_API_KEY, Options::WEBHOOK_SECRET) are wrapped via Secrets::encrypt(), and the cron token (Options::CRON_TOKEN) is replaced with its keyed HMAC hash via Secrets::hash_token(). The migration is idempotent (already-protected rows are skipped) and the cached OAuth bearer token is cleared so it is re-stored through the encrypted path. After all applicable steps, the option is updated to the current target version.

Deactivation

AS24CI\Plugin::deactivate() runs once when an administrator deactivates the plugin. Existing data is preserved. The method:

  1. Calls wp_clear_scheduled_hook( Scheduler::CRON_HOOK ) to remove pending as24ci_scheduled_import events.
  2. Calls wp_clear_scheduled_hook( Analytics::ANALYTICS_CLEANUP_CRON_HOOK ) to remove the daily analytics retention event.
  3. Calls Ai_Assistant::clear_ai_queue_schedule() to remove the background AI queue cron event.
  4. Calls Pricing_Engine::clear_schedule() to remove the daily pricing-engine cron event.
  5. Calls License_Manager::clear_cron() to remove the daily license re-validation event (as24ci_license_refresh).
  6. Calls wp_clear_scheduled_hook( 'as24ci_competitor_watcher_cron' ) as legacy cleanup only — the Competitor Watcher feature (and its class) has been removed; the hook name is hard-coded here so any leftover scheduled event on installations upgraded from older versions is still cleared.
  7. Deletes the Scheduler::LOCK_TRANSIENT transient (as24ci_cron_import_running) in case the plugin is deactivated while an import is in progress.
  8. Calls flush_rewrite_rules() and logs Plugin deactivated.

Because deactivation does not touch the database tables, custom post types or options, reactivating the plugin restores the previous state.

Uninstall

When the plugin is deleted from the WordPress admin (not just deactivated), WordPress executes uninstall.php. The script:

  1. Loads AS24CI\Options (if the file exists) so the authoritative list of option keys can be derived from Options::get_all_keys(), plus a small set of additional keys (as24ci_models_cache_keys, as24ci_page_archive_id, as24ci_page_compare_id and the legacy as24ci_api_total_cache). A hard-coded fallback list is used if the class cannot be loaded.
  2. Deletes plugin transients (as24ci_access_token, as24ci_cron_import_running, as24ci_image_queue_running).
  3. Unschedules pending events for as24ci_scheduled_import and as24ci_daily_cleanup.
  4. Reads the as24ci_delete_data_on_uninstall option. When the value is 1, the script deletes: - All as24ci_car posts (forced). - All attachment IDs stored in each car's _as24ci_image_ids postmeta. Manual gallery attachments (_as24ci_manual_image_ids) are intentionally not deleted. - The featured image attachment of each car (defensive cleanup). - All as24ci_lead posts. - The activation-created Cars, Compare and Favorites pages.
  5. Always drops the custom tables (regardless of the opt-in flag): - {$wpdb->prefix}as24ci_analytics - {$wpdb->prefix}as24_vehicles - {$wpdb->prefix}as24ci_search_agents - {$wpdb->prefix}as24ci_content_studio_assets - {$wpdb->prefix}as24ci_content_studio_jobs The matching schema-version options (as24ci_vehicles_db_version, as24ci_search_agent_db_version, as24ci_content_studio_db_version) are removed as well.
  6. Deletes all collected option keys for the site.
  7. On multisite installations the same routine runs once per site via switch_to_blog().

Configuration reference

The bootstrap and lifecycle behaviour is influenced by the following options (all defined as constants on AS24CI\Options). The full catalogue is documented in the Options And Settings Storage document; only the lifecycle-relevant entries are listed here.

Option keyConstantUsed by
as24ci_caps_versionOptions::CAPS_VERSIONmaybe_upgrade_caps() to decide whether caps must run.
as24ci_db_versionOptions::DB_VERSIONmaybe_upgrade() to decide which migrations to apply.
as24ci_create_default_pagesOptions::CREATE_DEFAULT_PAGESmaybe_create_default_pages() during activation.
as24ci_page_archive_idn/aStores the ID of the activation-created Cars page.
as24ci_page_compare_idn/aStores the ID of the activation-created Compare page.
as24ci_page_favorites_idn/aStores the ID of the activation-created Favorites page.
as24ci_delete_data_on_uninstallOptions::DELETE_DATA_ON_UNINSTALLuninstall.php to decide whether to delete content.

Operational notes

  • register_activation_hook is fired only when an administrator activates the plugin from the Plugins screen. Updating the plugin files does not trigger activation; that is why maybe_upgrade() exists.
  • Because maybe_upgrade() and maybe_upgrade_caps() are hooked on admin_init and only run during real admin page loads (not AJAX, REST or cron), frontend-only requests do not pay the cost of migration checks. The first admin page load after an update applies any outstanding migration.
  • The plugin's WP-Cron lock TTLs are conservative: the import lock (Scheduler::LOCK_TTL) is approximately 40 minutes. If a process is killed mid-run, the lock is released automatically when the transient expires. Manual recovery is described in the scheduler documentation.
  • Uninstall always drops the analytics, vehicles and search-agent tables because they may contain personal or visitor data. Posts and options are only removed when the administrator has opted in via the as24ci_delete_data_on_uninstall setting.

Troubleshooting

  • Activation does not create the Cars page. Confirm that as24ci_create_default_pages is set to 1 and that no theme or other plugin is filtering as24ci_default_pages_enabled to false. The page is also not recreated if a page with the same title already exists; in that case its ID is reused.
  • Custom capabilities are missing for an administrator. Visit any admin page so maybe_upgrade_caps() can run on admin_init, or temporarily delete as24ci_caps_version from wp_options to force the migration to re-execute. The migration is skipped on AJAX, REST and cron requests, so it must run during a real admin page load.
  • A migration step did not apply. Check the value of as24ci_db_version. If it already equals the target, the migration has already run. Lower the value manually only if you understand the consequences for your installation.
  • The /cars archive returns a 404 immediately after activation. Activation already calls flush_rewrite_rules(). If the archive is still not reachable, visit Settings → Permalinks and save once to force WordPress to rebuild its rewrite cache.