Version 8.12.0.2 - May 21, 2026 - PetPoint Field-Exclusion Bug Fix + Navigation Description Font-Size Fix 8.12.0.2 is a multi-file bug fix release that addresses two unrelated issues affecting PetPoint sites. Issue A: admin-configured field exclusions (Primary Breed, etc.) were not being applied to results, filter dropdowns, or card data attributes on six PetPoint search templates. The shared resolver (FieldExclusionFilterTrait::filterAnimalsByConfiguredFields) was wired into AF and RG search templates, the PP featured-search-* templates, but was never called from the six PP templates that iterate XML directly in processResults(). Symptom: a breed listed in the admin exclusion list still showed up in results and in the filter dropdown. Display-side label mapping (filter value -> custom label) worked because that happens per-card at render time via resolveFilteredFieldValue(); only the exclusion path was broken. Issue B: on the universal-details-navigation template (PP only template using it today), the admin "Description Text" font-size setting had no effect because the navigation-template CSS hardcoded font-size: 0.95rem on .pmp-details-description-value, overriding the CSS custom property the admin setting writes (--pmp-font-size-detail-description). + Item 1: Plugin header * pet-match-pro.php Version 8.12.0.1 -> 8.12.0.2. * pet-match-pro.php Constants::VERSION '8.12.0.1' -> '8.12.0.2'. * readme.txt Stable tag 8.12.0.1 -> 8.12.0.2. + Item 2: PetPoint search-template field-exclusion fix (6 templates) * Pattern applied: after constructor populates $this->resultsData from XML, call $this->resultsData = $this->filterAnimalsByConfiguredFields($this->resultsData); then switch processResults() to iterate $this->resultsData instead of re-extracting from XML. * public/templates/pp/universal-search-structured.php - added filter call after resultsData population in constructor. processResults() now iterates $this->resultsData. getUniqueFilterValues() override switched from re-extracting via extractSearchItem() to using $this->resultsData so filter dropdowns no longer show excluded values. * public/templates/pp/adopt-search-default.php - same pattern. processResults() while-loop now reads $this->resultsData[$counter] instead of calling extractAdoptableSearchItem() each iteration. * public/templates/pp/lost-search-default.php - same pattern. Constructor was already lowercasing and enriching items; the filter call slots in after that block. processResults() while-loop reads $this->resultsData[$counter] directly (lowercased+enriched copies), dropping the inline extractXmlItems() + array_combine() call. * public/templates/pp/found-search-default.php - mirror of lost-search-default.php. * public/templates/pp/universal-search-default.php - same pattern. processResults() while-loop reads $this->resultsData[$counter] instead of calling extractXmlItem() each iteration. * public/templates/pp/adopt-celebration-similar.php - added filter call after resultsData population and before the celebration-species filter block (so species filtering operates on the already-exclusion-filtered set). processResults() was already reading $this->resultsData[$counter], no other change needed. + Item 3: PP navigation detail description font-size fix * public/css/pet-match-pro-styles.css line 5419. Old shape: `font-size: 0.95rem;`. New shape: `font-size: var(--pmp-font-size-detail-description, 0.95rem);`. * public/css/pet-match-pro-styles.min.css - same change applied to the minified build (find/replace inside the .pmp-template-details-navigation .pmp-details-description-value rule block). * The base .pmp-details-description selector already used the custom property; the navigation-template override (.pmp-template-details-navigation .pmp-details-description-value) had higher specificity and a hardcoded value, silently winning over the admin setting. Other detail-template overrides (.pmp-template-cpa, .pmp-template-wide) already use the var, so navigation was the lone outlier. + Item 4: Files NOT changed * No AF or RG templates - audited all 8 AF and 5 RG search-list templates; every one already calls filterAnimalsByConfiguredFields() in the constructor or before iteration. * No PetPoint featured-search-default/carousel/compact - those three already had the filter call. * No detail templates other than universal-details-navigation CSS - the *-similar detail templates delegate the related-animal grid to a configured search template, so they inherit the new exclusion behavior automatically. * No admin UI, option keys, shortcode parameters, KB articles, JS, or schema. * No partner API classes, no AllApi (filterAnimalsByConfiguredFields lives in the trait the templates already use), no analytics. Behavioral notes for upgrading operators: - Sites that previously configured Primary Breed (or any other field-filter group) exclusions on a PetPoint search will now see excluded animals removed from search results, from filter dropdowns, AND from the data-* attributes used by client-side JS filtering. Sites that did not configure exclusions will see no behavioral change. - Sites using the universal-details-navigation template (PP) will see the admin "Description Text" font size take effect immediately. Sites that left this setting at default will see no change. - After upload, clear OPcache / LiteSpeed cache and reload affected pages. The version bump invalidates the ?ver= cache-buster on the enqueued CSS so browsers will refetch the stylesheet. - No database migration, no operator action beyond the file upload. Verification: - demo-pp.petmatchpro.com with [pmp-search type=adopt template="universal-search-structured"] - configure an admin Primary Breed exclusion entry matching a breed visible in the unfiltered results, save, reload search; the breed should disappear from results AND from the breed filter dropdown. - Repeat on each affected template (universal-search-default, adopt-search-default, lost-search-default, found-search-default, adopt-celebration-similar) to confirm parity. - Visit a PetPoint detail page rendered with the universal-details-navigation template; change admin "Description Text" font-size to a large value; reload - the description text should grow accordingly. - Regression check on the AF and RG search templates: confirm exclusion behavior is unchanged from 8.12.0.1. Version 8.12.0.1 - May 21, 2026 - AF Filter-Widget Search Name Render Fix 8.12.0.1 is a single-file bug fix for the AnimalsFirst universal-search-filter-widget template. Animal names were not rendering on result cards when the template was selected as the active adopt/lost/found search template, because the name-in-config detection compared the lowercased name field keys against the numeric indices of the allowedFields array (via an erroneous array_keys() wrapper) instead of against the field-name values themselves. Every other AF, PetPoint, and RescueGroups search template uses the correct values-based intersect; filter-widget was the lone outlier. + Item 1: Plugin header * pet-match-pro.php Version 8.12.0 -> 8.12.0.1. * pet-match-pro.php Constants::VERSION '8.12.0' -> '8.12.0.1'. * readme.txt Stable tag 8.12.0 -> 8.12.0.1. + Item 2: AF universal-search-filter-widget name-render fix * public/templates/af/universal-search-filter-widget.php line 438. Old shape: `array_intersect($nameFieldKeys, array_map('strtolower', array_keys($this->allowedFields)))`. New shape: `array_intersect($nameFieldKeys, array_map('strtolower', $this->allowedFields))`. * `$this->allowedFields` is a flat list of configured field names (e.g. ['name', 'breed', 'gender', 'age']), not an associative map. array_keys() returned the numeric indices [0,1,2,3], so 'name' never intersected and $nameInConfig was always false. The standalone name section at line 440 (`buildNameSection()`) was therefore unreachable on this template. * Symptom on the live site: filter-widget search cards rendered photo + sex + age + location with no animal name above the details block. * Pattern verified across all 13 other search templates (af universal-search-default, universal-search-no-filter, featured-search-default/carousel/compact; pp universal-search-default, featured-search-default/carousel/compact; rg adopt-search-default, adopt-search-carousel, adopt-search-compact; af adopt-celebration-similar, rg adopt-celebration-similar) - every one uses `array_map('strtolower', $this->allowedFields)` without array_keys(). Filter-widget was the sole regression. + Item 3: Files NOT changed * No other AF templates - the bug was isolated to filter-widget. * No PetPoint or RescueGroups templates - both partners' search templates use the correct pattern. * No admin UI, option keys, shortcode parameters, KB articles, JS, or CSS. * No partner API classes, no AllApi, no analytics, no schema. Behavioral notes for upgrading operators: - Sites on 8.12.0 with `search_template_adopt` (or lost/found) set to universal-search-filter-widget were the only affected sites. Other AF search templates rendered names correctly on 8.12.0. - After upload, clear OPcache / LiteSpeed cache and reload the search page; animal names will appear above the breed/age/gender/location block. - No database migration, no operator action beyond the file upload. Verification: - demo-af.petmatchpro.com with [pmp-search type=adopt template="universal-search-filter-widget"] - confirm name renders on each card above the details block. - Regression check on demo-af with other AF search templates (universal-search-default, featured-search-default) - confirm name rendering is unchanged from 8.12.0 behavior. Version 8.12.0 - May 20, 2026 - Typed Class Constants + json_validate() Adoption 8.12.0 is the follow-on to 8.11.5 that the PHP 8.3 floor unblocked. Two language features that only exist on PHP 8.3 are adopted across the codebase: typed class constants (PHP 8.3 RFC) on every public constant in the main plugin file, and json_validate() in the AnimalsFirst and RescueGroups response parsers. Neither change is functional - both are defense-in-depth refactors that move correctness checks from "runtime assertion" to "loadable-class-time verification" (for the constants) and from "decode-then-inspect-error-state" to "validate-then-decode-once" (for the JSON parsers). No admin UI changes, no template changes, no schema changes, no shortcode parameter changes, no partner API surface changes. Safe drop-in upgrade for sites already on PHP 8.3 + 8.11.5. + Item 1: Plugin header * pet-match-pro.php Version 8.11.5 -> 8.12.0. * pet-match-pro.php Constants::VERSION '8.11.5' -> '8.12.0'. * readme.txt Stable tag 8.11.5 -> 8.12.0. + Item 2: json_validate() adoption in AnimalsFirst parser * includes/af/class-pet-match-pro-af-api.php lines ~638-659. The old shape was `json_decode($body, true)` followed by `if ($jsonArray === null && !empty($body)) { $jsonError = json_last_error_msg(); ... }`. The new shape is `if (!empty($body) && !json_validate($body)) { $jsonError = json_last_error_msg(); ... }` followed by the single `json_decode($body, true)` call that runs only on validated input. * Empty body is intentionally preserved as a non-error case - downstream code handles a null/empty decode result with `?? []`. The `!empty($body) &&` short-circuit guard is retained for that reason. * Existing error taxonomy preserved: Connection Error / Authentication Error / API Error / Parse Error labels and message templates unchanged. The Parse Error branch still receives the same json_last_error_msg() detail string because json_validate() populates json_last_error_msg() on failure (per the PHP 8.3 RFC). * The three other json_decode() call sites in the file (lines ~700, ~734, ~2050) were NOT changed because they have no json_last_error() pairing - they intentionally use `?? []` fallbacks for non-critical response paths (filter values, paginated continuation, secondary filter fetch). The user instruction specifically scoped the refactor to json_decode + json_last_error pairs. + Item 3: json_validate() adoption in RescueGroups parser * includes/rg/class-pet-match-pro-rg-api.php lines ~511-531 rewritten with the same validate-then-decode shape as Item 2. * The private `getJsonError()` helper (a match expression over the seven JSON_ERROR_* constants) was removed. It was the only call site for json_last_error() in this file, and json_last_error_msg() returns equivalent operator-readable strings without the manual match table. * Net -14 lines in this file (the match expression + its callers). * Error taxonomy preserved identically to AF: Connection Error / Authentication Error / API Error / Parse Error. + Item 4: Typed class constants in pet-match-pro.php * Every public constant in the following ten classes received an explicit `public const string`, `public const int`, or `public const array` type declaration. 866 constants in total. - Constants (lines ~73-397) - 207 constants - Settings (lines ~402-774) - paths, option keys, method type identifiers, field labels - Shortcodes (lines ~775-849) - shortcode names and parameter names - LevelPrefix (lines ~850-872) - license-level lookup key prefixes - LabelPrefix (lines ~873-887) - label option key prefixes - ValuePrefix (lines ~888-897) - value option key prefixes - MethodTypes (lines ~898-931) - partner-specific API method name mappings - AnimalsFirstFields (lines ~932-1028) - AF API field name constants - RescueGroupsFields (lines ~1029-1177) - RG API field name constants - PetPointFields (lines ~1178-1308) - PP API field name constants * The IntegrationPartner enum at line ~1309 was intentionally skipped - backed enum cases (`case PetPoint = 'PetPoint';`) are already statically typed by the enum's `: string` backing-type declaration. * `public const PATH = __DIR__;` and `public const PATH_FILE = __FILE__;` were typed as `string` (both magic constants resolve to strings at compile time). * `INTEGRATION_PARTNERS`, `CURRENCY_SYMBOL_MAP`, `DB_ANALYTICS_CONVERSION_ACTION_TYPES`, `SEX_MAP`, `FIELD_ALIASES` (in AF, RG, and PP field classes), and `ZERO_IS_EMPTY_FIELDS` (in AF and PP field classes) were typed as `array`. * All other constants are `string` or `int` literal values; the transformation was mechanical and applied via regex over the whole file. + Item 5: Why typed constants matter even though the values are literals * Catching a mis-typed override at child-class compile time rather than at the call site. If a future subclass ever shadowed `public const int FREE_LEVEL = 3;` with a string value, PHP 8.3 will fatal at class-load time with a covariant-type violation. Without the type, the violation surfaces wherever the constant is consumed and the type mismatch happens to break something - days or releases later. * Static-analysis tools (Psalm, PHPStan, Rector) read the declared type as a stronger signal than inferring "probably string" from the literal. Future refactors that touch these constants get sharper IDE and CI feedback. * Required prerequisite for the 8.13.0 AI Breed Normalization backlog, where the new `Settings::FILTER_AI_NORMALIZE`, `Settings::FILTER_AI_LAST_RUN`, and `Settings::FILTER_AI_SUGGEST` constants will be added alongside their existing siblings - the new constants will inherit the typed style that 8.12.0 establishes. + Item 6: Files NOT changed * No PetPoint API class. PP uses SimpleXMLElement, not json_decode, so the json_validate refactor does not apply. * No partner template files (search, detail, poster). * No admin settings UI, option keys, shortcode parameters, or KB articles. * No JavaScript or CSS. * No translation strings - the json_validate refactor reuses the existing AnimalsFirst and RescueGroups Parse Error message templates verbatim, so .pot regeneration is not required for this release. * No analytics schema, rollup, or queue logic. Behavioral notes for upgrading operators: - Sites already on PHP 8.3 + 8.11.5: upload the three changed files (pet-match-pro.php, includes/af/class-pet-match-pro-af-api.php, includes/rg/class-pet-match-pro-rg-api.php) plus README.txt and CHANGE-LOG.txt and you are done. - Sites still on PHP 8.1 or 8.2: 8.11.5's activation hook already fails closed at those versions; 8.12.0 inherits that floor and additionally cannot be loaded by PHP < 8.3 because typed class constants are a parse-time PHP 8.3 syntax feature. Do not upload 8.12.0 until PHP is upgraded. - No database migration, no rewrite-rule flush, no operator action beyond the file upload. Verification on the three demo sites (planned post-upload): - demo-pp.petmatchpro.com: smoke-test [pmp-search type=adopt] and [pmp-details] - the constant-typing change touches PetPointFields, so the partner that exercises those constants most heavily should be verified first. - demo-af.petmatchpro.com: smoke-test the AF search response path through a deliberately malformed AnimalsFirst response to confirm the Parse Error message text and the admin notice carry the same json_last_error_msg() detail as before. - demo-rg.petmatchpro.com: same Parse Error smoke test against the RG endpoint to confirm the removal of getJsonError() did not regress the operator-visible message. - Plugin Check pass on all three. Note on local PHP availability: - The user's local Windows workstation does not have a PHP binary on PATH, so the `php -l` verification step requested in the implementation prompt was skipped locally. The typed-constant transformation was applied via a single PowerShell .NET regex pass against the raw file content (UTF-8, no BOM, CRLF preserved) and verified by re-reading the changed regions plus a final grep that confirms zero untyped `public const` declarations remain in the file. Authoritative `php -l` verification should run on the first upload to demo-pp / demo-af / demo-rg. Version 8.11.5 - May 20, 2026 - WordPress 7.0 Compatibility + PHP 8.3 Floor 8.11.5 is a one-purpose release: prepare PetMatchPro for WordPress 7.0 (released May 20, 2026) by bumping the "Tested up to" header to 7.0 and raising the minimum PHP requirement from 8.1 to 8.3 to align with WordPress 7.0's recommended PHP version. No functional changes, no schema changes, no template changes, no admin UI changes. Safe drop-in for any site already on PHP 8.3 or higher. The Connections Screen / AI Connectors API introduced in WP 7.0 was intentionally NOT adopted in this release - that screen is scoped to AI provider keys (Anthropic, Google, OpenAI), not shelter-management API keys. The Connectors API metadata explicitly notes that non-AI connector types are reserved for "future releases", so PetMatchPro's PetPoint / AnimalsFirst / RescueGroups keys remain in their current pet-match-pro-general option storage where they belong. Revisit when WP introduces a generic (non-AI) connector type. The 8.12.0 release will follow with the typed-class-constants pass and json_validate() adoption in the AF and RG response parsers - both PHP 8.3-only language features that depend on this floor bump. + Item 1: Plugin header * pet-match-pro.php Version 8.11.4 -> 8.11.5. * pet-match-pro.php Requires PHP 8.1 -> 8.3. * pet-match-pro.php Requires at least 6.0 -> 6.5 (aligned with readme.txt which already said 6.5). * pet-match-pro.php new "Tested up to: 7.0" header line. * pet-match-pro.php Constants::VERSION '8.11.4' -> '8.11.5'. + Item 2: Runtime PHP version checks * pet-match-pro.php line 42 (load-time check): version_compare floor '8.1.0' -> '8.3.0', user-facing message updated. * pet-match-pro.php line ~1824 (activation hook): same floor bump, same message update. * Both checks deactivate the plugin with a clear admin notice if PHP is below 8.3 - existing protective pattern, only the floor number changed. + Item 3: readme.txt * Tested up to 6.9 -> 7.0. * Stable tag 8.11.4 -> 8.11.5. * New "Requires PHP: 8.3" header (was previously absent from readme.txt - the value lived only in the plugin file). * New = 8.11.5 = Upgrade Notice block explaining the PHP floor change. + Item 4: CLAUDE.md * Coding-standards line "PHP 8.1+" -> "PHP 8.3+" with a note explaining the rationale and the version it shipped in. + Item 5: Files NOT changed * No partner API code (PP / AF / RG). * No template files. * No admin settings, option keys, or shortcode parameters. * No analytics schema or rollup logic. * No JavaScript or CSS. * No translation strings (the PHP-version messages were edited in place - text-domain wrappers unchanged, so .pot regeneration is not required for this release). Behavioral notes for upgrading operators: - Sites already on PHP 8.3 or higher: upload the four changed files (pet-match-pro.php, readme.txt, CLAUDE.md, CHANGE-LOG.txt) and you are done. - Sites still on PHP 8.1 or 8.2: do NOT upload 8.11.5 until you have upgraded PHP. The activation hook will fail closed and present a clear admin notice; the existing 8.11.4 install continues to run unchanged on PHP 8.1+ if you need to defer the bump. - Sites running WordPress 6.5 - 6.9: unaffected. The "Tested up to: 7.0" change does not raise the minimum supported WP version. - No database migration, no rewrite-rule flush, no operator action beyond the file upload. Verification on a WordPress 7.0 demo site (planned for the RescueGroups demo once 7.0 is generally available): - Activate cleanly on PHP 8.3 + WP 7.0. - Confirm the admin "Modern" theme renders the Settings tabs, wizard, Shortcode Builder, and accordions correctly. - Run Plugin Check from wordpress.org and resolve any flagged items. - Smoke-test [pmp-search] and [pmp-details] for adopt on the RG demo - the plugin's primary public-facing surface. Version 8.11.4 - May 18, 2026 - Unified Search Template Field Resolution 8.11.4 completes a long-running consistency cleanup across the partner search templates. Before this release, PetPoint search templates received their display-field list as a pre-resolved $allowedFields array via constructor injection, while AnimalsFirst and RescueGroups templates called the trait method getDetailsParam() and parsed a comma-separated string locally. AllApi::showDetails() applies license filtering and name-first ordering to the field list, so AF and RG were bypassing that pipeline and re-doing the resolution downstream. An 8.11.x hotfix had previously promoted AllApi::getDetailListString() to public so the trait could fall back to admin-configured fields - a stopgap that worked but kept the divergence intact. The 8.11.4 refactor brings AF and RG into line with the PP pattern: outputSearch() in each partner API class now derives $allowedFields = array_keys($searchResultDetails), passes it to the template via constructor, and the template consumes the flat indexed array directly. The getDetailsParam() method is removed from SearchTemplateTrait and getDetailListString() reverts to private. No admin settings change, no partner API surface change, no template file renames. This is the unification you would do if you were redesigning the search template contract from scratch - one resolution path, one place where license filtering happens, no string parsing inside templates. + Item 1: Partner API changes (2 files) * includes/af/class-pet-match-pro-af-api.php outputSearch() now derives $allowedFields immediately after $searchResultLabels is unpacked from prepareSearchContext(). * includes/rg/class-pet-match-pro-rg-api.php outputSearch() does the same. * Mirrors the PetPoint pattern that has been in place at includes/pp/class-pet-match-pro-pp-api.php:1171 since the original 8.x refactor. + Item 2: AF template migrations (8 files) * public/templates/af/universal-search-default.php - the worked example. Renames $resultFields property to $allowedFields, switches the card-rendering chain (buildAnimalCard / buildDetailsSection / getDisplayFields) from string $details to array $details, rewrites getDisplayFields() to iterate the array directly instead of explode-on-comma string parsing. * public/templates/af/universal-search-no-filter.php - same migration plus a name-heading parity fix in buildAnimalCard() that brings it in line with universal-search-default's pre-existing !$hasShortcodeDetails guard. Without the parity fix, the no-filter template would have continued to render the name as a heading even when a details="name,..." shortcode was active, causing a duplicate name render. * public/templates/af/universal-search-filter-widget.php - same migration plus the same name-heading guard fix. * public/templates/af/universal-search-structured.php - Variant B migration. Renames the pre-existing $searchResultDetails constructor parameter (which held the assoc array [fieldKey => label]) to $allowedFields (the flat key list). Internal call sites that did array_key_exists($key, $this->searchResultDetails) become in_array($key, $this->allowedFields, true). The preg_replace + explode re-ordering block at line 686 collapses to array_map('strtolower', $this->allowedFields). * public/templates/af/featured-search-default.php - Variant A migration with intentional behavior alignment. The wholesale getDetailsToDisplay() method (a 6-line CSV parser) is replaced by the canonical getDisplayFields() pattern; buildDetailsSection's loop is restructured to consume the assoc [fieldKey => fieldLabel] output. Two user-visible side effects come with the unification: label fallback for fields without a configured label changes from ucfirst($fieldKeyLower) to ucwords(str_replace('_', ' ', $field)) so date_of_birth now renders as "Date Of Birth" instead of "Date_of_birth"; and a details=name,... shortcode now places the name inline in the field section instead of suppressing it. Both bring this template into line with the canonical pattern and were explicitly approved during review. * public/templates/af/featured-search-carousel.php - same Variant A canonical-restructure migration as featured-search-default. * public/templates/af/featured-search-compact.php - same migration. The initial commit (08c9849) introduced a UTF-8 BOM (EF BB BF) from a PowerShell byte-level write that triggered a fatal "strict_types declaration must be the very first statement" error on demo-af.petmatchpro.com; the BOM was stripped in a follow-up commit (d80d4d8). Also re-derived $hasShortcodeDetails from $this->apiFunction->hasShortcodeDetails (was incorrectly reading $this->searchDetails[Shortcodes::DETAILS]) and added the name-overlay !$hasShortcodeDetails guard. * public/templates/af/adopt-celebration-similar.php - unique variant where getDisplayFields() previously returned [key, label, value] dicts; collapsed to the canonical [fieldKey => fieldLabel] assoc with value resolution moved inline into buildDetailsSection's foreach. Pre-existing U+FFFD replacement chars at line 146 cleaned up to " - " per the no-em-dash rule. + Item 3: RG template migrations (5 files) * public/templates/rg/adopt-search-default.php - Variant B migration mirroring AF universal-search-structured (rename $searchResultDetails to $allowedFields, swap array_key_exists for in_array, replace preg_replace+explode with array_map('strtolower', $this->allowedFields)). * public/templates/rg/adopt-search-carousel.php - same Variant B migration. The initial commit (c8d2306) introduced a double UTF-8 BOM (EF BB BF EF BB BF) at file start, stripped in a follow-up commit (73f233b). The double BOM appears to have come from the pre-migration file already carrying one BOM combined with a PowerShell write that added another. * public/templates/rg/adopt-search-compact.php - same Variant B migration. * public/templates/rg/adopt-search-structured.php - Variant B migration plus dead-code removal. The $fieldLevels property, getFieldLevels() method, $defaultFieldLabels property, getDefaultFieldLabels() method, getFriendlyFieldLabel() method, and unused LabelPrefix / LevelPrefix use statements were all reachable only through the old code paths that the migration replaces. Net -219 lines. * public/templates/rg/adopt-celebration-similar.php - same Variant B migration with the canonical-restructure shape (collapsed value-resolution second loop into the foreach). + Item 4: Trait cleanup * public/templates/includes/class-pet-match-pro-search-template-trait.php - getDetailsParam() method removed. No remaining callers across the codebase (verified by repository-wide grep before deletion). + Item 5: AllApi visibility revert * includes/class-pet-match-pro-all-api.php - getDetailListString() reverted from public to private. The hotfix that promoted it to public is no longer needed because the trait method that depended on it is gone. Only the internal AllApi caller at all-api.php:1134 remains. + Item 6: Files NOT changed * No PetPoint templates were touched. PP was already on the unified $allowedFields pattern and served as the reference. * No detail-page templates (*-details-*.php), poster templates, or details-navigation templates were modified. The detail flow uses BaseDetailTemplate and a different field-resolution path. * No admin settings, option keys, partner API surfaces, or shortcode parameters changed. Behavioral notes for upgrading operators: - Sites with custom themes that override AF or RG search templates from wp-content/themes//petmatchpro/... will need their overrides regenerated. The constructor signatures of every migrated AF and RG template changed. Sites that use only the plugin's bundled templates are unaffected. - The label-fallback formatting change in featured-search-default applies only to fields that have no configured admin label. Configured labels are honored unchanged. - The name-in-field-section behavior change in featured-search-default applies only when a details= shortcode parameter explicitly includes the name field. Default rendering (admin-configured field list) is unchanged. Version 8.11.3 - May 15, 2026 - Universal-Details-Default Method Detection (Second Hotfix) 8.11.2 cleared the constructor-order fatal but exposed a second bug: visiting an AF /pmp/lost/... or /pmp/found/... URL with the new universal-details-default template selected rendered the ADOPT layout (no descriptive fallback name, no lost/found CTAs, title placed inside content instead of above the wrapper). The fix landed but the dispatch was wrong - users got a clean page that was silently the wrong layout. Root cause: 8.11.1 / 8.11.2 read $_GET[Settings::METHOD] to drive the layout branch. That value is normally set by SeoManager::onParseRequest (for pretty permalinks) or by the original HTTP query string (for legacy URLs), but Divi's block-rendering pipeline routes the shortcode through MultiView and Loop modules that re-render content in contexts where $_GET is not reliable - the lost / found URLs reached the template with an empty $_GET[Settings::METHOD], so the match() fell through to the adopt default. Reproduced on demo-af.petmatchpro.com (Divi 5.x). Fix: stop reading $_GET. The PMP API layer already resolves the canonical method type before requiring the template - `$methodType = $this->extractMethodType($callFunc);` at AF api line 1564 and `$methodType = ltrim($keySuffix, '_');` at PP api line 1889. That local variable is in scope when the template is `require`d at AF line 1729 / PP line 2013 because PHP include/require inherits the calling method's variable scope. The template now reads $methodType directly, lower-cases it, and runs the same match() it always did. Why this is robust across all paths: - Pretty permalinks: SeoManager rewrites the URL, handleDetailsShortcode reads $_GET['method'], passes the value to createDetails as $callFunc, AF/PP extracts $methodType from $callFunc. Template gets the resolved value. - Legacy ?method=lost URLs: $_GET['method'] populated by PHP, same chain through handleDetailsShortcode -> $callFunc -> $methodType. Template gets the resolved value. - Divi MultiView / Loop / page-builder block re-rendering: the API call chain runs once per shortcode invocation and the $methodType local survives the call. Even if $_GET is empty or modified by the time the template renders, $methodType is still the value AF/PP authoritatively computed. + Item 1: AF template fix - public/templates/af/universal-details-default.php * Removed: $urlMethod = isset($_GET[Settings::METHOD]) ? strtolower(sanitize_text_field(wp_unslash($_GET[Settings::METHOD]))) : ''; * Added: $resolvedMethodRaw = isset($methodType) && is_string($methodType) ? strtolower($methodType) : ''; * The match() statement is unchanged - it still maps the resolved string to METHOD_TYPE_LOST / METHOD_TYPE_FOUND / METHOD_TYPE_ADOPT default. AF preferred sub-types (outcome / foster / stray / rehome / pending / all / other) continue to fall through to the adopt branch as designed. * Updated the leading comment block to document the rationale and reference the 8.11.2 Divi regression. + Item 2: PP template fix - public/templates/pp/universal-details-default.php * Identical fix. PP API at line 1889 also sets a $methodType local before the require at line 2013, so the same in-scope read works. + Item 3: No new error handling * The is_string() guard on $methodType is the only defensive check - covers the theoretical case where the template is included from a context that does not set the local (e.g. a third-party theme override that calls require directly). Cheap, no functional impact when the value is present. + Item 4: Version constants * pet-match-pro.php line 19: Plugin header "Version:" 8.11.2 -> 8.11.3. * pet-match-pro.php Constants::VERSION '8.11.2' -> '8.11.3'. * README.txt: Stable tag 8.11.2 -> 8.11.3; new = 8.11.3 = Upgrade Notice block. + Item 5: Operator action required * Upload the two template files and version-bumped pet-match-pro.php. Auto-flush fires on the version bump (8.10.1.5+ behavior). * Verify: visit an AF /pmp/lost/{slug}/ URL with the universal-details-default template configured for lost; the title appears ABOVE the wrapper div, the display name is "Lost {Species} #{ID}" when no actual name exists, and the CTAs are call+email+return. Repeat for /pmp/found/. + Item 6: Out of scope (followups) * Other templates that read $_GET in similar ways: adopt-default.php and friends use $_GET only for analytics ($animalId display), not for method-type routing - they hardcode their method via getMethodType(). No bug analog there. Still worth a quick grep next time someone touches the template loader, just in case a future template introduces method-type branching that reaches for $_GET. * universal-details-navigation.php and universal-details-poster.php: also accept method-type input via shortcode parameters and have their own method-aware logic. They were stable in 8.11.0 and earlier so the $_GET regression is specific to the 8.11.1+ combo template, not a class-wide pattern. No fix needed. ---- Version 8.11.2 - May 15, 2026 - Hotfix for 8.11.1 Constructor-Order Fatal Single-bug hotfix. 8.11.1 shipped both new universal-details-default templates with the anonymous class assigning $this->resolvedMethod AFTER parent::__construct(), but BaseDetailTemplate::__construct() calls loadFieldLevels() which calls $this->getMethodType() to build the field-levels filename. The anonymous getMethodType() override reads $this->resolvedMethod, which was still uninitialized at that point - PHP 8.1+ typed-property strict-init rules turn that read into a fatal "Typed property ... must not be accessed before initialization." Every AF and PP detail-page hit using the new template fataled before rendering a single byte of HTML. Fix: move `$this->resolvedMethod = $resolvedMethod;` to the FIRST statement of the constructor, before `parent::__construct($api, $animalDetails)`. PHP allows assignment to a typed property without prior initialization - the strict-init check fires only on READ. Once parent::__construct runs, its loadFieldLevels() call now sees the property as initialized and returns the correct method-specific filename. Detected via WP_DEBUG error log on demo-af.petmatchpro.com immediately after 8.11.1 deploy. No customer impact prior to that because demo-af was the first site to receive the build. + Item 1: Constructor order swap - public/templates/af/universal-details-default.php * Anonymous class __construct(): assign $this->resolvedMethod first, then call parent::__construct(). * Added a leading comment block warning future editors that the order is load-bearing - the parent constructor's loadFieldLevels() reaches into getMethodType() before parent::__construct() returns. + Item 2: Constructor order swap - public/templates/pp/universal-details-default.php * Identical fix, identical comment. PP template was vulnerable to the same fatal but never had a chance to trigger it - we caught the AF case first. + Item 3: No other files touched * Existing per-method templates (adopt-default.php, lost-default.php, found-default.php) are unaffected. Their anonymous getMethodType() returns a literal constant (e.g., `return Settings::METHOD_TYPE_ADOPT;`) - no instance state, no init-order dependency. * Other anonymous-class templates with state (universal-details-navigation.php, universal-details-poster.php) need to be audited for this pattern. None reported failing today but they were written before strict typed-property init was a routine concern. Out of scope for this hotfix - filed as a followup audit. + Item 4: Version constants * pet-match-pro.php line 19: Plugin header "Version:" 8.11.1 -> 8.11.2. * pet-match-pro.php Constants::VERSION '8.11.1' -> '8.11.2'. * README.txt: Stable tag 8.11.1 -> 8.11.2; new = 8.11.2 = Upgrade Notice block describing the fatal and the fix. + Item 5: Operator action required * Upload the two template files and the version-bumped pet-match-pro.php. The 8.10.1.5 auto-flush fires on the version bump - no manual permalinks save needed. * Verify: visit any AF detail URL configured with universal-details-default; no fatal in PHP error log, page renders with the appropriate method layout. + Item 6: Out of scope (followups) * Audit pass on other anonymous-class detail templates (universal-details-navigation, universal-details-poster) for parent-constructor-reads-subclass-state init-order traps. None reported broken, but the pattern is fragile and worth a sweep before adding more templates that store state. ---- Version 8.11.1 - May 15, 2026 - Universal-Details-Default Template (PetPoint + AnimalsFirst) New free-tier detail template that solves the AF-preferred-animals "no simple default" gap surfaced after 8.10.1.5. Until now, an AF preferred animal (type=other / outcome / foster / stray / rehome / pending / all) routed to either the heavy 890-line universal-details-navigation template (requires quick_fields, title_fields, stats_row, stats_full shortcode params to be useful) or universal-details-poster - no simple "just show the animal" option existed. 8.11.1 adds universal-details-default.php for both PetPoint and AnimalsFirst, mirroring how universal-search-default.php has been the catch-all search template for years. The template is method-aware: a single file internally branches between adopt / lost / found layouts based on the URL method ($_GET['method']), so picking ONE template handles all the partner's method types correctly. AF preferred and PP list both render in adopt layout (since they share the adoption-data shape). Why "method-aware combo" instead of a static adopt-style template: ~90% of adopt-default and lost-default are identical, but the differences (title position, fallback display name, CTAs) are user-visible. A static template would either skip lost-default's "Lost {Species} #{ID}" fallback name (broken for unnamed lost intakes) OR render lost CTAs on adopt animals (confusing). The combo template respects each method's existing conventions. + Item 1: New AnimalsFirst template - public/templates/af/universal-details-default.php (~180 lines) * Method resolution: $_GET[Settings::METHOD] sanitized and matched against METHOD_TYPE_LOST / METHOD_TYPE_FOUND. Everything else routes to adopt layout (covers ADOPT, PREFERRED, and all AF preferred sub-types). * Anonymous class stores the resolved method and returns it from getMethodType() so base-class field-level / hide-empty / label lookups use the correct method type. The anonymous class also overrides shouldSkipField() for AF field constants and getAnimalId() for the lost/found fallback name. * Body branches via $isMethodAdopt / $isMethodLost / $isMethodFound flags: - adopt: title inside .pmp-details-content; renderVideoPlayer + renderVideos with .pmp-details-thumbs-column wrapper; renderIcons + renderDescription; conversion button "meet_greet"; other button "return". - found: title above wrapper; thumbs render direct (no column wrapper); conversion buttons "call" + "email" with custom labels ("Call Us" / "Email Us"); other button "return". Matches found-default.php behavior. - lost: title above wrapper; thumbs render direct; conversion buttons "call" + "email"; other button "return". Matches lost-default.php behavior. * Wrapper data-method-type uses the partner-specific MethodTypes::ANIMALSFIRST_{ADOPT|LOST|FOUND} constant so existing CSS / JS selectors keyed on those values still match. * Follows AF's ob_start() / ob_get_clean() convention - body buffered into $outputDetails as the caller expects. + Item 2: New PetPoint template - public/templates/pp/universal-details-default.php (~170 lines) * Same dispatch and method-aware branching as AF. * PP-specific differences: - shouldSkipField() additionally checks PetPointFields::NAME_ANIMAL (PP has two name field variants). - URL animal-id param computed via the existing str_replace trick on PetPointFields::ID / PetPointFields::ID_ANIMAL (produces 'animalID'). - Lost / found unconditionally use the "Lost {Species} #{ID}" descriptive name (matches PP lost-default / found-default which sprintf unconditionally, unlike AF which conditionalizes the fallback). - Adopt layout uses renderOtherButtons(['call', 'return']) only - no renderConversionButtons call (PP adopt-default has no conversion-button row). - Lost / found layout uses renderOtherButtons(['call', 'email', 'return']) - no conversion-button row. - PP convention: renders directly to output, no ob_start. + Item 3: RescueGroups intentionally excluded * RG only supports adopt method (no lost / found / preferred / list). The existing adopt-default.php covers every scenario for RG, so a "universal" template would be a no-op. If RG ever adds method-type support, this template gains a third partner file at that time. + Item 4: License gating * Both new templates contain "default" in the filename, so the existing AllApi::getTemplateLicenseError() gate (which allows *-default-* templates for free tier) admits them. No level-file changes needed. * Template discovery is via glob() (per docs/split-prompts/phase-03-template-extensibility.md), so the new files appear in the admin Detail Template dropdown automatically the next time the page is rendered. No registry edit needed. + Item 5: KB update - docs/kb/03-templates-design/02-detail-templates-gallery.html * New row added to the PetPoint table (between lost-default.php and universal-details-navigation.php). * New row added to the AnimalsFirst table (same position). * Both rows are tagged "Basic" license tier and reference 8.11.1+ in the description. * RescueGroups table unchanged. + Item 6: Version constants * pet-match-pro.php line 19: Plugin header "Version:" 8.11.0 -> 8.11.1. * pet-match-pro.php Constants::VERSION '8.11.0' -> '8.11.1'. * README.txt: Stable tag 8.11.0 -> 8.11.1; new = 8.11.1 = Upgrade Notice block describing the template. + Item 7: Operator action required * None. The auto-flush from 8.10.1.5 fires on the version bump (Constants::VERSION changed), so any cached rewrite rules refresh on the next page hit. New templates are discovered via glob, so they appear in the admin dropdown without a cache clear. * To use the new template: in WP Admin > PetMatchPro > General > Templates, change the Detail Template for whichever method to "universal-details-default", OR pass `template="universal-details-default"` in a [pmp-details] shortcode. + Item 8: Out of scope (followups) * RG version - skipped because RG is adopt-only, see Item 3. * Method-aware combo for similar-grid templates (adopt-conversion-similar, adopt-profile-3-column-similar, adopt-details-navigation-similar). Those are adopt-specific by design; universalizing them would need lost/found similar-grid logic that does not exist yet. Defer until a customer asks. * Wizard step 6 (Detail Template) does not yet special-case the universal-details-default as a recommended preferred-method option. Worth flagging in the wizard so AF customers find it without reading the KB. Bundle into the next wizard polish release. ---- Version 8.11.0 - May 15, 2026 - Preconnect to Partner Photo CDN (Free-Tier Perf Feature) Minor-version release adding a single user-visible feature: a free-tier perf optimization that emits `` in the page head for the active integration partner's animal-photo CDN, but only on pages that render PMP content. Typical wins are 100-300 ms shaved off the first photo paint on desktop and up to ~900 ms on slow mobile - all from the browser opening the TCP + TLS handshake to the CDN in parallel with HTML parsing instead of waiting until it encounters the first . Why this release is 8.11.0 and not 8.10.2: the change introduces a new public API surface (Settings::PRECONNECT_PARTNER_CDN, IntegrationPartner::photoCdnHost(), PetMatchPro\Performance\ResourceHints class) and a new admin setting, which is a feature add - 8.10.x has been bugfix-only. Bumping the minor segment keeps the changelog easy to navigate: scan to 8.11 to see when preconnect landed. Why free tier: this is a pure perf improvement with no behavioral or feature trade-off. Locking it behind Premium would penalize free-tier shelters disproportionately - they are often the ones running on cheaper hosting where every saved ms matters. The partner CDN hosts were captured by curling demo-af.petmatchpro.com and demo-rg.petmatchpro.com on 2026-05-15 to read actual photo URLs from the rendered HTML (no API access required), and confirmed against the PetPoint CORS investigation brief at docs/specs/demo-site-cors-image-investigation-brief.md. + Item 1: New IntegrationPartner enum method - photoCdnHost(): string * pet-match-pro.php enum IntegrationPartner gains photoCdnHost() (placed after getDirectory()). * Returns the verified host for each partner: - PetPoint -> 'g.petango.com' - AnimalsFirst -> 'animalsfirstprod.s3.amazonaws.com' - RescueGroups -> 'cdn.rescuegroups.org' * Single source of truth for partner CDN identification - if a partner ever migrates CDNs, only this method needs to change. + Item 2: New Settings constant - PRECONNECT_PARTNER_CDN * pet-match-pro.php Settings class line ~445: public const PRECONNECT_PARTNER_CDN = 'preconnect_partner_cdn'. * Stored under the existing 'pet-match-pro-general' option (no new option row in wp_options). + Item 3: New ResourceHints class - includes/class-pet-match-pro-resource-hints.php (~140 lines) * Namespace PetMatchPro\Performance, final class, single responsibility. * Constructor reads the setting, returns early if disabled, otherwise hooks the wp_resource_hints filter at priority 10. * isEnabled(): default-on semantics - a missing key in general options is treated as enabled, so legacy sites upgrading from <8.11.0 (which never wrote the option) silently inherit the feature. Only an explicit empty / '0' disables. * filterResourceHints(): appends 'https://{host}' to the preconnect URL list when (a) the relation is 'preconnect', (b) pageHasPmpContent() returns true, and (c) IntegrationPartner::tryFrom() resolves the partner string from general options. Emitted as a plain string (not an array with crossorigin key) so WP renders without a crossorigin attribute - PMP templates use plain tags, so a crossorigin preconnect would open a separate unused connection. * pageHasPmpContent(): static request-scope cache. Detects PMP content via get_query_var('pmp_animal_id') (SEO pretty-permalink path) OR has_shortcode() against the queried post body for SEARCH / DETAILS / DETAIL tags. Cached because wp_resource_hints fires five times per page (one per relation type) and we do not want to scan post_content five times. * No CSS, no JS, no opcache concerns, no database touches. + Item 4: Wire-up in PublicFacing * public/class-pet-match-pro-public.php: where SeoManager is loaded (right after the public_constructor body), added an analogous require_once + new for ResourceHints. Same file-exists + class_exists guard pattern so a partial-upload deploy degrades gracefully. + Item 5: Activator default * includes/class-pet-match-pro-activator.php initializeGeneralOptions(): added Settings::PRECONNECT_PARTNER_CDN => '1' to the defaults array. * Fresh installs get the option explicitly set to '1'; upgrading sites pick up default-on via the ResourceHints::isEnabled() missing-key handling. + Item 6: Admin UI - General > Performance section * admin/class-pet-match-pro-admin-settings.php KB_LINKS: new 'field_preconnect_cdn' => 'preconnect-partner-cdn' entry under the Performance (secondary) block. * admin/partials/pmp-option-levels-general.php: new `LevelPrefix::LEVEL . Settings::PRECONNECT_PARTNER_CDN => Constants::FREE_LEVEL` entry in the Performance section, alongside the existing PREMIUM-gated API_CACHE_ENABLED / CLIENT_FEATURES_ENABLED / BUTTON_CONSISTENCY entries. Free-tier features still get an explicit FREE_LEVEL entry by convention - the level catalog is the single source of truth for "what tier sees what," even though the runtime check is currently bypassed for this field. * registerPerformanceFields(): the new preconnect checkbox is registered BEFORE the existing API-cache license gate, so free-tier sites see and can toggle it. The API-cache / client-features Premium fields remain behind the gate as before. * New callback preconnect_partner_cdn_callback() implements default-on semantics in the rendered checked state: !array_key_exists($fieldKey, $options) || !empty($options[$fieldKey]) - matches the runtime check in ResourceHints::isEnabled() so what the user sees in the admin UI matches what the browser receives. * admin/partials/pmp-admin-info.php: tooltip text for the new field, naming all three CDN hosts so admins can verify against their partner. * KB help icon on the field label links to docs/kb/10-best-practices/11-preconnect-partner-cdn.html via the standard renderHelpIcon() helper. + Item 7: KB article - docs/kb/10-best-practices/11-preconnect-partner-cdn.html * Title: "Preconnect to Your Partner Photo CDN" * Covers: what preconnect does, when PMP fires it (the scope rules), savings table (desktop / 4G / 3G), default-on behavior, when to disable (custom CDN proxy, CSP, debugging), verification steps (view-source + DevTools waterfall), related articles. * Hosted at petmatchpro.com/docs/preconnect-partner-cdn/ on next site rebuild. + Item 8: Version constants * pet-match-pro.php line 19: Plugin header "Version:" 8.10.1.5 -> 8.11.0. * pet-match-pro.php Constants::VERSION '8.10.1.5' -> '8.11.0'. * README.txt: Stable tag 8.10.1.5 -> 8.11.0; new = 8.11.0 = Upgrade Notice block describing the feature. + Item 9: Operator action required * None. Feature is default-on for both fresh installs and upgrades. Rewrite-rule auto-flush from 8.10.1.5 also fires on this version bump (Constants::VERSION changed), so any rewrite-rule caching on the operator's site is naturally refreshed. * Verification: view-source on a page with [pmp-search] should show in the head. + Item 10: Out of scope (followups) * dns-prefetch fallback for very old browsers - skipped. PMP requires WP 6.5+ which targets browsers that all support preconnect natively. Adding dns-prefetch would emit a second link tag with no measurable benefit. * Per-shortcode override (e.g., preconnect="false"). Not currently a use case, and the setting can already be flipped globally. Add only if a customer reports a specific need. * Preconnect to additional hosts (theme CDN, font CDN, analytics). Out of scope for PMP - those belong in theme/perf-plugin territory. * AnimalsFirst test/sandbox environment (animalsfirsttest.s3.amazonaws.com?). The current photoCdnHost() returns the prod host. If a customer uses an AF sandbox account, the preconnect would warm up the wrong host - a minor perf miss, not a bug. Add a test-mode branch if/when an AF customer reports it. ---- Version 8.10.1.5 - May 15, 2026 - Pretty-Permalink Fix for AnimalsFirst Preferred Animals + Auto-Flush on Version Change Two related changes shipped together. (1) A regex fix that lets pretty URLs for AnimalsFirst "preferred" animals resolve at all. (2) An automatic rewrite-rule flush on every version change, so this fix - and every future rewrite-rule change - lands without an operator visiting Settings > Permalinks or clicking the SEO Tools flush button. Single-bug fix uncovered on cincinnatianimalcare.org. A search built with [pmp-search type="other" ...] (or any other AnimalsFirst "preferred" sub-type: outcome, foster, stray, rehome, pending, all) emits result links of the form /pmp/preferred/{slug}-{id}/, because callMethod_Parameters() correctly maps these sub-types to METHOD_TYPE=preferred for routing. The SEO module's pretty-URL rewrite rule, however, was hardcoded to ^pmp/(adopt|lost|found)/... - so "preferred" URLs did not match any rule and WordPress returned its theme 404 before [pmp-details] ever ran. The [pmp-details type="adopt"] attribute on the destination page was a red herring; the details shortcode reads its method from the URL query var (pmp_method), not from the type attribute, so changing it would not have helped. Root cause was a regex literal duplicated in three files (one rewrite registration in includes/class-pet-match-pro-seo.php, two more in admin/class-pet-match-pro-admin-settings.php inside the save-handler and the AJAX flush handler, plus two array-key lookups and one displayed-pattern string in the SEO Diagnostics panel - six identical occurrences in total). All six were updated to ^pmp/(adopt|lost|found|preferred)/[^/]+-([0-9a-zA-Z]+)/?$. The PetPoint "list" method (adoptionlist) was not included in this fix because it surfaced as a separate path; if list animals start 404-ing in the wild, the same regex needs another extension. + Item 1: includes/class-pet-match-pro-seo.php - registerRewriteRules() * Line 118: regex updated from ^pmp/(adopt|lost|found)/[^/]+-([0-9a-zA-Z]+)/?$ to ^pmp/(adopt|lost|found|preferred)/[^/]+-([0-9a-zA-Z]+)/?$ * Match groups unchanged - $matches[1] is still the method, $matches[2] is still the animal ID hash. * Existing parseRequest/canonicalUrl logic already passes whatever method value is captured, so no other changes needed in this file. + Item 2: admin/class-pet-match-pro-admin-settings.php - five locations (all identical strings, all updated) * Line 4556 - SEO settings AJAX save handler (re-registers the rule in the same request before flush_rewrite_rules so the new pattern lands in the cached rule set). * Line 14592 - SEO Tools Diagnostics panel: array-key lookup against get_option('rewrite_rules') to display the "Rewrite Rule Registered" status indicator. * Line 14697 - SEO Tools Diagnostics panel: the user-visible "Current Pattern" code block under the registered-rule status row. * Line 15140 - ajax_seo_flush_rewrite_rules(): the manual "Flush Rewrite Rules" button handler re-registers the rule before flushing. * Line 15158 - ajax_seo_flush_rewrite_rules(): post-flush verification array-key lookup that decides what to return to the JS caller. + Item 3: Auto-flush on version change (eliminates the manual-flush step for this release and all future rewrite-rule changes) * pet-match-pro.php Constants block: new `Constants::ACTIVE_VERSION_OPTION = 'pmp_active_version'` (placed alongside ANALYTICS_SCHEMA_OPTION, with a docblock explaining its role). * pet-match-pro.php init-99 closure (the same one that already drains the activation `pmp_flush_rewrite_rules` transient): now also reads `pmp_active_version`, compares to `Constants::VERSION`, and calls flush_rewrite_rules(false) + update_option when they differ. Priority 99 keeps the flush after SeoManager::registerRewriteRules() (default prio 10), so the new pattern is already registered when the flush fires. * Why this option name, not the historical `pet-match-pro_version`: the 8.1.0 cleanup explicitly deleted that key on customer sites, so reviving it would collide with the cleanup. The `pmp_` namespace also matches the analytics-schema option naming convention. * What this covers: - WP.org auto-update path (no activation hook fires) - now self-heals. - Manual ZIP overwrite path (no activation hook fires) - now self-heals. - Activation path (existing transient still works) - now redundant on a fresh activation but harmless (one extra flush, idempotent). - Deactivate/reactivate path - version unchanged, our new check skips; the activation transient still flushes. No double-flush. * What this does NOT cover: an operator who downgrades a plugin will trigger a flush back to the older pattern, which is the desired behavior. Multisite network-activated installs need the option per-site, which is the default behavior of update_option without `$network_wide`. + Item 4: Version constants * pet-match-pro.php line 19: Plugin header "Version:" 8.10.1.4 -> 8.10.1.5. * pet-match-pro.php line 74: Constants::VERSION '8.10.1.4' -> '8.10.1.5'. * readme.txt line 8: Stable tag 8.10.1.4 -> 8.10.1.5. * readme.txt: new = 8.10.1.5 = Upgrade Notice block describing the fix and the required flush. + Item 5: Operator action required * None. The init-99 handler flushes rewrite rules automatically on the first request after the new code loads (any front-end OR wp-admin page hit triggers init). * Verification (optional): visit any page once after upgrade, then SQL `SELECT option_value FROM wp_options WHERE option_name = 'pmp_active_version'` returns '8.10.1.5'. SQL `SELECT option_value FROM wp_options WHERE option_name = 'rewrite_rules'` shows the new pattern with `|preferred` included. + Item 6: Out of scope (followups, not fixed here) * Regex DRY: the same 60-char literal is hand-copied across three files. Future cleanup should move it to a Constants::SEO_PRETTY_URL_PATTERN constant referenced by all six sites. Deferred to keep this release focused on the 404 fix. * PetPoint "list" (adoptionlist) is not in the regex. If a PetPoint customer ships a [pmp-search type="list" ...] page, those URLs will also 404. Not a known issue today; raise the next time it surfaces. * Sitemap generator (class-pet-match-pro-seo.php line 681) already builds /pmp/{methodType}/... URLs using whatever methodType the caller supplies, so once a preferred URL matches the rewrite rule, sitemap entries for preferred animals will also resolve correctly. No sitemap code change needed. ---- Version 8.10.1.4 - May 14, 2026 - Upgrade Notice Refresh for Legacy 6.x Updaters Version-bump release whose only purpose is to push a new readme.txt to WordPress.org so legacy users still on the 6.x line see a clear "MAJOR UPGRADE" notice the first time they click Update. WordPress only renders the Upgrade Notice block whose header matches the new version exactly, so the previous notice (which described only the 8.10.1.3 SVN-compatibility fix) was never going to reach a 6.x user. No PHP, schema, asset, or template changes - readme.txt copy and version strings only. + Item 1: README.txt - new = 8.10.1.4 = upgrade notice block * "MAJOR UPGRADE from 6.x" message: full modernization summary, "back up your site first" advisory, pointer to the Description tab for the long-form rundown. * Previous = 8.10.1.3 = block restored to its original WP.org SVN compatibility wording (kept for historical accuracy since that release already shipped). * Stable tag header bumped 8.10.1.3 -> 8.10.1.4. + Item 2: pet-match-pro.php - version strings bumped * Plugin header "Version:" 8.10.1.3 -> 8.10.1.4 (line 19). * Constants::VERSION '8.10.1.3' -> '8.10.1.4' (line 74). + Item 3: No code paths touched * No PHP, JS, CSS, template, or schema modifications. * No new constants, no new options, no new hooks. * No operator action required on update. ---- Version 8.10.1.3 - May 13, 2026 - WP.org SVN Compatibility (Trait Constant Removal) Single-file packaging fix discovered while pushing 8.10.1.2 to the WordPress.org SVN repository. The WP.org pre-commit hook lints every PHP file with an older PHP build that does not yet recognize trait constants (PHP 8.2+ feature). The 8.10.x line introduced one `private const` inside `SearchTemplateTrait`, which blocked the commit with `Fatal error: Traits cannot have constants in Standard input code on line 1604`. Resolved by converting the constant to a private static method - same data, same lookup pattern, accepted by the WP.org lint. + Item 1: SearchTemplateTrait - replaced `private const SORT_FILTER_FIELD_ALIASES` with `private static function getSortFilterFieldAliases(): array` * public/templates/includes/class-pet-match-pro-search-template-trait.php line 1604: the partner-specific sort/filter alias map (Constants::PETPOINT => ['breed' => 'primarybreed', 'name' => 'animalname', 'id' => 'animalid']) is now returned from a private static method instead of being declared as a trait constant. * Two internal call sites updated to use the method form: - line 1691 buildSortFilterAliasMirrorAttrs(): self::SORT_FILTER_FIELD_ALIASES[$partner] -> self::getSortFilterFieldAliases()[$partner] - line 1886 (active-sort detection logic): same rewrite. * Comment block at line 1604 retained verbatim - the alias map's rationale, shape contract, and "add new aliases here" guidance still apply unchanged. + Item 2: Codebase audit - no other PHP 8.2+ syntax found * Verified no other traits contain `const` declarations (poster-template-trait, field-filter-trait, field-exclusion-filter-trait all clean). * No `readonly class` declarations. * No DNF (disjunctive normal form) types - e.g., `(A&B)|C` in type positions. * No standalone `true` / `false` types in return-type or parameter-type positions. * No `#[\AllowDynamicProperties]` attribute usage. * Plugin remains PHP 8.1+ compatible. + Item 3: Version constants * pet-match-pro.php Version header and Constants::VERSION bumped 8.10.1.2 -> 8.10.1.3. * Constants::ANALYTICS_SCHEMA_VERSION stays at '2.1' (no schema change). * README.txt Stable tag bumped to 8.10.1.3; new Upgrade Notice entry above the 8.10.1.2 entry. * Operator action items: none. * Verification: * `svn commit` to https://plugins.svn.wordpress.org/petmatchpro/ succeeds without the trait-constant lint failure. * Sort buttons (Breed, Name, ID) on PetPoint search results still render with the correct active state after page load and after a sort change. * Filter dropdown using `breed` against a PetPoint result set still matches `data-primarybreed` cards. ---------------------------------------- Version 8.10.1.2 - May 10, 2026 - PetPoint XML Diagnostics, Foster-Location Warning, AF lost_found Combo Field Display Three operator-facing fixes uncovered during partner-by-partner testing on the demo sites. No schema change. No write-path change. No new shortcode params. Operator action items: none. + Item 1: PetPoint - Failed-XML logs now include libxml errors and a body snippet * includes/pp/class-pet-match-pro-pp-api.php animalDetail(): replaced `@simplexml_load_string()` with the standard libxml-internal-errors pattern. On parse failure the log entry now carries the actual libxml messages and the first 500 chars of the response body alongside animal_id and method. * Previously the warning "[PetMatchPro] PetPoint: Failed to parse animal detail XML" was unactionable - operators saw the animal_id but had no insight into what PP actually returned (HTML error page? truncated XML? BOM? Wrong content-type?). With the new fields a single log line tells us whether it's an authentication failure, a gone-animal celebration redirect from PP's side, or a malformed XML payload. * The `libxml_use_internal_errors()` toggle and `libxml_clear_errors()` calls are wrapped around the parse so the diagnostic does not leak libxml state into the rest of the request. + Item 2: AllApi - Foster-location check no longer emits "Array to string conversion" * includes/class-pet-match-pro-all-api.php isAnimalInFoster() line 2703: getAnimalProperty() can return an array when the partner field is an empty XML element (PetPoint's SimpleXML parses `` to `[]`) or a nested object without a 'value' key. Casting that array to string with `(string)` was emitting a PHP warning to the error log on every detail-page render and search-result card. * Fix: extract the raw value first, then `is_scalar() ? (string) : ''`. Arrays and objects degrade to empty string rather than throwing. Foster detection is unchanged for the scalar case; previously-warning paths now silently return false (animal not in foster) when the location is structurally invalid. + Item 3: AnimalsFirst lost_found combo type - admin-configured fields now display * Reported symptom: with `[pmp-search type="lost_found"]`, combo method set to "found", and "type" / "intake_type" admin checkboxes enabled under Found search results, neither field rendered on the cards even after hard refresh and cache purge. * Root cause: AllApi::callMethod_Parameters() assigns Settings::METHOD_TYPE_PREFERRED to any non-standard shortcode type. `lost_found` is non-standard, so `apiInstance->methodValue` became 'preferred'. In the three universal AF search templates, getMethodValue() short-circuited to `apiInstance->methodValue` before checking the resolved methodCall, so downstream admin-field lookup keyed off `preferred_search_details` (empty) instead of `found_search_details` (where the operator's checkboxes lived). * Fix: re-ordered getMethodValue() in three AF templates so combo-type detection runs BEFORE the apiInstance->methodValue short-circuit. When the shortcode `type` contains `lost_found`, the template now resolves via `methodCall` (foundSearch -> found, lostSearch -> lost) instead of trusting the synthetic 'preferred' assignment. * Files changed: public/templates/af/universal-search-default.php, public/templates/af/universal-search-no-filter.php, public/templates/af/universal-search-filter-widget.php. * Templates intentionally not touched: featured-search-{default,carousel,compact} (hardcoded to METHOD_TYPE_ADOPT - cannot hit combo path), universal-search-structured (already resolves via resolveMethodTypeFromCall() correctly), adopt-celebration-similar (adopt-only by design). + Item 4: Version constants * pet-match-pro.php Version header and Constants::VERSION bumped 8.10.1.1 -> 8.10.1.2. Constants::ANALYTICS_SCHEMA_VERSION stays at '2.1' (no schema change). * README.txt Stable tag bumped to 8.10.1.2; new Upgrade Notice entry above the 8.10.1.1 entry. * Operator action items: none. * Verification on a deployed site: * PetPoint detail page on a gone or invalid animal ID: error log entry includes `libxml` array and `body_snippet` showing what PP returned. * AnimalsFirst lost_found combo search with `type` and `intake_type` checked under Found in admin: both fields now display on every card. * PetPoint or AF detail render: no recurring "Array to string conversion in class-pet-match-pro-all-api.php on line 2703" warnings in the PHP error log. ---------------------------------------- Version 8.10.1.1 - May 8, 2026 - Detail Template Fixes (PP filter labels, currency, side-by-side video column) Bug-fix patch on top of 8.10.1. Three fixes covering the PetPoint adopt-details-navigation-similar template and the side-by-side detail layouts shared by AnimalsFirst and RescueGroups. No schema change. No write-path change. No new shortcode params. Operator action items: none. + Item 1: PetPoint adopt-details-navigation-similar - location filter label resolution * public/templates/pp/adopt-details-navigation-similar.php: getFieldValue() was returning the raw API location value (e.g. "Kennel - Greenhill") instead of running it through the admin-configured filter group mapping. With Filter Values configured under General > Filter Values (Filter 1 = "Kennel - Greenhill" / Filter 1 Label = "Main Kennel"), the search results showed the label correctly but the detail page showed the raw value. Brought to parity with af/adopt-details-navigation-similar by adding a getFilterGroupConfigs() lookup at the top of getFieldValue() that calls resolveDetailFieldDisplayValue() for any field a filter config covers (location, kennel, site, etc.). * The new lookup runs before the common-field switch and the keyMappings table, so admin-configured labels always win over raw API values without breaking the existing field resolution chain. + Item 2: PetPoint adopt-details-navigation-similar - currency formatting on all render paths * Same file: getFieldValue() was returning the price field unformatted ("250" instead of "$250.00") because formatCurrencyValue() was only invoked from renderQuickFields() and renderTitleSection(), not from renderStatsRow() or renderStatsFull(). Moved the formatCurrencyValue() call into getFieldValue() itself so every consumer benefits from a single chokepoint. * Added an explicit 'price' key mapping to the keyMappings table - previously price was reachable only via the 'fee' alias chain (price -> Price -> fee -> Fee -> AdoptionFee). Direct lookup is faster and more readable. * universal-details-navigation.php was already correct - this fix only applies to the navigation-similar variant. + Item 3: Side-by-side detail layout - videos no longer hijack a third column * Templates affected: rg/adopt-similar.php, rg/adopt-default.php, af/adopt-default.php, af/adopt-conversion.php, af/adopt-conversion-no-app.php, af/adopt-conversion-similar.php (6 templates). * Symptom: when an animal had video URLs, the .pmp-details-videos block rendered as a direct sibling of .pmp-details-image-column inside .pmp-details-image-row. With width:100% on the videos block competing against a 90px thumbnails block in the same flex row, the main image got squeezed out and three vertical columns appeared (image / thumbs / videos). * Fix: wrap renderThumbnails() and renderVideos() inside a new
so they share one right-side column. Videos now stack vertically below thumbnails inside that column instead of forming their own. * public/css/pet-match-pro-styles.css: added .pmp-details-thumbs-column rule (flex: 0 0 90px; flex-direction: column) plus child overrides forcing the nested .pmp-details-thumbnails and .pmp-details-videos to flex:0 0 auto with width:100% and column direction. The min file (pet-match-pro-styles.min.css) regenerated from source. * PetPoint detail templates were not modified - PP detail templates do not currently call renderVideos(), so the bug never manifested on PP. * Templates already using the correct pattern (universal-details-navigation, adopt-details-navigation-similar, adopt-wide, adopt-profile-3-column, rg/adopt-cpa) were not touched. + Item 4: Version constants * pet-match-pro.php Version header and Constants::VERSION bumped 8.10.1 -> 8.10.1.1. Constants::ANALYTICS_SCHEMA_VERSION stays at '2.1' (no schema change). * README.txt Stable tag bumped to 8.10.1.1; new Upgrade Notice entry above the 8.10.1 entry. * Operator action items: none. * Verification on a deployed site: * PetPoint adopt-details-navigation-similar template with Filter Values configured: location displays the admin label, not the raw API value. * Same template with price in stats_row or stats_full: value renders with the configured currency symbol (e.g. "$250.00"), matching the rendering in quick_fields and title_fields. * RescueGroups adopt-similar (and the other 5 fixed templates) on an animal with video URLs: main image renders at full size, thumbnails column on the right contains photo thumbs followed by video play tiles stacked vertically. No third column. * Templates not in the fix list render unchanged. ---------------------------------------- Version 8.10.1 - May 8, 2026 - Quality Pass Follow-Through Closes the items deferred from 8.10.0: the error_log REMOVE sweep, identifier quoting in the analytics ALTER and rollup queries, and a refreshed i18n new-strings inventory ready for the next Claude Cowork translation pass. No schema change. No write-path change. No public-facing UI change. Operator action items: none. + Item 1: Developer-leftover error_log sweep * Project-wide error_log count reduced from 93 to 34. Every remaining call now follows the wpdb-failure mandate or the structured [PMP Analytics] / [PMP Rollup] / [PetMatchPro] operational format. * pet-match-pro.php: 18 commented //error_log lines removed across loadDependencies(), defineAdminHooks(), and initializeAnalytics(). Empty-after-removal else branches collapsed. The unreachable "Try alternate path" license-file lookup that only existed to host commented logs was deleted. * admin/class-pet-match-pro-admin-settings.php: 4 commented checkpoint logs (PMP Settings constructor, Logo Path) removed. * admin/class-pet-match-pro-functions.php: 10 commented field-debug + input-callback logs removed. * includes/class-pet-match-pro-all-api.php: 2 commented fieldExclusions debug logs removed. * includes/cache/class-pet-match-pro-api-cache.php: the entire logDebug() helper removed (1 wrapped error_log + 3 callsites in get/set). Per-cache-check chatter with no caller value. * includes/rg/class-pet-match-pro-rg-api.php: 5 WP_DEBUG-gated chatter logs removed (init checkpoint, cURL retry, request dump, missing-apikey/orgID checkpoints). Doubly-redundant `if (defined('WP_DEBUG') && WP_DEBUG) { if (defined("WP_DEBUG") && WP_DEBUG) { error_log(...); } }` pattern eliminated. * public/templates/includes/class-pet-match-pro-search-template-trait.php: 1 commented labels-debug var_export removed. * 9 search templates (pp/found, pp/lost, pp/universal, af/featured-{compact,carousel,default}, af/universal-{default,filter-widget,no-filter}): 18 template-exception logs without [PMP] prefix removed; catch blocks preserved with "Silently degrade - template falls back to non-API rendering." comment for the apiFunction reflection path, and `$this->fieldLevels = [];` retained for the field-levels path. * Audit B row 55 (deactivator.php:206) intentionally kept: the surrounding commented code block is a documented example demonstrating the wpdb-failure logging pattern future devs should follow. + Item 2: Analytics SQL identifier quoting * includes/analytics/class-pet-match-pro-analytics-db.php (schema 2.0 -> 2.1 daily UNIQUE-key migration ALTER, lines 346-349): the table identifier, the index identifier, and all 8 column identifiers in the UNIQUE KEY definition are now wrapped in backticks. Disambiguates from MySQL reserved words and matches the standard identifier-quoting convention. * includes/analytics/class-pet-match-pro-analytics-daily-rollup.php (rollupDay() DELETE at :215 and INSERT at :271): every {$dailyTable}, {$eventsTable}, and column-name identifier interpolated from class-property constants now uses backticks. User-driven values (date, dayStart, dayEnd) were already routed through $wpdb->prepare() in earlier 8.10.x work; this release only adds the identifier-quoting layer. No behavior change today, but future column-name additions that collide with reserved words will work without further changes. * Audit D escape-3 spot-fix in admin-settings.php skipped after review: every echo $html / echo $var site sampled (settings builders, SEO diag block, color CSS output, do_settings_sections body capture) already wraps user data with esc_attr / esc_html / esc_html__ at the sprintf or concat layer. Per the 8.10.0 plan: don't refactor existing-correct code. + Item 3: i18n new-strings inventory refreshed * docs/superpowers/audits/2026-05-06-a-i18n-new-strings.md replaced with a 144-msgid catalog of every translatable string present in 8.10.1 source but not yet in the frozen 8.6.4 pet-match-pro.pot. Each entry lists first-observed file:line so Cowork can spot-check context before translating. * Local environment lacks WP-CLI / gettext, so the inventory was extracted via grep + awk diff; Cowork's `wp i18n make-pot` run remains the authoritative final list. All _n() plural pairs in current code (Month/Months, Year/Years, minute/minutes, animal/animals, two analytics-insights long-form plurals) are already in the 8.6.4 .pot - no new pairs in 8.7.0 - 8.10.1. * Translation notes added: placeholder preservation (%s, %d, %1$s reordering rules), HTML anchor tags inside msgids, the " -- " em-dash sentinel from feedback_no_em_dash.md, the do-not-translate PetMatchPro / PMP brand rule, and a 6-step Cowork workflow ending in the .pot/.po/.mo commit. * The .pot regeneration, .po msgmerge, and .mo recompile happen in the Cowork session so the translation pass lands as one focused commit. + Item 4: Version constants * pet-match-pro.php Version header and Constants::VERSION bumped 8.10.0 -> 8.10.1. Constants::ANALYTICS_SCHEMA_VERSION stays at '2.1' (no schema change). * README.txt Stable tag bumped to 8.10.1; new Upgrade Notice entry above the 8.10.0 entry. * Operator action items: none. The release is internal cleanup and audit follow-through. * Verification on a deployed site: * Reload search results, detail pages, and admin Settings: no PHP errors in debug.log. Intentional logs (analytics rollup, wpdb-failure mandate) still fire when their conditions are met. * Trigger a search with field exclusions configured: debug.log still shows `[PetMatchPro] [DEBUG] Field-Exclusion: ...` entries (PetPoint via the inline filter, AF/RG via the trait); the chatter that previously fired on every successful request is gone. * Visit the Analytics tab: dashboard widgets still render (rollup query identifier quoting is transparent under MySQL). * grep -rc error_log --include='*.php' from project root returns ~34, all wpdb-mandate or structured operational logs. ---------------------------------------- Version 8.10.0 - May 8, 2026 - Quality Pass + Audit Remediation External 5-track audit (i18n / error logging / license gating / WP coding standards / WP.org compliance) drove a code-quality and security-hardening pass across admin, public, and partner-API surfaces. No data-model or schema changes. No operator action required for upgrade. The single visible behavior change is on broken API keys: a previously-misleading "Parse Error: Unable to parse response" public message is now an actionable "Authentication Error: Verify your API key in general Settings." + Item 1: Security - unserialize() called on decrypted license payloads now passes ['allowed_classes' => false] * admin/license/class-pet-match-pro-license.php (2 sites, lines 498 and 881): forbids arbitrary object instantiation. If the licensing server is ever compromised or a MITM intercepts, gadget-chain code execution is no longer reachable from the deserialization paths. Test path: Free / Premium / Preferred all survive deserialization unchanged. + Item 2: Security - color values validated on save AND on render * admin/class-pet-match-pro-admin-settings.php sanitizeColorOptions(): each non-empty submitted value is validated against an allowlist of CSS color formats (hex 3/4/6/8-digit, the 147 named CSS colors plus transparent/currentColor, rgb/rgba/hsl/hsla, and the inherit/initial/unset/revert keywords). Invalid values are silently reverted to the previously-saved value (empty if never set, preserving cascade-default behavior) and a settings_error notice is queued so the admin sees the rejection. The new isValidCssColor() helper is private and reusable. * public/partials/pet-match-pro-public-color-css.php getColor(): blocks CSS-injection breakout chars (semicolon, brace, angle bracket, quote) at the read chokepoint. All ~30 echoes of color values get the protection automatically. Hex/named/functional CSS color formats all pass. + Item 3: Security - escaping fix in license-activation form * admin/index.php: replaced esc_attr_e(purchaseEmail, $this->slug) with echo esc_attr($purchaseEmail). The bareword 'purchaseEmail' was being treated as an undefined PHP constant (deprecation warning + literal string output); the form's email value now populates the configured PMP_lic_email correctly. + Item 4: License gating - admin and wizard hide-when-gated instead of showing locked UI * Tabs / accordions / partner method registers / wizard preset cards / wizard method types / wizard vanity URLs all reflect the active license tier. The surface shrinks to what the operator can actually use; matches WP.org repo team conventions for fewer locked-state hints. * Coordinated changes across admin/class-pet-match-pro-admin-settings.php, the admin/partials/* level files, admin/css/pet-match-pro-admin.css (lock-glyph removal from disabled-field labels), correct license-key for General Exclusions sub-accordion, per-method gating for Display > Empty Fields checkboxes, plugin menu position aligned across tiers. + Item 5: License gating - Free-tier shortcode params + admin features unblocked * admin/partials/pmp-option-levels-general.php: shortcode_thumbs param downgraded from PREMIUM to FREE to match the existing Settings::THUMBS admin checkbox tier. The admin always offered the feature; the param strip path was silently removing it on Free. * public/templates/includes/class-pet-match-pro-base-detail-template.php getMaxThumbs(): removed the redundant inner license check at line 695 that was ignoring the (now-Free) shortcode param. License gating is enforced upstream by stripPaidDetailParams(). * Settings::PAGE_DETAILS . '_' . Settings::POSTER bumped from PREMIUM to FREE so the "Poster Details Page" admin selector is configurable on Free (the print-poster feature itself was already free). + Item 6: Public-facing UX - silent template fallback to admin-configured (P1.10) * AllApi::getSafeFallbackTemplate(template, methodType, isSearch): when a Free-tier visitor requests a paid template, the renderer substitutes the admin-configured template name instead of showing "Template Upgrade Required" to the visitor. AllApi::getTemplateLicenseError() is deprecated to always return ''. Six callers updated (resolveSearchTemplate plus 4 direct callers in PP/AF/RG). + Item 7: Public-facing UX - audience-gating for admin notices * AllApi::buildUpgradeButton() / buildConfigurationNotice() and the inline poster notices in BaseDetailTemplate now wrap output in current_user_can('manage_options'). Visitors get '' (silent); admins still see the in-place "Not Configured" / "Upgrade to Use" hints. Pattern should extend to any other admin-domain UX leaking to public visitors. + Item 8: Public-facing UX - clearer errors and structured diagnostic logging across all 3 API clients * PetPoint, AnimalsFirst, RescueGroups now share the same error-message taxonomy: Connection Error (network/wp_error), Authentication Error (HTTP 401/403 with actionable "Verify your API key in " hint), API Error (other 4xx/5xx with HTTP code shown), Parse Error (200 OK + malformed body, with the partner-specific parser error included). * Each path emits a structured ErrorLogger entry with http_code + method + 200-char response_excerpt. Pre-8.10.0 the broken-key public message was the misleading "Parse Error: Unable to parse response" and the debug log was an empty {"context":""}. * RescueGroups postJson() now exposes http_code via curl_getinfo so the wrapper-based RG flow gets the same HTTP-status check as PP/AF. * AllApi::buildErrorMessage() drops the empty `context` field from log entries when the caller didn't supply one. + Item 9: Public-facing UX - PetPoint XML-level field-exclusion logging * includes/pp/class-pet-match-pro-pp-api.php applyFieldFiltering(): admin-configured field exclusions (Admin > General > Exclusions per method) are applied at the XML layer in PetPoint before the template renders. Added matching ErrorLogger::debug() entries (configuration, per-excluded-animal, summary) so the audit trail is consistent with what AF/RG produce via the FieldExclusionFilterTrait route. + Item 10: i18n wraps for previously-hardcoded translatable strings * Filter-AJAX success messages ("Filter group %d added", "Copied %d filter group(s) from %s to %s") * Meta titles in includes/class-pet-match-pro-all-api.php * Admin Labels-tab section header * PetPoint adoption share-button text (later removed in dead-code pass; AF/RG delegate share UI to Monarch) * Similar-template subtitles across PP/AF/RG (adopt-conversion-similar, adopt-details-navigation-similar, adopt-profile-3-column-similar, adopt-similar) * Title-case for visible button labels ("Not Configured", "Upgrade to Use Print Poster") - tooltips/title attrs stay sentence case. * .pot regeneration with the full set of new strings is deferred to 8.10.1 so the i18n catch-up is one focused commit. + Item 11: Logging consistency - operational error_log calls now route through ErrorLogger * 20 plain error_log() calls across the deactivator (12), RG API (3), color-CSS (1), field-exclusion-filter-trait (3), and AF celebration template (1) now use ErrorLogger::warning/error/debug. Format matches the existing structured logger: [PetMatchPro] [iso-timestamp] [LEVEL] : | Context: {...} * Subsystem prefixes: Deactivator, RG API, Color-CSS, Field-Exclusion, Celebration Template. Status messages (deactivated / uninstalled at