Skip to content

Releases: ultrapackadm/deckwp-connect

v0.41.0

Choose a tag to compare

@github-actions github-actions released this 07 Aug 02:53

Two places where the connector knew more than it told the dashboard. In
both, the operator-facing result was wrong in a way that pointed at the
wrong culprit — a theme that "wasn't premium" and a permissions problem
that wasn't a permissions problem.

Fixed

  • A theme installed under a folder name that differs from its catalog
    slug lost its premium identity.
    The theme result echoed back the
    slug it was asked for and discarded the stylesheet directory it had
    just resolved. Citadela ships a package that extracts to
    citadela-theme while the catalog calls it citadela, so the
    dashboard could not match its own install against the request: the
    next heartbeat saw an unknown directory, created a fresh row for it
    with the default wp.org source, and the item's premium classification
    — along with its update channel — was gone. Every theme result path
    now carries installed_slug, and it is present even when the two
    names agree, so its absence means one thing only: an older connector.

  • A failed install or update was reported as a filesystem-permissions
    problem regardless of what actually went wrong.
    When WordPress's
    upgrader returns a non-truthy result without a WP_Error, the
    connector used to state, flatly, that this is "usually a
    filesystem-permissions issue." The most common real cause is the
    opposite: a clean download that WordPress then refuses to unpack —
    a corrupt or truncated archive, or a package whose structure the
    upgrader rejects. Sending the operator to chmod for that is a dead
    end. The connector now asks the upgrader skin first — for a
    WP_Error it raised but did not return, then for the last upgrade
    message (markup stripped, since this lands in an operator-facing
    field) — and only falls back to a hint when the skin has nothing to
    say. That hint now names both possibilities instead of asserting one.

Internal

  • The repository gained a CI workflow that runs the test suite on PHP
    7.4 (the compatibility floor) and 8.3 (production) on every push, so
    a release can no longer be cut from a red tree. New coverage for the
    active-plugin upgrade path, which reactivates a plugin WordPress
    switched off mid-upgrade.

v0.40.0

Choose a tag to compare

@github-actions github-actions released this 04 Aug 20:55

An end-to-end pass — create an account, pair a site, update plugins and
themes, roll one back, scan, disconnect — turned up a set of problems
that shared one root: the connector kept telling the dashboard what an
operation was supposed to do instead of what WordPress actually did.
This release replaces that inference with read-back facts, and turns on
two schedulers that had been shipping switched off.

Fixed

  • The scheduled heartbeat and the scheduled scan never ran. On any
    site.
    Both were gated behind constants (DECKWP_CONNECT_ENABLE_ HEARTBEAT, DECKWP_CONNECT_ENABLE_SCAN) that defaulted to OFF while
    the dashboard's ingest endpoints were still being built. The
    endpoints went live long ago; nothing ever defined the constants. The
    practical effect was that a paired site reported its inventory once —
    or never — and then went quiet: last_seen_at never advanced, plugin
    lists went stale the moment anything changed on the site, and the
    only fresh data the dashboard ever saw came from an operator pressing
    Refresh by hand. Both constants are now opt-out, honoured only
    when defined and falsy:

    define( 'DECKWP_CONNECT_ENABLE_HEARTBEAT', false );
    define( 'DECKWP_CONNECT_ENABLE_SCAN', false );
    
  • A freshly paired site showed up on the dashboard with zero plugins
    and zero themes.
    The handshake ended at the credential write and
    left everything else to a cron tick that might be five minutes out —
    or, per the item above, never come. Pairing now finishes the job:
    Pairing\Setup schedules both cron events and pushes the first
    inventory before the pairing screen answers. If that first push
    can't be delivered the pairing still succeeds, and the settings page
    says why rather than leaving the operator staring at an empty site.

  • A rollback left the dashboard showing the version it had just
    rolled back from.
    restore-backup answered a bare {"ok": true},
    so the dashboard had nothing to go on but the backup row it had asked
    us to restore. The response now carries the post-state read back off
    the install — installed, version, active — after the folder
    swap and after WordPress's plugin cache is invalidated, which is what
    made the old read return pre-swap data.

  • A plugin could come back from a restore or a failed update switched
    off, while the panel went on calling it Active.
    Core's upgrader
    deactivates a plugin before replacing its files and leaves
    re-activation to an admin page load that never happens for us. The
    connector now restores the activation state itself — on the success
    path, on the error path, and again after a rollback — preserving
    network-wide activation as network-wide. When re-activation genuinely
    fails, the response says so in reactivation_error instead of
    reporting a clean success over a plugin that is no longer running.

  • The security scan reported three findings on a brand-new WordPress,
    and all three were wrong.
    Each came from treating a textual match
    as a behavioural one:

    • the two-line "Silence is golden" index.php stub — written by
      WordPress core, WooCommerce, most hosts, and by DeckWP's own backup
      directory — was reported as a critical webshell. A PHP file with no
      executable statement in it is now recognised by parsing it, not by
      keeping a list of filenames to forgive.
    • the scanner's own source file was flagged, because its
      documentation spelled out one of the signatures it looks for. Code
      inside a comment does not run, and no longer counts as evidence.
      (Every security plugin that ships a signature list had the same
      problem.)
    • wp-config.php was reported world-writable at 0666 on Windows,
      where every writable file reports 0666 because NTFS has no POSIX
      mode bits. That check now runs only where the answer means
      something.

    None of this is fixed by exempting DeckWP's own files — the
    connector's directory is still scanned like any other plugin's,
    because "the attacker hid it in the security plugin's folder" is a
    real thing that happens.

  • "All up to date" was also what the dashboard said when it hadn't
    been able to ask.
    When wordpress.org was unreachable, WP's update
    transient came back with an empty response and the panel rendered it
    as good news. Both the heartbeat and the inventory pull now carry an
    update_check block — whether a poll went out, whether it answered,
    the reason if it failed, and the transient's own timestamp — so an
    unanswered question can be told apart from an answer of "nothing".

  • Disconnecting from the WordPress side left the cron events
    running,
    and disconnecting from the dashboard side didn't tell the
    site at all: its settings page kept saying "Connected" until a
    heartbeat came back 401. There is now one Pairing\Teardown behind
    all four paths — the site's own Disconnect button, the dashboard's
    new POST /deckwp/v1/unpair push, and the 401 fallbacks in both
    schedulers — so every route out leaves the site in the same state,
    cron included.

