CreationellCaptchaGitPluginUpdater
in package
Class CreationellCaptchaGitPluginUpdater
Handles plugin updates from a GitHub-hosted JSON manifest.
Downstream rename of JPKComGitPluginUpdater (Jean Pierre Kolb,
shared across his plugin projects — see the file-level docblock for
the rename rationale).
Tags
Table of Contents
Constants
- FETCH_FAILURE_TTL = 300
- MANIFEST_JSON_DEPTH = 32
- MAX_MANIFEST_BYTES = 1048576
- Largest manifest body that is accepted, in bytes.
Properties
- $cache_enabled : bool
- $cache_key : string
- $current_version : string
- $manifest_url : string
- $plugin_file : string
- $plugin_slug : string
Methods
- __construct() : mixed
- Constructor
- check_update() : object
- Check for available plugin updates.
- clear_cache() : void
- Clear cached manifest after a successful update.
- plugin_info() : mixed
- Provide detailed plugin info in the “View Details” modal.
- verify_download_checksum() : bool|string|WP_Error
- Verify the package checksum and hand the verified file to the installer.
- default_avatar_url() : string
- The wordpress.org avatar URL used when the manifest supplies none.
- default_profile_url() : string
- The wordpress.org profile URL used when the manifest supplies none.
- failure_key() : string
- Transient key under which a failed manifest fetch is remembered.
- get_remote_manifest() : object|null
- Fetch and decode the remote manifest file.
- is_https_url() : bool
- Whether a URL is usable as a package/download source: valid per WordPress' own rules *and* https.
- manifest_string() : string
- A manifest field as a string, or the fallback when the manifest carried something else (array, object, number, bool, null).
- manifest_url_or_default() : string
- Returns a manifest-supplied URL if WordPress considers it a valid http(s) URL, otherwise the given default.
- remember_fetch_failure() : void
- Remembers a failed manifest fetch so the next call does not repeat it.
- validate_manifest() : object|null
- Structural check of a decoded manifest.
Constants
FETCH_FAILURE_TTL
private
int
FETCH_FAILURE_TTL
= 300
How long a failed manifest fetch is remembered, in seconds.
MANIFEST_JSON_DEPTH
private
int
MANIFEST_JSON_DEPTH
= 32
Maximum nesting depth accepted from the manifest JSON.
MAX_MANIFEST_BYTES
Largest manifest body that is accepted, in bytes.
private
mixed
MAX_MANIFEST_BYTES
= 1048576
The decoded manifest is cached in wp_options for 24 hours, so its size
is the site's problem, not the host's. The live manifest measured 180 KB
on 2026-07-30 (README-derived readme_html is the bulk of it); 1 MiB is
roughly five times that and only has to bound the transient, not track
it closely. Raise it here if the README ever grows past that.
Properties
$cache_enabled
private
bool
$cache_enabled
= true
Whether caching is enabled
$cache_key
private
string
$cache_key
Cache key for transient
$current_version
private
string
$current_version
Current plugin version
$manifest_url
private
string
$manifest_url
Remote manifest URL
$plugin_file
private
string
$plugin_file
Path to main plugin file
$plugin_slug
private
string
$plugin_slug
Plugin slug (directory name)
Methods
__construct()
Constructor
public
__construct(string $plugin_file, string $current_version, string $manifest_url) : mixed
Parameters
- $plugin_file : string
-
Absolute path to the main plugin file (FILE).
- $current_version : string
-
Current plugin version.
- $manifest_url : string
-
Full URL to the remote JSON manifest.
check_update()
Check for available plugin updates.
public
check_update(object $transient) : object
Parameters
- $transient : object
-
WordPress transient data.
Return values
objectclear_cache()
Clear cached manifest after a successful update.
public
clear_cache(WP_Upgrader $upgrader, array<string|int, mixed> $options) : void
Parameters
- $upgrader : WP_Upgrader
-
WordPress upgrader instance.
- $options : array<string|int, mixed>
-
Upgrade options.
plugin_info()
Provide detailed plugin info in the “View Details” modal.
public
plugin_info(mixed $result, string $action, object $args) : mixed
Parameters
- $result : mixed
-
Default response.
- $action : string
-
Current action.
- $args : object
-
API request arguments.
verify_download_checksum()
Verify the package checksum and hand the verified file to the installer.
public
verify_download_checksum(bool|string|WP_Error $reply, string $package, WP_Upgrader $upgrader[, array<string, mixed> $hook_extra = [] ]) : bool|string|WP_Error
Runs on upgrader_pre_download, i.e. instead of WP core's own
download: WP_Upgrader::download_package() returns the first non-false
value this filter produces, unchanged, and only downloads itself when
every handler returned false.
That is exactly why this method returns the temp file on success. The
previous version downloaded the package, hashed it, deleted it and
returned $reply (false) — so core downloaded a second copy and
installed that one. The SHA-256 covered a file that was thrown away
(LK-1). Now the hashed bytes and the installed bytes are the same file.
WP_Upgrader::run() deletes it after the install because the path
differs from $options['package'].
Note that core's optional signature verification is not lost here: it
only applies to wordpress.org/downloads.wordpress.org/s.w.org
(wp_signature_hosts), never to the github.com release asset this
updater fetches.
Parameters
- $reply : bool|string|WP_Error
-
Short-circuit value of the filter (default false).
- $package : string
-
The package file name or URL.
- $upgrader : WP_Upgrader
-
The WP_Upgrader instance.
- $hook_extra : array<string, mixed> = []
-
Extra arguments from the upgrader; carries
plugin(the plugin basename) for plugin updates.
Return values
bool|string|WP_Error —The verified package path, the untouched $reply, or a WP_Error.
default_avatar_url()
The wordpress.org avatar URL used when the manifest supplies none.
private
default_avatar_url(string $username) : string
Parameters
- $username : string
-
Contributor name from the manifest.
Return values
stringdefault_profile_url()
The wordpress.org profile URL used when the manifest supplies none.
private
default_profile_url(string $username) : string
sprintf()'s second parameter is variadic; passing it as the named
argument values: (as this file did) is an ArgumentCountError at
runtime, not a syntax error — positional only. rawurlencode() keeps a
manifest-supplied name inside the path segment it belongs to.
Parameters
- $username : string
-
Contributor name from the manifest.
Return values
stringfailure_key()
Transient key under which a failed manifest fetch is remembered.
private
failure_key() : string
Return values
stringget_remote_manifest()
Fetch and decode the remote manifest file.
private
get_remote_manifest() : object|null
Uses a locking mechanism to prevent race conditions when multiple requests try to fetch the manifest simultaneously.
Return values
object|null —Decoded manifest or null on failure.
is_https_url()
Whether a URL is usable as a package/download source: valid per WordPress' own rules *and* https.
private
is_https_url(string $url) : bool
wp_http_validate_url() accepts plain http, and the file behind this URL
is unpacked into wp-content/plugins and executed as PHP on the next
request — a downgrade to http is not something the manifest gets to
choose (LK-3).
Parameters
- $url : string
Return values
boolmanifest_string()
A manifest field as a string, or the fallback when the manifest carried something else (array, object, number, bool, null).
private
manifest_string(mixed $value[, string $fallback = '' ]) : string
Why this exists rather than “the sanitiser will handle it”: the WordPress sanitisers disagree about non-strings, and the ones this class uses land on both sides. Measured against WP 6.9 / PHP 8.3:
sanitize_text_field()is safe —_sanitize_text_fields()(wp-includes/formatting.php:5634) returns '' for array and object.esc_url_raw()is not:esc_url()(formatting.php:4483-4487) only short-circuits on'' === $urland then callsltrim( $url )→ TypeError for array and object.sanitize_title()is not: it reachespreg_match()insideremove_accents()(formatting.php:1612) → TypeError.trim()is not, for either.wp_kses_post()fatals on an object (preg_replace()inwp_kses_no_null(), kses.php:1939) but not on an array: every step of the chain (preg_replace(),str_replace(),preg_replace_callback()) accepts an array subject and returns an array, so the value comes back AS AN ARRAY — element by element filtered, but never turned into the string the caller expects. (W3-12: this line used to read “it returns one, unescaped”, which suggests the content passes unfiltered. The defect is the TYPE, not the escaping.)
So a manifest with e.g. "homepage": ["x"] would white-screen the
“View details” modal, and "icons": {"default": ["x"]} would do it on
every wp-admin page load via check_update(). validate_manifest()
bounds only the three fields the updater acts on (see there); this is
where the rest is bounded (LK-6).
Parameters
- $value : mixed
-
Raw value from the manifest.
- $fallback : string = ''
-
Value to use when $value is not a string.
Return values
stringmanifest_url_or_default()
Returns a manifest-supplied URL if WordPress considers it a valid http(s) URL, otherwise the given default.
private
manifest_url_or_default(mixed $url, string $default) : string
Parameters
- $url : mixed
-
Raw value from the manifest.
- $default : string
-
Fallback URL built by this class.
Return values
stringremember_fetch_failure()
Remembers a failed manifest fetch so the next call does not repeat it.
private
remember_fetch_failure() : void
The window is deliberately short: a manifest host that is down for a minute must not hide an update for a day. The cost of the miss is one delayed update check, the cost of not having it is a 15-second stall on every admin page load while the host is unreachable (LK-5).
validate_manifest()
Structural check of a decoded manifest.
private
validate_manifest(mixed $remote) : object|null
Only the three fields the updater acts on are validated — version
(drives the update decision), download_url (becomes the package URL) and
checksum_sha256 (is the gate). Every other field keeps whatever type the
JSON carried and is dealt with at the point of use — which for a field
that ends up in a string sink means manifest_string() first, because
the WordPress sanitisers are not uniformly type-safe (see there). A
manifest that fails any of these is discarded whole: a half-trusted
manifest is worse than none, and in particular a present-but-malformed
checksum must never degrade into the “no checksum” branch (LK-2/LK-6).
Parameters
- $remote : mixed
-
Decoded manifest.
Return values
object|null —The manifest, or null when it does not match the schema.