Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/acore-wp-plugin/src/Components/AdminPanel/Pages/Tools.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,18 @@
<input type="number" name="acore_resurrection_scroll_days_inactive" id="acore_resurrection_scroll_days_inactive" min="1" value="<?= esc_attr(Opts::I()->acore_resurrection_scroll_days_inactive) ?>">
</td>
</tr>
<tr>
<th>
<label for="acore_smartstone_enabled">Smartstone Token Gifting</label>
<p class="description">Requires the mod-chromiecraft-smartstone module. Lets buyers gift name/faction/race/customize services as tokens.</p>
</th>
<td>
<select name="acore_smartstone_enabled" id="acore_smartstone_enabled">
<option value="0">Disabled</option>
<option value="1" <?php if (Opts::I()->acore_smartstone_enabled == '1') echo 'selected'; ?>>Enabled</option>
</select>
</td>
</tr>
</tbody>
</table>
</div>
Expand Down
10 changes: 10 additions & 0 deletions src/acore-wp-plugin/src/Hooks/WooCommerce/CartValidation.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@ public static function add_to_cart_validation($flaq, $product_id, $quantity, $va
return false;
}

// Gift via smartstone token: for giftable char-change services, a
// recipient name replaces the self character selection. Validate the
// recipient and skip the remaining self-service field checks.
if (CharChange::isGiftEligible($sku)) {
$destName = isset($_REQUEST['acore_char_dest']) ? trim((string) $_REQUEST['acore_char_dest']) : '';
if ($destName !== '') {
return CharChange::validateGiftRecipient($destName);
}
}