Added

  • wporg_slug on every plugin and theme row. WordPress identifies
    a plugin by a path, which is not the same thing as its
    wordpress.org slug: hello.php is hello locally and hello-dolly
    in the directory. Anything building a download URL from the local
    slug 404s — and since Hello Dolly ships with every WordPress install,
    every paired site carried at least one item whose scan could only
    ever fail. The connector now reports the slug WordPress itself
    matched the item to, harvested from both buckets of the update
    transient (response and no_update, so an up-to-date plugin is
    covered too), and reports null when wordpress.org has no such item
    — which is a real answer, not a missing one.

  • POST /deckwp/v1/unpair — HMAC-signed, so the dashboard can
    clear a site's credentials at the moment the operator clicks
    Disconnect rather than waiting for the site to discover it.

  • active on every install-batch result row, read back from
    WordPress after the operation rather than echoed from what the
    operation intended.

Changed

  • Heartbeat\Scheduler and Scan\Scheduler take an optional
    Teardown collaborator; Pairing\Handler takes an optional Setup.
    All are defaulted, so existing construction sites keep working.
  • Pairing\Handler::pair() gained a first_heartbeat key in its
    result array, reporting whether the first inventory push landed.

v0.39.1

Choose a tag to compare

@github-actions github-actions released this 03 Aug 14:05
5fb52a7

