-
Notifications
You must be signed in to change notification settings - Fork 116
Permalink
Choose a base ref
{{ refName }}
default
Choose a head ref
{{ refName }}
default
Checking mergeability…
Don’t worry, you can still create the pull request.
Comparing changes
Choose two branches to see what’s changed or to start a new pull request.
If you need to, you can also or
learn more about diff comparisons.
Open a pull request
Create a new pull request by comparing changes across two branches. If you need to, you can also .
Learn more about diff comparisons here.
base repository: xwp/stream
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: develop
Could not load branches
Nothing to show
Loading
Could not load tags
Nothing to show
{{ refName }}
default
Loading
...
head repository: xwp/stream
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Could not load branches
Nothing to show
Loading
Could not load tags
Nothing to show
{{ refName }}
default
Loading
- 1 commit
- 0 files changed
- 7 contributors
Commits on May 28, 2026
-
* Comments Connector: Avoid a PHP Warning on pingbacks. * Respect the comment_author_email user details if set In some cases `require_name_email` and `comment_registration` will both be enabled, the details from the email lookup should be respected before falling back to the comment author name. * chore(deps): update dependency @types/node to ^22.19.12 (#1845) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update dependency @types/node to ^22.19.13 (#1846) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update node.js to ^22.22.1 (#1848) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update dependency @types/node to ^22.19.15 (#1849) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update node.js to ^22.22.2 (#1850) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update dependency @playwright/test to ^1.59.0 (#1851) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update dependency @playwright/test to ^1.59.1 (#1852) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * add missing workflow trigger * chore(deps): update dependency @types/node to ^22.19.17 (#1854) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Scaffold Stream Abilities API loader Add abstract Ability base class, Abilities loader with WP 6.9 + setting gating, and a new "Enable Abilities API" toggle under the existing Advanced settings section. The loader hooks wp_abilities_api_init and will register concrete abilities once they are added in subsequent commits. Falls back silently on WordPress < 6.9. * Add read-only Stream abilities Implements the six read-only abilities under stream/* namespace: get-records, get-record, get-settings, get-alerts, get-connectors, and get-exclusion-rules. Each ability has hand-written JSON Schemas, delegates to existing Stream APIs, and ships with a PHPUnit test covering name, schema, permission gating, and execution. Tests skip themselves on WordPress < 6.9 via the shared Abilities_TestCase base class. * Add Stream write abilities Add three abilities that mutate Stream state through the existing internal APIs: stream/create-alert (creates a wp_stream_alerts CPT post with alert_type and alert_meta), stream/update-settings (partial-merge update to the wp_stream option), and stream/create-exclusion-rule (appends to the parallel-array exclude_rules option columns Stream already uses). Each ability is gated behind the manage_options capability. Hand-written JSON schemas describe inputs and outputs for AI consumers; create-alert requires the four trigger fields, create-exclusion-rule requires at least one filter property, and update-settings requires a non-empty settings map. Each ability ships with PHPUnit coverage that verifies permissions, schema shape, and end-to-end execution against the option or post store. * Add destructive Stream abilities Add the two destructive abilities required by the ticket: stream/purge-records (filtered DELETE against the Stream records table with a cascading meta delete that mirrors Admin::erase()) and stream/delete-alert (force-delete a wp_stream_alerts post by ID). purge-records refuses to run unless confirm: true is supplied AND at least one filter (older_than_days, connector, context, action) is set, preventing an accidental full table wipe. The row count is computed before the DELETE so the response is meaningful even though the multi-table DELETE returns the combined affected rows. delete-alert returns a 404 WP_Error when the ID is unknown or refers to a non-alert post type, which makes the ability safely idempotent. Both abilities ship with PHPUnit coverage that exercises permissions, schema validation, the happy path, the refusal paths, and (for purge-records) the meta cascade. * Add Abilities loader and base class tests Cover the two infrastructure pieces left untested by the per-ability suites: tests/phpunit/test-class-ability.php exercises the Ability abstract base via an in-file Fake_Ability_For_Test subclass (verifies get_meta() emits category and show_in_rest, conditionally adds an annotations key, and that the default permission_callback denies subscribers and grants admins; also asserts register() makes the ability retrievable via wp_get_ability() when the API is available). tests/phpunit/test-class-abilities.php covers the loader: is_available() tracks the WP_Ability class presence, is_enabled() reflects the advanced_enable_abilities_api option, the constructor only hooks wp_abilities_api_init when both gates pass, get_ability_slugs() lists all eleven slugs, load_abilities() instantiates each, and register_abilities() does not double-load on a second invocation. Resolves: XWPENG-13 * Fix lint errors in abilities tests and purge ability * Register stream ability category and fix get-record lookup - Register 'stream' category on wp_abilities_api_categories_init so abilities with category=stream pass core's category-existence check - Move 'category' from meta to top-level args in Ability::register(), matching the wp_register_ability() contract in WP 6.9 - Replace get-record's broken DB::get_records(['record' => $id]) call (Query class never implemented the singular 'record' arg) with a direct $wpdb single-row lookup - Snapshot/restore $plugin->settings->options in Abilities_TestCase so in-memory mutations from write-ability tests don't leak across tests - Update tests to satisfy the doing_action() guards on wp_register_ability() and wp_register_ability_category() * Add ability instructions, REST integration tests, fix HTTP routing Add a per-ability 'instructions' annotation: a 1-2 sentence note for AI agents about when and how to call each ability, distinct from the description (which describes what it does). Add tests/phpunit/abilities/test-rest-integration.php covering all three ability types end-to-end: dispatches actual WP_REST_Requests through WP_REST_Server and asserts 200/403/404/405 paths plus the list-abilities endpoint exposes all 11 stream/* abilities. Catches breakage in the real REST stack that direct execute() tests miss. Add idempotent: true to purge-records annotations. WP core's REST router only routes to DELETE when destructive AND idempotent are both true; without idempotent the controller expects POST. Refactor test action-firing to use the documented core test pattern of pushing onto $wp_current_filter rather than registering callbacks through add_action(). Cleaner, no global hook pollution, matches the convention used in WordPress core's own abilities-api tests. Make Abilities::register_abilities() defensive: skip per-ability register() calls when the ability is already registered, preventing spurious _doing_it_wrong notices when load_abilities() runs more than once in the same process. * Default $input to null in Ability::execute() signatures WP core's WP_Ability::invoke_callback() spreads zero arguments into the execute callback when the ability declares no input_schema (see wp-includes/abilities-api/class-wp-ability.php:506-512). Our previous 'execute($input)' signature required one argument, so any GET request to a no-input-schema ability raised a fatal ArgumentCountError and returned HTTP 500 to the caller. Add '$input = null' as the default on the abstract Ability::execute() plus all 11 concrete subclasses and the test fake. Null matches WP core's own conventions (their invoke_callback and check_permissions both default $input to null). Abilities that DO declare an input_schema continue to receive the parsed value verbatim from core, so the default sits unused for those. Caught by live e2e testing against WP 6.9.1 (Phase 4 of XWPENG-13-e2e.md): get-settings, get-connectors, and get-exclusion-rules previously fataled. * Harden Abilities API against critical CR findings - Authorization: read abilities use 'view_stream'; base default uses WP_STREAM_SETTINGS_CAPABILITY. Abilities registers a user_has_cap filter for REST contexts where Admin (and its filter) isn't loaded, so allowed roles can call read abilities consistently with the UI. - update-settings: allowlist {section}_{field} keys from registered fields, run incoming values through Settings::sanitize_settings(), reject payloads with no recognized keys. - create-exclusion-rule: schema gains format:ip and maxLength bounds; execute() sanitizes via sanitize_text_field(), validates IPs with FILTER_VALIDATE_IP, validates connector against registered slugs, rejects all-empty payloads. - purge-records: use rows_affected from the DELETE itself (no stale pre-count); run orphan-meta sweep after; fix MySQL alias syntax. - get-record: kept direct query (Query::query has a real array_shift bug with record__in) but adds explicit blog_id scoping on multisite so cross-site record leakage cannot occur. * Extract Trait_View_Stream_Permission for read abilities The 5 read-only abilities (get-records, get-record, get-alerts, get-connectors, get-exclusion-rules) all carried an identical permission_callback() returning current_user_can( 'view_stream' ) with the same rationale docblock. Move it into a shared trait so the authorization rule lives in one place. Each ability file require_once's the trait directly so per-test loaders (which require ability files individually) keep working without any autoloader changes. Net -35 LOC. Single-site and multisite Ability suites unchanged: 316 tests pass with the same skipped/incomplete counts as before. * Tighten get-records schema: orderby enum, __in maxItems - orderby: add enum bound to Query::query()'s actual sortable columns, and change the default from 'date' (not a real Stream column; silently fell back to ID) to 'created'. This makes the silent fallback impossible at the schema layer for REST callers and surfaces the contract for direct PHP callers. - user_id__in / connector__in: add maxItems: 100 so a caller cannot force an unbounded IN(...) clause from a single request. Tests cover schema shape, REST schema validation (orderby=date now rejected, 101 items rejected), and a behavioral regression that seeds two records with out-of-order created/ID and asserts orderby=created ASC actually orders by created -- not by ID, which is what the old silent fallback was doing. * create-alert: validate alert_type, set real post_title, split connector-context Mirror the admin form's create-alert flow (classes/class-alerts.php:766- 806) so API-created alerts behave identically to UI-created ones: - Validate alert_type against $plugin->alerts->alert_types (the registered notifier slugs). Schema can't enum these because wp_stream_alert_types is a filter -- a hardcoded enum would lock out 3rd-party notifiers. Reject unknown slugs with stream_unknown_alert_type / status 400 BEFORE inserting the post. - Split 'connector-context' input into trigger_connector + trigger_context meta keys, exactly like the admin form does. Without the split, Alert_Trigger_Context::check_record() silently let any connector through because trigger_connector was never populated -- alerts created via the API were effectively connector-agnostic. - Build an Alert model from the split meta and use $alert->get_title() for post_title, so the admin list shows a meaningful title instead of 'Auto Draft'. Tests cover the title regression, the connector-dash-context split, and the alert_type rejection path (including no-side-effects: no post is inserted when validation fails). * Fix Abilities is_enabled() to honor network option on network-activated multisite On a network-activated multisite install, the Abilities API toggle is saved to the wp_stream_network site option via Network::update_site_option(). However, Settings::get_options() only reads from get_site_option() when is_network_admin() is true; in REST and frontend contexts $plugin->settings->options reflects the (typically empty) per-site option. As a result, is_enabled() returned false in REST even when the network admin had enabled the API, making the entire Abilities API silently unreachable on network-activated sites. Read the network option directly via get_site_option($settings->network_options_key) when is_multisite() && $plugin->is_network_activated(), and fall back to the existing in-memory per-site options otherwise (preserves single-site and per-site-activated behavior). Adds two regression tests: - test_is_enabled_reads_network_option_when_network_activated (@group ms-required) flips the wp_stream_is_network_activated filter and proves is_enabled() follows the network option even when in-memory options say disabled. - test_is_enabled_reads_per_site_options_when_not_network_activated proves the network option is ignored when the plugin isn't network-activated. * Fix PHPCS errors in CR-fix tests Add //end try comments to satisfy Squiz.Commenting.LongConditionClosingComment on the new is_enabled() multisite tests, and lift the inline 'not a registered notifier' comment above the array literal so it doesn't trip Squiz.Commenting.PostStatementComment in the unknown-alert_type test. * Correct misleading comment on IP validation in create-exclusion-rule The comment claimed format:ip is a hint not enforced by rest_validate_value_from_schema(), which is wrong. WP core's rest_is_ip_address() in wp-includes/rest-api.php DOES validate the format and rejects bogus IPs at the schema layer with ability_invalid_input before our execute() runs. Reframe the in-method check as defense-in-depth for direct PHP callers who invoke $ability->execute() outside the REST stack. * chore(deps): update dependency uuid to ^11.1.1 (#1860) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Fix PHPCS: add @param tags to execute/permission_callback, whitelist view_stream cap * Address Copilot review: multisite purge scope, alert_meta shape, orderby test, UI text - purge-records: scope DELETE by blog_id on non-network-activated multisite to prevent cross-site record deletion; mirrors Admin::erase_stream_records(). - get-alerts: coerce missing alert_meta to {} instead of [""] so the response matches the declared object output schema. - test_orderby_created_actually_orders_by_created_not_id: invert insertion order so ID-order conflicts with created-order; the test would now fail if the implementation silently fell back to ORDER BY ID. - Settings UI: drop the specific /wp-abilities/v1/stream/* path from the toggle description (the actual route is owned by core's Abilities API). - Add regression tests for the missing alert_meta normalization and for the per-blog purge isolation (multisite-only). * Address Copilot follow-up: REST scoping on network-activated multisite, UTC cutoff, settings UX, hook isolation - get-record / purge-records: scope by blog_id = get_current_blog_id() on any multisite request that is not is_network_admin() (REST is never network-admin). Replaces the previous is_multisite_not_network_activated() predicate, which left network-activated installs unprotected against a per-site admin reading or purging another site's records via REST. Mirrors Network::network_query_args() default scoping. - purge-records: compute the older_than_days cutoff as a UTC DateTime in PHP and bind as %s, mirroring Admin::purge_scheduled_action(). The previous DATE_SUB(NOW(), INTERVAL %d DAY) used the MySQL server timezone while Stream's created column is UTC, so it could over- or under-purge on hosts where the server timezone is not UTC. - Settings: hide the "Enable Abilities API" toggle on per-site settings screens when Stream is network-activated. The setting is read from the network option in that mode, so a per-site checkbox would have been a silent no-op. - Tests: snapshot and restore $wp_filter['wp_abilities_api_init'] in Test_Abilities::setUp/tearDown so the existing remove_all_actions() calls inside individual tests don't pollute the global hook registry for subsequent tests in the same process. - Tests: lock the new behaviors with regression coverage: * get-record returns stream_record_not_found for foreign-blog IDs on multisite (must not leak via guessing). * purge-records does not cross blog boundaries on multisite (rename drops the _when_not_network_activated suffix). * purge-records older_than_days uses a UTC cutoff: rows seeded with explicit UTC timestamps purge correctly regardless of server tz. * Settings field is visible on non-network-activated installs. * Address Copilot follow-up: combine stream + meta deletion into one statement Replace the post-DELETE orphan sweep over the entire streammeta table with a single multi-table DELETE that removes matching stream rows and their meta in one statement, mirroring Admin::purge_scheduled_action(). Capture the parent count up-front so the response still reports records-deleted independent of how many meta rows were attached. Also drop a stale comment that referenced a guard pattern no longer in the code, and update the output-schema description so it matches the implementation (no more 'cascade' wording, since there is no FK). * Address Copilot follow-up: refresh settings via get_options() and tighten schema descriptions After update_option() the raw option array is sparse (omits defaults). Both update-settings and create-exclusion-rule were assigning that sparse array directly to $plugin->settings->options, leaving default-only keys missing for any later code in the same request. Refresh the in-memory copy via Settings::get_options() so defaults are merged in. Also align two schema descriptions with actual behavior: - update-settings now says unknown keys are *ignored* (the request only fails when no key matches a registered setting). This matches the array_intersect_key() filtering in execute(). - get-settings no longer promises advanced_enable_abilities_api in every response; that field is only registered on WP 6.9+ and, on network-activated multisite, only from network admin. * Fix alert_meta JSON shape for empty meta in stream/get-alerts When a wp_stream_alerts post has no alert_meta postmeta row, the ability was emitting an empty PHP array(), which JSON-encodes as [] and violates the declared 'type: object' output schema. Replace the empty array with a stdClass instance so wp_json_encode() emits {} as the output schema requires. Also strengthen the regression test to assert against the JSON-encoded payload (the previous PHP-level array() comparison was passing despite the wire-format bug). * Add docblocks to REST integration test methods to satisfy PHPCS Squiz.Commenting.FunctionComment.WrongStyle was flagging the three test methods that had only inline section comments above them, and Generic.Commenting.DocComment.MissingShort was flagging the @expectedIncorrectUsage-only docblock on test_unknown_ability_returns_404. * Address Copilot follow-up: bool checkboxes, purge error surfacing, idempotent category, doc fix Four issues from the latest Copilot review on this branch: - update-settings: PHP booleans on checkbox keys were silently coerced to '' by Settings::sanitize_setting_by_field_type() (it gates on is_numeric()). JSON clients naturally send true/false for checkbox-typed settings, so the round-trip would store '' instead of 0/1. Walk the registered fields to identify checkbox keys and normalize bools to 0/1 before sanitization. Add a regression test that round-trips both true and false. - purge-records: the multi-table DELETE result was discarded, so a database-side failure (lock-wait timeout, deadlock, etc.) would still return the pre-counted 'deleted' as if the purge had succeeded. Check $wpdb->query()'s return value and surface a 500 WP_Error on false. - class-abilities: register_category() now bails when the category is already registered, mirroring the idempotency pattern in register_abilities() and avoiding a core _doing_it_wrong notice when multiple loader instances exist (which the test harness already works around). - get-records: the orderby description claimed unknown values fall back to ID in Query::query(), but the schema enum rejects them at REST validation. Tighten the description to match. * chore(deps): update dependency @types/node to ^22.19.18 (#1862) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update dependency @playwright/test to ^1.60.0 (#1863) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update dependency @types/node to ^22.19.19 (#1864) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Bump tar-fs from 3.0.6 to 3.0.9 (#1754) Bumps [tar-fs](https://github.com/mafintosh/tar-fs) from 3.0.6 to 3.0.9. - [Commits](https://github.com/mafintosh/tar-fs/compare/v3.0.6...v3.0.9) --- updated-dependencies: - dependency-name: tar-fs dependency-version: 3.0.9 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump http-proxy-middleware from 2.0.6 to 2.0.9 (#1729) Bumps [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) from 2.0.6 to 2.0.9. - [Release notes](https://github.com/chimurai/http-proxy-middleware/releases) - [Changelog](https://github.com/chimurai/http-proxy-middleware/blob/v2.0.9/CHANGELOG.md) - [Commits](https://github.com/chimurai/http-proxy-middleware/compare/v2.0.6...v2.0.9) --- updated-dependencies: - dependency-name: http-proxy-middleware dependency-version: 2.0.9 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump axios from 1.7.9 to 1.8.4 (#1723) Bumps [axios](https://github.com/axios/axios) from 1.7.9 to 1.8.4. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v1.7.9...v1.8.4) --- updated-dependencies: - dependency-name: axios dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): update dependency npm-run-all2 to v8 (#1735) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update dependency copy-webpack-plugin to v14 (#1847) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore: replace uuid dependency with native crypto.randomUUID() (#1866) The uuid package was used in exactly one place (an E2E test) to generate a unique post title. Node 22 (and all modern browsers) ship crypto.randomUUID() natively, so the dependency is unnecessary. Also unblocks XWPENG-29: uuid v14 dropped CJS exports, which broke eslint-plugin-import's resolver chain (eslint-import-resolver-node 0.3.9 does not understand ESM-only "exports" maps). Removing uuid avoids needing to upgrade the resolver chain. Refs XWPENG-29 Supersedes #1861 * chore(deps): update dependency eslint-plugin-react-hooks to v7 (#1802) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update dependency @wordpress/e2e-test-utils-playwright to ^1.45.0 (#1833) * chore(deps): update dependency @wordpress/e2e-test-utils-playwright to ^1.45.0 * fix: override @types/node peer for e2e-test-utils-playwright @wordpress/e2e-test-utils-playwright@1.45.0 declares a peer of @types/node@^20.17.10, but Stream uses @types/node@^22.x. Use npm overrides to satisfy the peer with our v22 types — types-only, no runtime impact. --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Utkarsh Patel <itismeutkarsh@gmail.com> * chore(deps): update Node.js to v24 (#1867) Bumps the Node.js requirement from v22 to v24: - engines.node: ^22.22.2 -> ^24.15.0 - .nvmrc: 22 -> 24 - @types/node: ^22.19.19 -> ^24.12.4 Supersedes #1811 (which was stale against develop). * chore(deps): update wordpress monorepo (major) (#1868) * chore(deps): update wordpress monorepo (major) Bumps: - @wordpress/eslint-plugin: ^22.22.0 -> ^25.1.0 - @wordpress/scripts: ^30.27.0 -> ^32.1.0 Brings ESLint 8 -> 9 via @wordpress/eslint-plugin@25. Legacy .eslintrc.js still works under ESLint 9 (deprecation warnings only). Migration to eslint.config.js will be required before ESLint 10. Validated locally on Node 24: npm install, npm run build, and npm run lint:js all pass. Supersedes #1820 (stale against develop). Merging this PR auto-closes #1820. Closes #1820 * chore: migrate to ESLint flat config @wordpress/scripts@32 forces a flat eslint config; legacy .eslintrc.js is ignored entirely by the new lint-js wrapper. The previous CI hang was caused by wp-scripts injecting its default flat config and our custom rules being dropped. Changes: - Replace .eslintrc.js + .eslintignore with eslint.config.js - Add 'globals' as an explicit devDependency - Drop DEBUG=eslint:cli-engine from lint:js script - Autofix trailing commas across src/js and ui/js (comma-dangle rule) * fix(e2e): repair new-post spec and add Playwright E2E job to CI (#1873) * fix(e2e): use editor helpers and dismiss welcome dialog in new-post spec The editor-new-post E2E spec was failing on develop because: - Gutenberg's first-visit welcome dialog blocked the title locator. - The publish flow is now two-step (top bar + pre-publish panel). - The obsolete meta-box-loader response wait timed out (no meta boxes registered, so that request never fires). - The Stream plugin is deactivated by the shared setup, so post-publish was not being logged. Refactor to use the official @wordpress/e2e-test-utils-playwright Editor helpers (setContent, publishPost), reactivate Stream in beforeAll, and deactivate it again in afterAll to keep the network spec's preconditions. Closes #1872 * ci: add Playwright E2E job to lint-and-test workflow Runs the Playwright E2E suite against the Dockerized WordPress stack after the lint job succeeds. The job: - maps stream.wpenv.net to localhost so the existing hardcoded test URLs resolve; - installs Playwright's Chromium with system deps; - builds the plugin so the mounted build/ directory contains the current assets; - starts the Docker stack, waits for MySQL and HTTP readiness, then runs wp core multisite-install; - runs npm run test-e2e; - uploads the playwright-report directory as a workflow artifact on both success and failure for easier triage. * ci: fix MySQL wait, bump login-action, opt actions into Node 24, set timeouts - Use mariadb-admin instead of mysqladmin in the readiness probe; the mariadb:11.4.2 image only ships the mariadb-admin binary. - Bump docker/login-action from v2 to v3 to drop a Node 20 runtime. - Set FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true at the workflow level so the remaining v4 JavaScript actions execute under Node 24 ahead of the June 2026 forced switch. - Add timeout-minutes to both jobs (lint 10, e2e 12) so a hung step fails fast instead of consuming the default 6h limit. * ci: add diagnostics to the WordPress HTTP wait step Print the HTTP status on each attempt and dump 'docker compose ps' plus recent wordpress container logs on timeout so the next CI failure gives us something actionable. * ci: install Composer deps in the E2E job so wp-cli is available The WordPress container's PATH includes the project's vendor/bin directory, where wp-cli/wp-cli-bundle installs the 'wp' binary. Without composer install, 'docker compose run wordpress wp ...' fails with 'wp: not found'. * ci: probe MySQL with a real connection instead of mariadb-admin ping mariadb-admin ping returns success as soon as the server process is up, even while the server is still refusing TCP connections during init. Run an actual 'SELECT 1' as the wordpress user against the wordpress database so the wait reflects what wp-cli will see. * ci: use xwp/wait-for for MySQL and WordPress readiness probes The project already ships xwp/wait-for as a dev dependency. Replace the hand-rolled curl/docker exec loops with a single PHP one-liner that uses XWP\\Wait_For\\Tcp_Connection to verify both services are accepting TCP connections, and dump container state in a separate on-failure step. * ci: wait for MySQL from inside the wordpress container The host-mapped 3306 port becomes available before the in-network 'mysql' hostname is reachable from a freshly created sibling container. wp-cli runs inside a one-shot 'docker compose run wordpress' which connects over the docker network, so the readiness check needs to happen there too. Use 'npm run cli -- php -r ...' to run the wait-for probe in the same context wp-cli will use. * ci: run container-state dump after install/e2e steps Previously the diagnostic step sat between wait-for and install-wordpress, so it only triggered on a wait-for failure. Move it after the e2e step so any failure in install-wordpress or test-e2e also dumps state. * chore(deps): update dependency jquery to v4 (#1834) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * test(e2e): add admin UI smoke spec for jQuery-dependent widgets (#1874) Covers the Stream records list table, jQuery UI datepicker, select2 filter, Settings tab, and Alerts tab, and asserts that no uncaught JS errors are emitted during navigation. Designed to catch regressions from upstream jQuery, jQuery UI, or select2 version bumps that the existing unit and integration suites do not exercise. * chore(deps): bump composer/installers to v2 and mercator to dev-master (#1875) Widens composer/installers from ~1.0 to ^1.0 || ^2.0 and updates humanmade/mercator (require-dev) from ^1.0 to "dev-master as 1.1.0". The tagged mercator 1.0.3 pins composer/installers to ~1.0 and blocks v2; dev-master allows ~1.0 || ~2.0. mercator is dev-only and is not shipped in the release zip. Lock now resolves to composer/installers v2.3.0 and mercator dev-master (5adf68c, 2024-12-06). Plugin install paths under the v2 installer are unchanged. Closes #1821 * Address review feedback: route abilities through existing classes Keep abilities thin and delegate to the existing data-flow classes so the admin UI and the Abilities API stay on one code path. - Alerts: add STATUS_ENABLED / STATUS_DISABLED constants and a get_alerts() listing method; consume both from get-alerts ability. - Alert: add delete() method; consume from delete-alert ability. - create-alert ability now delegates the insert + meta save to Alert::save() instead of duplicating wp_insert_post / update_post_meta. - Connectors: add get_all() and get_slugs() helpers; consume from get-connectors and create-exclusion-rule abilities. - Record: add static get_by_id($id, $blog_id) for single-row + meta fetch; consume from get-record ability (multisite scoping kept in the ability layer, where the REST/network-admin distinction belongs). - Settings: add get_setting_value() that transparently reads the network option on network-activated multisite; Abilities loader uses it instead of its own multisite branching. * chore(deps): update wordpress monorepo (#1880) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Address second-round review feedback: multisite scoping and option routing - Add Settings::get_all_setting_values() and update_all_setting_values() that route through the network option on network-activated multisite. Previously, update-settings and create-exclusion-rule wrote to the per-site option in REST contexts, while is_enabled() and the admin UI read from the network option. Writes ghost-saved. - Route Abilities::filter_user_caps() through get_setting_value() so general_role_access is honored on network-activated REST contexts. Previously, editors granted view_stream via the network admin's Role Access setting silently got 403 from read abilities. - Scope get-records to the current blog when is_multisite() and not in network admin. Mirrors guards already in get-record and purge-records. The wp_stream_query_args filter that normally injects this is only registered inside Admin, which doesn't load in REST. - Coerce empty record meta to stdClass in Ability_Get_Record so the response satisfies the declared meta: object output schema. Same fix class as the earlier alert_meta normalization. - Stop hiding the Abilities API toggle from network admin UI on network-activated installs (keep it hidden only from the per-site settings screen, where saving would be a no-op). REST/CLI clients can now flip it via update-settings since writes are routed correctly. * Expose admin-only connectors to abilities Connectors::load_connectors() skips connectors whose register_frontend is false when the request isn't wp-admin. REST is ! is_admin(), so connectors like settings, editor, menus, taxonomies, blogs, installer, jetpack, and mercator were absent from stream/get-connectors and unknown to stream/create-exclusion-rule's validation enum -- even though those connectors are real, configurable in the wp-admin UI, and log records on the site. Add Connectors::get_all_including_admin_only() and get_all_slugs_including_admin_only(), which walk the full connector class list and return metadata for every dependency-satisfied connector, ignoring the is_admin() gate. They never call register() and do not mutate the live $this->connectors registry, so hooks fire exactly as before. Route the two affected abilities through the new methods. Verified against the live xwp-demo deployment: stream/get-connectors was returning 6 entries; with this fix it returns all 14+ that an admin sees in the UI, and stream/create-exclusion-rule now accepts connector slugs like "settings". * Route ability reads through the network option on network-activated multisite E2E surfaced a read/write mismatch: update-settings and create-exclusion-rule correctly wrote to wp_stream_network on network-activated installs, but get-settings and get-exclusion-rules still read from $plugin->settings->options (per-site, populated via Settings::get_options() which gates on is_network_admin() and falls back to get_option() in REST). After a write, the read endpoint returned the old per-site value instead of the just- persisted network value. Route both read abilities through Settings::get_all_setting_values() and make sure the in-memory cache that update_all_setting_values() refreshes also reads from the network option (Settings::get_options() can't be trusted on network-activated REST because is_network_admin() is always false there). Also return from update-settings via get_all_setting_values() so the response reflects the authoritative store. * Address review-body nits: defensive output coercion and test cleanup - Filter Ability_Get_Alerts results to the declared status enum (STATUS_ENABLED / STATUS_DISABLED) so a third-party-trashed alert or future broadening of Alerts::get_alerts() doesn't leak a non-enum status into the response and violate the output schema. - Normalize empty alert_meta to stdClass in Ability_Create_Alert output, mirroring the coerce in Ability_Get_Alerts. Currently unreachable because Alert::save() always writes the merged trigger keys, but the coerce is cheap defense and keeps the JSON output consistent. - Add Test_Ability_Update_Settings::test_write_targets_network_option_when_network_activated -- mirror of the read-side coverage in Test_Abilities::test_is_enabled_reads_network_option_when_network_activated, proves update-settings writes land in wp_stream_network on network- activated multisite and do not touch the per-site option. - Merge duplicate Test_Abilities::test_load_abilities_instantiates_each_slug and test_load_abilities_populates_all_slugs into a single test that covers both population and instantiation in one pass. * Centralize Trait_View_Stream_Permission loading Each of the 5 read abilities (get-records, get-record, get-alerts, get-connectors, get-exclusion-rules) previously did its own require_once for trait-view-stream-permission.php at the top of the class file. A new read ability that forgot the require would silently fall back to Ability::permission_callback() and gate on WP_STREAM_SETTINGS_CAPABILITY instead of view_stream. Move the require_once into the two chokepoints that actually load ability files: Abilities::load_abilities() for production and Abilities_TestCase::setUp() for PHPUnit. New abilities now get the trait available automatically -- no per-file require to forget. Verified via PHP introspection (trait_exists, class_uses on all 5 ability classes) and a live REST smoke test that admin GETs return 200 and subscriber GETs return 403 across all five read abilities. * feat(purge): add Action Scheduler constants for auto-purge migration Refs XWPENG-28 * feat(purge): wire Admin to AS-based auto-purge callbacks Adds stub method bodies so the action registration resolves; the real implementations land in subsequent commits. Refs XWPENG-28 * feat(purge): schedule auto-purge via Action Scheduler Replaces the legacy twicedaily WP-Cron event with a recurring AS action scheduled at 12h intervals. Clears any pre-existing legacy event on upgrade so the two cannot double-fire. Idempotent: re-running the setup while a recurring action is pending is a no-op. Refs XWPENG-28 * feat(purge): replace inline DELETE with AS chain enqueue The recurring action now snapshots the TTL cutoff in UTC, fires wp_stream_auto_purge for back-compat, applies an overlap guard, and enqueues the first batch into the auto-purge chain. Multisite scoping (per-site activation) is encoded as blog_id; 0 means 'all blogs'. Defaults are merged into the options array via wp_parse_args so a partially-saved option still gets the missing keys. Refs XWPENG-28 * feat(purge): add batched auto_purge_batch worker Window-based deletion (ID range \u2264 wp_stream_batch_size) joined against stream_meta in a single statement, mirroring erase_large_records(). Snapshotted UTC cutoff is threaded through each batch in the chain. blog_id == 0 means 'all blogs' for network-activated installs; non-zero scopes to that blog. Schedules the orphan reaper as the terminal step when no more rows are eligible. Refs XWPENG-28 * feat(purge): add terminal orphan reaper to auto-purge chain Runs delete_orphaned_meta() once at the end of every chain so installs that already had orphan meta from historical timed-out purges heal over time without operator intervention. Lifts delete_orphaned_meta() visibility from private to protected so the reaper can call it. Refs XWPENG-28 * feat(purge): add manual 'Clean Orphaned Meta' link on Settings \u2192 Advanced Settings UI link is nonced for users with WP_STREAM_SETTINGS_CAPABILITY; the ajax handler schedules a one-shot reaper via Action Scheduler. Idempotent: re-clicking while a reaper is pending is a no-op. Bails out early under WP_STREAM_TESTS so PHPUnit doesn't exit the worker. Refs XWPENG-28 * test(e2e): cover manual orphan-meta cleanup link Asserts the Clean Orphaned Meta link renders on Settings \u2192 Advanced, points at admin-ajax.php with the expected action + nonce, and that following the link redirects to the settings page with a confirmation marker in the URL. Also fixes the redirect target in the handler to use network_settings_page_slug on network-activated installs. Refs XWPENG-28 * fix(purge): ensure forward progress on tables with concurrent writes The previous auto_purge_batch SELECT used 'WHERE created < cutoff ORDER BY ID DESC LIMIT 1' to find the next window's top, but on hosts that are actively logging during a chain the ID space is sparse (eligible rows interleaved with fresh rows whose created > cutoff). That caused each subsequent batch to find a top only ~30 IDs below the previous, stalling progress. Match Admin::erase_large_records()'s pattern instead: pass last_entry (the lower bound of the previous window) through the chain and use 'WHERE ID < last_entry' to guarantee the next batch starts strictly below the previous window. Stride is now exactly wp_stream_batch_size IDs per batch. Verified end-to-end on a multisite install seeded to ~320k aged records: chain drains in ~35 batches at batch_size=10000 and terminates with zero orphans. Refs XWPENG-28 * style: address PHPCS findings in auto-purge implementation - Replace interpolated-SQL pattern in auto_purge_batch with explicit prepared statements per (blog_id, last_entry) combination. - Correct @return doctype on wp_ajax_clean_orphan_meta to bool|void. - Settings UI array alignment. - Add @param to set_records_ttl test helper. Refs XWPENG-28 * docs(changelog): note XWPENG-28 auto-purge migration Refs XWPENG-28 * fix(purge): harden missing-option fallback against filtered defaults Settings::get_defaults() runs every field through wp_stream_settings_option_fields, which Network::get_network_admin_fields() uses to strip 'records_ttl' from the per-site option's defaults set. Outside any admin context (Action Scheduler, WP-CLI, system cron) the per-site option_key is in effect, so the filtered defaults never contained general_records_ttl. Without this fallback the auto-purge silently no-ops on every install where the option is missing, defeating the whole point of fixing this on bloated sites. Hardcoded fallback to 30 days (the documented default on the settings field itself) when general_records_ttl is absent after the merge. Caught by section 9 of the e2e plan. Refs XWPENG-28 * feat(purge): consult wp_stream_is_large_records_table for small-table fast path The acceptance criteria require the auto-purge to honor the same wp_stream_is_large_records_table filter the manual reset uses, so ops only have one knob to tune table-size semantics. The recurring callback now counts eligible rows once, passes the count through Plugin::is_large_records_table(), and: - Small table (filter returns false): runs a single inline multi-table DELETE for the eligible rows, then enqueues the orphan reaper as a terminal AS action so the heal step stays observable in Tools \u2192 Scheduled Actions. - Large table (filter returns true, default for record_count > 1M): enqueues the batched chain as before. Tests: - New test_purge_scheduled_action_small_table_fast_path covering the inline-DELETE branch. - New test_purge_scheduled_action_large_table_uses_batched_chain exercising the batched branch via the filter. - Existing batched-path tests now opt into the chain explicitly via add_filter('wp_stream_is_large_records_table','__return_true'). Refs XWPENG-28 * feat(purge): expose Admin::is_running_auto_purge() state probe Mirrors is_running_async_deletion() but checks the auto-purge group (batch + reaper). The recurring scheduler is intentionally excluded from the probe so it doesn't always report 'running' under normal operation. Settings UI uses this in the next commit to render an 'Auto-purge currently running' notice on Settings \u2192 Advanced. Refs XWPENG-28 * feat(purge): hide manual Clean Orphaned Meta link while chain is running When the auto-purge chain is active (batch worker or reaper pending), the manual Settings \u2192 Advanced cleanup link is hidden and the field description is swapped to explain that the reaper will run as part of the active cycle. Mirrors how Reset Stream Database hides itself during async deletion. Refs XWPENG-28 * test(e2e): cover purge-active and purge-idle UI states Two new specs exercise the Settings \u2192 Advanced field behaviour driven by Admin::is_running_auto_purge(): - Active state: seed a pending reaper action via wp-cli, assert the Clean Orphaned Meta link is removed from the DOM and the swapped description ('Auto-purge is currently running') is visible. - Idle state: drain the seeded action and assert the link is restored. Both specs use a small wp-cli helper (execSync into the wordpress container) to seed/clear AS state, keeping the test free of any browser-side timing on the AS worker. Refs XWPENG-28 * test(e2e): harden orphan-cleanup spec against activation races - wpEval helper now swallows non-zero exits from the wordpress container instead of throwing into the test runner. State seeding is best-effort; the test's own assertions are the source of truth. - beforeAll waits for the post-activation navigation and explicitly confirms 'Network Deactivate Stream' is visible before any test runs, failing fast instead of letting every test silently hit the 'Sorry, you are not allowed to access this page' redirect when a prior suite leaves Stream deactivated. In-isolation: spec is stable across 3 consecutive runs. Pre-existing cross-spec activation races (editor-new-post, admin-ui-smoke) remain out of scope here. Refs XWPENG-28 * docs(changelog): note small-table fast path + running-state UX Refs XWPENG-28 * fix(e2e): drop unused catch binding for ESLint CI runs ESLint with no-unused-vars; the wpEval helper's catch block declared 'err' but never referenced it. Use the optional binding form 'catch {}' which is supported on the runner's Node version (22+). Refs XWPENG-28 * test: drop two low-value auto-purge unit tests - test_auto_purge_action_constants_exist was a tautology: it asserted that four constants equal the string values they are declared with. The test catches nothing that static analysis or a typo in the consumer wouldn't catch. - test_auto_purge_batch_respects_wp_stream_batch_size_filter only verified the filter was *called* (invocation counter), not that the returned value affected behaviour. test_auto_purge_batch_deletes_ window_and_chains_next_batch already drives the chain with a custom batch_size and asserts on the resulting chain, which is the functional contract that matters. No coverage lost. Refs XWPENG-28 * review: address code review feedback 1. auto_purge_batch() now throws InvalidArgumentException on empty cutoff instead of silently returning. A bare 'return' caused AS to mark the action complete; throwing makes AS log it as failed and surface it in Tools > Scheduled Actions. New PHPUnit test covers the throw path. In practice this branch is unreachable because purge_scheduled_action() always populates the cutoff, but the guard exists for third-party code that may enqueue with bad input. 2. wp_ajax_clean_orphan_meta() now uses $this->settings_cap to match the rest of the file (wp_ajax_reset, ajax_filters). The bare WP_STREAM_SETTINGS_CAPABILITY constant could break installs that override the capability via the property after construction. 3. The Settings 'Clean Orphaned Meta' field now calls Admin::is_running_auto_purge() once per render instead of twice. Extracted the field-building logic into a small helper so the state probe runs once and both the 'type' and 'desc' branches reuse the result. Refs XWPENG-28 * Expose Stream abilities via MCP when the WordPress MCP Adapter is present Stream advertises every ability with meta.mcp.public = true so that the WordPress MCP Adapter's default server picks them up via its discover-abilities tool without Stream having to load or initialize the adapter itself. Bartosz's local test showed an empty list of abilities because the adapter plugin was installed but its sub-dependency vendor/ was missing, so its autoloader bailed and McpAdapter never booted. - Add meta.mcp.public to Ability::get_meta() so the flag is set on every registered ability via wp_register_ability(). - Add wordpress/mcp-adapter to require-dev so composer install drops it into local/public/wp-content/plugins/mcp-adapter as a wordpress-plugin. Contributors only need to activate it. - Wire a composer post-install/post-update script that recursively installs the adapter's own sub-dependencies (wordpress/php-mcp-schema). Without this the adapter's Autoloader returns false and its Plugin bootstrap silently skips. - Update the Advanced settings field label and description to make the MCP exposure explicit: enabling the Abilities API also publishes abilities via MCP when the adapter is installed. - Document the MCP setup steps in contributing.md. The MCP HTTP transport at /wp-json/mcp/mcp-adapter-default-server now exposes all 11 stream/* abilities through the adapter's default server discover-abilities tool. * Add HTTPS to the local dev environment via mkcert and an Apache SSL vhost WordPress Application Passwords and several MCP clients require HTTPS, so contributors had to either set up TLS manually or work around the limitation. Wire a dedicated mkcert Docker service (adopted from xwp/vip-site-template) that generates a locally-trusted cert + key into local/certs/, and configure the WordPress container's Apache to serve the site on :443 using those files. - New mkcert service in docker-compose.yml: builds local/docker/mkcert/ (golang:alpine + mkcert v1.4.4), generates cert.pem + key.pem into local/certs/ once per environment, skips if the files already exist. - WordPress service depends on mkcert and mounts ./local/certs read-only into /etc/ssl/certs/stream-local. - New Apache vhost local/docker/wordpress/apache-ssl.conf serves the same DocumentRoot on :443 with the mounted cert. Dockerfile enables mod_ssl and a2ensite's the new vhost. HTTP on :80 keeps working alongside. - local/certs/ is gitignored. Contributors still need to run `mkcert -install` on the host once to add the local CA to their system trust store; without it HTTPS works but the browser shows an untrusted-cert warning. The certificate generation itself happens inside the container, so contributors do not need to install mkcert on the host just to spin up the environment. * Publish the mkcert helper image via docker-images.yml Add a mkcert build target to docker-compose.build.yml so the existing "Build and Publish Docker Images" workflow rebuilds and pushes ghcr.io/xwp/stream-mkcert alongside the stream-wordpress images on master pushes. Reference the published image from docker-compose.yml (with the local build context retained as a fallback for fresh contributor checkouts and PR branches that haven't been published yet). The lint-and-test E2E job pulls both wordpress and mkcert with --ignore-pull-failures so first pushes of feature branches stay green even before docker-images.yml has uploaded the new mkcert image to the registry; Docker Compose then falls back to the local build context for any image it couldn't pull. * Extract mcp-adapter post-install hook into a dedicated PHP script The inline @php -r string in composer.json had grown into an unreadable nested-quote mess. Move the same logic into local/scripts/install-mcp-adapter-deps.php with proper comments explaining the three branches: - Adapter directory absent (production --no-dev releases): silent no-op. - Adapter directory present, vendor populated: silent no-op. - Adapter directory present, vendor missing (typical dev install): run `composer install --no-dev` inside the adapter directory so its Autoloader can find vendor/autoload.php on plugin activation. No behavior change for end users. Release safety is unchanged: --no-dev installs skip wordpress/mcp-adapter entirely, so the adapter directory never exists in shipped artifacts and the script's first guard returns silently. /local/ is also excluded by .distignore as a second backstop. * Force HTTPS across the local dev environment and tests Move the dev environment from HTTP to HTTPS so it matches what WordPress Application Passwords and most MCP clients expect by default. - wp-cli.yml: set url to https://stream.wpenv.net so wp core multisite-install writes https values to wp_options at install time. No wp-config overrides needed -- the DB values are authoritative. - Playwright: swap all http://stream.wpenv.net references to https://. Add ignoreHTTPSErrors: true so Playwright's Chromium accepts the mkcert-issued certificate without requiring `mkcert -install` on the runner / host. - CI workflow: wait for TCP :443 instead of :80 in the E2E job since HTTPS is now the canonical entry point. - contributing.md: dev-environment URL is now https; documented one-line wp search-replace for existing checkouts to upgrade their DB values without a full reinstall. No Apache HTTP->HTTPS redirect added; with siteurl and home both https in the DB, WordPress already generates https URLs everywhere and redirects http wp-admin / wp-login hits via the canonical-URL machinery, so the extra redirect would only catch direct `curl http://` cases that are useful to keep working for quick TCP probes. * Rebuild the WordPress image in CI so branch Dockerfile changes apply The published ghcr.io/xwp/stream-wordpress image is refreshed only by docker-images.yml on master pushes. When this branch updates local/docker/wordpress/Dockerfile (e.g. `a2enmod ssl` + the stream-ssl vhost), `docker compose pull wordpress` happily fetches the stale published image and skips the local build context. Add an explicit `docker compose build wordpress` step after the pull so the branch's Dockerfile actually lands in the running container. Without this, CI's HTTPS smoke fails with ERR_CONNECTION_CLOSED on :443 because Apache has no SSL vhost configured. Docker's layer cache keeps the rebuild cheap when the Dockerfile is unchanged from the published base. * Simplify the Enable Abilities API setting description The previous description packed three sentences and an internal URL into a single line, making the Stream settings UI visually dense and forcing users to scan past technical details to find the primary action. Trim to one sentence that surfaces what enabling does, treats MCP as a parenthetical secondary fact, and keeps the WP version requirement. Title is unchanged (still "Enable Abilities API and MCP"). * review: address second round of code review feedback 1. Settings::updated_option_ttl_remove_records() now triggers an immediate purge directly. Previously it relied on the legacy wp_stream_auto_purge action being hooked to purge_scheduled_action, which this PR severed. Without this fix, shortening the TTL did not take effect until the next 12h recurring tick. 2. TTL fallback uses isset() instead of empty(), so an explicit '0' set via CLI/SQL is no longer silently overridden to 30. Added an explicit short-circuit: a non-positive TTL bails out of the cycle entirely. The UI enforces min=1; the only paths to 0 are operator error, and bailing out (records stop being purged) is a less destructive failure mode than honoring it (records get wiped repeatedly every 12h). 3. Overlap guard now reuses Admin::is_running_auto_purge(), so a pending reaper also blocks a new chain. Previously only a pending batch action blocked the guard, leaving a small window between chain completion and reaper completion where a new chain could stack. Three new PHPUnit tests cover each behaviour. Refs XWPENG-28 * review: address third round of code review feedback - Move wp_stream_auto_purge BC action to after all bail-out checks so it fires only when a purge actually runs (was firing on every recurring tick regardless of whether work happened). - Extend is_running_auto_purge() to also check IN-PROGRESS actions via as_get_scheduled_actions() so the overlap guard cannot let a second chain stack against rows the running batch worker is still touching. - Settings TTL-shortened path now enqueues AUTO_PURGE_ACTION via as_enqueue_async_action() so the immediate purge serializes through Action Scheduler instead of bypassing the overlap guard with an inline call. Inline fallback retained for when AS is unavailable. - Render an admin notice for wp_stream_message=orphan_meta_cleanup_scheduled on both admin_notices and network_admin_notices so the post-redirect UX is actually visible (was a half-built feature: redirect happened, no notice rendered). - E2E wpEval(): switch from 'docker compose run --rm' to 'docker compose exec -T' to attach to the long-lived container (~3-5s saved per call) and surface failures via console.warn instead of silently swallowing errors. - PHPUnit: add coverage for both the BC-action-suppressed-on-bailout path and the in-progress-action overlap guard. Update the TTL-shortened test to assert the AS enqueue path. * fix(e2e): pass --user www-data to docker compose exec The previous quick-win switch from 'docker compose run --rm --user $(id -u)' to 'docker compose exec -T' dropped the user mapping. On local dev that happened to work because the wordpress container's default exec user is what the runtime image inherits; on CI it defaults to root and wp-cli refuses to run with: YIKES! It looks like you're running this as root. $(id -u) only worked on the host because UID 1000 mapped to www-data inside the container — that's not portable to the GitHub Actions runner (UID 1001). Pin the exec user explicitly to www-data, which owns the WordPress files inside the container. Failing run: actions/runs/26144103763 * chore(deps-dev): bump johnbillion/query-monitor from 3.16.3 to 3.20.4 (#1879) Bumps [johnbillion/query-monitor](https://github.com/johnbillion/query-monitor) from 3.16.3 to 3.20.4. - [Release notes](https://github.com/johnbillion/query-monitor/releases) - [Commits](https://github.com/johnbillion/query-monitor/compare/3.16.3...3.20.4) --- updated-dependencies: - dependency-name: johnbillion/query-monitor dependency-version: 3.20.4 dependency-type: direct:development ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Merge pull request #1878 from xwp/dependabot/composer/symfony/process-5.4.51 chore(deps-dev): bump symfony/process from 5.4.46 to 5.4.51 * Bump phpunit/phpunit from 9.6.20 to 9.6.33 (#1876) Bumps [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) from 9.6.20 to 9.6.33. - [Release notes](https://github.com/sebastianbergmann/phpunit/releases) - [Changelog](https://github.com/sebastianbergmann/phpunit/blob/9.6.33/ChangeLog-9.6.md) - [Commits](https://github.com/sebastianbergmann/phpunit/compare/9.6.20...9.6.33) --- updated-dependencies: - dependency-name: phpunit/phpunit dependency-version: 9.6.33 dependency-type: direct:development ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Merge pull request #1869 from xwp/dependabot/npm_and_yarn/axios-1.16.0 Bump axios from 1.8.4 to 1.16.0 * chore(deps-dev): bump composer/composer from 2.7.7 to 2.9.8 (#1877) Bumps [composer/composer](https://github.com/composer/composer) from 2.7.7 to 2.9.8. - [Release notes](https://github.com/composer/composer/releases) - [Changelog](https://github.com/composer/composer/blob/main/CHANGELOG.md) - [Commits](https://github.com/composer/composer/compare/2.7.7...2.9.8) --- updated-dependencies: - dependency-name: composer/composer dependency-version: 2.9.8 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): update dependency npm-run-all2 to v9 (#1881) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * refactor(e2e): use Playwright baseURL for admin URLs Set baseURL in playwright.config.js and replace absolute https://stream.wpenv.net references in every spec with relative paths. Also drops the docker-exec state seeding from admin-orphan-cleanup.spec.js so it matches the browser-only convention of the rest of the suite; the running-state UX it seeded is covered by PHPUnit. * Switch dev dependencies from WPackagist to WP Packages (#1858) Co-authored-by: Utkarsh Patel <itismeutkarsh@gmail.com> * Update Node.js to ^24.16.0 (#1883) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update dependency npm-run-all2 to ^9.0.1 (#1886) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * review: address fourth round of code review feedback 1. wp_ajax_clean_orphan_meta() now uses is_running_auto_purge() for its idempotency check instead of as_has_scheduled_action(), which only probes PENDING. The new probe checks PENDING + RUNNING across the batch worker and reaper, matching the UI hide condition and closing the small CSRF/stale-URL window where a duplicate reaper could be enqueued while a chain is mid-flight. function_exists() guard updated to as_get_scheduled_actions accordingly. 2. Add explicit case 'none' in Settings::render_field() so the intent of the running-state UI swap (hide the value column, let the description carry the message) is captured in code instead of relying on the switch's implicit fall-through. Used by both the Reset Stream Database row and the new Clean Orphaned Meta row. 3. Expand auto_purge_batch() docblock to acknowledge the last_entry-based forward-progress trade-off: any eligible row that lands inside the already-touched ID range after that batch ran is skipped by the current chain and picked up on the next tick. Sources are bounded (dev seeders, importers, clock skew); steady-state logging is monotonic so this is a no-op for production traffic. 4. Changelog clarifies that the wp_stream_auto_purge BC action's semantics have changed: it used to fire on every WP-Cron tick regardless of whether work happened, now fires only after all bail-out checks pass. Integrations that relied on the legacy tick-rate semantics should switch to AUTO_PURGE_ACTION. 5. New PHPUnit test test_settings_ttl_shortened_via_option_update_enqueues_purge exercises the full hook wiring (update_option / update_site_option → Settings::updated_option_ttl_remove_records → AS enqueue) end-to- end. The existing unit test calls the method directly and would still pass if Network::__construct() lost its update_site_option_wp_stream_network hook registration. 6. New PHPUnit test test_ajax_clean_orphan_meta_denies_users_without_settings_cap covers the security boundary on the manual cleanup handler. Uses _handleAjax() so WP_Ajax_UnitTestCase's buffer machinery runs. 7. New PHPUnit test test_clean_orphan_meta_field_reflects_running_state asserts the integration between is_running_auto_purge() and the field-rendering decision in Settings::build_clean_orphan_meta_field(). Replaces the e2e specs removed in b4c8f287 for activation-race fragility with deterministic PHPUnit coverage. 8. New PHPUnit test test_purge_scheduled_action_scopes_to_current_blog_when_not_network_activated forces the multisite-not-network-activated branch via a Plugin stub and asserts the enqueued batch carries get_current_blog_id() rather than 0. The acceptance criterion ("per-site activations only purge the current blog") was only covered at the batch-worker layer; the routing decision in purge_scheduled_action() was untested. Refs XWPENG-28 * test(phpunit): load Trait_View_Stream_Permission in bootstrap (#1888) Production loads the trait via Abilities::load_abilities() before any class-ability-*.php file is included, but PHPUnit's coverage post-processor with processUncoveredFiles="true" walks the <coverage><include> directories directly via include_once and bypasses the plugin's loader. On PHP versions where the order surfaces the issue, each read ability fatals on `use Trait_View_Stream_Permission` during coverage generation, breaking the clover.xml emit step and failing the test target with a non-zero exit even when all tests pass. Require the trait at bootstrap so it is available before any include_once walks the abilities directory. Mirrors the production chokepoint without reintroducing per-file require_once at the top of each ability class. * Skip async deletion AS query outside admin (#1885) * Skip async deletion AS query outside admin and memoise it `Admin::is_running_async_deletion()` is called from `Settings::get_fields()` and `Settings::get_deletion_warning()` to decide whether to render a "deletion in progress" warning on the Stream settings screen. Because `Settings::__construct` eagerly calls `get_options()` on `init`, and `get_options()` walks the field definition via `get_defaults()`, the AS query runs twice on every pageload — front-end included — even though the result is only ever consumed by admin UI. Short-circuit when `is_admin()` is false (the only callers are admin UI render paths) and memoise per request to collapse the duplicate within an admin pageload. Fixes #1884 * Lift AS gate into Settings; also skip auto-purge query on front-end The previous commit short-circuited Admin::is_running_async_deletion() outside admin and memoised it. That works for the immediate cost, but has two issues: 1. It changes the semantic contract of a public static m…Configuration menu - View commit details
-
Copy full SHA for e47dc08 - Browse repository at this point
Copy the full SHA e47dc08View commit details
Loading
This comparison is taking too long to generate.
Unfortunately it looks like we can’t render this comparison for you right now. It might be too big, or there might be something weird with your repository.
You can try running this command locally to see the comparison on your machine:
git diff develop...master