2.1.10.3
- Fix: Plugin and theme names were blank in the admin notification email,
and version numbers appeared in the Plugin/Theme column instead of names.
Root cause: batch results were collected by scraping the DOM after updates
completed, which read the wrong elements. Fixed by collecting result data
directly from each AJAX response as updates complete and passing it through
the callback chain — no DOM scraping involved.
2.1.10.2
- Fix: All three batch completion banners (green/amber/red) were visible
simultaneously on the Updates page after a batch completed. The CSS
display:flex rule on .wpmm-notice was overriding the HTML hidden
attribute, causing all three banners to render visible regardless of
the batch outcome. Fixed by adding .wpmm-notice[hidden] { display:
none !important } to admin.css so the hidden attribute always wins.
2.1.10.1
- Fix: WordPress core updates run through Greenskeeper were not appearing
in the email report. Root cause: after a successful core update WordPress
forces a page reload which resets the JS sessionId variable. The Email
Reports page hidden session field was populated from wpmm_last_session
at PHP render time, but since the page was not reloaded after the core
update the field still referenced the previous session. Fixed by
populating the hidden field from the most recent wpmm_pending_sessions
entry instead of wpmm_last_session — pending sessions is updated
server-side and survives the page reload correctly.
- Fix: WordPress core updates made outside Greenskeeper (via the standard
WordPress Updates screen) are now captured in the Update Log. The
external update catcher previously excluded core updates explicitly.
2.1.10
- Fix: Update Log now suppresses failed rows when a retry in the same
session succeeded. The failed row remains in the database for audit
purposes but is hidden from the Update Log display and excluded from
email reports. Rule: most recent row per plugin slug wins within a
session. Applied to both the Update Log page renderer and the email
body builder.
- Feature: Three batch completion states on the Updates page. Previously
a single green banner appeared after every batch regardless of outcome.
Now: (A) all succeeded — green banner with Send Report link; (B) partial
success — amber warning with Retry Failed and Send Partial Report options;
(C) all failed — red warning with Retry All option. Send Report is never
shown when all updates failed.
- Feature: Admin notification emails — Greenskeeper can now send an
internal notification email to the performing administrator when a batch
completes. Three notification types, each independently toggleable in
Settings: all updates succeeded (off by default), some updates failed
(on by default), all updates failed (on by default). The notification
email is separate from the client-facing report and includes a summary
of successes and failures with links to the Update Log and Email Reports.
- Feature: Email Notifications settings card in Settings — toggle each
admin notification type on or off independently.
- Fix: WordPress core updates made outside Greenskeeper (via the standard
WordPress Updates screen) are now captured in the Update Log and included
in the next email report. Previously the external update catcher
explicitly excluded core updates. The new version is read from the global
$wp_version (already updated at hook time); the old version from the
update_core transient’s version_checked property.
2.1.9
- Feature: Resend from Update Log — each session in the Update Log now
has a “Send Report →” button that navigates to the Email Reports page
with that session pre-loaded. If the session was previously emailed,
an amber notice shows when it was sent and to whom. The subject line
is automatically prefixed with [Updated Report] and the email body
includes a blue banner informing the recipient this is an update to
a previously sent report, with the original send date and time.
- Fix: Manual updates on the Email Reports page are now always included
alongside the automated session log entries when sending — previously
adding manual entries caused only the manual entries to appear in the
email, dropping the full automated update list.
2.1.8
- Fix: Email Reports scope selector now honored on multisite (Codex
audit issue #3). When a specific site is selected in Network Admin,
the Email Reports page now switches to that site’s context before
reading the recipient email, site name, site URL (for the subject
line), pending sessions, last session, default administrator, and
email log history.
- Fix: Scoped update filtering no longer applied in wrong blog context
(Codex audit issue #4). A redundant second filtering pass read
active_plugins and active theme AFTER restore_current_blog() was
called, filtering against the main site instead of the selected site.
Removed — the first pass already filters correctly using values
captured while switch_to_blog() was active.
- Fix: Removed blocking wp_update_plugins() and wp_update_themes()
calls from wpmm_get_available_updates() which is called from AJAX.
These caused HTTP 500 on managed hosting (Kinsta, WP Engine).
- Fix: Performing administrator no longer lost on page navigation
(Codex audit issue #5). The admin_id selected on the Updates page
is now stored in wpmm_pending_sessions alongside the session_id.
When the email is sent from the Email Reports page (cross-page flow),
the stored admin_id is read from the most recent pending session
rather than defaulting to 0. Falls back to the site default admin
if no session admin_id is available. Previously all of these were read from the main
site regardless of which site was selected in the scope bar.
2.1.7
- Feature: Site Activity Log — a new dedicated admin page that tracks and
logs key site events including user logins, failed login attempts,
logouts, password resets, user account creation and deletion, role
changes, plugin activation/deactivation/deletion, theme switches,
WordPress core updates, and key settings changes. Built with GDPR
compliance as a first principle: IP addresses are anonymised by default
(last octet of IPv4 / last 80 bits of IPv6 zeroed), a configurable
retention period (default 90 days) auto-purges old entries via daily
wp-cron, a full CSV data export tool supports Subject Access Requests,
and all entries can be bulk-deleted at any time. Full IP storage is
available as an explicit opt-in for sites with a documented legitimate
purpose. The activity log can be enabled or disabled independently from
a new section in Greenskeeper Settings.
2.1.6
- Feature: Administrator notes are now stored permanently with each sent
email. Previously the note was only included in the rendered email body
at send time — if the template changed, historical previews lost the
note. A new ‘note’ column is added to wpmm_email_log (existing installs
are upgraded automatically on first load). The raw note text is stored
separately from the HTML body so previews always show the exact note the
administrator typed, regardless of how the email template has evolved.
The preview modal displays the note in a prominently styled amber block
above the email iframe. Emails sent before v2.1.6 show no note block
(null note value) — only emails sent from this version onward will have
the note stored.
2.1.5
- Fix: Update Notes field content was being added below the spam section
where it was easy to miss. Note now appears prominently at the top of the
email body immediately below the report heading, ensuring clients always
see administrator notes before the update tables.
- Fix: Failed update rows in the email template now use amber styling
(“Needs Attention” with amber background) instead of large red text
(“Update Failed” in red). License-gated plugins show a friendly
client-facing message instead of a technical error code. DEBUG diagnostic
strings are stripped from messages before they reach the client email.
- Fix: iThemes Security Pro, Google Site Kit, and ShortPixel Image
Optimizer were being deactivated after updates and not restored.
These plugins intentionally self-deactivate during their update
process via their own upgrader_process_complete hooks, then expect
the updater to re-activate them afterward. The previous snapshot/
restore approach ran before these self-deactivation hooks fired and
therefore could not catch them.
Added a new wpmm_post_update_reactivate() hook on
upgrader_process_complete at priority 99 — deliberately very late
so it runs after all plugin-specific hooks. It has two jobs:
(1) Re-activate the plugin that Greenskeeper just updated if it
ended up inactive after its own hooks ran; (2) Restore any other
plugin that was active before the update but is now inactive as
collateral damage. Uses is_plugin_active_for_network() on multisite
to correctly handle network-activated vs site-activated plugins,
and activate_plugin($slug, ”, $is_network) accordingly.
2.1.4
- Fix: PHP Warning “Undefined variable $api_key” on the Settings page.
The variable was renamed to $api_key_set in v2.0.5 but one reference
in the REST API endpoints section was not updated.
- Fix: Email reports only showing the most recent session when multiple
unsent sessions exist. The pending sessions list is now always cleared
after any successful send, regardless of whether it was triggered from
the Updates page or the Email Reports page.
- Fix: WooCommerce and other hook-sensitive plugins (WP Rocket, Wordfence,
Really Simple SSL) not fully restored after collateral deactivation.
These plugins register activation hooks that are bypassed by direct
option writes. After the option write, activate_plugin() is now called
for any hook-sensitive plugin in the restore list to ensure their
activation hooks fire and they are fully re-initialized.
- Fix: Sent Email History row not appearing after send when the email
log database insert returns ID 0 (can occur on multisite when the
email_log table is in a different blog context). A timestamp-based
fallback row ID is now used when email_id is 0, ensuring the row
always appears immediately in the history table.
2.1.3
- Fix: HTTP 500 errors on some multisite networks caused by the per-site
snapshot loop introduced in v2.1.1. Both the snapshot and restore loops
are now wrapped in try/catch blocks for graceful fallback.
- Fix: HTTP 500 caused by premium plugins with custom updater libraries
(e.g. WP Offload Media Pro / Delicious Brains updater) throwing ValueError
or other exceptions during Plugin_Upgrader::upgrade(). All four upgrader
calls (plugin normal path, plugin injected-entry path, theme normal path,
theme injected-entry path) are now wrapped in try/catch Throwable blocks.
When a plugin’s own updater throws an exception, Greenskeeper catches it
and returns a descriptive error message rather than an HTTP 500, directing
the user to update via Dashboard → Updates as a fallback.
2.1.1
- Fix: Plugins activated only on a specific sub-site (not network-activated)
could still be deactivated by collateral damage during updates. Previous
versions only snapshotted the primary site’s active_plugins and the network
active_sitewide_plugins — missing plugins like Custom Post Type UI that are
activated per-site on individual sub-sites. The snapshot now reads
active_plugins from every site in the network. The restore checks each
sub-site’s current active_plugins against its snapshot and re-activates
any plugin that was deactivated as collateral damage. The updated snapshot
is persisted back to the network transient so retries also have access to
the corrected per-site state.
2.1.0
- Critical fix: network-activated plugins (AIOSEO, Co-Authors Plus,
Gravity Forms, Sucuri) were still being deactivated on subdirectory
multisite networks despite the v2.0.8 fix. Three root causes identified
and resolved: (1) the snapshot was being taken AFTER switch_to_blog(),
meaning it captured the sub-site plugin list rather than the main site
network state; (2) per-blog transients (get_transient/set_transient)
were used for the snapshot, which are stored in the current blog’s
options table and lost when blog context switches mid-request — replaced
with network-level site transients (get_site_transient/set_site_transient);
(3) restore_current_blog() was called after reading the post-update plugin
state, meaning the comparison was in the wrong blog context — blog context
is now restored before reading post-update state so both snapshot and
post-update reads happen in the same (main site) context.
2.0.9
- Critical fix: HTTP 500 errors on managed hosting (Kinsta, WP Engine)
during plugin and theme updates. Root cause: wp_update_plugins() and
wp_update_themes() make loopback HTTP requests back to the WordPress.org
API. When called from within an AJAX request, these loopback requests
are blocked or time out on managed hosting, causing the outer AJAX
request to return HTTP 500. All blocking wp_update_plugins/themes()
calls during AJAX update requests have been replaced with a non-blocking
wp-cron background event (wpmm_refresh_update_transient) that fires
after the request completes. The upgrader proceeds with the existing
transient URL and the next retry benefits from the refreshed URL.
2.0.8
Critical fix: network-activated plugins (Site Kit, Sucuri, Clarity) restored after collateral deactivation.
Email: multiple sessions grouped by date with clear headers.
Email history: live AJAX update after send. Unit tests added.
2.0.7
Backup warning modal before any update. Collateral deactivation restore improved with session-keyed transient.
2.0.6
Fix: collateral plugin deactivation — active plugins snapshotted before each update and restored after.
2.0.5
Security: cross-site AJAX cap bypass (#1), Akismet site scoping (#7), spam log actions (#8), REST API key hashed (#9).
Fix: All-Sites network email order (#2), dashboard date (#6), log pagination in SQL (#10), false success banner (#11).
2.0.4
Feature: email reports accumulate all unsent sessions — separate plugin/theme updates combined into one email.
2.0.3
Fix: AIOSEO Pro incorrectly flagged as manual update — scan now refreshes before deciding.
2.0.2
Fix: Divi and premium themes now update correctly — freshness check, skin error surfacing, auto-retry.
2.0.1
Fix: Jetpack copy error now reported correctly. Gravity Forms add-ons show manual update warning.
2.0.0
Premium plugin updates confirmed working. Auto-retry with fresh signed URL. AIOSEO Pro verified.
1.9.9
Diagnostic build for AIOSEO Pro null result issue.
1.9.8
Fix: AIOSEO Pro reporting “version unchanged” — auto-retry with fresh signed URL.
1.9.7
Premium plugin updates: forces fresh wp_update_plugins() when package URL is stale.
1.9.6
Critical fix: prevent premium plugin deactivation on failed updates.
1.9.5
Feature: Sent Email History updates instantly via AJAX after send.
1.9.4
Fix: batch update timeouts on shared hosting.
1.9.3
Plugin Check compliance: gmdate(), esc_sql(), prepared queries, short description length.
1.9.2
WordPress.org compliance: sanitize $_SERVER variables, External Services disclosure, inline comments.