Commit Graph
71 Commits
Author SHA1 Message Date
184918d914 fix(security): handle special characters in .env key values and improve insertion logic (#4656)
* 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>
2026-09-21 17:35:45 +02:00
objecttothis 9ecabf6f41 fix(sales): harden unsuspend with auth, status gating, and null safety
- Require reports_sales grant on postUnsuspend; return 403 on denial
- Reject unsuspend of non-SUSPENDED sales; skip silently on invalid state
- Move clear_all() after validation so an invalid sale_id no longer wipes
  the active in-progress cart
- Null-guard get_sale_status() on missing row instead of fatal property
  access; widen return type to ?int
- Fix getSaleType null-coalescing — CI4 session default only fires when
  key is unset, not when value is null
- Rename get_sale_type → getSaleType, sale_id → saleId (PSR-12 camelCase)
- Extract SaleFixtureTrait with createSale()/createSuspendedSale(); add
  regression coverage for auth denial, status gating, and cart preservation
2026-09-07 12:18:11 +04:00
Vighnesh Nilajakar 3bec7d5c92 fix(barcode): resolve string interpolation issue in barcode display html (#4692)
Fixes an issue in Barcode_lib.php where $barcode was enclosed in single quotes, preventing string interpolation and rendering the literal string "$barcode" on the item barcode generation page instead of the barcode graphic.

Changes Made :
Refactored the string assignment in app/Libraries/Barcode_lib.php to properly concatenate $barcode.

How to Test:
1. Open Items in OSPOS.
2. Select any item and click Generate Barcodes.
3. Verify that the rendered barcode image displays correctly rather than showing literal text.
2026-09-06 21:52:22 +02:00
objecttothis d63d31700d Ensure payload data is escaped to prevent XSS (#4664)
fix(barcode): escape payload fields to prevent XSS; PSR-12 refactor

- Apply `esc()` to name, ID, item number, category, and company name in `Barcode_lib` payloads
- Remove redundant `urldecode()` in `Item_kitsController` to prevent triple decoding
- Rename variables and methods to camelCase across barcode, item_kits, and tests
- Add type hint for `$layoutType` parameter in `manageDisplayLayout`
- Add/update unit tests covering escaping and HTTP response assertions

Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
2026-08-28 10:08:31 +04:00
objecttothis 8b0f8533de fix(email): update method call to camelCase for PSR-12 compliance (#4659)
Corrected `check_encryption()` to `checkEncryption()` to align with coding standards.

Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
2026-08-24 21:46:12 +04:00
objecttothis c701d21886 fix(sales): gate per-record endpoints behind reports_sales grant (#4627)
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
2026-08-23 17:40:06 +04:00
objecttothis 9ee530e454 Hotfix: Fix CI3 database migration caused by regression (#4649)
* 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>
2026-08-21 21:00:48 +04:00
cdf477f823 fix(login): skip auth validation on new install to allow migration (#4609)
* fix(login): skip auth validation on new install to allow migration

On fresh installs with no DB version, login validation would fail
before migrations could run. Check MY_Migration::getCurrentVersion()
and bypass credential check when no version exists.

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

* fix(login): distinguish DB unavailable from empty install

`getCurrentVersion()` previously returned `int` with 0 used for both
\"empty database\" and \"DB connection failure\". This conflated two
distinct states, causing login to skip auth when DB was unreachable.

Changes:
- Return type widened to `?int`: null = DB unavailable, 0 = confirmed
  empty DB (new install), positive int = migrated
- Login controller now returns 503 on null (DB error) before checking
  isNewInstall
- isNewInstall gate now uses `=== 0` instead of falsy check

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

* fix(login): return 503 JSON response when DB connection unavailable

Extract getCurrentVersion() before building data array to distinguish
null (connection failure) from 0 (new install). Return early with JSON
503 when DB is unreachable instead of rendering broken view.

Update return type hint to include ResponseInterface.

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

---------

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
Co-authored-by: Travis Garrison <travis@chiraqbookstore.com>
Co-authored-by: jekkos <jekkos@users.noreply.github.com>
2026-08-05 11:48:05 +04:00
objecttothisandTravis Garrison 9c542efaf6 Feature: Payment reference code (#4587)
* 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>
2026-07-10 13:49:05 +04:00
objecttothisandTravis Garrison dec736ad6c Bugfix: Fix problems with migration UI in login (#4589)
* refactor(login): rename methods and keys to camelCase, add initialization keys

- Rename snake_case methods to camelCase in MY_Migration and callers:
  is_latest() → isLatest(), migrate_to_ci4() → migrateToCI4(),
  get_latest_migration() → getLatestMigration(),
  get_current_version() → getCurrentVersion()
- Rename snake_case array keys to camelCase in Login controller:
  has_errors → hasErrors, is_new_install → isNewInstall,
  is_latest → isLatest, latest_version → latestVersion,
  gcaptcha_enabled → gcaptchaEnabled
- Add initialization_required, initialization_message, initialize keys
  to all language files to support new install flow messaging

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

* refactor(login): move auth validation before migration, reorder language keys alphabetically

- Validate credentials with login_check rule before attempting migration
- Return 401 with invalid credentials message on validation failure
- Alphabetically reorder and align language file keys across all locales
- Add new migration status keys: migrating_database, migration_complete,
  migration_complete_login, migration_complete_migrate
- Remove deprecated keys: migration_needed, migration_initializing,
  migration_running, migration_required

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

---------

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
Co-authored-by: Travis Garrison <travis@chiraqbookstore.com>
2026-07-07 10:35:25 +04:00
jekkosandOllama 5450404cb2 fix: cast string returns to int in MY_Migration (#4560)
basename() returns string and database column values are strings,
but get_latest_migration() and get_current_version() declare int
return types. PHP 8.0+ enforces strict return types and no longer
silently coerces strings to int, causing a TypeError on fresh
installs.

Fixes #4559

Co-authored-by: Ollama <ollama@steganos.dev>
2026-05-22 16:07:21 +02:00
jekkosandOllama 2f51c4ef52 fix(security): SQL injection and path traversal vulnerabilities (#4539)
Security fixes for two vulnerabilities:

1. SQL Injection in Summary Sales Taxes Report (GHSA-5j9m-2f98-cjqw)
   - Fixed unsanitized user input concatenation in getData() method
   - Applied proper escaping using $this->db->escape() for start_date/end_date
   - Consistent with existing _where() method implementation

2. Path Traversal in Receipt Template (GHSA-h6wm-fhw2-m3q3)
   - Added ALLOWED_RECEIPT_TEMPLATES whitelist constant
   - Added isValidReceiptTemplate() validation method
   - Validate receipt_template before saving in Config controller
   - Validate receipt_template before rendering in receipt view
   - Default to 'receipt_default' for invalid values
   - Consistent with invoice_type fix pattern (commit 31d25e06d)

Affected files:
- app/Models/Reports/Summary_sales_taxes.php
- app/Libraries/Sale_lib.php
- app/Controllers/Config.php
- app/Views/sales/receipt.php

Co-authored-by: Ollama <ollama@steganos.dev>
2026-05-15 23:10:04 +02:00
WShellsandWShells 81213f0434 Assignable Keyboard Shortcuts Updates (#4532)
* Add configurable sales shortcuts

* Fix sales shortcut payment flow

* Resolve shortcut keys review comment

* Sanitize shortcut config notifications

* Clarify keyboard shortcut configuration labels

---------

Co-authored-by: WShells <26513147+WShells@users.noreply.github.com>
2026-05-07 22:53:25 +04:00
jekkosandOllama f1c6fe2981 fix: Catch mysqli_sql_exception in DB fallback handlers for fresh Docker installs (#4525)
* fix: Catch mysqli_sql_exception in DB fallback handlers for fresh Docker installs

On a fresh Docker install with an empty database, the ospos_sessions
table doesn't exist yet. The CSRF filter triggers session initialization
before the login/migration page can be reached.

The existing code in Session.php, OSPOS.php, and MY_Migration.php
catches DatabaseException, but the MySQLi driver throws
mysqli_sql_exception (which extends RuntimeException, not
DatabaseException) when the table doesn't exist. This causes an
unhandled exception resulting in HTTP 500.

Fix: Change all three catch blocks from  to
 so that mysqli_sql_exception and any other unexpected
database errors are caught, allowing the app to fall back gracefully:

- Session.php: Falls back to FileHandler so sessions work without DB
- OSPOS.php: Falls back to empty settings so config loads work
- MY_Migration.php: Falls back to version 0 / false so the migration
  check passes gracefully

This allows the login page with migration UI to be served on first
access, so the initial schema migration can run.

Fixes #4524
---------

Co-authored-by: Ollama <ollama@steganos.dev>
2026-04-22 21:13:52 +02:00
jekkosandOllama 12e3c7e31f fix: Add missing $img_tag variable in Sales::getSendPdf() (#4515)
* fix: Add missing $img_tag variable in Sales::getSendPdf()

The receipt_email.php view expects $img_tag but getSendPdf() wasn't passing it.
This caused 'Undefined variable $img_tag' error when sending receipt emails.

Closes #4514

* refactor: Extract img_tag building into helper method

Refactored duplicate img_tag building code into _build_img_tag helper method.
Both getSendPdf and getSendReceipt now use this shared method.

* refactor: Move logo-related methods to Email_lib

Moved buildLogoImgTag and getLogoMimeType methods to Email_lib library
where they logically belong alongside email-related functionality.

This removes duplicate code and centralizes email-related helpers.
Sales controller now uses email_lib->buildLogoImgTag() and
email_lib->getLogoMimeType() instead of private methods.

* fix: Address CodeRabbit review comments

- buildLogoImgTag now uses getLogoMimeType for actual MIME type instead of hardcoding image/png
- getLogoMimeType returns empty string instead of false for consistency
- Consolidated logo path/exists check logic between both methods

---------

Co-authored-by: Ollama <ollama@steganos.dev>
2026-04-17 21:02:45 +00:00
objecttothis 6fec2464f8 Update to CodeIgniter 4.7.2 (#4485)
- 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>
2026-04-14 01:05:10 +04:00
jekkosandOllama 7f9321eca0 fix: Handle empty database on fresh install (#4467)
* fix: Handle empty database on fresh install
* feat: Add migration progress bar with jQuery AJAX

- Session.php: Switch to file-based sessions when migrations table doesn't exist
- OSPOS.php: Catch DatabaseException when config table missing, set defaults
- MY_Migration.php: Handle database connection failures gracefully
- Load_config.php: Set default language settings when config empty
---------

Co-authored-by: Ollama <ollama@steganos.dev>
2026-04-08 20:19:25 +00:00
Ollama 234f930079 Fix strftime directives handling and tighten test assertions
- Remove incorrect %C mapping (was mapping century to full year)
- Add special handling for %C (century), %c (datetime), %n (newline), %t (tab), %x (date)
- Add %h mapping (same as %b for abbreviated month)
- Tighten edge-case test assertions to use assertSame/assertMatchesRegularExpression
- Add tests for new directives: %C, %c, %n, %t, %x, %h
2026-03-14 23:08:39 +00:00
Ollama 3001dc0e17 Fix: Pass parameter to generate() and add composite format tests
- Fixed bug where render() was not passing caller-supplied  to
  generate(), causing ad-hoc tokens to be ignored
- Added %F (yyyy-MM-dd) and %D (MM/dd/yy) composite date formats to
  the IntlDateFormatter pattern map
- Added test coverage for composite date format directives (%F, %D, %T, %R)
2026-03-14 23:08:39 +00:00
Ollama d684c49ebd Fix Token_lib::render() for PHP 8.4 compatibility
- Replaced deprecated strftime() with IntlDateFormatter
- Added proper handling for edge cases:
  - Strings with '%' not in date format (e.g., 'Discount: 50%')
  - Invalid date formats (e.g., '%-%-%', '%Y-%q-%bad')
  - Very long strings
- Added comprehensive unit tests for Token_lib
- All date format specifiers now mapped to IntlDateFormatter patterns
2026-03-14 23:08:39 +00:00
jekkos 31d25e06dc fix(security): whitelist and validate invoice template types (#4393)
- Add whitelist validation for invoice_type to prevent path traversal and LFI
- Validate invoice_type against allowed values in Sale_lib
- Sanitize invoice_type input in Config controller before saving
- Default to 'invoice' template for invalid types

Security: Prevents arbitrary file inclusion via user-controlled invoice_type config
2026-03-06 13:18:47 +01:00
jekkos 690f43578d Use Content-Type application/json for AJAX responses (#4357)
Complete Content-Type application/json fix for all AJAX responses

- Add missing return statements to all ->response->setJSON() calls
- Fix Items.php method calls from JSON() to setJSON()
- Convert echo statements to proper JSON responses
- Ensure consistent Content-Type headers across all controllers
- Fix 46+ instances across 12 controller files
- Change Config.php methods to : ResponseInterface (all return setJSON only):
  - postSaveRewards(), postSaveBarcode(), postSaveReceipt()
  - postSaveInvoice(), postRemoveLogo()
  - Update PHPDoc @return tags

- Change Receivings.php _reload() to : string (only returns view)
- Change Receivings.php methods to : string (all return _reload()):
  - getIndex(), postSelectSupplier(), postChangeMode(), postAdd()
  - postEditItem(), getDeleteItem(), getRemoveSupplier()
  - postComplete(), postRequisitionComplete(), getReceipt(), postCancelReceiving()
- Change postSave() to : ResponseInterface (returns setJSON)
- Update all PHPDoc @return tags

Fix XSS vulnerabilities in sales templates, login, and config pages

This commit addresses 5 XSS vulnerabilities by adding proper escaping
to all user-controlled configuration values in HTML contexts.

Fixed Files:
- app/Views/sales/invoice.php: Escaped company_logo (URL context) and company (HTML)
- app/Views/sales/work_order.php: Escaped company_logo (URL context)
- app/Views/sales/receipt_email.php: Added file path validation and escaping for logo
- app/Views/login.php: Escaped all config values in title, logo src, and alt
- app/Views/configs/info_config.php: Escaped company_logo (URL context)

Security Impact:
- Prevents stored XSS attacks if configuration is compromised
- Defense-in-depth principle applied to administrative interfaces
- Follows OWASP best practices for output encoding

Testing:
- Verified no script execution with XSS payloads in config values
- Confirmed proper escaping in HTML, URL, and file contexts
- All templates render correctly with valid configuration

Severity: High (4 files), Medium-High (1 file)
CVSS Score: ~6.1
CWE: CWE-79 (Improper Neutralization of Input During Web Page Generation)

Fix critical password validation bypass and add unit tests

This commit addresses a critical security vulnerability where the password
minimum length check was performed on the HASHED password (always 60
characters for bcrypt) instead of the actual password before hashing.

Vulnerability Details:
- Original code: strlen($employee_data['password']) >= 8
- This compared the hash length (always 60) instead of raw password
- Impact: Users could set 1-character passwords like "a"
- Severity: Critical (enables brute force attacks on weak passwords)
- CVE-like issue: CWE-307 (Improper Restriction of Excessive Authentication Attempts)

Fix Applied:
- Validate password length BEFORE hashing
- Clear error message when password is too short
- Added unit tests to verify minimum length enforcement
- Regression test to prevent future vulnerability re-introduction

Test Coverage:
- testPasswordMinLength_Rejects7Characters: Verify 7 chars rejected
- testPasswordMinLength_Accepts8Characters: Verify 8 chars accepted
- testPasswordMinLength_RejectsEmptyString: Verify empty rejected
- testPasswordMinLength_RejectsWhitespaceOnly: Verify whitespace rejected
- testPasswordMinLength_AcceptsSpecialCharacters: Verify special chars OK
- testPasswordMinLength_RejectsPreviousBehavior: Regression test for bug

Files Modified:
- app/Controllers/Home.php: Fixed password validation logic
- tests/Controllers/HomeTest.php: Added comprehensive unit tests

Security Impact:
- Enforces 8-character minimum password policy
- Prevents extremely weak passwords that facilitate brute-force attacks
- Critical for credential security and user account protection

Breaking Changes:
- Users with passwords < 8 characters will need to reset their password
- This is the intended security improvement

Severity: Critical
CVSS Score: ~7.5
CWE: CWE-305 (Authentication Bypass by Primary Weakness), CWE-307

Add GitHub Actions workflow to run PHPUnit tests

Move business logic from views to controllers for better separation of concerns

- Move logo URL computation from info_config view to Config::getIndex()
- Move image base64 encoding from receipt_email view to Sales controller
- Improves separation of concerns by keeping business logic in controllers
- Simplifies view templates to only handle presentation

Fix XSS vulnerabilities in report views - escape user-controllable summary data and labels

Fix base64 encoding URL issue in delete payment - properly URL encode base64 string

Fix remaining return type declarations for Sales controller

Fixed additional methods that call _reload():
- postAdd() - returns _reload($data)
- postAddPayment() - returns _reload($data)
- postEditItem() - returns _reload($data)
- postSuspend() - returns _reload($data)
- postSetPaymentType() - returns _reload()

All methods now return ResponseInterface|string to match _reload() signature.
This resolves PHP TypeError errors.
2026-03-04 21:42:35 +01:00
jekkos 30da69a382 Fix attachment cid (#4314)
* Add attachment cid when sending emails (#4308)

Also check if an encryption key is set before decrypting the SMTP
password.

* Upgrade to CI 4.6.3 (#4308)

* Fix for changing invoice id in email (#4308)
2025-11-23 21:37:32 +01:00
Joe WilliamsandJoe Williams bcddf482fe [Feature] Add logging to migrations (#4327)
* `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>
2025-10-19 22:10:28 -07:00
jekkos 9cd2f685ff Fix barcode generation in items (#4270) 2025-07-29 23:56:50 +02:00
jekkos 29c3c55fcc Fix item number lookup in sales/receivings (#4212) (#4250)
* Fix item number lookup in sales/receivings (#4212)

* Remove item_number check in exists()
2025-05-30 22:29:35 +02:00
BudsieBuds e83c23cf0c Improve code style and PSR-12 compliance (#4204)
* 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
2025-05-02 19:37:06 +02:00
BudsieBuds 82f0e75bf0 Fix PHP 8.4 errors (#4200) 2025-04-15 20:38:52 +02:00
objecttothis e90b5b87da Replace tabs with spaces (#4196)
Signed-off-by: objecttothis <objecttothis@gmail.com>
2025-03-28 21:24:21 +04:00
El_Coloso 7ad1bfa0fb Fix requisitions (#4147)
* Fix data types on null values
* Fix receiving receipt image tag
* Fix error on Receiving Model
2025-01-28 23:32:05 +01:00
El_Coloso a5b2b5f771 Fixes for receipt + invoice (#2682)
* Email invoice bar code
* Send invoice by email
* Remove default comment on invoice if comment was set
2025-01-24 00:17:25 +01:00
jekkos 248299521b Revert IntlFormatter refactor (#4126) 2024-12-03 00:15:06 +01:00
jekkos 4b8d009c76 Add english fallback if no translation (#3995) 2024-09-17 17:47:30 +02:00
Steve Ireland 82aac4ec79 Resolving issue #3997 for discount type toggle not staying put. 2024-07-26 21:36:19 +02:00
Steve Ireland d2622e94d7 An attempt to resolve issue #4025. Since a kit item code is prefixed by "KIT" it's not going to work to always assume that the item id is numeric. So "int" needs to be replaced with "string". 2024-07-22 08:38:18 +02:00
objecttothis 1328b4d9b8 - Removed TODOs that had been completed
- Added TODO where we need to convert to querybuilder
- Converted to switch statement.
- Removed unnecessary local variable
- Replaced Qualifiers with imports
- Replaced isset() call with null coalescing operator
- Replaced strpos function calls in if statements with str_contains calls
- Removed unnecessary leading \ in use statement
- Replaced deprecated functions
- Updated PHPdocs to match function signature
- Added missing type declarations
- Made class variables private.
- Explicitly declared dynamic properties
- use https:// links instead of http://
- Fixed type error from sending null when editing transactions
- Fixed Search Suggestion function name in Employees, Persons, Suppliers controller
- Fixed function name on Receivings Controller

Signed-off-by: objecttothis <objecttothis@gmail.com>
2024-06-15 17:19:15 +02:00
WShells 95a1d0b4f1 Fix for Receivings Receipt display
Receivings receipt returning the following errors:
. Param count in the URI are greater than the controller method
. ($supplier_id) must be of type int
2024-06-15 17:19:15 +02:00
WShells f75c7fad15 Fix for Sale Suspend/Unsuspend 2024-06-15 17:19:15 +02:00
WShells 9508770f47 Fix for Serialnumber & Discount Type
($discount_type) must be of type int
($serialnumber) must be of type string
2024-06-15 17:19:15 +02:00
objecttothis b593de9f83 Receivings Bugfixes
- Fixed incorrect variable name
- Return empty string on null
- Added return types for mixed return functions

Signed-off-by: objecttothis <objecttothis@gmail.com>
2024-06-15 17:19:15 +02:00
objecttothis 34246ee885 Prepped barcode for being able to be set by width. 2024-06-15 17:19:15 +02:00
objecttothis a6b674e995 Barcode & escaping
- Removed overflow-visible as it is not needed.
- Bumped TamTamChik/nameCase to latest.
- Workaround to prevent nameCase from capitalizing the first letter of html entities
- Autoload security_helper.php
- Develop means of escaping outputs without encoding characters we don't want encoded.
- proof of concept in form_basic_info.php
2024-06-15 17:19:15 +02:00
objecttothis b42d43d71d Change generated Barcodes to SVG 2024-06-15 17:19:15 +02:00
objecttothis 7cb9ffd7aa Formattings
- Format of ternary changed for readability
- Corrected CSS to use : instead of =
- Removed Label for item name to allow more text
2024-06-15 17:19:15 +02:00
objecttothis 453ee6c061 Formattings
- Format of ternary changed for readability
- Corrected CSS to use : instead of =
2024-06-15 17:19:15 +02:00
objecttothis a5b5fccd5e Barcode Changes
- Strip out old code
- Added missing variable declaration
2024-06-15 17:19:15 +02:00
objecttothis 20828ea421 Barcode Changes
- Barcode content is item_id if barcode_number is empty.
- Styling fixes
- Bump picquer/php-barcode-generator version to 2.4.0
- Ported over Receipt Barcode Generation
2024-06-15 17:19:15 +02:00
objecttothis 24fd80e4fd Barcode Changes
- Removed mixed type-hint
- Replacing emberlabs code with picquer/php-barcode-generator
2024-06-15 17:19:15 +02:00
objecttothis 183f2472eb Fixed issues related to barcode generation 2024-06-15 17:19:15 +02:00
objecttothis cc58cecff0 Compatibility changes
- Removed `mixed` function return type from some functions for backward compatibility with php 7.4
- Refactored string concatination for readability.
- Added TODO for later
- Corrected PHPdocs
- Removed unneeded TODO
- Refactored function names with mixed snake and pascal case names
2024-06-15 17:19:15 +02:00