Commit Graph
127 Commits
Author SHA1 Message Date
objecttothisandClaude Sonnet 5 3b36c20f68 Merge remote-tracking branch 'OpensourcePOS/master' into plugin-system-fresh
Resolve language-file conflicts by taking master's English-removal
cleanup across non-English locales, while stripping the MailChimp
keys master reintroduced (already moved to MailchimpPlugin on this
branch) and reapplying this branch's plugins/integrations rename and
new Module.php plugins keys where master lacked them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-11-06 13:43:47 +04:00
jekkos 86d3e8a609 fix(migrations): prevent concurrent runs with pessimistic DB lock (#4769)
* fix(migrations): prevent concurrent runs with pessimistic DB lock

Adds GET_LOCK/RELEASE_LOCK around the migration sequence so a second
request (page refresh, double-click, supervisor retry) cannot re-trigger
the same migration while the first is still executing. Also adds
ignore_user_abort(true) so a client disconnect does not tear down the
PHP process mid-migration.

Fixes #4763
2026-11-02 15:21:02 +01:00
jekkos 90495a508f fix(sales): set discount_type on cart lines in apply_customer_discount (#4780)
When a customer is selected after items are already in the cart,
apply_customer_discount() set the discount value and recomputed totals
using the customer's discount type, but never updated the line's
discount_type field. get_totals() later recomputes from the line's
discount_type (still the store default), producing wrong totals.

Fixes #4778
2026-10-29 23:31:35 +01:00
BudsieBuds 386f976dcc feat(php): add PHP 8.5 support, resolve deprecations, and optimize CI workflows (#4776)
- Extend composer.json and composer.lock PHP requirement to include PHP 8.5
- Add PHP 8.5 to test matrix in phpunit.yml and main.yml workflows
- Modernize php-linter.yml to use setup-php@v2 with parallel matrix for PHP 8.2–8.5
- Propagate PHP 8.2 lint failures via shell loop in find -exec + while retaining batching for 8.3–8.5
- Add permissions: contents: read and cancel-in-progress concurrency to php-linter.yml
- Remove obsolete Node.js and npm install steps from phpunit.yml
- Update phpunit.yml runner to ubuntu-latest and composer cache action to v4
- Address PHP 8.5 deprecations and syntax:
  - Replace deprecated switch-case semicolon with standard colon in app/Config/Database.php
  - Replace non-standard (double) cast with (float) in app/Libraries/Token_lib.php
  - Remove obsolete curl_close() calls in app/Config/Validation/OSPOSRules.php and app/Libraries/Mailchimp_lib.php
- Update INSTALL.md, workflows README, and bug report issue template to include PHP 8.5
2026-10-28 15:58:14 +04:00
objecttothisandClaude Sonnet 5 600417bfdf refactor: extract PSR-12 renames and independent bugfixes from plugin system PR (#4784)
build/refactor/fix: tighten tooling, rename accessors to camelCase, and correct model bugs

* build: tighten gitignore, lockfile, and script config
- gitignore /build and /.php-cs-fixer.cache
- allow core-js@3.50.0 postinstall script in package.json

* refactor: rename snake_case data accessors to camelCase
Rename get_info, get_multiple_info, get_sale_items, get_sale_payments,
and save_customer across Customer, Employee, Item, Sale, and Supplier
models to PSR-12 camelCase, updating every call site in controllers,
libraries, and views.

* test: update calls to match renamed camelCase accessors
Update HomeTest, ItemsControllerTest, and ItemTest to call
getInfo()/getMultipleInfo() instead of the old snake_case model methods.

* fix: correct independent bugs in Item, Item_taxes, and Attribute models
- Item::delete()/delete_list(): let $this->db->transStatus() alone decide
  success instead of accumulating a bitwise-AND over intermediate results,
  which could report failure (and trigger error toasts) even when the
  delete had actually completed
- Item_taxes: correct $table property from 'item_taxes' to 'items_taxes'
- Attribute::getDefinitionNames(): pass an array to
  QueryBuilder->whereNotIn(), which requires one

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-28 13:00:30 +04:00
objecttothis 0302808961 Merge remote-tracking branch 'OpensourcePOS/master' into plugin-system-fresh
# Conflicts:
#	app/Controllers/Items.php
#	app/Models/Item.php
#	app/Models/Stock_location.php
#	app/Views/items/form_count_details.php
2026-10-27 15:32:14 +04:00
objecttothisandClaude Sonnet 4.6 74e825ba6f feat(stock-locations): add default location and sort order support (#4643)
Refactor Stock_location model to camelCase naming and introduce
is_default and sort_order fields with full migration, UI, and
item-sync support. Multiple follow-up fixes consolidated.

Database
- Add is_default (TINYINT, backfilled to oldest non-deleted row) and
  sort_order (nullable unsigned TINYINT unique, dense 0..N-1) columns
  via new migration; MySQL 5.7-compatible backfill using user variables
  instead of ROW_NUMBER(); down() drops both columns

Model
- Rename show_locations → showLocations (camelCase convention)
- Add getDefaultLocationId(), setDefaultLocation(), saveSortOrder();
  validate location existence before setting as default
- delete() compacts sort_order via two-pass CASE update to avoid
  collisions after prior reorders
- Atomic sort_order assignment via explicit SQL COUNT query

Controller / Config
- Modularize save logic into sanitizeSubmittedLocations(),
  deleteRemovedLocations(), saveSubmittedLocations(),
  saveLocationOrderAndDefault(); wrap in single transaction
- Support stock_location_new inputs for new-row creation
- Resolve new-* ID placeholders to real IDs post-save
- Abort loop on save failure; default missing POST arrays to []
- Validate that at least one location array is non-empty

Views
- Add Default column with center-aligned radio buttons; disable for
  unsaved rows; use attr() instead of data() for location IDs
- Escape dynamic location IDs against XSS in all affected partials
- form_count_details.php: fall back to default location when current
  is undefined; guard quantity input similarly
- Add aria-label to default radio inputs

Libraries / Items
- Widen Item_lib::get_item_location() and get_stock_destination() to
  nullable return types
- Guard null location_id in Items::getRow() and getGenerateBarcodes()
- Item::get_multiple_info(): accept ?int $location_id; short-circuit
  with where('item_id', -1) instead of always-false where('1','0')

i18n
- Add stock_location_default, stock_location_default_header,
  stock_location_name_header across all locale files

Tests
- Stock_locationTest: getDefaultLocationId, setDefaultLocation,
  saveSortOrder, delete compaction, saveValue; rename to
  testMethod_Scenario convention
- Add ConfigControllerTest / StockLocationsControllerTest with
  AdminAuthTrait covering CRUD, reorder, default assignment, and
  malformed/empty/blank payload validation
- DatabaseBootstrapTrait-based setup for ItemTest and Stock_locationTest

Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-10-27 14:36:34 +04:00
objecttothisandClaude Sonnet 5 8cda8551d7 Merge branch 'master' into plugin-system-fresh
Resolve language file conflicts: take master's translations, strip
MailChimp phrases (MailChimp is now a plugin instead of core), and
retain plugin-system additions (plugins/plugins_configuration keys).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-26 17:31:32 +04:00
Rayan Abdul CaderandCursor Agent 7ea3f21365 Return early from sortAndFilterCart() when no items are printable (#4713)
* Return early from sort_and_filter_cart() when no items are printable (#4685)
* Use camelCase naming for cart sorting helper

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-10-08 21:20:53 +04:00
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 cc68b75b26 fix(plugins): block install completion until plugin migrations run
Previously, plugin migrations only ran once during PluginManager
initialization for already-enabled plugins, which caused the login
"latest version" check to report the system as up to date even when
plugin migrations were still pending. Newly enabled plugins also had
no path to run their migrations at enable time.

- Login controller: factor pending-migration check into
  isLatest determination, and explicitly run pending plugin
  migrations after core migrations complete
- PluginManager: remove implicit migration run from init(); add
  public hasPendingMigrations() and runPendingMigrations() so
  callers can check/trigger migrations explicitly
- PluginManager: extract getPendingMigrationFiles() helper shared
  by hasPendingMigrations() and per-plugin migration runner
- PluginManager: run pending migrations immediately when a plugin
  is enabled, instead of waiting for next request/init
- PluginManager: throw RuntimeException on missing migration class
  or failed migration instead of silently breaking the loop, so
  failures propagate to callers

Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
2026-09-10 12:26:51 +04:00
objecttothis 974ba4acc6 fix(plugins): rerun pending plugin migrations on every request
Session flag `plugin_migrations_ran` skipped migration checks after
first run per session, so newly added plugin migrations were not
applied until session reset/logout.

- app/Libraries/Plugins/PluginManager.php: remove session-based
  guard in runPendingMigrations(); migrations table check now runs
  every request, relying on existing per-migration tracking to
  avoid redundant work

Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
2026-09-09 13:55:44 +04:00
objecttothis e581cecdd8 Merge remote-tracking branch 'OpensourcePOS/master' into plugin-system-fresh 2026-09-07 12:29:40 +04: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 459610cded Merge branch 'master' into plugin-system-fresh 2026-08-31 16:18:59 +04: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
Travis Garrison 2a8e82a050 Merge remote-tracking branch 'OpensourcePOS/master' into plugin-system-fresh 2026-08-05 12:30:42 +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
Travis Garrison 59aa7f1ec2 refactor(plugins): enforce plugin id naming convention and auto-cleanup on uninstall
- Add `idMatchesPluginConvention()` guard to `BasePlugin` — rejects
  module/permission ids that don't match `{plugin_id}` or `{plugin_id}_*`
- Add `unregisterPluginModules()` to `PluginManager` — called automatically
  on uninstall; removes all modules and sub-permissions by convention prefix
- Plugins no longer need to call `unregisterModule()` in `uninstall()`
- Update README to document enforced naming convention, auto-cleanup
  behavior, and corrected language key / file location guidance

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
2026-07-29 17:48:51 +04:00
Travis Garrison b5ce31397b feat(plugins): add user_logged_in event and return_completed event
- Fire user_logged_in event on successful login authentication
- Add return_completed event distinct from sale_completed for return mode
- Update README with new events and clarify sale_completed fires non-return only
- Import CodeIgniter\\Events\\Events in Login controller

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
2026-07-27 23:48:15 +04:00
Travis Garrison a6036cb082 feat(plugins): add registerModule helper and replace asset injection with explicit CSS links
- Add BasePlugin::registerModule() to register permission-system modules,
  auto-grant to admin (person_id=1), idempotent via INSERT IGNORE
- Replace inject:css/js placeholders in header.php with explicit versioned
  asset links for reliable loading outside build pipeline
- Update home.php module icons to use pluginContent() hook with SVG fallback,
  enabling plugins to override module icons

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
2026-07-22 17:02:42 +04:00
Travis Garrison 1a03706c05 Merge remote-tracking branch 'OpensourcePOS/master' into plugin-system-fresh
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>

# Conflicts:
#	app/Controllers/Sales.php
#	app/Language/ar-EG/Config.php
#	app/Language/ar-EG/Sales.php
#	app/Language/ar-LB/Config.php
#	app/Language/ar-LB/Sales.php
#	app/Language/az/Config.php
#	app/Language/az/Sales.php
#	app/Language/bg/Config.php
#	app/Language/bg/Sales.php
#	app/Language/bs/Config.php
#	app/Language/bs/Sales.php
#	app/Language/ckb/Config.php
#	app/Language/ckb/Sales.php
#	app/Language/cs/Config.php
#	app/Language/cs/Sales.php
#	app/Language/da/Config.php
#	app/Language/da/Sales.php
#	app/Language/de-CH/Config.php
#	app/Language/de-CH/Sales.php
#	app/Language/de-DE/Config.php
#	app/Language/de-DE/Sales.php
#	app/Language/el/Config.php
#	app/Language/el/Sales.php
#	app/Language/en-GB/Config.php
#	app/Language/en-GB/Sales.php
#	app/Language/en/Config.php
#	app/Language/en/Sales.php
#	app/Language/es-ES/Config.php
#	app/Language/es-ES/Sales.php
#	app/Language/es-MX/Config.php
#	app/Language/es-MX/Sales.php
#	app/Language/fa/Config.php
#	app/Language/fa/Sales.php
#	app/Language/fr/Config.php
#	app/Language/fr/Sales.php
#	app/Language/he/Config.php
#	app/Language/he/Sales.php
#	app/Language/hr-HR/Config.php
#	app/Language/hr-HR/Sales.php
#	app/Language/hu/Config.php
#	app/Language/hu/Sales.php
#	app/Language/hy/Config.php
#	app/Language/hy/Sales.php
#	app/Language/id/Config.php
#	app/Language/id/Sales.php
#	app/Language/it/Config.php
#	app/Language/it/Sales.php
#	app/Language/km/Config.php
#	app/Language/km/Sales.php
#	app/Language/lo/Config.php
#	app/Language/lo/Sales.php
#	app/Language/ml/Config.php
#	app/Language/ml/Sales.php
#	app/Language/nb/Config.php
#	app/Language/nb/Sales.php
#	app/Language/nl-BE/Config.php
#	app/Language/nl-BE/Sales.php
#	app/Language/nl-NL/Config.php
#	app/Language/nl-NL/Sales.php
#	app/Language/pl/Config.php
#	app/Language/pl/Sales.php
#	app/Language/pt-BR/Config.php
#	app/Language/pt-BR/Sales.php
#	app/Language/ro/Config.php
#	app/Language/ro/Sales.php
#	app/Language/ru/Config.php
#	app/Language/ru/Sales.php
#	app/Language/sv/Config.php
#	app/Language/sv/Sales.php
#	app/Language/sw-KE/Config.php
#	app/Language/sw-KE/Sales.php
#	app/Language/sw-TZ/Config.php
#	app/Language/sw-TZ/Sales.php
#	app/Language/ta/Config.php
#	app/Language/ta/Sales.php
#	app/Language/th/Config.php
#	app/Language/th/Sales.php
#	app/Language/tl/Config.php
#	app/Language/tl/Sales.php
#	app/Language/tr/Config.php
#	app/Language/tr/Sales.php
#	app/Language/uk/Config.php
#	app/Language/uk/Sales.php
#	app/Language/ur/Config.php
#	app/Language/ur/Sales.php
#	app/Language/vi/Config.php
#	app/Language/vi/Sales.php
#	app/Language/zh-Hans/Config.php
#	app/Language/zh-Hans/Sales.php
#	app/Language/zh-Hant/Config.php
#	app/Language/zh-Hant/Sales.php
#	app/Libraries/Sale_lib.php
#	app/Models/Sale.php
2026-07-10 14:28:45 +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
Travis Garrison 584d38426c feat(plugins): add uninstall confirmation modal and soft-delete uninstall
- Intercept uninstall action to show confirmation modal before AJAX call
- Change uninstall to soft-delete: set installed=0 instead of purging all config
- Add deleteAllNonControlForPlugin to preserve control rows on uninstall
- Add isPluginInstalled helper to PluginManager for installed state checks
- Fix isInstalled guard to treat installed=0 as not installed

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
2026-07-07 17:54:18 +04:00
Travis Garrison 9aec82927c refactor(models): rename snake_case methods to camelCase per PSR-12
Rename get_info, get_sale_items, get_sale_payments across Item, Sale
models and all call sites in controllers, libraries, and tests.

Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
2026-07-07 16:26:06 +04:00
Travis Garrison 00490d2ff2 Merge remote-tracking branch 'OpensourcePOS/master' into plugin-system-fresh 2026-07-07 12:00:43 +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
Travis Garrison bdb2ba5b51 Add table existence check in PluginManager before initializing migrations
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
2026-06-29 12:04:08 +04:00
objec 150cdfb2b8 Add plugin migration system
- Add `runPendingMigrations()` to `PluginManager` for executing plugin-specific migrations.
- Create `PluginMigrationModel` for managing plugin migration versions.
- Add migration for creating the `plugin_migrations` table to track migration states.

Signed-off-by: objec <objecttothis@gmail.com>
2026-06-26 16:19:04 +04:00
objec 962407b3f6 Review suggestion corrections
- Remove unused function
- reorder parameters in log(), logTo() and log_plugin_message()

Signed-off-by: objec <objecttothis@gmail.com>
2026-06-12 11:16:58 +04:00
objec 71377eb2ac Plugin logger
Added missing PluginLogger.php

Signed-off-by: objec <objecttothis@gmail.com>
2026-06-12 01:09:35 +04:00
objec 7dde48c0ed Plugin logger
- Create log_plugin_message() to prevent plugin logs from spamming the core logs
- Create ability to log to different logs or a base log if parameter is not specified.
- Update README.md
- Change BasePlugin::log() wrapper function to log to log_plugin_message() and add logTo() to log to a specific plugin log.
- Add plugin logger service to keep the logger loaded.

Signed-off-by: objec <objecttothis@gmail.com>
2026-06-12 00:57:35 +04:00
objec b18e9ea785 Merge remote-tracking branch 'OpensourcePOS/master' into plugin-system-fresh 2026-05-29 14:01:57 +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
objec 139f754a07 Refactor manage plugin configuration and settings
- Add column to indicate control setting (installed, enabled).
- Add column to indicate plugin.
- Rework business logic to read the status properly.
- Renamed the migration to properly reflect which version it's released in.

Signed-off-by: objec <objecttothis@gmail.com>
2026-05-20 17:43:42 +04:00
objecttothis df24ef5193 Merge branch 'master' into plugin-system-fresh 2026-05-18 16:25:10 +04:00
objec ad097adccd Refactor get_info to getInfo
Signed-off-by: objec <objecttothis@gmail.com>
2026-05-18 16:12:38 +04:00
objec 796657118a Feature to pass data to config view modals
- Add getConfigViewData() to BasePlugin.php
- Add function to plugin interface

Signed-off-by: objec <objecttothis@gmail.com>
2026-05-18 16:11:12 +04: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
objec cd91ac3ff3 Fix bugs
- Add missing `MailchimpPlugin.` prefix to lang() calls.
- Do not subscribe customer if consent is not true.
- Escape output in tabular_helper.php
- Removed testConnection() as unneeded code
- Fix activity count logic
- Whitelist Sort Column Headers for Plugins.php
- Store encrypted API key as base64 instead of raw binary to prevent truncation
- Rollback on batchSave partial failure.
- Remove dead code.
- Disable plugin before uninstalling it.
- Fix getPluginSettings() internal key leak
- Add action column to plugin headers function
- Automatically add grant to all admins in case person_id 1 is not active

Signed-off-by: objec <objecttothis@gmail.com>
2026-05-05 15:06:04 +04:00
objec 478934321d Merge remote-tracking branch 'origin/master' into plugin-system-fresh
# Conflicts:
#	app/Language/th/Sales.php
2026-05-05 13:09:43 +04:00
objec 4246a915c4 - Correct README.md reference to views and information about renderView()
- Fix the output of pluginContent in the pluginHelper
- Register view injection events
- Correct the parameter type in getMailchimpViewData
- Correct the statusOptions creation business logic
- Removed unnecessary view injection point
- Corrected which variable was passed to the customer_saved event
- Assigned $customer_data['person_id'] on customer update
- Added renderView() function to BasePlugin.php

Signed-off-by: objec <objecttothis@gmail.com>
2026-04-30 14:11:54 +04:00
objec fe331c34dd Mailchimp Bugfixes
- Update README.md to reflect information about routes
- Add registerAllNamespaces() function to correctly load plugin  namespaces
- center text in modal title
- Properly decrypt the api key
- Refactor getAllLists to getLists
- Naming simplification of strings when mailchimp_ is redundant or unnecessary
- Do not attempt to decrypt a plaintext api_key pasted into the form
- Register namespaces early on in system init

Signed-off-by: objec <objecttothis@gmail.com>
2026-04-29 18:25:21 +04:00
objec 6630fb56f6 Fix language discovery bugs
- Remove unneeded keys from Config.php
- Remove unneeded lang() function override from BasePlugin.php
- Update README.md to reflect changes to language loading
- Correct language file string
- Correct lang() function calls to remove `$this->` from the call since we aren't overriding it anymore.
- Add code to correctly register namespace so that languages load.
- Fix plugin view render bug

Signed-off-by: objec <objecttothis@gmail.com>
2026-04-29 16:31:14 +04:00
objec 2f48e0499f Plugin discovery bugfix
- Fix namespace typo causing plugin to not load
- Code cleanup

Signed-off-by: objec <objecttothis@gmail.com>
2026-04-29 15:20:08 +04:00