The Security page only handed out an in-game 2FA key when TOTPMasterSecret
was configured. It doesn't have to be: with the value unset the core stores
account.totp_secret in the clear and authserver reads it back the same way,
so the site can do that too.
isConfigured() still says no when a master secret is set but unusable, since
writing a key the server can't decrypt would lock the player out of the game.
Swept every admin screen for visible light backgrounds, then for text that
ended up too dark once the surface behind it went dark.
Most of it was core striping list tables on the <tr> while plugins repaint
the cells, so rows are painted at row level now and the cells go
transparent. PMPro drives its whole admin UI from :root variables, so
remapping those covers all of its screens at once. myCRED tints the body
with !important from a selector as specific as ours and loads later, hence
the html prefix on that rule.
Also covers screen options and help panels, My Sites, nav tabs, the menus
and widgets accordions, Site Health and Privacy headers, the dashboard
welcome panel, the media filter bar, ACF, and the buttons, selects and row
actions plugins were forcing light.
The whole myCRED block in theme.css is scoped to
body.acore-dark-mode.mycred-log-page, but nothing ever set that class,
so none of it applied and the points history page kept its white card,
light thead and light alternating rows.
Add the class from admin_body_class on myCRED screens, and match tr.alt
alongside :nth-child(even), since myCRED tags its alternating rows with
a class instead of relying on parity.
Co-authored-by: Benjamin Jackson <38561765+heyitsbench@users.noreply.github.com>
Co-authored-by: Andrew <47818697+Nyeriah@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Stefano Borzì <stefanoborzi32@gmail.com>
Co-authored-by: Andrew <47818697+Nyeriah@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Stefano Borzì <stefanoborzi32@gmail.com>
## Summary
Adds optional gifting for the **name**, **faction**, **race** and
**customization** character services using the smartstone token system
in `mod-chromiecraft-smartstone` (the feature was recently renamed from
"voucher" to "token").
When a buyer enters a recipient character name on the product page, the
purchase grants a token to that account (`.smartstone token grant
<account> <type>`, run over SOAP) instead of applying the change
directly. The recipient then redeems it in-game (`.smartstone token
claim`) on the character of their choice. Leaving the field blank keeps
the existing self-service behaviour (instant apply to the selected
character), unchanged.
Token types map 1:1 to the services (from the mod's `TokenType` enum):
rename=1, faction=2, race=3, customize=4. `char-restore-delete` has no
token equivalent and is excluded.
## Config
Gated behind a new **Smartstone Token Gifting** toggle
(`acore_smartstone_enabled`) on the admin Tools page, **disabled by
default**. With it off, all the new code paths are inert, so behaviour
is identical to today unless a server is running the smartstone module
and opts in.
## Changes
- `SmartstoneService`: new `grantToken($account, $type)` SOAP helper.
- `CharChange`: optional gift field, recipient resolution (character
name to account), token grant on `payment_complete`, and gift display at
checkout / in order meta.
- `CartValidation`: validates the gift recipient (exists, not banned,
account not banned) and skips the self character-selection requirement
in gift mode. Mirrors the existing account-wide smartstone gifting
checks.
- `Opts` / `Tools` admin page: the new toggle.
- The recipient account login is stored under a hidden
`_acore_gift_account` order-item meta key so WooCommerce keeps it out of
customer order details and emails (only the character name is shown).
The same fix is applied to the existing account-wide smartstone gifting
path, which shared the key.
## Testing
- Toggle off: services behave exactly as before.
- Toggle on, gift field blank: normal self-service.
- Toggle on, recipient name entered: order line shows "Gift for:
<name>", and on checkout a token is granted to the recipient account
(verified with `.smartstone token list <account>`). Free/$0 orders also
work since WooCommerce fires `payment_complete` for zero-total carts.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Split from the main PR:
https://github.com/azerothcore/acore-cms/pull/197
Split by Claude
Full overhaul: confirm prompt before returning, a 6×2 grid matching the
12-item mail cap, shows the recipient's faction/class, Wowhead tooltips
+ higher-res icons, and clean empty/loaded states.
<img width="1920" height="1030" alt="image"
src="https://github.com/user-attachments/assets/e3ed47b2-582e-40ec-a218-6ddc998be498"
/>
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
## Release Notes
* **New Features**
* Added dark mode toggle for WordPress admin dashboard
* **Improvements**
* Redesigned character ordering screen with visual enhancements and
reset order option
* Redesigned mail return interface with sidebar character navigation
* Enhanced mail display with item quality information and improved
metadata
* Expanded admin styling and dark mode support across all admin pages
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Split from the main PR:
https://github.com/azerothcore/acore-cms/pull/197
Split by Claude
Unstuck restyled to the new rows; Scroll of Resurrection gets
green/red/yellow status colours; RAF (recruit-a-friend) gets a CSRF
nonce + validation. Minor shared-service tidy.
<img width="1920" height="1030" alt="image"
src="https://github.com/user-attachments/assets/97b69047-870e-41bd-878d-5c52a665384a"
/>
<img width="1920" height="1030" alt="image"
src="https://github.com/user-attachments/assets/dca4c6e3-b29d-4175-a227-84e7d52a730a"
/>
Not testde the RAF page, as the changes were made by Rabbit's review.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Dark mode toggle in WordPress admin bar
* Character order reset functionality in character management
* Item quality indicators in mail and restoration displays
* **Improvements**
* Redesigned character management UI with card-based layout, icons, and
metadata
* Revamped mail return interface with character sidebar navigation
* Reimagined item restoration page with card grid and improved
organization
* Enhanced security with nonce validation and permission checks across
endpoints
* Improved visual styling with expanded theme and dark mode CSS
* **Bug Fixes**
* Module availability validation for Resurrection Scroll feature
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
Builds on #190 (account-wide smartstone unlocks) with two related UX
changes on the same WooCommerce flow.
### 1. Gifting (Costumes, Perks)
Lets buyers gift any account-wide smartstone product to another player
by entering a **character name** on the product page. The plugin
resolves it to that character's owning AC account and routes the SOAP
unlock there at payment time.
UX mirrors the existing item-gifting flow in `ItemSend.php` — buyers
never type an account name (AC account logins are semi-private and
unfriendly UX); they type a character name they don't need to own.
- **Every account-wide product page** renders a
`FieldElements::destCharacter` input below the 3D viewer with the label
*"Gift to a character (optional — leave blank to unlock for your own
account)"*. No per-product opt-in toggle.
- **Empty input** → buyer's own account unlocks (existing #190 behavior,
unchanged).
- **Recipient name supplied:**
- At `add_to_cart_validation`: looked up via `findOneByName`. Rejected
with a notice if the character doesn't exist, the character is banned,
or the recipient's account is banned.
- At `add_cart_item_data`: resolved to the recipient's account name and
stashed in cart_item_data as `acore_gift_account` (functional) +
`acore_gift_charname` (display).
- At `get_item_data`: cart line shows *"Gift for: \<character name\>"*
instead of *"Unlock for: Entire account"*.
- At `add_order_item_meta`: both gift fields persisted on the order
item.
- At `payment_complete`: `addAccountVanity()` is called with the gift
account instead of the buyer's.
- **Self-gift** (recipient character belongs to the buyer's own AC
account, case-insensitive compare): silently treated as a regular
self-unlock. No error notice — the gift fields are simply not stashed,
and the cart line falls back to the standard *"Unlock for: Entire
account"* display.
- **Per-character categories** (Companion=0, Pet=1, others) are
completely untouched.
### 2. Quantity cap
Every smartstone SKU is a one-shot unlock — per-character for
ACTION_TYPE_COMPANION (0) / ACTION_TYPE_PET (1), per-account for
ACTION_TYPE_COSTUME (2) / ACTION_TYPE_PERK (9). A quantity > 1 is
nonsensical: a second unlock for the same SKU on the same target is
either rejected by the mod (`LANG_MOD_SERVICE_ALREADY_UNLOCKED`) or a
silent no-op. The buyer just pays N times for the same thing.
- `woocommerce_quantity_input_args` filter: forces `min = max =
input_value = 1` on the product-page selector for any SKU starting with
`smartstone`. WC's `wc_quantity_input()` renders the field as plain text
"1" (no +/- buttons) when min == max.
- `woocommerce_cart_item_quantity` filter: returns a plain `"1"` string
for the cart-row quantity, making it non-editable after add-to-cart.
Multiple cart lines for the same SKU are still supported (e.g. gifting
the same costume to two different recipients) — the existing
`unique_key` in `add_cart_item_data` forces each add into its own line.
Using the `sold_individually` flag would block this, so it's
intentionally not used.
## Scope
- Only `src/acore-wp-plugin/src/Hooks/WooCommerce/Smartstone.php` is
touched (+104, −5).
- All repository methods used here (`findOneByName`, `findOneById`,
`getCharactersBannedRepo`, `getAccountBannedRepo`) are already used in
`CartValidation.php` and `FieldElements.php`.
- No new SOAP wrapper, no new SKU shape, no changes to the per-character
flow.
## Known limitation (deferred)
The original spec asked for an **at-add-to-cart "already unlocked"
check** for gifting, but the mod stores account-wide unlocks in
`acore_auth.smartstone_account_settings.data` as a serialized
`PlayerSettingVector` binary blob — and perks share a `settingId` slot
per class (all Druid perks → `settingId=110`), so a row-existence check
would incorrectly block buying a second Druid perk if the recipient owns
any. A proper duplicate check requires parsing the AC binary settings
format from PHP, which is out of scope here.
Current behavior on a duplicate gift: the SOAP server returns
`LANG_MOD_SERVICE_ALREADY_UNLOCKED`, `payment_complete` catches it and
writes it to the `acore_log` WooCommerce logger, and the buyer's
WooCommerce order still completes. Worth a follow-up issue.
## Test plan
### Gifting
- [ ] Account-wide product page (e.g. `smartstone_2_<costumeId>`)
renders the *"Gift to a character"* input field below the 3D viewer.
- [ ] Empty input → checkout completes as before, buyer's account gets
the unlock, cart shows *"Unlock for: Entire account"*.
- [ ] Recipient name of a character on **another** account: cart line
shows *"Gift for: \<charname\>"*; `payment_complete` fires `.smartstone
unlock account <recipient-account-login> <cat> <id> true`.
- [ ] Recipient name of a character on the **buyer's own** account:
silently behaves like empty input (no notice, no gift fields,
self-unlock path).
- [ ] Non-existent character name → *"Recipient character not found."*
notice; Add to cart fails.
- [ ] Banned character → *"Recipient character is banned."* notice; Add
to cart fails.
- [ ] Character on a banned account → *"Recipient account is banned."*
notice; Add to cart fails.
- [ ] Per-character products (`smartstone_0_*`, `smartstone_1_*`) are
unaffected — character selector still appears, validation still requires
`acore_char_sel`.
- [ ] Mixed cart (one gifted costume + one self-unlock perk + one
per-character pet) checks out cleanly and fires three distinct SOAP
commands with the right targets.
### Quantity cap
- [ ] Any smartstone product page renders quantity as plain text "1" (no
+/- buttons).
- [ ] Same product added to cart twice with different gift recipients
produces two cart lines, each locked at qty 1.
- [ ] Cart row qty for any smartstone line is non-editable text.
- [ ] Non-smartstone products' quantity selector is unaffected.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added an optional “Gift to a character” destination field for
account-wide smartstone unlocks during checkout.
* Checkout and order item details now display “Gift for” with the
recipient name when gifting; otherwise show “Unlock for: Entire
account.”
* **Bug Fixes / Improvements**
* Smartstone quantity is capped to `1`, and cart-row quantities for
smartstone items are no longer editable.
* Self-gifting is allowed, while ban checks are enforced for the
recipient when gifting.
* Account-wide unlock routing now targets the recipient’s linked account
when a destination character is provided.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
- Add `end_raf_on_same_ip` setting to the RAF config defaults and admin
panel dropdown
- When `endRAFOnSameIP` is **enabled**: keep the existing warning that
RAF will be automatically removed
- When `endRAFOnSameIP` is **disabled**: show a more coherent message
matching [ChromieCraft's
wording](https://www.chromiecraft.com/en/recruit-a-friend-is-available-now/)
— _"Whilst you can recruit a friend who shares an IP address, you will
only get the teleport & XP bonuses, no rewards will be given"_
- Wrap Eluna DB queries in a try/catch so the RAF page shows a clear "No
RAF installation found" error instead of a blank page when the database
is not configured
## Test plan
- [ ] Verify the admin panel shows the new "RAF: End RAF on same IP"
dropdown under Eluna settings
- [ ] Toggle `end_raf_on_same_ip` to **Enabled** and confirm the
original warning text appears on the RAF page
- [ ] Toggle `end_raf_on_same_ip` to **Disabled** and confirm the
updated ChromieCraft-style text appears
- [ ] Disconnect or misconfigure the Eluna database and verify the RAF
page shows the friendly error message instead of a blank page
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Current behavior (before this PR, as my mistake and lack of knowledge)
smartstone can be added to cart without selecting a character (from the
catologue of costumes or others from the smartstone), which leads people
buying costumes and not getting them because there's no character
associated. This fixes the current issue for the current implementation
(for character unlock the service, if this get changed to account bound,
we change smartstone impelemtnation then).
This will redirect them to the page (of "add cart") and ask them to
select the character
This is hasn't be tested, and can't test it for now.
And i tried submitting this fix on the same night of invesgiating, I
couldn't update `acore-cms` within my `chormiecraft-cms` in my WSL, lack
of linux and git together maybe.
---------
Co-authored-by: Yehonal <yehonal.azeroth@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Plugin files were not properly loaded because the web.local container
didn't have the volume for it
NOTE: nginx now also logs into /dev/stdout directly to make sure docker
can catch its logs, also, we filter out non-error access logs
## Description of the Change
This PR introduces a static method `AcoreUtils::handle_acore_error()`
that provides unified error handling for both GraphQL and standard
WordPress requests.
## Purpose
The goal is to centralize error handling logic across the plugin and
ensure consistent behavior depending on the request context:
- **GraphQL requests**: If `GRAPHQL_HTTP_REQUEST` is defined and set to
`true`, a `UserError` exception is thrown, which is properly handled by
the GraphQL layer.
- **Standard WordPress requests**: The user is redirected to the
plugin's settings page, and an admin notice is displayed with the
provided error message.
## Benefits
- Avoids duplicated error-handling logic throughout the codebase.
- Ensures GraphQL clients receive well-structured error responses.
- Provides a better UX for admin users when issues occur in the standard
WP context.
## Technical Notes
- The `GRAPHQL_HTTP_REQUEST` constant is defined by WPGraphQL to
indicate incoming GraphQL HTTP requests.
- `wp_redirect()` is followed by `exit` to stop further execution after
redirecting.
- `esc_html()` is used to ensure safe output in the admin notice.
## Potential Improvements
- Consider integrating logging for error tracking and diagnostics.
- Optionally extend support to handle REST API requests with JSON error
responses in the future.
---------
Co-authored-by: vittorio pinti <vittorio.pinti@gmail.com>
Co-authored-by: Yehonal <yehonal.azeroth@gmail.com>
* feat: update Docker environment variables for database configuration
* fix: add check for password key in user login action to prevent undefined index error
* feat: add maintenance service configuration to Docker Compose
* feat: add maintenance mode HTML page and update Docker Compose configuration for nginx
* fix: rename maintenance service to acore-cms-maintenance in Docker Compose
* feat: configure maintenance mode in nginx and update Docker Compose for acore-cms-maintenance service
* feat: add maintenance mode configuration and HTML page for nginx