Fixed

  • PHP 8.4 deprecation notices on every plugin boot. 58 constructor
    parameters across 24 classes were written as Foo $bar = null, which
    PHP 8.4 deprecates ("Implicitly marking parameter as nullable is
    deprecated") and PHP 9 will reject outright. On a host running 8.4
    with WP_DEBUG_DISPLAY on, these notices printed on every request
    that booted the connector — including REST responses, where leading
    output corrupts the JSON body. All 58 are now explicitly ?Foo $bar = null.

    No behaviour changes: ?Type has been valid since PHP 7.1 and the
    plugin's floor is 7.4, so this does not move the compatibility floor.
    Verified against a live PHP 8.4.19 install — 21 notices before, zero
    after — with the HMAC channel re-tested end to end afterwards.

v0.39.0

Choose a tag to compare

@github-actions github-actions released this 11 Jul 04:04

Added

  • License protection. New License\LicenseDetector reports, per
    installed plugin/theme, whether the site carries an ACTIVE official
    license (license_state = licensed_active | unknown, plus a
    license_provider label). Signals: an update in the WP transient
    whose package is neither wp.org nor DeckWP (a third-party author
    updater), a custom Update URI: header off wp.org, and known
    frameworks (EDD <slug>_license_status, Freemius fs_accounts),
    extensible via the deckwp_detect_active_license filter. Fail-open:
    only flags when a signal is confident.
  • The inventory / heartbeat payload now carries license_state +
    license_provider per plugin and theme (consumed by the dashboard to
    skip auto-updates on licensed items and require an explicit override
    on manual ones).
  • Install-time safeguard. Installer refuses to overwrite a plugin
    or theme that carries an active official license with a catalog build
    (download_url present) unless the request includes
    license_override: true. Uses framework signals only (the update
    transient is circular at the point of application). Final line of
    defense behind the dashboard gate.

Testing

  • First unit-test harness for the connector: tests/bootstrap.php
    (minimal WP stubs) + phpunit.xml.dist + LicenseDetectorTest
    (11 cases).

Compatibility

  • Purely additive — extra payload fields (ignored by older dashboards)
    and a new refusal path that only triggers for licensed items without
    an override. No change to existing endpoints or the wire contract.

v0.38.0

Choose a tag to compare

@github-actions github-actions released this 02 Jun 02:42

Added

  • Async off-site upload endpoint. New
    POST /deckwp/v1/backup-offsite-upload ships an already-written local
    backup zip to a pre-signed PUT URL (Backblaze B2), given
    { local_path, url, headers?, key? }. It resolves local_path
    relative to the uploads basedir and streams the zip via
    BackupManager::uploadOffsite() (containment-checked to the managed
    backups directory).

    This is the off-site path for pre-update backups: those are taken
    inside install-batch (the critical upgrade path), so rather than
    extend that request with a potentially-large B2 upload — and risk a
    timeout mid-upgrade — the dashboard snapshots locally during the
    upgrade as before, then calls this route from a queued job afterwards.
    Egress latency is fully decoupled from the upgrade. Works for any
    existing local backup, not just pre-update ones.

Compatibility

  • Purely additive — a new route, no change to existing endpoints.
    Requires cURL (already required).

v0.37.0

Choose a tag to compare

@github-actions github-actions released this 02 Jun 02:42

Added

  • Off-site restore fallback. POST /deckwp/v1/restore-backup now
    accepts an optional download_url — a short-lived pre-signed GET url
    for the backup's off-site (Backblaze B2) copy. It's used only when
    the local zip at local_path is missing
    on this server (disaster
    recovery: the site was rebuilt, the uploads dir was wiped, the server
    was migrated). When the local zip is present, restore uses it exactly
    as before — no egress, no behaviour change.

    When the fallback kicks in, the connector streams the off-site copy
    into the managed backups directory (via BackupManager::downloadOffsite())
    and then validates + extracts it through the normal restore path,
    including the SHA-256 checksum guard. The download streams to a temp
    file and renames atomically into place, so an interrupted fetch never
    leaves a half-written zip for restore to choke on. The target path is
    containment-checked to the managed directory.

Compatibility

  • Fully backwards-compatible. A dashboard that omits download_url (or
    any older dashboard) gets identical local-only restore behaviour.
    Requires cURL (already required); pairs with the dashboard sending a
    download_url only for backups that have an off-site copy.

v0.36.0

Choose a tag to compare

@github-actions github-actions released this 02 Jun 02:42

Added

  • Off-site backup upload (opt-in). POST /deckwp/v1/backup-create
    now accepts an optional offsite descriptor — a short-lived
    pre-signed PUT url, the signed headers to send with it, and the
    target object key. When present, the connector streams the
    just-written backup zip straight to remote object storage
    (Backblaze B2) right after taking the local snapshot, and reports the
    outcome back to the dashboard under backup.offsite (ok plus the
    stored key, or ok:false with an error code).

    The local snapshot remains the source of truth and is unchanged: the
    off-site copy is purely additive, enabled per-team on the dashboard,
    and best-effort — a failed or rejected upload is reported but
    never turns a locally-successful backup into an error.

    The upload streams from disk via cURL with an explicit content length
    (never chunked, which S3/B2 pre-signed PUTs reject) so even a large
    plugin/theme zip never has to sit in PHP memory. The file is
    containment-checked to live inside the managed backups directory
    before upload, so a malformed request can't exfiltrate an arbitrary
    server file.

Compatibility

  • Fully backwards-compatible. A dashboard that omits offsite (or any
    older dashboard) gets the exact same local-only behaviour as before —
    no backup.offsite block is returned. Requires PHP 7.4+ and cURL
    (already required); when cURL is unavailable the upload is skipped
    with an offsite_no_curl code and the local backup still succeeds.

v0.35.0

Choose a tag to compare

@github-actions github-actions released this 30 May 01:12

Added

  • Fallback transport for sites that block the REST API. Some hosts
    and "harden WordPress" security plugins firewall /wp-json entirely,
    which previously left a paired site unreachable from your DeckWP
    dashboard even though the connector was installed and healthy.

    DeckWP Connect now accepts the dashboard's management commands over a
    second, REST-independent path: an HMAC-signed request to a normal
    front-end URL, caught early on the init hook and routed to the same
    command handlers as the REST API. If /wp-json works, nothing
    changes; the fallback only matters when it's blocked.

    Security and privacy are unchanged from the REST surface: every
    command is HMAC-SHA256 verified with the same 60-second anti-replay
    window, the requested action is cryptographically bound to the
    signature (it can't be tampered into a different command), and the
    connector still does nothing on ordinary visitor page loads — it only
    acts when your dashboard sends a signed request.

Compatibility

  • No configuration needed. WordPress 5.2+ / PHP 7.4+ unchanged.

v0.33.0

Choose a tag to compare

@github-actions github-actions released this 22 May 23:25

Added

  • Theme post-update smoke check — pairs with the theme backup
    pipeline shipped in v0.32.0 to enable auto-rollback when a theme
    upgrade leaves the site in a broken state.

    The new PostUpdateChecker::verifyTheme() mirrors the plugin
    smoke shape with theme-specific verification points:

    • Theme folder exists after upgrade (themes are always
      folder-based, no single-file equivalent to plugin layouts
      like Hello Dolly).
    • style.css exists — WP refuses to load a theme without
      its main metadata file.
    • index.php exists — bare-minimum template required by
      WP for any active theme.
    • functions.php PHP token parse when present (optional
      file, but a syntax error there breaks the whole site since
      it loads on every page).
    • Active-state survived when the theme was active before
      the upgrade — catches the rare case where the upgrade
      payload internally renamed the slug or shipped a different
      theme's content under the same folder name.

    An optional home-page 5xx probe (smoke_check_home: true on
    the install-batch payload) extends coverage to "the site as a
    whole is up" the same way the plugin path does.

Changed

  • Installer::handleSmokeFailure() is now kind-aware
    ('plugin' | 'theme') and dispatches the rollback to the
    matching BackupManager method (restore vs restoreTheme).
    The error-envelope shape, snapshot-resolution logic, and
    rolled_back response payload are identical between kinds.

  • Installer::installOneTheme() captures the active-theme state
    via get_stylesheet() before running Theme_Upgrader::upgrade()
    so the smoke check has the $wasActive signal it needs. Mirrors
    the plugin path's isPluginActive() capture.

v0.32.0

Choose a tag to compare

@github-actions github-actions released this 21 May 18:10

Added

  • Theme backup pipeline — pre-update theme backups now work the
    same as plugins. The dashboard sends type: theme on the wire and
    the connector materialises a zip of wp-content/themes/<slug>/
    under wp-content/uploads/deckwp-backups/ with the same retention
    and checksum semantics as plugin backups.

    Three integration points now accept the discriminator:

    • BackupManager::snapshotTheme(string $slug) — mirror of
      snapshot() but targets the themes tree. Themes are always
      folder-based so we skip the single-file (Hello Dolly) branch.
      Ceiling raised to 800 MB to accommodate Avada / Divi / Beaver
      Builder all-in-ones; below the disk-fill safety floor.

    • BackupManager::restoreTheme(...) — mirror of restore().
      Same temp-extract / move-old-aside / move-new-into-place /
      rollback choreography as plugin restore.

    • POST /wp-json/deckwp/v1/backup-create accepts
      { slug, type: 'plugin'|'theme' } — default plugin keeps
      backwards compatibility with v0.12.0+ dashboards.

    • POST /wp-json/deckwp/v1/restore-backup accepts the same
      type discriminator.

    • Installer::installOneTheme honors backup_required on the
      upgrade path — the dashboard's pre-update backup row now
      actually materialises a zip on disk when the orchestrator
      targets a theme installation. Previously themes silently
      skipped the snapshot ("no backup/smoke check yet" — fixed
      here for the backup leg; smoke check remains TODO).

Wire contract change

/backup-create + /restore-backup body field type is now
read. Dashboards on connector v0.31.0 or earlier ignore the field
on outbound (always sends nothing for type, default 'plugin'
applies). Backwards-compatible by design.