Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,23 @@ jobs:
- name: Lint every PHP file
run: find . -name '*.php' -not -path './.git/*' -print0 | xargs -0 -n1 -P4 php -l

phpcs:
name: WordPress coding standards
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
coverage: none
tools: composer:v2
- run: composer install --no-interaction --no-progress
# phpcs.xml.dist: WordPress-Extra + PHPCompatibilityWP (7.4-) on the files the archive ships. Every
# phpcs:ignore in the code carries its reason on the same line; the gate honours them, and the second run
# lists what they cover so a review can read it in the log without trusting the annotations.
- run: composer lint
- run: composer lint:all -- --report=source || true

metadata:
name: Plugin metadata is consistent
runs-on: ubuntu-latest
Expand Down
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,15 @@ SHA-256 is stored.
GET. The first accepted call of any route pairs the key with it — an access
check that reads a non-existent export (`404 bridge_not_found`) included;
another pairing is refused.
- It acts as the administrator who created it, while they still have `export`
+ `manage_options`, and opens these routes only. It sees only the exports it
started, and runs one at a time: another scope while one runs is
`409 bridge_export_live`; once that one is `ready` or `failed`, a rerun starts.
- It runs with **no WordPress user** (`get_current_user_id()` is 0 for the whole
request) and opens these routes only; it is valid while the administrator who
created it still has `export` + `manage_options` (checked with `user_can()` on
their id, never by signing them in). It sees only the exports it started —
indexed in the plugin's own option, never in a user's meta — and runs one at a
time: another scope while one runs is `409 bridge_export_live`; once that one
is `ready` or `failed`, a rerun starts. An export the administrator started
with an application password is never reused, bound or replaced by the key.
Revoking the key removes its exports.
- Unpaired, it stops working an hour after it was created. Paired, it has no
idle limit (reruns can be days apart) and works until 14 days after it was
created, or until it is revoked (the Bridge screen, or `DELETE /key`) or
Expand Down
13 changes: 8 additions & 5 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ listed at the end, and they are not paperwork.

```bash
npm ci --ignore-scripts
composer install && composer lint # PHP_CodeSniffer: WordPress-Extra + PHPCompatibilityWP (phpcs.xml.dist)
npm run test:wordpress # container acceptance suite
npm run test:plugin # official Plugin Check
npm run test:store # contentrain validate + canonical byte parity
Expand All @@ -35,10 +36,11 @@ nothing to check. Start from `docker compose -f tests/compose.yml down -v` — a
reused stack keeps the content of previous runs, which inflates the check and
entry counts and makes a local number look unlike CI's.

Last full clean run (0.7.0): 394 acceptance checks · Plugin Check 0 errors / 4
warnings (the four WPML hook names, below) · `contentrain validate` 14 models,
62 entries · canonical byte parity 55 files · 5 documents carry frontmatter ·
handoff, reader and uninstall pass · archive 33 runtime files.
Last full clean run (0.7.1): 399 acceptance checks · Plugin Check 0 errors / 0
warnings (the four WPML hook names, below, now carry a reasoned `phpcs:ignore`)
· `contentrain validate` 14 models, 62 entries · canonical byte parity 55 files
· 5 documents carry frontmatter · handoff, reader and uninstall pass · archive
33 runtime files · `composer lint` 0 errors / 0 warnings on 27 packaged files.

## The reader gate

Expand Down Expand Up @@ -158,7 +160,8 @@ another green CI run.

Plugin Check 2.1.0 (all categories, including plugin_repo and security) on the 0.6.5 release zip: zero errors,
26 warnings, in three classes. Since 0.7.0 each direct query carries a `phpcs:ignore` with its reason on its own line,
so the run shows only the first class. Reviewers accept all three with a stated reason; do not silence them by
and since 0.7.1 the WPML hook names do too, so the run shows none of them; `composer lint:all -- --report=source`
in CI lists every ignored sniff. Reviewers accept all three with a stated reason; do not silence them by
weakening the check.

- **Third-party hook names (4):** `wpml_default_language`, `wpml_post_language_details`, `wpml_element_trid` and
Expand Down
17 changes: 16 additions & 1 deletion assets/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,23 @@
const STEP_TIMEOUT = 120000;
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
function retryable(message) { return Object.assign(new Error(message), { retryable: true }); }
/**
* The request as plain form fields, one per value, the way PHP reads them: a list as `name[]`, an object as
* `name[key]`, a flag as 1 or 0. The server reads each field once with the function for its type; no JSON blob.
*/
function fields(payload, body = new URLSearchParams(), prefix = '') {
for (const [name, value] of Object.entries(payload)) {
const key = prefix ? `${prefix}[${name}]` : name;
if (value === null || value === undefined) continue;
if (Array.isArray(value)) value.forEach((item) => body.append(`${key}[]`, String(item)));
else if (typeof value === 'object') fields(value, body, key);
else if (typeof value === 'boolean') body.append(key, value ? '1' : '0');
else body.append(key, String(value));
}
return body;
}
async function api(payload, timeout = 0) {
const body = new URLSearchParams({ action: 'contentrain_bridge', nonce: ContentrainBridge.nonce, payload: JSON.stringify(payload) });
const body = fields({ action: 'contentrain_bridge', nonce: ContentrainBridge.nonce, ...payload });
const controller = new AbortController();
const timer = timeout ? setTimeout(() => controller.abort(), timeout) : 0;
let response;
Expand Down
24 changes: 24 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "contentrain/wordpress-bridge",
"description": "Development dependencies for Contentrain Bridge: coding-standards checks. Not part of the installed plugin.",
"type": "wordpress-plugin",
"license": "GPL-2.0-or-later",
"require": {
"php": ">=7.4"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
"wp-coding-standards/wpcs": "^3.1",
"phpcompatibility/phpcompatibility-wp": "^2.1"
},
"scripts": {
"lint": "phpcs --standard=phpcs.xml.dist",
"lint:all": "phpcs --standard=phpcs.xml.dist --ignore-annotations"
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
},
"sort-packages": true
}
}
Loading
Loading