feat(items): optimize search, attribute filtering, and sort for items view (#4652)
BREAKING CHANGE: none
## Search & Query Optimization
- Split item search into two-phase query: Phase A resolves qualifying IDs,
Phase B joins scoped display tables for better performance and readability
- Sanitize `definition_ids` via `array_map` to prevent injection vulnerabilities
- Introduce subquery for SUM aggregation to prevent over-counting across joins
- Add validation requiring both start and end dates before applying date range filter
## Attribute Search (fixes#2919, #2722)
- Add `SHOW_IN_SEARCH` flag (value 8) to Attribute model to separate
searchability from table visibility
- Add `parse_attribute_search()` to parse syntax like `color:blue AND size:large`
- Add `applyNamedAttributeSearch()` supporting decimal and date types with
locale-aware parsing
- Support AND/OR logic for multi-attribute queries
## Sorting
- Add `get_attribute_sort_definition_id()` to detect attribute column sorting
- Join attribute tables dynamically when sorting by attribute columns
- Use `MAX()` for consistent results when sorting by attribute values
- Replace static sort column list with dynamic headers via `itemSortColumns()`
- Add `sanitizeSortColumnAttribute()` to validate attribute definition IDs as sort columns
## Tax & Data Row
- Streamline tax computation in `getItemDataRow()`
## Low Inventory Filter
- Require valid `stock_location_id` before applying low inventory filter
- Add conditional logic to sort by sum of quantities across all locations
when `stock_location_id` is invalid
## Localization
- Add `show_in_search` / `show_in_search_visibility` strings to all language files
- Translated: de-DE, es-ES, fr, it; English placeholder for remaining locales
- Unify single-quote style across all attribute language files
## Refactoring & Style
- Adopt camelCase naming throughout (variables, helpers, methods)
- Replace `sanitizeSortColumnAttribute` with reusable `sanitizeSortColumn`
from `Secure_Controller`
- Simplify column key extraction using `array_key_first`
- Apply PSR-12 formatting
## Tests
- Add tests for tax computation, quantity aggregation (single- and multi-location),
named attribute search, free-text parsing, and date/decimal type handling
- Add `ensureStockLocation` helper to auto-create missing stock locations in tests
- Refactor tests to handle config cache issues
Co-authored-by: Ollama <ollama@steganos.dev>
* fix(security): report unwritable .env.lock, make throttle limits configurable
envFileIsWritable() previously checked is_writable(.env) (the file), which passes in Docker even when the mutex file is root-owned by a prior env:provision run. It now checks the real write path: the directory (to create .env.tmp.* + .env.lock) and any existing .env.lock must be writable, so the app throws the clear 'run env:provision' error instead of crashing on 'Unable to open .env.lock'.
The Throttle filter now reads throttle.capacity / throttle.seconds from env (default 5 per 60s); capacity <= 0 disables throttling, so operators serving sequential HTTP clients (e.g. Zabbix) are not caught by the lockout.
* fix(security): address CodeRabbit review findings
- Throttle: validate throttle.capacity as an integer before treating a non-positive value as 'disabled', so a non-numeric value (e.g. 'five') falls back to the default instead of silently bypassing the lockout. Apply the same validation to throttle.seconds.
- envFileIsWritable(): also reject an existing non-writable .env on Windows (where rename() cannot replace a read-only destination); keep the check Windows-only since POSIX rename() replaces a read-only dest when the directory is writable.
- Tests: restore the prior throttle.capacity env state in ThrottleTest (capture/restore instead of delete); add an invalid-capacity fallback case; skip the not-writable fixtures when running as root (where is_writable() is bypassed).
* fix(migration): guard ConvertToCI4 key-write branches with envFileIsWritable()
The migration's 'no key' and 'CI3 key' branches called rotateEncryptionKey()/rotateEncryptionKeyTransaction() directly, bypassing the envFileIsWritable() guard that checkEncryption() uses. On a fresh Docker/Compose install where the web runtime cannot write /app/.env.lock, this produced a raw 'fopen(/app/.env.lock): Permission denied' error instead of the actionable 'run php spark env:provision' message.
A valid CI4 key now short-circuits to checkEncryption() (no write); every write branch is gated on envFileIsWritable() first.
* fix: read provisioned encryption.key so config sees it
env:provision persists the key as 'encryption.key' in .env (matching the
throttle path and .env.example), but Config\Encryption only read the
ENCRYPTION_KEY env var. On a fresh Docker instance the provisioned key was
invisible to config('Encryption')->key, so the app believed no key existed
and tried to write one -- hitting the .env.lock permission wall.
Read encryption.key (via $_SERVER/$_ENV/getenv) first, then fall back to
ENCRYPTION_KEY for Docker '-e' usage. Mirrors checkThrottleEncryption().
* fix: cascade encryption.key lookup past empty-string sources
* refactor: extract Encryption::resolveKey() and test it without global env mutation
* fix(security): decode fallback-selected encryption key like BaseConfig
A key picked in the constructor fallback (notably ENCRYPTION_KEY, which
BaseConfig never inspects) was assigned verbatim, bypassing the
hex2bin:/base64: decode the parent applies to `encryption.key`. Route the
selected key through a parseKey() helper mirroring BaseConfig's
parseEncryptionKey() so prefixed values decrypt consistently, and add a
pure regression test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: remove advisory ID from comments and tighten verbose comments
Per review: treat GHSA advisory IDs like secrets (drop from code) and
replace the multi-paragraph comments with concise one-liners that keep
the non-obvious "why". No logic changes.
---------
Co-authored-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
checkThrottleEncryption() now consults the THROTTLE_KEY environment
variable when throttle.key is empty, mirroring the ENCRYPTION_KEY
fallback in Config/Encryption. This lets Docker/Compose deployments
supply the throttle HMAC secret without writing a shared value into a
read-only .env. An explicit throttle.key always takes precedence.
Adds regression tests to the existing security_helperTest suite and
documents THROTTLE_KEY in .env.example.
* fix(security): handle special characters in `.env` key values and improve insertion logic
- Escape backslashes and dollar signs in `applyEnvKeyReplacement` to prevent unintended value corruption.
- Ensure new keys are inserted after `encryption.key` for better organization and manageability.
- Add explicit cast to int to prevent wrong concatenation operator warning.
Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
* fix(security): handle null return in `applyEnvKeyReplacement` and ensure proper `.env` updates
- Update `applyEnvKeyReplacement` to return `null` on failure, improving error handling.
- Adjust calls to `atomicWriteFile` with updated content to prevent unintended behavior.
Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
* fix(security): improve error logging and exception messages in file locking
- Add detailed logging for file open and locking errors in `security_helper`.
- Remove unused `helper` and `checkThrottleEncryption` calls from `Events` for cleanup.
Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
* fix(security): improve atomic file write and handle encryption key placement
- Throw `RandomException` for better error reporting in `atomicWriteFile`.
- Simplify Windows-specific `rename()` fallback logic.
- Fix `encryption.key` assignment order to ensure consistency in `.env` updates.
Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
* fix(security): improve `.env` file handling and add unit tests for helper functions
- Suppress warnings in `file_get_contents` to prevent unnecessary error logs.
- Update `applyEnvKeyReplacement` to use `preg_replace_callback` for better safety.
- Add comprehensive unit tests for `security_helper` functions to ensure `.env` updates and key management work as expected.
Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
* fix(security): enhance `.env` update logic and add robust exception handling
- Add `RandomException` to improve error reporting in encryption key management.
- Introduce environment file locking for safer `.env` updates.
- Ensure `applyEnvKeyReplacement` properly handles and inserts old key comments.
- Replace direct file writes with `atomicWriteFile` for consistency.
Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
* fix(security): refactor `.env` file initialization and encryption key handling
- Introduce `initializeEnvFile` for reusable `.env` setup logic.
- Add `backupEnvFile` and `writeNewEncryptionKey` for robust key management with backups.
- Simplify and clean up redundant `.env` handling code paths.
Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
* fix(security): clarify `checkEncryption` docblock return value description
Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
* fix(security): escape backslashes and dollar signs in `applyEnvKeyReplacement`
- Ensure `applyEnvKeyReplacement` properly escapes special characters when inserting or appending `.env` keys.
- Add new unit tests to validate correct handling of backslashes and dollar signs.
Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
* fix(i18n): add localized error messages and improve error reporting in `security_helper`
- Add missing translations for error messages across multiple language files.
- Update `security_helper` to use localized exception messages with placeholders.
Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
* Redesign encryption/throttle key provisioning as read-only runtime
- checkEncryption()/checkThrottleEncryption() are now read-only guards that
throw when no valid key is provisioned, instead of writing .env at
request time.
- Add rotateEncryptionKey() and provisionThrottleKey() for explicit,
idempotent provisioning.
- Add php spark env:provision (app/Commands/EnvProvision.php) so Docker can
provision keys once at container startup before any request.
- Add app/Libraries/CI3SecretConverter.php shared CI3->CI4 secret converter
(AES-128-CBC decrypt + CI4 re-encrypt/verify/save) used by both the
interactive migration and the docker startup path.
- Refactor convertToCI4 migration to use the shared converter.
- Persist .env in a named volume and run spark env:provision on boot; stop
baking .env into the shipped image.
- Add guard/rotation/throttle + converter tests; clean up orphaned
msg_pwd_required language keys across all locales.
* fix: save CI4 ciphertext in env:provision and bind-mount a .env file
Addresses CodeRabbit review on PR #4656:
- env:provision CI3 branch was persisting *plaintext* secrets (saveAll($plain))
instead of the CI4 ciphertext, unlike the ConvertToCI4 migration. Now
encrypts with encryptAll(), verifies the round trip, and saves the ciphertext.
- The ospos_env named volume mounted at /app/.env made .env a directory, so
atomicWriteFile's rename() failed and spark env:provision could not start apache.
Switch to a bind mount of a host file (./.env) which persists and stays a file.
- Add a regression test asserting the command persists ciphertext (not plaintext).
* chore: trim redundant docblocks in EnvProvision and provision throttle.key in CI
Follow up on @objecttothis review comments:
- app/Commands/EnvProvision.php: remove the boilerplate docblocks the
property names already convey (group/name/usage/description, run()),
the two inline step comments, the anyNonEmpty() param docblock, and the
legacySecretsPresent() docblock. Keeps the class-level docblock since it
is the only place that states the read-only runtime design + the
never-persist-plaintext invariant.
- .github/workflows/phpunit.yml: provision a per-run throttle.key the same
way the encryption key is already provisioned. The PR makes
checkThrottleEncryption() a read-only guard that throws when
env('throttle.key') is unset; CI only started exporting ENCRYPTION_KEY,
so every test that goes through the Throttle filter (7 ThrottleTest
cases + 4 LoginTest cases) failed with
"No throttle key is provisioned. Run `php spark env:provision`".
Writing `throttle.key=<KEY>` into .env matches what
`php spark env:provision` does on a real container start.
* fix(ci): write throttle.key into .env instead of exporting an OS env var
The previous attempt exported throttle.key via GITHUB_ENV, but CodeIgniter's
env() helper resolves in the order $_ENV[$key] ?? $_SERVER[$key] ?? getenv($key),
and DotEnv populates $_ENV['throttle.key'] from the .env file first. Because the
.env (copied from .env.example) ships with the empty placeholder throttle.key='',
that $_ENV entry exists as '' and short-circuits the ?? chain before getenv()
is reached — so the OS env var was never consulted and every Throttle/Login test
still threw 'No throttle key is provisioned'.
Write the per-run key into the .env file itself (sed-replacing the empty
placeholder), which is exactly what `php spark env:provision` does in
production and is the single source env() actually reads from.
Verify the replacement happened (grep -Eq '^throttle\.key=.') so a future change
to the placeholder format fails the run loudly instead of silently breaking
the 11 throttle-dependent tests.
* fix(security): restore CI3->CI4 auto-provisioning gated by .env writability
checkEncryption()/checkThrottleEncryption() again provision the keys
inline when .env is writable (empty key -> generate; short key -> decrypt,
rotate, re-encrypt, verify, persist legacy CI3 secrets). When .env is not
writable they assume the key was provisioned externally (e.g. docker
env:provision) and throw. Update helper tests to match and correct the
EnvProvision docblock that claimed the runtime was strictly read-only.
* test(security): make short-key conversion branch injectable and test it
checkEncryption() now accepts an optional CI3SecretConverter so the
CI3->CI4 conversion branch can be exercised in unit tests without a
database. Adds testCheckEncryptionConvertsCi3ShortKeyWhenEnvWritable
which seeds CI3-era ciphertexts via a fake Appconfig model and asserts
the key is rotated and the payload verifies back to the original
plaintext.
* fix(security): abort on backup/read/saveAll failure to avoid data loss
Three related data-integrity fixes:
- backupEnvFile() now returns true/false based on whether the backup
actually exists and is readable. rotateEncryptionKey() aborts before
destroying the key when the backup could not be written to disk.
- rotateEncryptionKey() and provisionThrottleKey() throw
RuntimeException(Error.unable_to_read_env_file) when the .env read
fails, instead of silently replacing the whole file with an empty
string. This prevents a permission error from wiping all keys.
- checkEncryption() and EnvProvision::run() now both roll back to the
backup with abortEncryptionConversion() when the post-rotation
saveAll() throws, matching the migration path (which already did this).
A failing fake Appconfig is used to exercise this in the new
testCheckEncryptionRollsBackWhenSaveAllFails test.
* fix(ci): skip comment job in deploy-pr.yml when prepare was not run
The comment job had if: always(), so it ran even when the prepare job
was skipped (e.g. review was not approved). With PR_NUMBER empty the gh
api call posted to issues//comments, received a 404, and the entire run
showed up as failure. Guard the job with
needs.prepare.result == 'success' so it only runs when PR_NUMBER is valid.
* address coderabbit open items: placeholder guards, message neutrality, ar-EG alignment
- backupEnvFile(): fail when mkdir() or either chmod() fails, so the
pre-rotation backup is actually persisted before the key is replaced
- email/message config views: only show the 'already set' placeholder when
the secret is actually present (prevented false positives on fresh installs)
- Error.unable_to_create_env_file / .unable_to_read_env_file (en + en-GB):
use key-neutral wording since both keys are provisioned with the same keys
- ar-EG/Error.php: align all => arrows on the longest key
Item 7 (filesystem test isolation) is a larger refactor — the tests are
serial on CI and tearDown() restores state per test. Left for follow-up.
* test(security): isolate helper FS tests via Config\SecurityEnv
Introduce Config\SecurityEnv holding envPath/backupPath/lockPath so the
security helper reads its target paths from shared configuration instead of
hardcoded ROOTPATH/WRITEPATH literals. security_helperTest.php now redirects
all three to a unique per-run sandbox under sys_get_temp_dir() and tears it
down in tearDown(), so the suite no longer reads/writes the repository's real
.env and is safe to run in parallel.
No helper signature changes; production callers unaffected.
Addresses CodeRabbit item 7 (issue #4700).
Co-Authored-By: opencode <bot@opencode.ai>
* fix(security): run key-conversion as one locked transaction
Address CodeRabbit Major findings from the 4th re-review of the env
helper and its callers:
1. Hold .env.lock for the entire CI3 -> CI4 conversion transaction
(backup -> rotate -> re-encrypt -> verify -> persist -> cleanup) so a
concurrent worker cannot interleave a key write between the rotation
and the ciphertext save. Split rotateEncryptionKey into a lock-free
core (rotateEncryptionKeyUnlock) plus the existing lock wrapper and a
new rotateEncryptionKeyTransaction that owns the lock across the full
unit and performs both the in-lock rollback (abortEncryptionConversion)
and the in-lock backup removal on success.
2. Treat the legacy value '0' as non-empty data so key rotation still
persists the re-encrypted ciphertext when '0' is the only stored
secret (array_filter would have dropped it and skipped saveAll).
3. Wrap the post-rotation re-encrypt/verify/saveAll sequence in a
catch (Throwable) across all three call-sites so CI4
EncryptionException, ReflectionException from batch_save, a failed
round-trip verify, and any other failure all roll the .env key back
to the pre-rotation state.
4. In Docker Compose, use long-syntax bind with create_host_path: false
and document in INSTALL.md that the host .env must be a regular file
(a missing one is no longer auto-created as a directory, and the
mount now rejects a missing source on Compose implementations that
support the flag).
Files touched: app/Helpers/security_helper.php, app/Commands/EnvProvision.php,
app/Database/Migrations/20220127000000_convertToCI4.php, docker-compose.yml,
INSTALL.md. All 4 existing helper tests still pass via CI.
* fix(security): make abortEncryptionConversion fail loudly on restore failure
The rollback path restored the .env backup with a suppressed
file_put_contents() and an unchecked file_get_contents(). If the restore
failed after the key had already been rotated, .env was left holding the new
CI4 key while the DB still held CI3-era ciphertext, so the data became
undecryptable after the next restart.
Now the backup read is checked for false and the restore goes through the
existing atomicWriteFile() helper; either failure throws so the error is
surfaced instead of silently corrupting the config. Adds a regression test
that forces an unreadable backup and asserts the throw plus that .env is
left untouched.
* fix(security): guard abortEncryptionConversion backup read before touching it
Validate the backup is a regular readable file (is_file/is_readable) before
reading it, so a missing/malformed backup fails loudly instead of emitting a
file_get_contents() warning. The unreadable-backup regression test now
exercises this guard rather than relying on a promoted warning.
---------
Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
Co-authored-by: jekkos <jeroen.peelaerts@gmail.com>
Co-authored-by: jekkos <jekkos@users.noreply.github.com>
Co-authored-by: opencode <bot@opencode.ai>
- Sales::getSearch now enforces reports_sales grant before running
search, returns 403 with lang message when missing
- Rename snake_case helpers/methods to camelCase across
Sales controller, Sale model, and tabular_helper
(get_sale_data_row -> getSaleDataRow, get_payments_summary ->
getPaymentsSummary, sales_headers -> salesHeaders, etc.)
- Config/OSPOS: reset DB data cache before checking app_config
table existence to avoid stale schema cache in tests
- TestDatabaseBootstrapSeeder: expose static reset() so tests can
rebuild schema once per class instead of only via seeder run()
- SalesControllerTest: bootstrap DB once per class, seed once,
refresh app settings each setUp, add tests for search endpoint
authorization (cashier denied, supervisor allowed), move
createTestItem into shared ItemFixtureTrait
Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
fix(sales): gate per-record endpoints behind reports_sales grant (REDACTED)
Cashiers holding only the base `sales` grant could reach per-sale endpoints
(getRow, getEdit, postSave, getReceipt, getInvoice, getSendPdf, getSendReceipt)
that require `reports_sales`. getManage() enforced this at the list level, but
individual endpoints did not re-check. Regression tests added.
Auth:
- Introduce `IsLoggedIn` filter to centralize login checks across controllers
- Replace custom `AccessDeniedRedirectException` with built-in `RedirectException`
Employees:
- Add `DISALLOW_PASSWORD_CHANGE` and `DISALLOW_GRANT_CHANGE` env vars to restrict
credential and permission changes in locked-down environments
- Extract `hasGrantsChanged()` to streamline `postSave`
Refactor:
- Rename snake_case variables to camelCase in Sales, Items, and Employees
controllers for PSR-12 compliance
- Use explicit `db_connect()` for transaction clarity in Items controller
Fixes:
- SMTP config entries fall back to defaults via null coalescing
- Migration uses `DROP FOREIGN KEY` instead of `DROP CONSTRAINT`
- Password hash upgrade only sets session on successful `hash_version` update
- Correct lang key for unknown error in Module model
Language:
- Translate `error_grant_change_disallowed` / `error_password_change_disallowed`
across all 44 supported locales with => alignment matching en reference
- Fix "cannot be deleted" messages and misc typos across ~15 language files
Tests:
- Bootstrap seeder only once in ItemsCsvImportTest; close connection after
- Restore `DISALLOW_GRANT_CHANGE` in teardown to prevent side effects
- Use `uniqid()` for test user data to avoid collisions
Signed-off-by: 17935339+objecttothis@users.noreply.github.com
* refactor: standardize function and variable names to camelCase and improve naming consistency across files
* refactor(config): remove spaces around `=` in configuration files for improved consistency and formatting as is required by .env formatting rules.
* refactor(security): extract `.env` key management logic into reusable `writeEnvKey` helper, add throttle key provisioning logic, and streamline encryption key updates
* fix(migration): improve error handling in CI3 to CI4 encryption data migration
- Secure `up` and `convertCI3EncryptedData` methods with detailed exception handling for script execution and data saving.
* fix(migration): ensure empty string is correctly handled in CI3 to CI4 encryption data conversion
* refactor(security): enhance `.env` management with durable writes, better locking, and helper abstraction
- Update `writeEnvKey` to return a success flag and handle file locks robustly.
- Introduce `atomicWriteFile` for atomic writes to prevent partial file updates.
- Add `applyEnvKeyReplacement` to streamline `.env` key insertion and updates.
- Improve throttle key provisioning with validation and runtime persistence safeguards.
* refactor(security): implement dedicated `.env` file locking for robust and cross-platform safe write operations
- Add `lockEnvFile` and `unlockEnvFile` helpers to manage `.env` mutex files.
- Refactor `.env` write logic to use lock helpers, improving reliability and preventing race conditions.
- Enhance `atomicWriteFile` for better handling of file overwrites on Windows and POSIX systems.
* fix(migration): improve encryption error handling during CI3 to CI4 data conversion
- Add conditional checks for `checkEncryption` to prevent failed key persistence.
- Introduce `abortEncryptionConversion` for cleanup on failure.
- Update `writeEnvKey` to handle and return errors gracefully.
* refactor(security): improve `atomicWriteFile` for better file locking and cross-platform durability
- Replace `uniqid` with `bin2hex(random_bytes())` for more secure temp file naming.
- Add explicit file permissions and locking for safe concurrent writes.
- Enhance error handling to ensure atomicity on both Windows and POSIX systems.
* Add env temp files to gitignore so they don't get tracked.
---------
Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
* Reject item CSV imports whose header row is missing required columns
Signed-off-by: Sai Asish Y <say.apm35@gmail.com>
* Require all template columns when validating CSV import headers
Signed-off-by: Sai Asish Y <say.apm35@gmail.com>
* fix: derive required CSV import headers from the template generator
---------
Signed-off-by: Sai Asish Y <say.apm35@gmail.com>
Co-authored-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
Renames the Attribute-specific definition methods from snake_case to
camelCase and updates every call site:
get_definition_by_name -> getDefinitionByName
get_definition_names -> getDefinitionNames
get_definition_values -> getDefinitionValues
get_definitions_by_type -> getDefinitionsByType
get_definitions_by_flags -> getDefinitionsByFlags
get_definition_flags -> getDefinitionFlags
Also documents getDefinitionByName()'s return contract: a single
definition row as an associative array, or [] when none matches,
matching the getRowArray() behaviour introduced in #4464.
get_found_rows() and get_total_rows() are deliberately left alone -
they are declared across 15 models and renaming them only here would
break that shared convention.
Refs #4622
Co-authored-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
* Add `reference_code` to sale payment queries and group by statements
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
* Refactor `Sales` controller to improve payment handling readability and replace snake_case with camelCase variables
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
* Add missing translations for `Sales` language file and include new keys like `must_enter_rrn` and `reference_code`
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
* Refactor `Sales` payment handling to use camelCase and extend `addPayment` with `referenceCode` support
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
* Refactor `Sales` models, controllers, and libraries to adopt camelCase naming conventions and improve readability
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
* Add translations and updates for `must_enter_reference_code` and `reference_code` across language files and update `Sales` controller to replace `must_enter_rrn` with the new key
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
* feat(sales): add reference code input and payment type helper
- Add `get_reference_code_payment_types()` to locale_helper as single
source of truth for card-requiring payment types
- Add reference code row to register view, shown/hidden via JS based
on selected payment type
- Fix payment type dropdown width to 100% for consistent layout
- Add min-width to payment buttons and right-padding to button group
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
* feat(config): add payment reference code length configuration
- Add payment_reference_code_min and payment_reference_code_max fields
to Config controller save logic
- Add translation keys for reference code length limits across all
language files (min/max label + section header)
- Align array key formatting in Config controller for readability
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
* style(lang): normalize string delimiters to single quotes across all language files
Convert double-quoted array keys and values to single quotes in all
app/Language/*/Config.php and app/Language/*/Sales.php variants.
No translation content changed — formatting only.
Also add Localization section to AGENTS.md documenting language file
conventions for new keys and fallback behavior.
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
* feat(lang): add payment reference code length translations
Add localized strings for payment_reference_code_length_limits,
payment_reference_code_length_max_label, and
payment_reference_code_length_min_label across all supported locales.
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
* test(config,sales): add payment reference code min/max validation tests
- Add baseLocalePayload() helper in ConfigTest for postSaveLocale tests
- Add testSaveLocale_AcceptsValidReferenceCodeMinMax and related boundary tests
- Add Sale_libPaymentTest for payment reference code validation in Sale_lib
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
* fix(sales): add type-specific validation for amount_tendered
Gift card payments use amount_tendered as giftcard number (integer);
cash/other payments require decimal_locale format. Apply correct
validation rule per payment type instead of generic required.
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
* fix(lang): replace self-closing </br> with <br> in all locales
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
* fix(sales): use configurable precision in discount comparison
Replace hardcoded precision 2 with totals_decimals() when comparing
discount against item total via bccomp/bcmul, so discount validation
respects the configured decimal precision setting.
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
* fix(lang): correct Azerbaijani translations in Config.php
Replace placeholder/mismatched strings with accurate translations:
- email_mailpath, email_smtp_pass, invoice_email_message
- number_locale_invalid/required, receipt_template
- reward_configuration, right, tax_decimals, theme
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
* feat(sales): add reference code support to payment edit flow
- Add reference_code field to new payment row in sale edit form
- Persist reference_code on insert in Sale model
- Validate reference_code_new in Sales controller using configurable min/max length rules
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
* feat(lang): add Georgian (ka) language stubs for Config and Sales
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
* Match the fallback maximum reference_code length to the maximum of the field in the db
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
* Bug fixes
- Add validation of UI settings to prevent overridden values from being passed.
- Correct maximum value in JS for payment_reference_code maximum length to 40.
- Fix bug causing copy_entire_sale() to incorrectly copy the reference code and cash_adjustment
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
---------
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
Co-authored-by: Travis Garrison <travis@chiraqbookstore.com>
* Fix overly lenient date validation
- In the past date validation would roll over dates that didn't exist into the next month. Now they return an error.
Signed-off-by: objec <objecttothis@gmail.com>
* Refactor naming
- Refactor parameter
- Refactor function name.
Signed-off-by: objec <objecttothis@gmail.com>
* Add unit tests for LocaleHelper
Signed-off-by: objec <objecttothis@gmail.com>
* Remove files from being tracked.
Signed-off-by: objec <objecttothis@gmail.com>
---------
Signed-off-by: objec <objecttothis@gmail.com>
* fix(security): Fix DOMPDF RCE and customer email sanitization
- Disable isPhpEnabled in DOMPDF to prevent RCE via embedded PHP in HTML
- Disable isRemoteEnabled to prevent SSRF attacks
- Add email validation and sanitization in CSV import (FILTER_SANITIZE_EMAIL, FILTER_VALIDATE_EMAIL)
- Reject invalid email formats during customer import
* fix(security): Escape email addresses in mailto() to prevent XSS
Email columns in bootstrap tables had escaping disabled (line 52) and
mailto() function doesn't escape its parameters. This fix escapes email
addresses before passing to mailto() in:
- get_person_data_row() (employees)
- get_customer_data_row() (customers)
- get_supplier_data_row() (suppliers)
Attack vector: Malicious email via CSV import renders XSS in table view.
* test(security): Add tests for customer CSV import email validation
Tests cover:
- Valid email acceptance
- Invalid email rejection with row-specific error
- XSS payload sanitization in email field
- Mixed valid/invalid email handling
- Email with special characters sanitization
Verifies fixes for customer email import vulnerability.
* fix(security): Allow empty email addresses in customer import
- Empty emails are now allowed (customers may not have email addresses)
- Validation only applies when email is non-empty
- Added test case for empty email acceptance
This fixes a regression where FILTER_VALIDATE_EMAIL rejected empty
strings, breaking imports for customers without email addresses.
---------
Co-authored-by: Ollama <ollama@steganos.dev>
* Properly replace key in config file when encryption key is updated
This fixes a break caused if there is a commented out key in the .env. It's a more robust replacement.
Signed-off-by: objec <objecttothis@gmail.com>
* Guard against dropping constraint that doesn't exist
- Updated wording in migration_helper.php PHPdoc
- Use migration_helper function to drop key which only drops the constraint if it exists.
The core logic was not changed here. It only adds a safety mechanism.
Signed-off-by: objec <objecttothis@gmail.com>
* Remove duplicate call to getResultArray in attribute_links loop
Signed-off-by: objec <objecttothis@gmail.com>
* PSR refactoring
Signed-off-by: objec <objecttothis@gmail.com>
* Remove dead parameter from reassignDuplicateAttributeValues method
The attribute value was not needed and is never used since we have the attribute_ids and those are unique.
Signed-off-by: objec <objecttothis@gmail.com>
* Create potentially missing primary keys before attempting to add constraints.
Signed-off-by: objec <objecttothis@gmail.com>
* Guard datetime creation
Signed-off-by: objec <objecttothis@gmail.com>
* Update regex pattern to handle double-quoted and non-quoted encryption keys
Signed-off-by: objec <objecttothis@gmail.com>
* Issue warning and fallback on unparseable attribute_date during
Signed-off-by: objec <objecttothis@gmail.com>
---------
Signed-off-by: objec <objecttothis@gmail.com>
* Bugfixes to get Migration working on MySQL
Signed-off-by: objec <objecttothis@gmail.com>
* MariaDB compatibility fixes
- Drop foreign key constraints before making charset changes
- Fix dropAllForeignKeyConstraints helper function.
- Added `IF EXISTS` to DROP statements
- Do not try to readd FK constraints for tables which were dropped.
- MariaDB 11.8.x changes the default implicit collation to uca1400 which breaks the IndiaGST migration, et. al. Explicitly declare utf8_general_ci in affected migrations.
Signed-off-by: objec <objecttothis@gmail.com>
* Fix changes which break MySQL migrations
- MySQL does not support IF EXISTS in foreign key constraints. Since the PHP is now handling dropping those constraints, these lines are redundant. Remove them.
Signed-off-by: objec <objecttothis@gmail.com>
* Resolve code review recommendations
- Add try/catch around DB connect statement
- Heed result of execute_script function and throw an exception on failure.
Signed-off-by: objec <objecttothis@gmail.com>
* Refactor out duplicate code
Signed-off-by: objec <objecttothis@gmail.com>
* Initialize array variable causing potential issues
Signed-off-by: objec <objecttothis@gmail.com>
---------
Signed-off-by: objec <objecttothis@gmail.com>
Root cause: In commit 7f9321eca, the refactoring incorrectly used object
notation ($config->language_code) on an array instead of array notation
($config['language_code']).
The settings property in OSPOS config is an array, so:
- $config->language_code returns null (object access on array)
- $config['language_code'] returns the actual value
This caused both functions to always fall back to defaults, making the
language dropdown show incorrect values.
Fix: Change both functions to use array notation:
- Line 25: $config['language_code'] (returns saved language code)
- Line 46: $config['language'] (returns saved language name)
Also fixed the wrong DEFAULT_LANGUAGE_CODE fallback on line 46 - should be
DEFAULT_LANGUAGE since current_language() returns a name not a code.
Fixes#4517
Co-authored-by: Ollama <ollama@steganos.dev>
- Merge Config and Core File Changes 4.6.3 > 4.6.4
- Merge Config and Core File Changes 4.6.4 > 4.7.0
- Added app\Config\WorkerMode.php
- Merge Config and Core File Changes Not previously merged
- Added app\Config\Hostnames.php
- Corrected incorrect CSS property used in invoice.php view.
- Corrected unknown CSS properties used in register.php view.
- Used shorthand CSS in debug.css
- Corrected indentation in barcode_sheet.php view.
- Corrected indentation in footer.php view.
- Corrected indentation in invoice_email.php view.
- Replaced obsolete attributes with CSS style attributes in barcode_sheet.php
- Replaced obsolete attribute in error_exception.php
- Replaced obsolete attribute in invoice_email.php
- Replaced obsolete attribute in quote_email.php
- Replaced obsolete attributes in work_order_email.php
- Fixed indentation in system_info.php
- Replaced <strong> tag outside <p> tags, which isn't allowed, with style attributes.
- Simplified js return logic and indentation fixes in tax_categories.php
- Simplified js return logic in tax_codes.php
- Simplified js return logic in tax_jurisdictions.php
- Removed unnecessary labels in manage views.
- Rewrite JavaScript function and PHP to be more readable in bar.php, hbar.php, line.php and pie.php
- Added type declarations, return types and an import to app\Config\Services
- Updated Attribute.php parameter type
- Updated Receiving_lib.php parameter type
- Updated Receivings.php parameter types and updated PHPdocs
- Updated tabular_helper.php parameter types and updated PHPdocs
- Added type declarations and corrected PHPdocs in url_helper.php
- Added return types to functions
- Revert $objectSrc value in ContentSecurityPolicy.php
- Correct return type in Customer->get_stats()
- Correct return type in Item->get_info_by_id_or_number()
- Correct misspelling in border-spacing
- Added missing css style semicolons
- Resolve operator precedence ambiguity.
- Resolve column mismatch.
- Added missing escaping in view.
- Updated requirement for PHP 8.2
- Resolve unresolved conflicts
- Added PHP 8.2 requirement to the README.md
- Fixed bugs in display of UI
- Fixed duplicated `>` in app\Views\Expenses\manage.php
- Removed excess whitespace at the end of some lines in table_filter_persistence.php
- Added missing `>` in app\Views\Expenses\manage.php
- Corrected grammar in PHPdoc in table_filter_persistence.php
- Remove bug causing `\` to be injected into the new giftcard value
- Fix bug causing DROPDOWN Attribute Values to not save correctly
- Added check for null in $normalizedItemId
- Removing < PHP 8.2 from linting and tests
- Update Linter to not include PHP 8.2 and 8.1
- Remove PHP 8.1 unit test cycle.
- Update Bug Report Template
- Update Composer files for CodeIgniter 4.7.2
- Updated INSTALL.md to reflect changes.
---------
Signed-off-by: objec <objecttothis@gmail.com>
PSR and Readability Changes
- Removed unused import
- Corrected PHPdoc to include the correct return type
- Refactored out a function to get attribute data from the row in a CSV item import.
- refactored snake_case variables and function names to camelCase
- Refactored the naming of saveAttributeData() to better reflect the functions purpose.
- Improved PHPdocs
- Remove whitespace
- Remove unneeded comment
- Refactored abbreviated variable name for clarity
- Removed $csvHeaders as it is unused
- Corrected spacing and curly brace location
- Refactored Stock Locations validation inside general validation
Bugfixes
- Fixed bug causing attribute_id and item_id to not be properly assigned when empty() returns true.
- Fixed bug causing CSV Item import to not update barcode when changed in the import file.
- Fixed saveAttributeValue() logic causing attribute_value to be updated to a value that already exists for a different attribute_id
- Fixed bug preventing Category as dropdown functionality from working
- Fixed bug preventing barcodes from updating. in Item CSV Imports
- Corrected bug in stock_location->save_value()
- Corrected incorrect helper file references.
- Removed duplicate call to save attribute link
- Rollback transaction on failure before returning false
- Rollback transaction and return 0 on failure to save attribute link.
- Account for '0' being an acceptable TEXT or DECIMAL attributeValue.
- Corrected Business logic
- Resolved incorrect array key
- Account for 0 in column values
- Correct check empty attribute check
- Previously 0 would have been skipped even though that's a valid value for an attribute.
- Removed unused foreach loop index variables
- Corrected CodeIgniter Framework version to specific version
UnitTest Seeder and tests
- Created a seeder to automatically prepare the test database.
- Modified the Unit Test setup to properly seed the test database.
- Wrote a unit test to test deleting an attribute from an item through the CSV.
- Corrected errors in unit tests preventing them from passing. save_value() returns a bool, not the itemId
- Fix Unit Tests that were failing
- Corrected the logic in itemUpdate test
- Replaced precision test with one reflecting testing of actual value.
- This test does not test cash rounding rules. That should go into a different test.
- Correct expected value in test.
- Update app/Database/Seeds/TestDatabaseBootstrapSeeder.php
- Added check to testImportDeleteAttributeFromExistingItem
- Correct mocking of dropdowns
- Remove code depending on removed database.sql
- Removed FQN in seeder() call
- Added checks in Database seeder
- Moved the function to the attribute model where it belongs which allows testability.
Case Change Capability (CSV Import and Form)
- CSV Import and view Case Changes of `attribute_value`
- Store attribute even when just case is different.
- Add getAttributeValueByAttributeId() to assist in comparing the value
- Corrected Capitalization in File Handling Logic
CSV Import Attribute Link Deletion Capability
- Validation checks bypass magic word cells.
- Delete the attribute link for an item if the CSV contains `_DELETE_`
- Added calls to deleteOrphanedValues()
- Items CSV Import Attribute Delete
- Exclude the itemId in the check to see if the barcode number exists
Error Checking and Reporting Improvements
- Fail the import if an invalid stock location is found in the CSV
- Return false if deleteAttributeLinks fails
- Match sanitization of description field to Form submission import
- Fold errors into result and return value
- Populated $allowedStockLocations before sending it to the validation function
- Added logic to not ignore failed saveItemAttributes calls
- Add error checking to failed row insert
- Reworked &= to && logic so that it short-circuits the function call after if success is already false.
- Add transaction to storeCSVAttributeValue function to prevent deleting the attribute links before confirming the new value successfully saved.
- Modified generate_message in Db_log.php to be defensive.
Attribute Improvements
- Move ATTRIBUTE_VALUE_TYPES to the helper
- Normalize AttributeId in saveAttributeLink()
- normalize itemId in saveAttributeLink()
- Account for '0' in column values for allow_alt_description
- Remove duplicate saveAttributeValue call
- Correct return value of function
- Like other save_value() functions, the location_data variable is passed by reference.
- Unlike other save_value() functions, the location_data variable is not being updated with the primary key id.
- Added updateAttributeValue() function as part of logic fix.
- Added attribute_helper.php
- Simplified logic to store attribute values
---------
Signed-off-by: objec <objecttothis@gmail.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
The get_tax_rates_data_row() function in tax_helper.php was calling
service('router') without handling cases where the router service is
unavailable, causing the form modal to fail to open.
This fix adds a fallback to 'taxes' controller name when router service
returns null or fails.
Also adds missing 'id' field in postSave() JSON response for proper
row highlighting after save operations.
Fixes#4477
Co-authored-by: Ollama <ollama@steganos.dev>
Co-authored-by: odiea <odiea@users.noreply.github.com>
* Fix DECIMAL attribute not respecting locale format
Issue: DECIMAL attribute values were displayed as raw database values
instead of being formatted according to the user's locale settings.
Fix:
1. Modified Attribute::get_definitions_by_flags() to optionally return
definition types along with names (new $include_types parameter)
2. Updated expand_attribute_values() in tabular_helper.php to detect
DECIMAL attributes and apply to_decimals() locale formatting
3. Updated callers (Reports, Items table) to pass include_types=true
where attributes are displayed
The DECIMAL values in table views (items, sales reports, receiving reports)
now respect the configured locale number format, matching DATE attributes
which already use locale-based formatting.
* Apply PSR-12 camelCase naming to new variables
Response to PR review comments:
- Rename to
- Rename to
- Rename to
---------
Co-authored-by: Ollama <ollama@steganos.dev>
This commit adds URL-based filter persistence for table views, allowing
users to navigate away from a filtered view (e.g., clicking into sale
details) and return without losing their filter settings.
The solution uses history.replaceState() to update the URL without
triggering a page reload, providing a seamless user experience while
maintaining shareable/bookmarkable URLs.
Fixes navigation issue where filters are lost when viewing details or
navigating away from table views.
* Move filter restoration to server-side for cleaner architecture
Changes:
- Controllers now restore filters from URL query string on initial page load:
* Sales.php: Reads start_date, end_date, and filters[] from GET
* Items.php: Reads start_date, end_date, filters[], and stock_location
* Expenses.php: Reads start_date, end_date, and filters[]
* Cashups.php: Reads start_date, end_date, and filters[]
- Views now receive restored filter values from controllers:
* Server-side date override via JavaScript variables
* form_multiselect() receives $selected_filters from controller
* Removed setTimeout hack from table_filter_persistence.php
- Simplified table_filter_persistence.php:
* Now only handles URL updates on filter changes
* No longer responsible for restoring state
* Cleaner, single responsibility (client-side URL management)
Benefits:
- Works without JavaScript for initial render
- Cleaner architecture (server controls initial state)
- Client-side JS only handles "live" filter updates
- Filters persist across navigation via URL query string
- Shareable/bookmarkable URLs
How it works:
1. User visits /sales/manage?start_date=2024-01-01&filters[]=only_cash
2. Controller reads GET params and passes to view
3. View renders with correct initial filter values
4. User changes filter → JavaScript updates URL via replaceState()
5. User navigates away and back → Controller restores from URL again
* Refactor filter restoration into helper function and use PSR-12 naming
* Use array_merge with helper to reduce code duplication
---------
Co-authored-by: Ollama <ollama@steganos.dev>
1. Stock Location XSS (GHSA-7hg5-68rx-xpmg):
- Stock location names were rendered unescaped in employee form
- Malicious stock locations could contain XSS payloads that execute
when viewing employee permissions
- Fixed by adding esc() to permission display in employees/form.php
2. Customer Name XSS (GHSA-hcfr-9hfv-mcwp):
- Bootstrap-table columns had escape disabled for customer_name,
email, phone_number, and note fields
- Malicious customer names could execute XSS in Daily Sales view
- Fixed by removing user-controlled fields from escape exception list
- Only 'edit', 'messages', and 'item_pic' remain in exception list
(these contain safe server-generated HTML)
Both vulnerabilities allow authenticated attackers with basic permissions
to inject JavaScript that executes in admin/other user sessions.
* `execute_script()` now returns a boolean for error handling.
* Added transaction to `Migration_MissingConfigKeys.up()`.
* Added logging to various migrations.
* Added transaction to `Migration_MissingConfigKeys.up()`.
* Added logging to various migrations.
* Formatting and function call fixes
Fixed a minor formatting issue in the migration helper.
Replaced a few remaining error_log() calls.
Updated executeScriptWithTransaction() to use log_message()
* Function call fix
Replaced the last error_log() calls with log_message().
---------
Co-authored-by: Joe Williams <hey-there-joe@outlook.com>
* `execute_script()` now returns a boolean for error handling.
* Added transaction to `Migration_MissingConfigKeys.up()`.
* Added `executeScriptWithTransaction()` to migration helpers.
* Many changes for testing; also minor formatting fixes.
* Removed test code and pointed the `NullableTaxCategoryId` migration at the right SQL file.
* Fixed header.php
* Code cleanup from code review:
- Added IGNORE to SQL scripts.
- Added try-catch to executeScriptWithTransaction().
- Various comment changes.
* Fixed naming issue
Nullable tax category ID migration now runs the correct script.
* Updated SQL
Replaced INSERT WHERE NOT EXISTS in missing config keys sql script to use a single INSERT IGNORE.
* Updated migration helper
Updated executeScriptWithTransaction to use transRollback
---------
Co-authored-by: Joe Williams <hey-there-joe@outlook.com>
- Refactored function names for PSR-12 compliance
- Programmatically cascade delete attribute_link rows when a drop-down attribute is deleted but leave attribute_link rows associated with transactions.
- Added `WHERE item_id IS NOT NULL` to migration to prevent failure on MySQL databases during migration
- Retroactive correction of migration to prevent MySQL databases from failing.
- Refactored generic functions to helper
- Reverted attribute_links foreign key to ON DELETE RESTRICT which is required for a unique constraint on this table. Cascading deletes are now handled programmatically.
- Migration Session table to match Code Igniter 4.6
- Add index to attribute_links to prevent query timeout in items view on large databases
- Added overridePrefix() function to the migration_helper. Any time QueryBuilder is adding a prefix to the query when we don't want it to, this query can be used to override the prefix then set it back after you're done.
- Added dropAllForeignKeyConstraints() helper function.
- Added deleteIndex() helper function.
- Added indexExists() helper function.
- Added primaryKeyExists() helper function.
- Added recreateForeignKeyConstraints() helper function.
- Added CRUD section headings to the Attribute model.
- Replaced `==` with `===` to prevent type juggling.
- Removed unused delete_value function.
- Reworked deleteDefinition() and deleteDefinitionList() functions to delete rows from the attribute_links table which are associated.
- Added deleteAttributeLinksByDefinitionId() function
Implement Cascading Delete
- Function to delete attribute links with one or more attribute definitions.
- Call function to implement an effective cascading delete.
- Refactor function naming to meet PSR-12 conventions
Fix Migration
- Add drop of Generated Column to prevent failure of migration on MySQL databases.
Fix Migration
- Removed blank lines
- Refactored function naming for PSR compliance
- Reformatted code for PSR compliance
- Added logic to drop dependent foreign key constraints before deleting an index then recreating them.
Migrate ospos_sessions table
- DROP and CREATE session table to prevent migration problems on populated databases
Fixed Bug in Migration
- In the event that item_id = null (e.g., it's a dropdown) it should not be included in the results.
Fixed bug in Dropdown deletes
- Removed delete_value function in Attributes Controller as it is unused.
- Renamed postDelete_attribute_value function for PSR-12 compliance.
- Renamed delete_value Attribute model function for PSR-12 compliance.
- Refactored out function to getAttributeIdByValue
- Replaced == with === to prevent type juggling
- Reorganized parts of model to make it easier to find CRUD functions.
Refactoring
- PSR-12 Compliance formatting changes
- Refactored several generic functions into the migration_helper.php
- First check if primary key exists before attempting to create it.
- Grouped functions together in migration_helper.php
- phpdoc commenting functions
Optimizing Indices
- There are two queries run while opening the Items view which time out on large databases with weak hardware. These indices cut the query execution in half or better.
Add Unique constraint back into attribute_links
- This migration reverts ospos_attribute_links_ibfk_1 and 2 to ON DELETE RESTRICT. Cascade delete is done programmatically. This is needed to have a unique column on the attribute_links table which prevents duplicate attributes from begin created with the same item_id-attribute_id-definition_id combination
Correct spacing after if for PSR-12
Minor code cleanup.
- Removed Comments separating sections of code in Attribute model
- Removed extra log line to prevent cluttering of the log
* Improve code style and PSR-12 compliance
- refactored code formatting to adhere to PSR-12 guidelines
- standardized coding conventions across the codebase
- added missing framework files and reverted markup changes
- reformatted arrays for enhanced readability
- updated language files for consistent styling and clarity
- minor miscellaneous improvements