Skip to content

Fix - Front-end form inputs show a double border on focus (themegrill/user-registration-pro#1896) - #1548

Merged
bhattaganesh merged 96 commits into
v5.3.0from
fix/1896-frontend-input-double-focus-border
Oct 8, 2026
Merged

bhattaganesh merged 96 commits into
v5.3.0from
fix/1896-frontend-input-double-focus-border

Conversation

@bhattaganesh

@bhattaganesh bhattaganesh commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

All Submissions:

Changes proposed in this Pull Request:

Focused text inputs on front-end forms showed two rings: the primary-colour border that the existing :focus styles already apply, and the :focus-visible outline added in #1535 sitting 2px outside it.

The outline stays, since it is what guarantees a visible focus indicator when a form's own styles override the border (WCAG 2.4.7). Only the gap is removed: text-like inputs, textareas and selects now use outline-offset: 0, so the border and the outline read as one ring. Buttons, submit inputs, checkboxes, radios and other non-text inputs keep the 2px offset, because they have no border to merge with (a submit input's fill is the same colour as the ring).

The regression check is a separate e2e test in login-forms.spec.ts with its own @guards #1896. The existing #1725 test is unchanged.

Before (develop) and after, login form, username focused by keyboard:

Before: border plus separate outer ring
After: single ring

Closes themegrill/user-registration-pro#1896

How to test the changes in this Pull Request:

  1. Open the My Account login form and press Tab into the username field. You should see one ring, not a ring with a gap around the border.
  2. Do the same on a registration form for a text field, textarea and select, then tab to the checkbox, radio and submit button. Those keep a visible ring with a small gap.

Types of changes:

  • Bug fix (non-breaking change which fixes an issue)
  • Enhancement (modification of the currently available functionality)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Other information:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you successfully ran tests with your changes locally?
  • Have you updated the documentation accordingly?

Verified in a browser on a local site: login, and registration with text, email, password, textarea, select, radio, checkbox and submit controls. The compiled user-registration.css and its RTL copy carry only the new rule, because the QA suite loads the tracked CSS without running Sass; the rest of a full grunt build was left out as unrelated drift. QA suite: 9 of 9 passed on the current head.

The branch is rebased onto v5.3.0 and was merged with it to clear the conflict after the base change, so squash-merge it to keep develop-only commits out of the release branch.

Changelog entry

Fix - Front-end form inputs no longer show a double border when focused.

y000yal and others added 30 commits September 10, 2026 16:24
…hemegrill/user-registration-pro#1659) (#1488)

