saggre / wordpress-org-repository-php-wrapper
A Wordpress.org Repository API wrapper for PHP
Package info
github.com/Saggre/wordpress-org-repository-php-wrapper
pkg:composer/saggre/wordpress-org-repository-php-wrapper
Requires
- php: ^8.1
- ext-curl: *
- league/flysystem: ^3.30
- league/flysystem-webdav: ^3.30
Requires (Dev)
- phpunit/phpunit: ^10.5
- saggre/phpdocumentor-markdown: ^1.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-09 13:19:58 UTC
README
Use cases: Plugin and theme directory data, update checks, analysis.
This library provides a simple way to access the WordPress.org plugins and themes repositories. It allows you to retrieve raw plugin and theme files, list directories, read commit logs, query the plugin directory and download releases.
Installation
Installation via Composer
composer require --dev saggre/wordpress-org-repository-php-wrapper
Usage examples
Configuring the client
Plugin client
// Client for the latest version (trunk) of the WooCommerce plugin $config = new PluginClientConfig('woocommerce', 'trunk'); $client = new PluginClient($config);
Theme client
// Client for version 1.2 of the Twenty Twenty-Five theme $config = new ThemeClientConfig('twentytwentyfive', '1.2'); $client = new ThemeClient($config);
Client methods
Get plugin or theme file contents
$content = $client->getFile('readme.txt'); /* * === WooCommerce === * Contributors: automattic, woocommerce * Tags: online store, ecommerce, shop, shopping cart, sell online * ... */
Get plugin or theme file contents as a stream
$file = $client->getFileStream('readme.txt'); $content = stream_get_contents($file); /* * === WooCommerce === * Contributors: automattic, woocommerce * Tags: online store, ecommerce, shop, shopping cart, sell online * ... */
List plugin or theme directory contents
Pass true as the second argument to list the contents of subdirectories as well.
use League\Flysystem\StorageAttributes; $directory = $client->getDirectory(); $directory = array_map( fn(StorageAttributes $item) => $item->jsonSerialize(), $directory->toArray() ) /* * array( * ... * array( * 'type' => 'file', * 'path' => 'woocommerce/trunk/woocommerce.php', * 'file_size' => 1851, * 'visibility' => null, * 'last_modified' => 1753778097, * 'mime_type' => 'text/xml; charset="utf-8"', * 'extra_metadata' => array(), * ), * ... * ); */
List a plugin's tagged versions
$tags = $client->getTagsDirectory(); // array('1.5', '1.6', '1.7.2') $versions = array_map(fn($tag) => basename($tag->path()), $tags->toArray());
Read the commit log
getLog() reads the history of the configured plugin or theme, getRepositoryLog() the history of every plugin or
theme at once. Both return the newest revision first, and both accept a revision range.
$log = $client->getLog(limit: 10); foreach ($log as $entry) { echo "r{$entry->revision} by {$entry->author} at {$entry->date->format('c')}: {$entry->message}\n"; foreach ($entry->paths as $path) { // Tags are copies, so a copied path resolves the release a version was cut from. echo " [{$path->action->value}] {$path->path} {$path->copyFromPath}\n"; } }
Compare two published versions
diffVersions() lists the files a release touched without downloading either tree. It resolves both tags, then reads
the revision range between them in a single request. Paths come back relative to the plugin root, deduplicated across
trunk and the new tag, which vendors commonly commit the same edit to. A deleted or copied directory is listed in place
of the files it removed or brought along, since the log does not name them.
$paths = $client->diffVersions('4.4.3', '4.4.4'); foreach ($paths as $path) { // A path with textMods false changed only its properties, its bytes are identical. echo "[{$path->action->value}] {$path->path}\n"; } // Fetch only what changed, then diff locally. $before = (new PluginClient(new PluginClientConfig('gdpr-cookie-consent', '4.4.3')))->getFile('gdpr-cookie-consent.php');
A version that was published without ever being tagged throws TagNotFoundException rather than silently comparing
the wrong pair, and an old version that was tagged after the new one throws InvalidArgumentException.
Resolving the two tags is the expensive half: it reads the tag history, which for a plugin with hundreds of releases
costs far more than the diff itself. A caller comparing recent releases can cap that read, at the price of a
TagNotFoundException for a version tagged before the window.
// Read the newest twenty tag revisions instead of a decade of them. $paths = $client->diffVersions('4.4.3', '4.4.4', 20);
Map versions to revisions
$tags = $client->getTagRevisions(); // Ordered by revision, oldest release first. Version strings cannot be sorted as text, // where '1.10.4' lands between '1.1.9' and '1.2.0'. foreach ($tags as $version => $entry) { // A tag is a directory copy, so it also names the trunk revision the release was cut from. echo "{$version} => r{$entry->revision} from {$entry->paths[0]->copyFromRevision}\n"; }
getTagRevisions($limit) reads only the newest $limit revisions of the tags directory. A release usually takes one
revision, but retagging a release and editing a file inside a tag take their own, so the window can hold fewer versions.
Read a revision range
getChangedPaths() reads the revisions between two bounds, optionally scoped to a subtree. Both bounds are inclusive,
and both are required: the server answers an empty report with HTTP 200 when the end revision is missing. An inverted
range throws InvalidArgumentException, as it does on getLog() and getRepositoryLog().
$log = $client->getChangedPaths(3686273, 3679496, 'trunk/admin');
Export a tagged version
Writes the whole tree of the configured version to a local directory, which recovers releases that are no longer served by the distribution host.
$files = $client->export('/tmp/hello-dolly-1.7.2');
Plugin API client
Reads plugin metadata from the WordPress.org plugin API.
use Saggre\WordPress\Repository\PluginApiClient; $client = new PluginApiClient();
Enumerate plugins
Switch off the bulky prose and switch on the contributors to keep a page of results small.
use Saggre\WordPress\Repository\Model\PluginBrowse; use Saggre\WordPress\Repository\Model\PluginQuery; $result = $client->queryPlugins(new PluginQuery( browse: PluginBrowse::Updated, page: 1, perPage: 250, fields: [ 'sections' => false, 'description' => false, 'screenshots' => false, 'icons' => false, 'contributors' => true, ], )); // 71793 plugins on 288 pages, newest last_updated first echo "{$result->results} plugins on {$result->pages} pages\n"; foreach ($result->plugins as $plugin) { echo "{$plugin->slug} {$plugin->version} {$plugin->lastUpdated->format('c')}\n"; }
Read one plugin's record
$info = $client->getPluginInformation('hello-dolly'); // array('1.5' => 'https://downloads.wordpress.org/plugin/hello-dolly.1.5.zip', ...) $versions = $info->versions;
Check whether a plugin is closed
$status = $client->getPluginStatus('hana-flv-player'); if ($status->closed) { // 'security-issue' as of 2021-06-21 echo "{$status->reason} as of {$status->closedDate->format('Y-m-d')}\n"; }
Plugin download client
Downloads plugin releases from the WordPress.org distribution host. Only the current release is available without a version, and withdrawn releases are no longer served even when they still exist in SVN.
use Saggre\WordPress\Repository\PluginDownloadClient; $client = new PluginDownloadClient(); $zip = $client->getZip('hello-dolly', '1.7.2'); $stream = $client->getZipStream('hello-dolly');
API reference
PluginClient and ThemeClient
Every method reads the slug and version held by the client's config. getTagsDirectory(), getTagRevisions() and
diffVersions() are plugin only, since the theme repository has no tags directory.
| Method | Returns | Description |
|---|---|---|
getFile(string $path) |
string |
Contents of a file. |
getFileStream(string $path) |
resource |
Contents of a file as a stream. |
getDirectory(string $path = '', bool $deep = false) |
DirectoryListing |
Directory contents, optionally including subdirectories. |
getTagsDirectory() |
DirectoryListing |
One entry per published version tag, with lastModified populated. |
export(string $destination) |
int |
Writes the tree to a local directory and returns the number of files. |
getLog(int $limit = 100, ?int $start = null, int $end = 0) |
LogEntry[] |
Commit log of this plugin or theme, newest revision first. |
getRepositoryLog(int $limit = 100, ?int $start = null, int $end = 0) |
LogEntry[] |
Commit log of every plugin or theme at once. |
getChangedPaths(int $start, int $end, string $path = '', int $limit = 0) |
LogEntry[] |
Revisions in an inclusive range, optionally scoped to a subtree. |
getTagRevisions(int $limit = 0) |
LogEntry[] |
Every published version to the revision that created its tag. |
diffVersions(string $old, string $new, int $limit = 0) |
LogPath[] |
Files changed between two published versions, keyed by path. |
getFilesystem() |
Filesystem |
The underlying Flysystem instance, for anything the client does not do. |
PluginApiClient
| Method | Returns | Description |
|---|---|---|
queryPlugins(PluginQuery $query) |
PluginQueryResult |
One page of the plugin directory. |
getPluginInformation(string $slug, array $fields = []) |
PluginInfo |
Full record of one plugin, including its versions map. |
getPluginStatus(string $slug) |
PluginStatus |
Whether a plugin is closed, and why. |
PluginDownloadClient
| Method | Returns | Description |
|---|---|---|
getZipUrl(string $slug, ?string $version = null) |
string |
Download URL of a release. |
getZip(string $slug, ?string $version = null) |
string |
Contents of the release archive. |
getZipStream(string $slug, ?string $version = null) |
resource |
Release archive as a stream. |
Repository reads throw League\Flysystem\FilesystemException. Everything else throws
Saggre\WordPress\Repository\Exception\ClientException, whose code is the HTTP status of the failed response.
TagNotFoundException extends it and is thrown when a version has no tag.
Running tests
# Clone the repository git clone git@github.com:Saggre/wordpress-org-repository-php-wrapper.git # Go to the cloned repository cd wordpress-org-repository-php-wrapper # Install dependencies composer install # Run PHPUnit in project root directory ./vendor/bin/phpunit
Documentation
Code documentation is available in the docs directory.