Skip to content

Releases: jwincek/shelterkit-donations

3.0.0

Choose a tag to compare

@github-actions github-actions released this 24 Aug 21:52
c47f1de

See CHANGELOG.md for details.

2.0.1

Choose a tag to compare

@jwincek jwincek released this 06 Jul 00:51

[2.0.1] - 2026-07-05

Fixed

  • WordPress 6.7+ "translation loading for the shelter-donations domain was triggered too
    early" notice on every admin request. Meta_Boxes::init() (running at plugins_loaded)
    eagerly built the meta-box config, whose projection translates manifest labels before the
    init action. Hook registration now uses a new keys-only
    Field_Manifest::get_meta_box_post_types() (never translates), and the translated config
    is built lazily on first access — the same pattern List_Columns already used.

2.0.0

Choose a tag to compare

@jwincek jwincek released this 05 Jul 23:19

[2.0.0] - 2026-07-05

Changed

  • Renamed the plugin's public identity to shelter-donations ("Shelter Donations") in
    preparation for the first WordPress.org release, joining the shelter-* plugin family
    (shelter-pet-sync, shelter-events-wrapper). This is a breaking release for existing
    installs — follow migration-scripts/MIGRATION-2.0.0.md when upgrading a live site.
    • Text domain: starter-shelter → shelter-donations (plugin header, all gettext calls,
      translation template renamed to languages/shelter-donations.pot).
    • Main plugin file: starter-shelter.php → shelter-donations.php (existing installs must
      reactivate; the plugin directory must be renamed to shelter-donations/ to match the slug).
    • Block namespace: starter-shelter/* → shelter-donations/* (12 blocks), including the
      Interactivity API store namespaces and block-binding sources. Stored content that uses
      these blocks must be migrated
      with wp search-replace — see the migration guide.
    • Editor bootstrap global: window.starterShelterBlocks → window.shelterDonationsBlocks.
    • WP-CLI command: wp starter-shelter validate → wp shelter-donations validate.
    • Display name, log prefixes, and metadata (composer, PHPCS ruleset, Plugin/Author URI)
      updated to the new identity; Author is now "VCPA Humane Society", crediting the
      organization the plugin was originally built for.
  • Unchanged on purpose (no data migration needed): the Starter_Shelter\ PHP namespace and
    STARTER_SHELTER_* constants (internal-only), all starter_shelter_* hook/filter/cron names,
    all sd_* option/meta keys and post types, and WooCommerce product/order data.
  • Dist packaging: .distignore now also excludes dev artifacts (phpcs.xml.dist,
    .editorconfig, .wp-env.json, PHPUnit caches, AUDIT-*.md, migration-scripts/).

1.2.0

Choose a tag to compare

@jwincek jwincek released this 24 Jun 00:35

[1.2.0] - 2026-06-23

Added

  • My Account → Annual Statement now produces a real, printable tax receipt. The "Print / Save as PDF" button opens a standalone, print-optimized contribution statement (no theme/account chrome) that auto-opens the browser print dialog — so members get a PDF via their browser with no server-side PDF dependency. It itemizes the year's donations, memorials, and memberships with a grand total, includes the organization details (from the WooCommerce store settings) and the donor's name/address, and carries a deductibility note that's filterable (starter_shelter_receipt_tax_note) so a site can add its EIN / 501(c)(3) language. The donor is resolved from the session, so a member only ever prints their own statement. (Previously the Print button just reloaded the full account page.)
  • My Account → Donor Dashboard now has an editable Contact Details form (name, phone, mailing address), pre-filled from the donor record. Saving posts through the nonce-verified PRG handler and the owner-scoped shelter-donors/update-profile + shelter-donors/update-address abilities — completing the self-service set (previously the account area was entirely read-only).
  • My Account → My Memberships now lets members manage their memberships directly: an auto-renew toggle and a Cancel membership action (with confirm) on each active membership, plus the existing Renew link (now pointing at the configured membership page instead of the site home). Both post through the same nonce-verified PRG handler, which checks the membership belongs to the current user's donor before acting; cancellation runs through the shelter-memberships/cancel ability. Cancelling clears the term so the member also drops off the public Members Wall.
  • My Account → My Memberships now lets a member control their public recognition: a "List me on the public Members Wall" preference (opt-out model) that members can toggle themselves. This is the self-service counterpart to the Members Wall — previously a member had no way to opt out. Posts back via a nonce-verified PRG handler that resolves the current user's own donor (so a user can only change their own record), sets is_anonymous across their memberships, and the recognition-wall ability honors it immediately. Logic lives in WooCommerce-free My_Account::set_recognition_preference() / is_listed_publicly() (integration-tested).
  • New Members Wall block (starter-shelter/members-wall) — a static (SSR, no-build) tiered recognition wall of active members who haven't opted out, grouped into membership-level sections ordered highest-first. Business members show their approved logo (toggleable) and link to their website; individuals show their display name. Inspector controls: group-by-tier, show-logos, columns, max-items. Backed by a new public read ability shelter-memberships/recognition-wall (active + not anonymous; resolves business name or donor display name; sorts by tier amount then name). Opt-out model: members appear unless is_anonymous is set — so placing this block publishes the names/logos of all current active members. Renders nothing on the front end when no members qualify.
  • New Business Member Slideshow block (starter-shelter/business-slideshow) — a no-build, Interactivity-API (WP 6.9/7.0) carousel of active business members with an approved logo. All eligible members are server-rendered into a CSS-transform track and rotated client-side (no fetch/loading state); off-screen slides are inert for correct tab order and screen-reader behavior. Autoplay pauses on hover/focus, respects prefers-reduced-motion, and offers prev/next + play-pause + dot controls; an explicit pause isn't overridden by hover. Each logo links to the member's website (target="_blank" rel="noopener noreferrer") when set; the business-name caption is an optional block attribute (plus autoplay toggle, interval, and max-items in the inspector). Backed by a new public read ability shelter-memberships/business-directory (active + membership_type=business + logo_status=approved + resolvable logo, sorted by business name) and a new logo_url computed field on sd_membership (mirrors the memorial photo_url). Renders nothing on the front end when no members qualify.
  • Memorial wall now has a Dedication filter dropdown (All Dedications / In Memory Of / In Honor Of) alongside the existing Type (person/pet) filter — the two axes are independent and compose. Mirrors the type filter end-to-end: shelter-memorials/list gained a dedication input (all/memory/honor), list_memorials() adds a dedication_type query constraint, and the block render + Interactivity store thread it through the memorial-dedication URL param (server re-render on change, bookmarkable, carried through pagination + load-more, and reset by Clear). No new block attribute — the control defaults to "all".
  • PHPUnit test suites (PHPUnit 9.6 — the version the WordPress core test framework supports) with CI jobs across the PHP 8.1–8.3 matrix. The manifest validator checks structural contracts statically; these tests cover runtime behavior it can't see.
    • Unit suite (composer test, unit-tests job): pure functions, no WordPress bootstrap — the memorial/dedication helpers (normalize_memorial_type, normalize_dedication_type, get_memorial_type_label, get_dedication_type_label) and Donor_Lookup::sanitize_display_name. The few WP functions these call (__, wp_strip_all_tags) are stubbed in tests/bootstrap.php; runs in ~15ms with no database.
    • Integration suite (composer test:integration, integration-tests job): boots a real WordPress 7.0 + MySQL (no WooCommerce — the code under test is WC-free and the plugin self-guards) and exercises the create/list data flows across all three entities: memorials (donor display-name preference, dedication persistence + filter, anonymous path, hydrator computed labels, CSV Dedication column), donations (allocation default + override, anonymous, donor lifetime-giving accumulation, pre-resolved donor_id, campaign attach, list-by-allocation), and memberships (tier/type/amount, +1-year term, tier-amount fallback, business fields, lifetime giving, and renewal extending an active term + recording the order). Also covers the SQL aggregation reports — shelter-donations/get-stats (totals, count, distinct-donor count, average, by-allocation breakdown, campaign scoping), shelter-reports/dashboard-stats (cross-entity roll-up of donations / active+new memberships / memorials / donor totals), and the type-aware shelter-reports/campaign-progress (donation-drive raised vs membership-drive member-count, goal/progress/remaining math, tier filtering, the 100%-cap, and is_active) — the hand-written SUM/COUNT queries the Query builder can't express and where a silent miscount would hide. Backed by bin/install-wp-tests.sh, a curl-based (no-svn) installer for the WP test library; see tests/README.md.
    • Tooling: phpunit/phpunit ^9.6 in require-dev; config.platform.php pinned to 8.1.0 and composer.lock committed for reproducible CI; the CI integration-tests job runs against a MySQL service container.
  • dedication_type (In Memory Of / In Honor Of) is now surfaced on the display layers, not just stored. Three places: the memorial wall block shows it as an eyebrow label above each honoree name (both the SSR fallback and the Interactivity client template); the admin Memorials list table shows it as a sub-line under the Type (Person/Pet) badge; and the memorial CSV export gains a "Dedication" column. Driven by a new dedication_type_label computed field on the sd_memorial manifest (mirroring memorial_type_label), backed by two new helpers Helpers\normalize_dedication_type() and Helpers\get_dedication_type_label(). Legacy rows with an empty _sd_dedication_type read as "In Memory Of" (the field default).
  • Memorial admin edit screen now exposes the memorial-form's two orthogonal axes — Dedication (In Memory Of / In Honor Of) and Honoree Type (Person / Pet) — as separate fields in the (classic) meta box, instead of the single conflated human/pet/honor select. Rendered declaratively from the manifest. (An alternative block-editor sidebar UX exists in assets/js/memorial-panel.js but is parked/un-enqueued — see that file's header for why and how to revive it.)
  • New first-class dedication_type memorial field (memory / honor, default memory, show_in_rest). Wired end-to-end: manifest field + shelter-memorials/create input + create ability persists _sd_dedication_type + product input-mapping reads _sd_dedication_type from cart/order item meta. This closes a data-loss gap: the memorial-form already collected the customer's In Memory / In Honor choice and the cart handler saved it to order-item meta, but it was never mapped into the memorial post — so the wall couldn't distinguish a memorial from an honorarium. New memorials (customer-submitted and admin-entered) now persist it.
  • New shelter-reports/campaigns-progress (plural) batch ability returns campaign-progress entries for many campaigns in 2-3 SQL queries: one GROUP BY for donation totals, one GROUP BY for membership counts, plus a update_termmeta_cache prime. Per-campaign tier_filter falls back to a focused count (rare path). Reports → Campaigns tab now uses this single call instead of N invocations of the singular shelter-reports/campaign-progress; the singular version stays as-is for one-off lookups (e.g., campaign-card).
  • shelter-reports/campaign-report ability is now type-aware. Donation drives return { progress: { total_raised, ... }, donations: [...] } (unchanged shape); membership drives return { progress: { member_count, percent_of_goal, remaining }, memberships: [...] }. The per-campaign CSV export on the Reports → Campaigns tab now produces a membership-shaped CSV (M...
Read more