* Fix undefined array key warning on subscription cancel with no order (#1659)

get_order_by_subscription() returns an empty array when a subscription
has no matching order row, so accessing order_type on it threw an
undefined array key warning without affecting the outcome (empty()
already short-circuited the condition to true).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Guard undefined array key on upgrade-branch subscription cancel

$order can be empty array() when the upgrade branch has no matching
order row, which trips the same undefined-array-key warning on the
switch as the case this PR already fixes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: asheshmagar <ashesh.ido@gmail.com>
…tion submit (#1432)

* #1409 Fix - Cloudflare Turnstile token reused after a failed registration submit

* #1409 Add - Regression spec for a spent Cloudflare Turnstile token

* #1409 Fix - Hold the submit button while Turnstile mints a replacement token
* Fix - Guard Membership Groups status column against null post_content JSON.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix - Treat numeric and boolean Membership Group status values as enabled

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…1496)

* Fix - Add missing utm_content to remaining free outbound upgrade links

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix - Premium template upgrade link now tracks the clicked template

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fix - Address Ganesh review for UTM payment attribution and PHPCS.

Payment Mollie/Authorize upsells now use payment-settings source/content;
doing_it_wrong version reads UR_VERSION; expand multi-key ur_utm_url arrays.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Fix billing emails using member's most-recent order instead of the subscription's own order (#1700)

get_membership_plan_details() and send_cancel_emails() fell back to
MembersOrderRepository::get_member_orders(), which returns the member's
single most-recent order with no subscription filter. For a member with
more than one order, cancellation/expiring/ended/payment emails could
show another order's plan, amount, or transaction ID instead of the one
the email is actually about.

Both now resolve the order via OrdersRepository::get_order_by_subscription(),
scoped to the subscription the email concerns, matching the ownership-
checked lookup the thank-you page already used.

* Trust the caller-supplied order over a re-derived subscription lookup

Payment-success callers (Stripe, PayPal) already resolve and pass the
exact order the receipt is about via $data['order']. The subscription-
scoped fallback added for #1700 ignored it and re-queried for the
subscription's newest order instead, which can differ from the order
the caller supplied if a later order exists on the same subscription
by the time the email builds (e.g. a fast renewal or backfill).

* Validate the caller-supplied order against the resolved subscription

The legacy PayPal IPN handler and the Stripe payment-retry-failed path
build their own order via get_member_orders(), the same member-wide,
unscoped lookup this PR fixes, then pass it through $data['order']
even though both already know the specific subscription. Trusting
$data['order'] unconditionally let their own wrong order back in
ahead of the correct subscription-scoped lookup.

Now the supplied order is only trusted when its subscription_id
matches the resolved subscription; otherwise it falls through to the
subscription-scoped lookup, same as any caller that didn't supply an
order at all.

* Don't inherit an unrelated order when a resolved subscription has none

The member-wide fallback ran whenever no order had been found yet,
even if a subscription ID HAD been resolved but simply had no order of
its own (e.g. get_order_by_subscription() found nothing). That
re-introduced the original bug for that case - a subscription with no
order could inherit another subscription's order via
get_member_orders(). Now the member-wide fallback only runs when no
subscription could be resolved at all; otherwise the order is left
empty.

Also fixed membership_plan_payment_method reading directly from the
caller-supplied $data['order'] instead of the resolved $order - when a
mismatched supplied order gets rejected, this field was still leaking
its payment method through untouched. Hardened three direct
$order['trial_status'] accesses that started producing undefined-
array-key warnings now that $order can legitimately be empty more
often.

* #1700 Fix - admin payment email showing wrong order's payment date

get_membership_plan_details() resolved the correct subscription-scoped
order (per the earlier commits on this branch) but never exposed its
raw date as membership_tags['payment_date'] - the exact key the
{{payment_date}} smart tag already checks for before falling back to
the member's single most-recent order account-wide. Populate it from
the resolved order's created_at so the admin "Payment Received" email
shows the approved order's own date.

Closes themegrill/user-registration-pro#1793.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…rs (#1502)

* Fix - Guard Addons newest/oldest sort when released_date is missing.

parseDate called .split on undefined and crashed the React Addons page.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix - Combine Addons plan, status, and sort as AND filters.

Sort was applied to the full addon list and wiped active status/plan filters.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix CSRF protection for bulk user status actions

* test: run security regressions through the QA suite
* Mask payment gateway secret keys with show/hide toggle (#1653)

* Add visible focus outline for toggle button and add regression test spec

* Fix - Enter key does not toggle payment secret key visibility

The #mainform keyup/keypress handler called preventDefault() on every
Enter keypress to stop accidental form submits, which also blocked the
browser's native Enter-to-click behaviour on the .ur-toggle-password
button. Skip the guard when the event target is a button.

Closes themegrill/user-registration-pro#1795

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* fix: make Delete User work from the user edit/view screen

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: trigger zip build for QA #build-zip

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix: drop redundant class causing duplicate delete confirmation

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: trigger zip build for QA #build-zip

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…1404)

Rows are identified by a creation-order id that never renumbers, while
user_registration_form_row_ids stores them in visual order. The first row
of every form is hardcoded to id 0, so as soon as any later-created row is
dragged above it, row 0 leaves index 0.

At that point `! empty( $row_ids[ $index ] )` evaluates false for row 0
(both int 0 and the string "0" that the meta actually stores) and the row
silently falls back to `$index`, stamping it with another row's id. The
multi-step addon then wraps it in whichever part owns that id, and row
level conditional logic matches the wrong row for the same reason.

Guard on the array instead of the element so a legitimate row id of 0 is
kept, matching the pattern already used in class-ur-users-menu.php.
The login_redirect() filter callback for the 'previous-page' redirect
option always re-derived the target from wp_get_referer(). For the
AJAX login flow, that request's own Referer is always the login page
itself (the page that issued the AJAX call), so it silently clobbered
the already-correct target that ur_process_login() had computed from
the client-captured previous_page field, sending users back to the
login page after every successful login instead of their previous
page.

Prefer previous_page when present, and never resolve to the login
page itself -- fall back to My Account instead, matching the guard
ur_process_login() already applies. The resolved referer is validated
with wp_validate_redirect() before use, since previous_page is
client-controlled and this filter also feeds callers (automatic
login after registration/SMS OTP) that redirect without validating
it themselves. The login-page check itself now compares post IDs via
url_to_postid() instead of exact string matching, so query-string or
trailing-slash variants of the login URL are still caught.

Fixes #1411
…election query (#1465)

wums.status = 'failed' can never match: the subscriptions table's status
column is enum('active','canceled','expired','trial','pending'), and no
code path ever writes 'failed' to it (only the orders table's status
enum has 'failed'). Removing the dead branch is a behavioral no-op -
only wums.status = 'expired' was ever selectable.
…vior (#1715) (#1491)

The setting (urm_enable_no_conflict) is a script-deduplication mode
that removes other plugins' captcha scripts site-wide - it never
enforces or enables a captcha on any form. Renamed to "Prevent Captcha
Conflicts" and rewrote the tooltip to match includes/class-ur-captcha-
conflict-manager.php's actual behavior.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…stration page for logged-in members (#1440)

* fix: respect shortcode registration page attribute for logged-in members

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: trigger zip build for QA #build-zip

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix: recognize membership checkout intent by query params, not page identity

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: trigger zip build for QA #build-zip

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
---------

Co-authored-by: asheshmagar <ashesh.ido@gmail.com>
* Keep untrusted smart-tag values from executing shortcodes

* test: run security regressions through the QA suite
…to logged out visitors (#1434)

* #1508 Fix - Login and account pages served a cached logged in notice to logged out visitors

* #1508 Fix - Cover registration pre-form message, membership and remaining UR shortcodes

* #1508 Fix - Cover membership shortcodes and buy-now block, cut WPML page lookups

* #1508 Fix - Only treat registered UR blocks as dynamic content
…1429)

* #1401 Fix - Profile picture discarded when saving a profile via ajax

* #1401 Fix - Prevent profile picture loss when a replacement upload fails
* fix: skip Stripe key-prefix validation on save when Stripe is being disabled

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: trigger zip build for QA #build-zip

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: rebuild settings.min.js #build-zip

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Revert "chore: rebuild settings.min.js #build-zip"

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
import_form() unserializes exported form_post_meta values but then
calls sanitize_text_field() on the result unconditionally. WordPress
core's sanitize_text_field() returns an empty string for anything
that isn't a scalar, so any array/object meta (field lists, condition
rules, etc.) was silently replaced with '' on import - no warning, no
visible failure, which is why the same template imported cleanly in
support's own test. The emptied meta then breaks later, whenever the
builder expects an array for that key and gets a string instead.

Recurse into arrays/objects and only sanitize their scalar leaves,
matching how a plain scalar meta value was already sanitized. A
decoded JSON object is normalized back to a plain array rather than
left as stdClass, since form meta that was ever a PHP array (e.g.
role_based_redirection) is read back elsewhere as an array.

Companion fix for themegrill/user-registration-pro#1507, in the
shared copy of the same code.
…1436)

* fix: exclude pending-cancel subscriptions from renewal reminder query

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: trigger zip build for QA #build-zip

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix: filter pending-cancel subscriptions in PHP, not a SQL usermeta join

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: trigger zip build for QA #build-zip

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* fix: add lowest/highest tier anchor labels to upgrade path order list

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: trigger zip build for QA #build-zip

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix: put highest tier at top and lowest tier at bottom of upgrade path list

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: trigger zip build for QA #build-zip

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix: reverse upgrade path list for highest-at-top UI

Render stored low→high paths as high→low, reverse drag order before path rebuild, and keep dragged DOM on sort so the list does not snap back.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix - Upgrade path anchor styles missing from compiled membership admin CSS

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: remove unreachable payment_approval email type

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* #build-zip

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…mail (#1443)

* fix: use passed option to check membership email enabled state

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* #build-zip

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…ree (#1442)

* feat: send member/admin notification when a membership downgrades to free

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: trigger zip build for QA #build-zip

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix: resolve previous plan tag and bold plan names in downgrade email

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* #build-zip

* fix: gate downgraded-to-free notifications on verified paid-to-free change

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* #build-zip

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
daily_payment_retry_check() retried a failing subscription's payment
every day indefinitely - there was no comparison anywhere between the
per-user retry counter (urm_is_payment_retrying) and the admin's
configured max attempts (user_registration_payment_retry_count), so a
subscription could never actually be given up on. That's why the
Payment Retry - Final Notice email (payment_retry_cancel) was dead
code: nothing ever reached the point where it should be sent.

failed_payment_retry_callback() now checks the retry count against
the configured max before attempting another retry. Once exhausted,
it cancels the subscription via the existing gateway-agnostic
SubscriptionRepository::cancel_subscription_by_id() and sends the
final-notice email - but only once cancellation actually succeeds,
so a gateway-side cancellation failure doesn't tell the member their
registration was cancelled when it wasn't. On failure, the retry
counter is left as-is so the next daily run retries the cancellation
itself.

Companion fix for themegrill/user-registration-pro#1603, in the
shared copy of the same code.
* Constrain public nonce refresh to supported actions and forms

* test: run security regressions through the QA suite
asheshmagar and others added 17 commits October 7, 2026 16:50
…grill/user-registration-pro#1643) (#1531)

* Enhancement - Simplify setup wizard to one membership question (themegrill/user-registration-pro#1643)

* Keep the prototype wording for the No option

* Record the raw wizard answer (yes, no, later) in the onboarding snapshot

* Enhancement - Refine setup wizard welcome copy (themegrill/user-registration-pro#1643)

Ask about membership plans rather than memberships, and give each answer a descriptive title so the options read on their own.

* Enhancement - Reduce the welcome question to Yes / Not now (themegrill/user-registration-pro#1643)

No and Maybe later ran the same flow, so they are merged into one "Not now" answer, and the option descriptions are dropped. The two answers sit side by side and stack on narrow screens. The unselected border is darkened to meet the 3:1 non-text contrast minimum, the focus ring shows only for keyboard focus, and the card wraps Chakra's radio label instead of nesting one label inside another.

* Fix - Show a previously saved "Maybe later" as Not now in the setup wizard (themegrill/user-registration-pro#1643)

The welcome step now offers only Yes and Not now, so a stored "later" answer from an earlier build matched no card. The saved membership type alone decides the selected card, and the unused "later" setup type is removed.

* Enhancement - Drop the membership_interest answer now that the welcome step has two answers (themegrill/user-registration-pro#1643)

Yes and Not now map one to one onto paid_membership and normal, so a separate stored answer, its REST argument, snapshot field and regression test add nothing. The wizard saves and reloads from membership_type alone, as it does on develop.

* Fix - Meet 3:1 contrast on the welcome cards and keep legacy free_membership sites on the Payment step (themegrill/user-registration-pro#1643) #build-zip

* Fix - Drive the welcome card selection from state so it shows without :has(), and add a setup-wizard regression spec (themegrill/user-registration-pro#1643) #build-zip
#1495)

* Fix - Recover renewal/expiring reminder emails after a missed cron day

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix - Duplicate reminder emails for users with multiple subscriptions

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… renewal, and keep trials intact (#1445)

* fix: hold membership expiry while Stripe subscription is still past_due

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* #build-zip

* fix: only hold expiry for past_due Stripe subscriptions, not unpaid

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* #build-zip

* fix: sync local membership when Stripe renewal has no payment_intent

Renewal invoices without a PI were skipped entirely, and expiry/retry ignored an still-active Stripe sub — leaving members expired while Stripe kept billing.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: keep trial status on zero-amount invoices and retrieve the Stripe subscription once in the expiry cron

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Fix - Read the Stripe subscription period end from the items on current API versions

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Fix - Keep the member and stop the replaced subscription when a Stripe renewal is declined or paid (#1866, #1867)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Fix - Free the renewal marker and fail the pending order when a Stripe renewal is declined (#1872)

A declined renewal left urm_membership_process['renew'] set and its order pending, so every later renewal hit 'Membership renew process already initiated'. The failed-payment branch also unset the upgrade list instead of the renew list.

- Clear the renew marker on both decline paths (client-side failure and create_subscription error) and fail the pending order.
- Reuse a stale marker in renew_membership() instead of blocking the member.
- Keep the one-period cancel_at for manual renewals only, so automatic renewals keep recurring now that $is_renewing is detected.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Fix - Restore spacing in the Stripe customer cleanup

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Fix - Discard the pending member when a guest's Stripe registration payment is declined (#1874)

The member was only deleted for the logged-in member or an admin, so a logged-out registrant kept a user with no orders, and the follow-up confirm_payment answered 'Invalid Request.'. Allow the browser whose registration session created the pending member (the check confirm_payment already uses), and show the card error when the pending order is already gone.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…ons without invoice.payment_intent (themegrill/user-registration-pro#1823) (#1515)

* Fix - Resolve the PaymentIntent of a paid Stripe renewal on API versions without invoice.payment_intent (themegrill/user-registration-pro#1823)

* Run the Stripe renewal PaymentIntent regression through the Playwright suite and keep its test doubles lint-clean (themegrill/user-registration-pro#1823)

* Document the Invoice test double's __call parameters (themegrill/user-registration-pro#1823)
…hemegrill/user-registration-pro#1884) (#1546)

* Fix - Keep the setup wizard answer picked before it finishes loading

The Welcome step was interactive while the saved answers were still
loading, so a choice made in that window was overwritten when they
arrived and Next saved the old answer. The step now shows a spinner
until the first load finishes, and step clicks wait for it too.

Also restyles the Welcome step's Yes / Not now options to match the
plan Type options on the Membership step.

Closes themegrill/user-registration-pro#1884

* Fix - Add the regression spec for the setup wizard loading guard

Holds the wizard's loading requests and checks the answer cannot be
picked until the saved one has loaded, then that Not now saves normal.
Fails on develop, where the options are clickable while loading.

Refs themegrill/user-registration-pro#1884

---------

Co-authored-by: asheshmagar <ashesh.ido@gmail.com>
* Fix - Cancel membership subscription when a Stripe charge is fully refunded

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix - Create Stripe webhook when saving payment keys from setup wizard #build-zip

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix - Full Stripe refund not revoking access after a partial refund or on one-time orders

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fix - Stripe refund handling no longer fails webhooks or wizard saves, and repeat backfills are a no-op

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: asheshmagar <ashesh.ido@gmail.com>
…megrill/user-registration-pro#1604) (#1539)

* Fix content rules not restricting WooCommerce product purchase (themegrill/user-registration-pro#1604)

Match product rules against the post object so Post Type targets work, and stop Whole Site targets (enabled or not) from switching product rules off. Covers themegrill/user-registration-pro#1861.

* Restrict WooCommerce variations through their parent and map the content-restriction QA area (themegrill/user-registration-pro#1604)

Variations are checked against the parent product's rules. The new regression is tagged content-restriction and the module is mapped to that area so the suite selects it.

* Read content rules through the request-cached helper in the WooCommerce product check (themegrill/user-registration-pro#1604)

* Check a variation's restriction meta on its parent product (themegrill/user-registration-pro#1604)

The per-product meta box lives on the parent, so a variation skipped it. Resolve the variation to its parent at both entry points.

* Only a matching Access rule grants WooCommerce product access (themegrill/user-registration-pro#1604)

A Restrict rule the user does not match was counted as granting access and cancelled other rules that blocked the user. It is now skipped. A variation with no parent is left open instead of falling back to the current post.

* Hide the price of a restricted WooCommerce product (themegrill/user-registration-pro#1870) (#1541)

* Hide the price of a WooCommerce product the user cannot purchase (themegrill/user-registration-pro#1870)

* Skip the restriction check when the price is already empty (themegrill/user-registration-pro#1870)

* Test that the price filter is registered (themegrill/user-registration-pro#1870)

* Silence the file_get_contents sniff in the price registration check (themegrill/user-registration-pro#1870)

* Update the regression wrapper to the new assertion count (themegrill/user-registration-pro#1870)

---------

Co-authored-by: asheshmagar <ashesh.ido@gmail.com>

---------

Co-authored-by: asheshmagar <ashesh.ido@gmail.com>
…ents (#1882) (#1545)

A renewal once saved the invoice list nested inside another list, so the Members view passed an empty amount to number_format() and fataled on PHP 8. Read invoices through ur_get_valid_payment_invoices() in the Members view and My Account, and cast the amount.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
…1467, #1469) (#1468)

* Fix - Upgrade form breaks when the selected plan's price has trailing zeros (#1467)

* Fix - Payment History lists membership payments twice (#1469)

* Fix - Prorated upgrade Total throws TypeError for .00-priced plans (#1467)

convert_currency_and_calculate_tax() read membershipAmount straight off the
data-urm-membership-amount attribute, so a plan stored as "299.00" stayed a
string (jQuery's .data() only casts a value that round-trips exactly) and the
later .toFixed(2) call threw, leaving Total at 0 with no gateway options for
a mid-cycle prorated upgrade. Coerce it with parseFloat(...) || 0, matching
the same pattern already used in calculate_total().

Documents the prorated-upgrade path as untestable in the current e2e suite
(no seam to seed an active subscription without a real gateway checkout) in
.themegrill-qa/knowledge.md, per review on #1468.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* Fix - Form payment View link uses user_id and type=form.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix - Show subscription expiry from existing stored dates when display fields are empty.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix - Form payment View link opens the selected invoice, not always the first

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fix - Only show fallback billing or expiry dates when they match renewal status

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: asheshmagar <ashesh.ido@gmail.com>
…sage for a declined Stripe payment (#1874) (#1544)

can_discard_pending_member() referenced the AJAX class without its namespace separators, so a declined guest registration fataled with a 500. Also pass the Stripe error message to confirm_payment when the subscription could not be created, so a declined renewal or registration shows the card error instead of 'Invalid Request.' once its pending order has been failed.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: asheshmagar <ashesh.ido@gmail.com>
…ep active members while Stripe retries, and keep the backfill window after a Stripe error (themegrill/user-registration-pro#1888) (#1547)

* Fix - Resolve the PaymentIntent of a paid Stripe renewal on API versions without invoice.payment_intent (themegrill/user-registration-pro#1823)

* Run the Stripe renewal PaymentIntent regression through the Playwright suite and keep its test doubles lint-clean (themegrill/user-registration-pro#1823)

* Document the Invoice test double's __call parameters (themegrill/user-registration-pro#1823)

* Fix - Recover paid Stripe renewals in the missed-payment backfill on current API versions, and keep the member's status while Stripe retries a failed invoice (themegrill/user-registration-pro#1888)

- The backfill read invoice.subscription and invoice.payment_intent, which Stripe removed in 2025-03-31.basil, so every paid invoice was skipped. It now reads the subscription from parent.subscription_details and resolves the PaymentIntent with the shared invoice lookup, only for subscriptions this site owns. A failed lookup skips that invoice and the run continues.
- The invoice lookup now throws to its caller; the webhook keeps logging it and aborting with an error so Stripe redelivers.
- invoice.payment_failed no longer moves an active or trial subscription to pending while Stripe still has a retry scheduled (next_payment_attempt). A failure with no retry left still sets pending.

* Address review on the Stripe backfill and failed-invoice fix (themegrill/user-registration-pro#1888)

- Handle each invoice once per run: invoice.paid and invoice.payment_succeeded list the same invoice, so the second costs no extra Stripe call.
- Link the signup invoice to a registration order saved without a PaymentIntent instead of creating a duplicate order, via a new OrdersRepository::get_unlinked_order_by_subscription().
- Keep only active members during Stripe retries; an ended trial whose first charge fails goes to pending as before.
- Read the invoice creation time from the same array as the other fields, and update the delayed-start comment that described the old failed-invoice behaviour.

* Keep the Stripe backfill window after a Stripe error, and read the webhook subscription from parent on current API versions (themegrill/user-registration-pro#1888)

- The missed-payment scheduler moved its shared sync time forward even when the Stripe backfill threw, so one failed run dropped that window's paid renewals for good. Stripe now keeps its own sync time (urm_last_stripe_backfill_sync_time), advanced only after a fully successful run, like PayPal's. A skipped invoice lookup also keeps the window.
- handle_webhook() now falls back to parent.subscription_details.subscription, so invoice events from an endpoint on API 2025-03-31.basil or newer no longer stop at NULL_SUBSCRIPTION_ID.

* Address review on the Stripe sync-time fix (themegrill/user-registration-pro#1888)

- Keep a failed window only for the order backfills (payments, one-time), which match by PaymentIntent. Status, refund and dispute backfills stay on the shared sync time, because replaying their events re-applies stale snapshots.
- Also keep the window when a backfilled order cannot be saved or linked, not only when Stripe throws.
- Normalize the webhook's subscription ID through extract_stripe_id().

* Address Copilot review on PR #1547 (themegrill/user-registration-pro#1888)

- Link a signup invoice only to a Stripe order of the subscription, and only when exactly one unlinked non-trial order exists, so a payment is never written onto a PayPal/bank order or onto a guessed order when an upgrade left a second unlinked one.
- Normalize the PaymentIntent read from invoice.payments through extract_stripe_id(), in case Stripe returns it expanded.

---------

Co-authored-by: asheshmagar <ashesh.ido@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…/user-registration-pro#1896)

Bordered fields already turn the border primary on focus, and the WCAG 2.4.7 outline sat 2px outside it. Remove the gap on text fields, textareas and selects so the border and outline read as one ring; buttons, checkboxes and radios keep their offset.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ck its own test (themegrill/user-registration-pro#1896)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@tg-autopilot

Copy link
Copy Markdown
Contributor

Build for 24cb8929 is ready 🛎️

⬇️ Download user-registration-5.3.0.zip (8.2M)

Installs directly via Plugins → Add New → Upload Plugin.
Link expires in 30 days · updated Oct 8, 2026 3:38 PM +0545

A later push only rebuilds this if its commit message includes #build-zip.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Rebuild the tracked CSS/RTL assets and extend tests to textarea and select controls.

1 open finding
What changed in this PR

Fixes the double focus-ring gap on front-end form controls while preserving focus visibility.

Changes:

  • Sets outline-offset: 0 for text-like inputs, textareas, and selects.
  • Adds end-to-end focus-ring regression coverage.
File Summary
assets/​css/​user-registration.scss Updates focus-outline offsets; generated CSS and RTL assets also need rebuilding.
tests/​e2e/​specs/​login-forms.spec.ts Adds regression coverage; textarea and select cases remain to be covered.

🧠 Review effort: Lite


Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.

Comment thread assets/css/user-registration.scss
@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

QA suite — passed ✅

All 9 checks passed.

9 passed · 0 failed · 0 skipped · 0 flaky · 176s

Automated check — no AI involved. It runs the tests in this branch.

@bhattaganesh
bhattaganesh changed the base branch from develop to v5.3.0 October 8, 2026 10:31
Resolve the conflict from retargeting #1548 to v5.3.0: the tree is the branch rebased onto v5.3.0, so the diff against it is only the focus ring fix.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@bhattaganesh

Copy link
Copy Markdown
Contributor Author

@claudegrill suite

@bhattaganesh
bhattaganesh merged commit 6474cdb into v5.3.0 Oct 8, 2026
1 check passed
@bhattaganesh
bhattaganesh deleted the fix/1896-frontend-input-double-focus-border branch October 8, 2026 10:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants