From bda5d0596967699a44d1c4b89a609c6a94bbf211 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sat, 25 Apr 2026 13:55:01 -0600 Subject: [PATCH] Prevent the attribute Items from getting invalid keys --- langs/en-ca.json | 3 ++- module/api.mjs | 3 ++- module/data/Item/attribute.mjs | 28 ++++++++++++++++++++++++++++ module/utils/toID.mjs | 27 ++++++++++++++++++++++++--- 4 files changed, 56 insertions(+), 5 deletions(-) diff --git a/langs/en-ca.json b/langs/en-ca.json index 3c23e4a..d418041 100644 --- a/langs/en-ca.json +++ b/langs/en-ca.json @@ -134,7 +134,8 @@ "missing-id": "An ID must be provided", "invalid-socket": "Invalid socket data received, this means a module or system bug is present.", "unknown-socket-event": "An unknown socket event was received: {event}", - "duplicate-attribute-key": "Cannot create an Attribute with a key (\"{key}\") that already exists on the Actor" + "duplicate-attribute-key": "Cannot create an Attribute with a key (\"{key}\") that already exists on the Actor", + "invalid-attribute-key": "Attribute keys must be alphanumeric (a-z, 0-9) with underscores, invalid key provided: \"{key}\"" }, "warn": { "migration-in-progress": "Applying data migrations for version {version}. Please do NOT refresh the window while this warning is present." diff --git a/module/api.mjs b/module/api.mjs index d0b8c62..c294981 100644 --- a/module/api.mjs +++ b/module/api.mjs @@ -5,11 +5,11 @@ import { PlayerSheet } from "./apps/PlayerSheet.mjs"; import { QueryStatus } from "./apps/QueryStatus.mjs"; // Utils +import { toID, isValidID, } from "./utils/toID.mjs"; import { attributeSorter } from "./utils/attributeSort.mjs"; import { DialogManager } from "./utils/DialogManager.mjs"; import { localizer } from "./utils/localizer.mjs"; import { QueryManager } from "./utils/QueryManager.mjs"; -import { toID } from "./utils/toID.mjs"; const { deepFreeze } = foundry.utils; @@ -26,5 +26,6 @@ export const api = deepFreeze({ attributeSorter, localizer, toID, + isValidID, }, }); diff --git a/module/data/Item/attribute.mjs b/module/data/Item/attribute.mjs index 833c2c8..3b2f7b0 100644 --- a/module/data/Item/attribute.mjs +++ b/module/data/Item/attribute.mjs @@ -1,3 +1,7 @@ +import { isValidID, toID } from "../../utils/toID.mjs"; + +const { hasProperty } = foundry.utils; + export class AttributeItemData extends foundry.abstract.TypeDataModel { // #region Schema static defineSchema() { @@ -37,6 +41,17 @@ export class AttributeItemData extends foundry.abstract.TypeDataModel { // #region Lifecycle async _preCreate(data, options, user) { + // Assign the key as the ID'd name if isn't provided, or validate if + // it is provided. + if (!this.key) { + this.updateSource({ key: toID(this.parent.name) }); + } else if (!isValidID(this.key)) { + ui.notifications.error(_loc( + `taf.notifs.error.invalid-attribute-key`, + { key: this.key }, + )); + return false; + }; // Prevent duplicate Attribute keys from existing on a single Actor if (this.parent.isEmbedded) { @@ -55,6 +70,19 @@ export class AttributeItemData extends foundry.abstract.TypeDataModel { return super._preCreate(data, options, user); }; + + async _preUpdate(data, options, user) { + // Prevent invalid IDs + if (hasProperty(data, `system.key`) && !isValidID(data.system.key)) { + ui.notifications.error(_loc( + `taf.notifs.error.invalid-attribute-key`, + { key: data.system.key }, + )); + delete data.system?.key; + }; + + return super._preUpdate(data, options, user); + }; // #endregion Lifecycle // #region Methods diff --git a/module/utils/toID.mjs b/module/utils/toID.mjs index 312766e..8e854b1 100644 --- a/module/utils/toID.mjs +++ b/module/utils/toID.mjs @@ -1,6 +1,6 @@ /** - * A helper method that converts an arbitrary string into a format that can be - * used as an object key easily. + * A helper method that converts an arbitrary string into a format + * that can be used as an object key easily. * * @param {string} text The text to convert * @returns The converted ID @@ -9,5 +9,26 @@ export function toID(text) { return text .toLowerCase() .replace(/\s+/g, `_`) - .replace(/\W/g, ``); + .replace(/\W/g, ``) + .replace(/(^_|_$)/); +}; + +/** + * A helper method that reports if an arbitrary string is considered a + * valid ID for use in the system + * + * @param {string} text The text to check + * @returns Whether or not the text is a valid ID + */ +export function isValidID(text) { + return !( + // any uppercase characters + text.match(/[A-Z]/) + + // any non-word characters + || text.match(/\W/) + + // any whitespace characters + || text.match(/\s/) + ); };