Push and pull post syndication between WordPress sites and external endpoints.
Branch note: this file describes
develop, the 2.x line. A separate long-lived3.xbranch holds an unmerged DDD rewrite (namespacedAutomattic\Syndication,Domain/Application/Infrastructurelayers, a DI container). PR #197 targets3.x, notdevelop. Nothing below applies to that branch.
| Property | Value |
|---|---|
| Main file | push-syndication.php |
| Text domain | push-syndication |
| Namespace | None — classes use the Syndication_ prefix |
| Source directory | includes/ |
Version, Requires PHP and Requires at least are not repeated here: read them
from the plugin headers in push-syndication.php, which the release workflow
treats as canonical. README.md carries the same headers for WordPress.org.
syndication/
├── includes/ # Flat directory of class-*.php and interface-*.php files
│ └── css/ # Admin styles for the site editor and site list
├── languages/ # push-syndication.pot
├── tests/
│ ├── Unit/ # Brain Monkey, no WordPress bootstrap
│ └── Integration/ # Real WordPress via wp-env
├── .github/workflows/ # cs-lint, unit, integration, release, deploy
├── .phpcs.xml.dist # PHPCS configuration
└── .wp-env.json # wp-env configuration
There is no PSR-4 autoloading. Everything is require_onced from
push-syndication.php, so a new class file must be wired in there explicitly.
WP_Push_Syndication_Server— the main controller. Registers thesyn_sitepost type andsyn_sitegrouptaxonomy, admin UI, settings, and the push/pull cron jobs. Instantiated into$GLOBALS['push_syndication_server'].Syndication_Client_Factory— maps a transport type to a class by string concatenation:Syndication_{$transport_type}_Client. Also proxiesdisplay_settingsandsave_settingsto that class.Syndication_Client(interface) — implemented bySyndication_WP_REST_Client,Syndication_WP_RSS_Client,Syndication_WP_XML_ClientandSyndication_WP_XMLRPC_Client.Syndication_Encryption— wraps aSyndication_Encryptorimplementation.Syndication_Encryptor_OpenSSLis used on PHP 7.1+;Syndication_Encryptor_MCryptonly below that. Instantiated into$GLOBALS['push_syndication_encryption'].Syndication_Logger— logging, withSyndication_Logger_Viewer,Syndication_Logger_List_TableandSyndication_Logger_Admin_Noticefor display.Syndication_Event_Counter,Syndication_Site_Failure_Monitor,Failed_Syndication_Auto_Retry— failure tracking and retry, each instantiated at load time.Syndication_CLI_Command— registered aswp syndication, loaded only whenWP_CLIis defined. Four subcommands:push_all_posts,push_post,pull_site,pull_sitegroup.
Runtime requires only composer/installers. Dev: automattic/vipwpcs,
yoast/wp-test-utils, phpunit/phpunit ^9, php-parallel-lint/php-parallel-lint,
phpcompatibility/phpcompatibility-wp, and sirbrillig/phpcs-changed (pinned to
^2.13; see the comment in cs-lint.yml before bumping it).
composer cs # Check code standards (PHPCS)
composer cs-fix # Auto-fix code standard violations (PHPCBF)
composer lint # PHP syntax lint
composer lint-ci # Syntax lint with checkstyle output
composer i18n # Regenerate languages/push-syndication.pot
composer test:unit # Unit tests (no WordPress needed)
composer test:integration # Integration tests (requires wp-env running)
composer test:integration-ms # Integration tests under WP_MULTISITE=1
composer coverage # HTML coverage report into .phpunit.cache/
composer coverage-ci # Coverage via wp-envwp-env start must be running first. The test:integration* scripts hardcode
--env-cwd=wp-content/plugins/syndication, but wp-env mounts the plugin under
the checkout directory name. In a worktree or differently named clone, run
phpunit directly instead:
wp-env run tests-cli --env-cwd=wp-content/plugins/<dir-name> ./vendor/bin/phpunit --testsuite integrationFollow the standards documented in ~/code/plugin-standards/ for full details.
Key points:
- Commits: Use the
/commitskill. Favour explaining "why" over "what". - PRs: Use the
/prskill. Squash and merge by default. - Branch naming:
feature/description,fix/descriptionfromdevelop. - Code style: WordPress coding standards via PHPCS —
WordPress-Extra,WordPress-Docs,WordPress-VIP-GoandPHPCompatibilityWP, withtestVersionat7.4-. Tabs for indentation.tests/is excluded from PHPCS. - Naming: New global functions, classes and constants need the
syndicationprefix, enforced byWordPress.NamingConventions.PrefixAllGlobals. - i18n: All user-facing strings must use the
push-syndicationtext domain. - Testing: two suites, and the names are case-sensitive and inconsistent —
--testsuite Unitbut--testsuite integration.- Unit: extend
tests/Unit/TestCase.php(which extendsYoast\WPTestUtils\BrainMonkey\TestCase). No WordPress loaded, so mock it. - Integration: extend
Yoast\WPTestUtils\WPIntegration\TestCase.
- Unit: extend
- cs-lint.yml —
composer validate, PHP parse-error lint, an XML lint ofphpunit.xml.dist, and a code style gate. The gate runsphpcs-changedon pull requests only, reporting just the violations a change itself introduces rather than the whole backlog. Errors fail the build and appear as inline annotations on the diff; warnings annotate without failing. - unit.yml — unit tests on PHP 7.4, 8.1, 8.2 and 8.3.
- integration.yml — wp-env integration tests, single site and multisite, on
WP 6.4 with PHP 7.4 and WP
masterwith PHP latest. Installs@wordpress/envglobally at its latest version, so CI can drift from a developer's pinned local install. - release.yml — on any pushed tag: checks the tag matches both versions in
push-syndication.php, builds apush-syndication/-rooted ZIP from.distignore, and publishes a GitHub Release titledSyndication X.Y.Zwith the matchingCHANGELOG.mdsection as its body. A hyphenated tag (3.0.0-rc.1) is published as a pre-release. - deploy.yml — on any pushed non-hyphenated tag: checks
README.md'sStable tag:matches, then deploys to WordPress.org SVN via the 10up action withSLUG: push-syndication. Deliberately not triggered byrelease: [released]— release.yml publishes with the defaultGITHUB_TOKEN, andGITHUB_TOKENevents do not start further workflow runs. Needs theSVN_USERNAMEandSVN_PASSWORDsecrets.workflow_dispatchoffers a dry-run.
Use the /release skill. It covers the branch, version bumps, changelog, i18n,
the three release commits, the main-based PR, the signed tag, and the sync
back to develop. Never hand-write GitHub Release notes or a WordPress.org
changelog — release.yml derives the release from CHANGELOG.md.
Repo-specific facts the skill cannot infer:
- The wp.org slug is
push-syndication, not the repo namesyndication. It appears in the ZIP's top-level directory,deploy.yml'sSLUG, and the.potheader. A ZIP rooted atsyndication/installs alongside an existing wp.org copy instead of updating it. README.mdis the WordPress.org readme — there is deliberately noreadme.txt. Its plugin headers need two trailing spaces per line.- Tags are bare (
2.2.0), notv-prefixed, and signed. composer i18npasses--slug=push-syndication. The slug only sets theReport-Msgid-Bugs-Toheader, andwordpress.org/plugins/syndication/is an unrelated plugin that was closed in 2018 — pointing there sends translators to a dead forum.bin/backfill-wporg-tags.shis a one-off for adding 2.0.0 and 2.0.1 to WordPress.org SVN. It writes only totags/, nevertrunk/, so no older release ever becomes the stable download. Not part of the release flow.
- Sites are posts: each syndication endpoint is a
syn_sitepost, grouped by thesyn_sitegrouptaxonomy. Site settings live in post meta on that post. - Transport by naming convention:
Syndication_Client_Factoryresolves a class name by string concatenation, so a new transport must be namedSyndication_{Type}_Client, implementSyndication_Client, expose staticdisplay_settings/save_settings, and berequire_onced in the main file. - Push is the primary direction; pull is supported and runs on cron.
- Credentials are encrypted at rest via
Syndication_Encryption. Never store or log them in plain text. - Globals, not injection: the server and encryption objects are passed around
as
$GLOBALS. This is legacy, and the main file carries a@TODOabout it, but match the existing pattern rather than introducing a container ondevelop.
- Do not edit WordPress core files or bundled dependencies in
vendor/. - Run
composer csbefore committing, but expect a wall of pre-existing violations: the backlog is 511 errors and 106 warnings across 17 files, andphpcbffixes none of them. CI only holds you to the lines you actually changed, socomposer csoutput is mostly other people's debt — read it for your own files, not as a pass/fail signal. - Adding a class file is not enough — add the
require_oncetopush-syndication.php, as there is no autoloader. - Do not re-add
processUncoveredFilestophpunit.xml.dist. It makes PHPUnit statically include every file underincludes/, three of whichrequire_once ABSPATH . ...at the top level. The unit suite runs without WordPress, so that crashes coverage generation and makescomposer test:unitexit non-zero for anyone with Xdebug or PCOV installed. CI does not catch it, as it runs withcoverage: none. - mcrypt is absent from modern PHP, so the
Syndication_Encryptor_MCrypttests skip locally and in CI. Skipped encryption tests are expected, not a regression. - Site context matters: when syndicating across sites in multisite, be
explicit about which site you are operating in. Use
switch_to_blog()/restore_current_blog()and always restore. - Transport errors from remote sites are unpredictable. Handle exceptions gracefully and give meaningful messages.
- The text domain is
push-syndication, notsyndication. Get this right in all i18n calls.