Describes Stream 5.0.0 (Plugin::VERSION). Read top to bottom. Every class link is repo-relative.
Related: contributing.md (env + commands) Β· connectors.md (generated inventory) Β· docs/hooks.md (generated hook reference) Β· docs/adding-a-connector.md (how to add a connector) Β· docs/adding-an-alert-type.md (how to add an alert type) Β· changelog.md
Not covered here: hook reference Β· Abilities/MCP internals Β· scheduler internals Β· admin UI / settings internals.
WordPress loads stream.php before plugins_loaded. Connectors, alerts, and settings are not built then β they are constructed on init priority 9. Scan the diagram; the table is the same sequence with file links.
flowchart TD
start[stream.php loaded] --> php{PHP β₯ 7.4?}
php -->|no| fail["admin_notices only β plugin inactive<br/>global never set"]
php -->|yes| a1
subgraph phaseA["A β include, before plugins_loaded"]
a1["new Plugin() β locate_plugin β $locations"]
a2["Plugin::boot β autoload Β· scheduler Β· DB Β· Log"]
a3["register hooks Β· Admin + Install if admin / CLI / debug"]
a1 --> a2 --> a3
end
subgraph phaseB["B β plugins_loaded"]
b1["priority 20: wp_stream_db_driver swap"]
end
subgraph phaseC["C β init"]
c1["priority 9: Settings Β· Connectors Β· Alerts"]
c2["priority 10: Admin::init β Export constructed Β· Alerts CPT"]
c1 --> c2
end
subgraph phaseD["D β later in the request"]
d1["admin_init β export download"]
d2["wp_head β frontend indicator"]
d3["shutdown β delayed_log_commit"]
end
a3 --> b1
b1 --> c1
c2 --> d1
c2 --> d2
c2 --> d3
| # | Phase | When | What runs | File |
|---|---|---|---|---|
| 1 | A | plugin file include | ABSPATH guard; define WP_STREAM_SETTINGS_CAPABILITY (manage_options), WP_STREAM_MIN_PHP_VERSION (7.4) |
stream.php |
| 2 | A | PHP gate: too old β hook wp_stream_fail_php_version on admin_notices / network_admin_notices and stop (global unset); else require classes/class-plugin.php and $GLOBALS['wp_stream'] = new Plugin() |
stream.php | |
| 3 | A | Plugin::__construct() |
locate_plugin() β $locations (plugin, dir, url, inc_dir, class_dir) |
classes/class-plugin.php |
| 4 | A | Plugin::boot() |
spl_autoload_register (classes under classes/ only) |
|
| 5 | A | create_scheduler() β filter wp_stream_use_action_scheduler; AS_Scheduler (bundled Action Scheduler) or Cron_Scheduler |
classes/class-scheduler.php | |
| 6 | A | require includes/functions.php |
||
| 7 | A | wp_stream_db_driver β new DB( $driver ); wp_die if the class is missing or not a DB_Driver |
classes/class-db.php, classes/class-db-driver.php, classes/class-db-driver-wpdb.php | |
| 8 | A | wp_stream_log_handler β $log; client IP captured |
classes/class-log.php | |
| 9 | A | hooks registered: init@9βinit; wp_headβfrontend_indicator; plugins_loaded@20βplugins_loaded |
||
| 10 | A | if admin / WP_CLI / WP_STREAM_DEV_DEBUG: new Admin() (Menu, Assets, Screen_Records, Screen_Settings, Purge, Ajax) and $install = $driver->setup_storage(); elseif DOING_CRON: Admin only; WP-CLI also registers stream |
classes/class-admin.php, classes/class-install.php, classes/class-cli.php | |
| 11 | B | plugins_loaded@20 |
wp_stream_db_driver re-applied; $db rebuilt for late add-ons |
|
| 12 | C | init@9 |
Plugin::init(): Settings, Connectors (loads + registers connectors now), Alerts (engine, admin UI, types, triggers), Alerts_List, Abilities, User_Picker |
|
| 13 | C | init@10 |
Admin::init(): Network, Live_Update, Export; Alerts::register_post_type() |
|
| 14 | D | later | admin_init β export download (only page=wp_stream); wp_head β frontend indicator; shutdown β delayed_log_commit |
Filters that must be registered before Streamβs plugin file loads (mu-plugin, wp-config.php, or an earlier plugin). Too late on plugins_loaded:
| Filter | Applied in | Default |
|---|---|---|
wp_stream_use_action_scheduler |
create_scheduler() during boot() |
true if the bundled Action Scheduler file exists |
wp_stream_db_driver |
boot() (first pass) |
DB_Driver_WPDB |
wp_stream_log_handler |
boot() |
new Log( $this ) |
wp_stream_db_driver is applied again at plugins_loaded@20.
Scheduler (boot one-liner): AS_Scheduler vs Cron_Scheduler β classes/class-scheduler.php, classes/class-as-scheduler.php, classes/class-cron-scheduler.php; bundled library vendor/woocommerce/action-scheduler/action-scheduler.php.
Abilities (boot one-liner): constructed in Plugin::init(); no-op unless WordPress 6.9+ (WP_Ability) and the Advanced setting is on. Stream does not load the MCP Adapter. See classes/class-abilities.php and contributing.md.
Admin collaborators (one line): constructed from Admin: Admin_Menu, Admin_Assets, Admin_Screen_Records, Admin_Screen_Settings, Admin_Purge, Admin_Ajax.
Plugin is not a singleton (no static instance, no guard; new Plugin() twice would double-hook). $GLOBALS['wp_stream'] is a process-wide service locator. wp_stream_get_instance() in stream.php returns that global with no isset guard. On the PHP-gate path the global is never set.
| Property | Available from |
|---|---|
locations, scheduler, db, log |
end of boot() (plugin include) |
admin, install |
same time, only admin / WP-CLI / WP_STREAM_DEV_DEBUG (install) and cron (admin only); otherwise null |
settings, connectors, alerts, alerts_list, abilities, user_picker |
init priority 9 |
admin->network, admin->live_update, admin->export |
init priority 10 |
| Request | admin |
install |
connectors registered | notes |
|---|---|---|---|---|
| Front-end | no | no | those with register_frontend |
Log/DB exist; no menus, no dbDelta |
| wp-admin / admin-ajax | yes | yes | those with register_admin |
|
| WP-Cron | yes | no | register_frontend (is_admin() is false) |
|
| WP-CLI | yes | yes | register_frontend (is_admin() is false) |
stream command |
| REST | no | no | register_frontend |
Abilities re-register caps when admin is null |
register_connector_instances() gates on is_admin() only β cron, CLI, and REST take the front-end branch.
Do not call Log::log() (it reads $plugin->settings->options) before init@9.
Files: classes/class-connectors.php, classes/class-connector.php, classes/class-log.php, classes/class-db.php, classes/class-db-driver-wpdb.php.
- Registration.
Connectors::__construct()βload_connectors():get_available_connectors()(includeconnectors/class-connector-{slug}.phpfor eachBUILTIN_CONNECTOR_SLUGSentry) βinstantiate_connector_classes()(keyed by$instance->name) β filterwp_stream_connectors(instances, not class names) βregister_connector_instances()(dependency,register_admin/register_frontend, then$connector->register()). See classes/class-connectors.php for remaining gates. - Hook attachment.
Connector::register()adds[ $this, 'callback' ]at priority 10, 99 args, for each$this->actions. - Event capture. WP action β
callback()βcallback_{sanitized_action}()β$this->log()ordelayed_log()(committed onshutdown). - Record write.
Connector::log()βwp_stream_log_data(falseaborts) βLog::log()βwp_stream_is_record_excludedβDB::insert()(bails onWP_IMPORTING) βwp_stream_record_arrayβDB_Driver_WPDB::insert_record()({prefix}stream+{prefix}stream_meta) βwp_stream_record_inserted.
In this plugin: add the slug to Connectors::BUILTIN_CONNECTOR_SLUGS (classes/class-connectors.php), create connectors/class-connector-{slug}.php with class Connector_{Slug} extends Connector (classes/class-connector.php); set $name and $actions, implement get_label(), get_context_labels(), get_action_labels(), and callback_{action}() methods that call $this->log(). Template: connectors/class-connector-posts.php. Regenerate connectors.md with npm run document:connectors.
From another plugin: hook wp_stream_connectors and append a Connector instance keyed by its $name.
Step-by-step guide: docs/adding-a-connector.md.
Storage: CPT wp_stream_alerts; statuses wp_stream_enabled / wp_stream_disabled.
Configuration: types via wp_stream_alert_types. Defaults: none, highlight, email, ifttt, slack, webhook (alerts/class-alert-type-webhook.php). IFTTT has $creatable = false β hidden from the new-alert UI; existing alerts still fire. alerts/class-alert-type-menu-alert.php and alerts/class-alert-type-die.php are on disk but not default-loaded (only via the filter).
Triggers: author, context, action via wp_stream_alert_triggers. Match is AND via wp_stream_alert_trigger_check.
Evaluation + dispatch is synchronous on each record insert (a WP_Query per insert): wp_stream_record_inserted β Alerts_Trigger_Engine::check_records β Alert::check_record β send_alert β Alert_Type::alert().
FaΓ§ade: Alerts builds the trigger engine and Alerts_Admin_UI. To add a type: extend Alert_Type, add via wp_stream_alert_types. Step-by-step guide: docs/adding-an-alert-type.md.
Export is constructed in Admin::init() (init@10). It only activates when $_GET['page'] === 'wp_stream'.
Trigger: admin_init β render_download(), gated by the view cap, a nonce, and record-actions=export-{slug}. Not AJAX, not CLI.
Data: the records list table (classes/class-admin-screen-records.php) with pagination disabled; cap wp_stream_export_limit (default 10000).
Output: Exporter::output_file() sends headers then exits unless WP_STREAM_TESTS. βStreamedβ here means a full-payload synchronous HTTP download, not chunked streaming.
Defaults CSV + JSON (exporters/class-exporter-csv.php, exporters/class-exporter-json.php); add more via wp_stream_exporters. Menu items via wp_stream_record_actions_menu.
Pointer only. Commands live in contributing.md Β§ Scripts and Commands.
| Tier | Needs | Config | Command |
|---|---|---|---|
| Unit (no WP bootstrap) | PHP 8.2+ on host, or wp-env | phpunit-unit.xml, tests/phpunit/unit/ |
composer test-unit; npm run test:php-unit |
| Integration PHPUnit | wp-env | phpunit.xml, phpunit-multisite.xml, tests/phpunit/ excluding unit |
npm run test (both); npm run test:php / test:php-multisite |
| E2E | wp-env at http://localhost:8888 |
playwright.config.js, tests/e2e/ |
npm run test-e2e |