if (in_array('acore_char_sel', self::$skuList[$activeSku])) {
$guid = intval($_REQUEST['acore_char_sel'] ?? 0);

Expand Down
121 changes: 120 additions & 1 deletion src/acore-wp-plugin/src/Hooks/WooCommerce/CharChange.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace ACore\Hooks\WooCommerce;

use ACore\Manager\ACoreServices;
use ACore\Manager\Opts;

class CharChange extends \ACore\Lib\WpClass {

Expand All @@ -15,6 +16,62 @@ class CharChange extends \ACore\Lib\WpClass {
"char-restore-delete"
);

/**
* SKUs that can be gifted through the smartstone token system, mapped to
* the mod's TokenType enum (src/Smartstone.h):
* 1 = Rename, 2 = Faction change, 3 = Race change, 4 = Customize.
* char-restore-delete has no token equivalent and is intentionally absent.
*/
private static $tokenTypes = array(
"char-change-name" => 1,
"char-change-faction" => 2,
"char-change-race" => 3,
"char-change-customize" => 4,
);

private static function giftingEnabled(): bool {
return Opts::I()->acore_smartstone_enabled == '1';
}

/** A concrete service SKU the buyer may gift (smartstone must be enabled). */
public static function isGiftEligible($sku): bool {
return self::giftingEnabled() && isset(self::$tokenTypes[$sku]);
}

/**
* Whether to offer the gift field on the product page. Uses the display SKU,
* which for the variable "char-change" product is the parent (variations all
* map to giftable services), so the base SKU is accepted here too.
*/
private static function isGiftableDisplaySku($sku): bool {
return self::giftingEnabled()
&& ($sku === 'char-change' || isset(self::$tokenTypes[$sku]));
}

/**
* Validate a gift recipient (character name -> account). Adds a WooCommerce
* notice and returns false on any problem. Mirrors the smartstone
* account-wide gifting checks.
*/
public static function validateGiftRecipient($destName): bool {
$ACoreSrv = ACoreServices::I();

$char = $ACoreSrv->getCharactersRepo()->findOneByName($destName);
if (!$char) {
\wc_add_notice(__('Recipient character not found.', 'acore-wp-plugin'), 'error');
return false;
}
if ($ACoreSrv->getCharactersBannedRepo()->isActiveByGuid($char->getGuid())) {
\wc_add_notice(__('Recipient character is banned.', 'acore-wp-plugin'), 'error');
return false;
}
if ($ACoreSrv->getAccountBannedRepo()->isActiveById($char->getAccount())) {
\wc_add_notice(__('Recipient account is banned.', 'acore-wp-plugin'), 'error');
return false;
}
return true;
}
Comment thread
Nyeriah marked this conversation as resolved.

public static function init() {
add_action('woocommerce_after_add_to_cart_quantity', self::sprefix() . 'before_add_to_cart_button');
add_filter('woocommerce_add_cart_item_data', self::sprefix() . 'add_cart_item_data', 20, 3);
Expand All @@ -36,14 +93,43 @@ public static function before_add_to_cart_button() {
if ($current_user) {
FieldElements::charList($current_user->user_login, self::isDeletedSKU($product->get_sku()));
}

// Optional gifting via smartstone token: leaving the field blank keeps
// the normal self-service (instant apply to the selected character).
if (self::isGiftableDisplaySku($product->get_sku())) {
FieldElements::destCharacter(__('Gift to a character (optional, leave blank to apply to your own selected character):', 'acore-wp-plugin'));
}
}

// 3) SAVE INTO ITEM DATA
// This code will store the custom fields ( for the product that is being added to cart ) into cart item data
// ( each cart item has their own data )
public static function add_cart_item_data($cart_item_data, $product_id, $variation_id) {
$product = $variation_id ? \wc_get_product($variation_id) : \wc_get_product($product_id);
if (!in_array($product->get_sku(), self::$skuList)) {
$sku = $product->get_sku();
if (!in_array($sku, self::$skuList)) {
return $cart_item_data;
}

// Gift mode: a recipient name was supplied for a giftable service. Resolve
// it to an account now so payment_complete can grant the token there.
// add_to_cart_validation has already vetted existence/ban state.
$destName = self::isGiftEligible($sku) && isset($_REQUEST['acore_char_dest'])
? trim((string) $_REQUEST['acore_char_dest'])
: '';
if ($destName !== '') {
$cart_item_data['acore_item_sku'] = $sku;
$cart_item_data['unique_key'] = md5(microtime() . rand());

$ACoreSrv = ACoreServices::I();
$char = $ACoreSrv->getCharactersRepo()->findOneByName($destName);
if ($char) {
$account = $ACoreSrv->getAccountRepo()->findOneById($char->getAccount());
if ($account) {
$cart_item_data['acore_gift_account'] = $account->getUsername();
$cart_item_data['acore_gift_charname'] = $char->getName();
}
}
return $cart_item_data;
}

Expand Down Expand Up @@ -71,6 +157,14 @@ public static function get_item_data($cart_data, $cart_item = null) {
return $custom_items;
}

if (isset($cart_item['acore_gift_account'])) {
$giftCharname = isset($cart_item['acore_gift_charname'])
? $cart_item['acore_gift_charname']
: '(recipient)';
$custom_items[] = array("name" => 'Gift for', "value" => $giftCharname);
return $custom_items;
}

if (isset($cart_item['acore_char_sel'])) {
$ACoreSrv = ACoreServices::I();
$charRepo = $ACoreSrv->getCharactersRepo();
Expand All @@ -95,6 +189,19 @@ public static function add_order_item_meta($item_id, $values, $cart_item_key) {
return;
}

// Gift: persist the resolved recipient so payment_complete can grant the
// token. The account login is stored under an underscore-prefixed key so
// WooCommerce keeps it out of customer-facing order details and emails;
// only the character name is shown to the buyer. Charname is informational.
if (isset($values['acore_gift_account'])) {
wc_add_order_item_meta($item_id, "acore_item_sku", $values['acore_item_sku']);
wc_add_order_item_meta($item_id, "_acore_gift_account", $values['acore_gift_account']);
if (isset($values['acore_gift_charname'])) {
wc_add_order_item_meta($item_id, "acore_gift_charname", $values['acore_gift_charname']);
}
return;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

if (isset($values['acore_char_sel']) && isset($values["acore_item_sku"])) {
wc_add_order_item_meta($item_id, "acore_char_sel", $values['acore_char_sel']);
wc_add_order_item_meta($item_id, "acore_item_sku", $values['acore_item_sku']);
Expand Down Expand Up @@ -135,6 +242,18 @@ public static function payment_complete($order_id) {

foreach ($items as $item) {
if (isset($item["acore_item_sku"])) {
// Gift: grant a token to the recipient's account instead of
// applying the change directly. The recipient redeems it in-game.
if (!empty($item["_acore_gift_account"])
&& isset(self::$tokenTypes[$item["acore_item_sku"]])) {
$tokenType = self::$tokenTypes[$item["acore_item_sku"]];
$res = $WoWSrv->getSmartstoneSoap()->grantToken($item["_acore_gift_account"], $tokenType);
if ($res instanceof \Exception) {
throw new \Exception("There was an error granting a token to " . $item["_acore_gift_account"] . " - " . $res->getMessage());
}
continue;
}

switch ($item["acore_item_sku"]) {
case "char-change-name":
$charName = $WoWSrv->getCharName($item["acore_char_sel"]);
Expand Down
11 changes: 6 additions & 5 deletions src/acore-wp-plugin/src/Hooks/WooCommerce/Smartstone.php
Original file line number Diff line number Diff line change
Expand Up @@ -279,10 +279,11 @@ public static function add_order_item_meta($item_id, $values, $cart_item_key) {
}

// Gifting: persist the resolved recipient so payment_complete can route
// the SOAP unlock to their account. Charname is informational (for admin
// order view); only acore_gift_account is functional.
// the SOAP unlock to their account. The account login is stored under an
// underscore-prefixed key so WooCommerce keeps it out of customer-facing
// order details and emails; only the character name is shown to the buyer.
if (self::isAccountWide($smartstone_category) && isset($values['acore_gift_account'])) {
\wc_add_order_item_meta($item_id, "acore_gift_account", $values['acore_gift_account']);
\wc_add_order_item_meta($item_id, "_acore_gift_account", $values['acore_gift_account']);
if (isset($values['acore_gift_charname'])) {
\wc_add_order_item_meta($item_id, "acore_gift_charname", $values['acore_gift_charname']);
}
Expand Down Expand Up @@ -340,8 +341,8 @@ public static function payment_complete($order_id) {
if (self::isAccountWide($smartstone_category)) {
// Gifted items route to the recipient's account; otherwise unlock
// for the buyer. Resolution + ban-check happened at add-to-cart time.
$target = !empty($item['acore_gift_account'])
? $item['acore_gift_account']
$target = !empty($item['_acore_gift_account'])
? $item['_acore_gift_account']
Comment thread
Nyeriah marked this conversation as resolved.
: $accountName;
if (!$target) {
throw new \Exception("Smartstone account-wide unlock requires a buyer with a linked AC account; order $order_id has none.");
Expand Down
1 change: 1 addition & 0 deletions src/acore-wp-plugin/src/Manager/Opts.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class Opts {
public $acore_resurrection_scroll="";
public $acore_resurrection_scroll_days_inactive="180";
public $acore_item_restoration="";
public $acore_smartstone_enabled="";
public $acore_name_unlock_thresholds = [
[5, 30], // level < 5 -> 30 days
[30, 90], // level < 30 -> 90 days
Expand Down
15 changes: 15 additions & 0 deletions src/acore-wp-plugin/src/Manager/Soap/SmartstoneService.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,19 @@ public function addAccountVanity($accountName, $category, $vanityID) {
return $this->executeCommand(".smartstone unlock account $accountName $category $vanityID true");
}

/**
* Grant a service token to an account. The recipient redeems it in-game
* (`.smartstone token claim`) on the character of their choice.
*
* $tokenType values come from the mod's TokenType enum (src/Smartstone.h):
* 1 = Rename, 2 = Faction change, 3 = Race change, 4 = Customize.
*
* $accountName is a WordPress user_login (sanitize_user restricts it to
* alphanumerics, dot, hyphen, underscore, at - no spaces).
*/
public function grantToken($accountName, $tokenType) {
$tokenType = (int) $tokenType;
return $this->executeCommand(".smartstone token grant $accountName $tokenType");
}

}
Loading