fix(reports, home): strengthen method grant validation and URI decoding (#4660)
- Reports, Home: fix URI decoding in hasGrant() method checks to use
urldecode consistently, preventing malformed URI segments from
bypassing access controls
- Reports: rename snake_case variables to camelCase for PSR-12 compliance
- Reports: adjust access checks to accurately handle null submodule IDs
Tests:
- Add grant check tests for encoded URI inputs across Reports and Home
- Add test case for employee access with base reports grant
- Add secondary grant check for reports_customers in relevant test cases
- Confirm logout bypass remains functional and properly controlled
- Refactor TestDatabaseBootstrapSeeder to expose static reset() for
per-class DB re-initialization instead of only via seeder run()
- Standardize session handling, setup logic, and boolean declarations
- Use unique data in test helpers to avoid collisions
- Add docblocks to ReportsControllerTest and HomeTest for PSR-5 compliance
- Add exception handling for failed employee creation in test setup
- Wrap password validation test in try-finally to guarantee state cleanup
Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
- Sales::getSearch now enforces reports_sales grant before running
search, returns 403 with lang message when missing
- Rename snake_case helpers/methods to camelCase across
Sales controller, Sale model, and tabular_helper
(get_sale_data_row -> getSaleDataRow, get_payments_summary ->
getPaymentsSummary, sales_headers -> salesHeaders, etc.)
- Config/OSPOS: reset DB data cache before checking app_config
table existence to avoid stale schema cache in tests
- TestDatabaseBootstrapSeeder: expose static reset() so tests can
rebuild schema once per class instead of only via seeder run()
- SalesControllerTest: bootstrap DB once per class, seed once,
refresh app settings each setUp, add tests for search endpoint
authorization (cashier denied, supervisor allowed), move
createTestItem into shared ItemFixtureTrait
Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
fix(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>
fix(employees): harden permissions UI and access control
- Prevent admins from removing their own minimum module grants (employees, home, office)
- Add session_status check before session regeneration
- Disable submit button and return no_access view for AJAX requests
- Replace fade class with active-only for Bootstrap compatibility
- Update permission toggle selectors to .module-toggle
- Add error_cannot_remove_own_minimum_grant translations for Armenian, Bulgarian, Georgian, Swedish, and Ukrainian
---------
Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
fix(sales): gate per-record endpoints behind reports_sales grant (REDACTED)
Cashiers holding only the base `sales` grant could reach per-sale endpoints
(getRow, getEdit, postSave, getReceipt, getInvoice, getSendPdf, getSendReceipt)
that require `reports_sales`. getManage() enforced this at the list level, but
individual endpoints did not re-check. Regression tests added.
Auth:
- Introduce `IsLoggedIn` filter to centralize login checks across controllers
- Replace custom `AccessDeniedRedirectException` with built-in `RedirectException`
Employees:
- Add `DISALLOW_PASSWORD_CHANGE` and `DISALLOW_GRANT_CHANGE` env vars to restrict
credential and permission changes in locked-down environments
- Extract `hasGrantsChanged()` to streamline `postSave`
Refactor:
- Rename snake_case variables to camelCase in Sales, Items, and Employees
controllers for PSR-12 compliance
- Use explicit `db_connect()` for transaction clarity in Items controller
Fixes:
- SMTP config entries fall back to defaults via null coalescing
- Migration uses `DROP FOREIGN KEY` instead of `DROP CONSTRAINT`
- Password hash upgrade only sets session on successful `hash_version` update
- Correct lang key for unknown error in Module model
Language:
- Translate `error_grant_change_disallowed` / `error_password_change_disallowed`
across all 44 supported locales with => alignment matching en reference
- Fix "cannot be deleted" messages and misc typos across ~15 language files
Tests:
- Bootstrap seeder only once in ItemsCsvImportTest; close connection after
- Restore `DISALLOW_GRANT_CHANGE` in teardown to prevent side effects
- Use `uniqid()` for test user data to avoid collisions
Signed-off-by: 17935339+objecttothis@users.noreply.github.com
* refactor: standardize function and variable names to camelCase and improve naming consistency across files
* refactor(config): remove spaces around `=` in configuration files for improved consistency and formatting as is required by .env formatting rules.
* refactor(security): extract `.env` key management logic into reusable `writeEnvKey` helper, add throttle key provisioning logic, and streamline encryption key updates
* fix(migration): improve error handling in CI3 to CI4 encryption data migration
- Secure `up` and `convertCI3EncryptedData` methods with detailed exception handling for script execution and data saving.
* fix(migration): ensure empty string is correctly handled in CI3 to CI4 encryption data conversion
* refactor(security): enhance `.env` management with durable writes, better locking, and helper abstraction
- Update `writeEnvKey` to return a success flag and handle file locks robustly.
- Introduce `atomicWriteFile` for atomic writes to prevent partial file updates.
- Add `applyEnvKeyReplacement` to streamline `.env` key insertion and updates.
- Improve throttle key provisioning with validation and runtime persistence safeguards.
* refactor(security): implement dedicated `.env` file locking for robust and cross-platform safe write operations
- Add `lockEnvFile` and `unlockEnvFile` helpers to manage `.env` mutex files.
- Refactor `.env` write logic to use lock helpers, improving reliability and preventing race conditions.
- Enhance `atomicWriteFile` for better handling of file overwrites on Windows and POSIX systems.
* fix(migration): improve encryption error handling during CI3 to CI4 data conversion
- Add conditional checks for `checkEncryption` to prevent failed key persistence.
- Introduce `abortEncryptionConversion` for cleanup on failure.
- Update `writeEnvKey` to handle and return errors gracefully.
* refactor(security): improve `atomicWriteFile` for better file locking and cross-platform durability
- Replace `uniqid` with `bin2hex(random_bytes())` for more secure temp file naming.
- Add explicit file permissions and locking for safe concurrent writes.
- Enhance error handling to ensure atomicity on both Windows and POSIX systems.
* Add env temp files to gitignore so they don't get tracked.
---------
Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
* fix(items): add explicit sentinel value for clearing supplier in bulk edit
This fixes a regression introduced in the fix for [REDACTED]
Introduce `Item::CLEAR_SUPPLIER_OPTION = 'NONE'` to distinguish between
\"leave supplier_id unchanged\" (empty string) and \"clear supplier_id\"
(sentinel). Previously, empty string was ambiguous.
- Add `CLEAR_SUPPLIER_OPTION` constant with doc comment explaining intent
- Update supplier dropdown to include sentinel as first real option
- Shift empty string to mean \"do nothing\" across all bulk edit fields
* test(items): add regression tests for mass assignment in bulk edit
Cover [REDACTED]: Item::update_multiple() bypasses model
$allowedFields via Query Builder, allowing unintended field writes
during bulk edit operations.
* fix(items): add type validation to bulk-edit field filter
filterBulkEditFields now validates field values before accepting them:
- Non-scalar values are rejected (array injection guard)
- Price/quantity fields are locale-parsed to floats, invalid strings skipped
- Boolean fields must be 0 or 1, other values skipped
- supplier_id must be numeric; CLEAR_SUPPLIER_OPTION still nulls it
Update tests to assert parsed types (float for prices, int for
supplier_id) and replace the fill-all-fields fixture with a realistic
input that only covers fields a form would actually submit.
* test(items): add supplier cleanup and helper methods to bulk update tests
- Track created supplier person IDs for teardown cleanup
- Delete supplier records in tearDown to prevent test pollution
- Extract item/supplier creation into reusable helper methods
* style(tests): rename variables to camelCase in ItemBulkUpdateTest
* refactor(items): rename snake_case variables to camelCase
Convert Item model, Items controller, and bulk update tests to
PSR-compliant camelCase naming per project conventions.
- Rename update_multiple to updateMultiple in Item model
- Rename local variables (item_data, items_to_update, tax_names, etc.)
to camelCase across Items controller and Item model
- Update ItemBulkUpdateTest to use new updateMultiple method name
- Reorder and update AGENTS.md naming conventions
* style(tests): convert snake_case variables to camelCase in ItemBulkUpdateTest
Rename local variables and property names to camelCase for PSR-12
consistency, matching convention used elsewhere in new test code.
* fix(auth): hash throttler keys to improve security
- Use MD5 hashing for IP and username-based throttler keys to obfuscate sensitive data while maintaining functionality.
* test(auth): add IPv6 throttling test and hash used throttler keys
- Add a test to ensure throttling works correctly with IPv6 addresses.
- Update throttler keys to use MD5 hashes for IPs and usernames for improved security and consistency.
* fix(auth): handle non-scalar usernames in throttler keys
- Ensure username input is validated as scalar before processing to prevent errors and maintain throttling logic integrity.
* fix(auth): enhance throttler key security with HMAC hashing
- Replace MD5 with HMAC-SHA256 for generating throttler keys.
- Include encryption key from app configuration for added security.
* test(filters): update ThrottleTest to use HMAC-SHA256 for throttler keys
- Replace MD5 with HMAC-SHA256 for generating throttler keys in tests.
- Introduce `check_encryption()` to ensure encryption configuration is available.
* fix(events): validate encryption key on app initialization
- Throw ConfigException if encryption key is missing or invalid during `pre_system` event.
- Remove redundant `check_encryption()` call from Throttle filter and tests.
* fix(events): improve encryption key validation in `pre_system`
- Add `check_encryption()` helper call for additional security verification.
- Update error message to highlight `.env` writability issues if the key is invalid.
* test(filters): handle non-scalar usernames in ThrottleTest
- Update `makeRequest` to validate usernames as scalar and cast them to strings before processing.
- Add a test to ensure array usernames are ignored, and throttling is applied only based on IP.
- Improve status code assertions for throttled requests.
---------
Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
* Implement atomic updates for gift card and reward point decrements, enhance error handling for insufficient balances, and add regression tests for concurrency safety.
* Add translations for insufficient gift card balance and reward points error messages across all supported languages.
* Reorder `clear_suspended_sale_detail` call to ensure transactional consistency.
* Reorder `clear_all` call to align with success and error handling logic.
* Ensure soft-deleted gift cards are excluded in balance updates.
* Refactor change_quantity logic with atomic upserts, improve error handling for insufficient stock, and update related tests and constants.
* Added check for NEW_ENTRY
* Added unit tests to test changes.
* Fix class name casing in ItemQuantityTest for consistency.
* Fix Bulgarian translations for insufficient balance error messages in Sales module.
* Fix Greek translations for insufficient balance error messages in Sales module.
* Fix Armenian translations for insufficient balance error messages in Sales module.
* Fix Tamil translations for insufficient balance error messages in Sales module.
* Implement race condition testing for database methods with concurrent process support.
* Fix class name casing in ItemTest for consistency.
* Improve concurrent process handling in race condition tests; add readiness and synchronization barriers.
* Improve handling of process I/O streams and timeout management in race condition tests.
* Add test for decrementing gift card value when marked as deleted
* Add `finally` block to ensure proper cleanup in async database race condition tests
* Improve error handling and timeout management in async database race condition tests.
* Refactor test utilities to use shared `EmployeeFixtureTrait` and `ItemFixtureTrait`.
* Track process exit codes explicitly in race condition tests for improved error detection and debugging.
* Improve error handling in `ConcurrentDbRaceTrait` by adding exceptions for `mysqli_poll` and `mysqli_reap_async_query`.
Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
---------
Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
* fix(auth): throttle login attempts to prevent brute-force attacks
Add Throttle filter and wire into login route to mitigate
credential-stuffing/brute-force risk (redacted).
- Register App\\Filters\\Throttle in Filters config
- Add \"too_many_attempts\" language string for throttled responses
- Add tests for Throttle filter and Login controller throttling
* Correct bug causing error to not display.
* i18n(login): add too_many_attempts translation for login throttling
Add localized \"too many attempts\" message across all language files
to support login throttling feature. Message informs users to wait
before retrying after exceeding attempt limit.
* fix(auth): rate limit login attempts to prevent brute-force attacks
Add Throttle filter that rate limits login/migrate POST requests,
keyed by IP and submitted username, using CodeIgniter's cache-based
Throttler (redacted).
- Wire filter into login/migrate routes
- Show localized error message when rate limit exceeded
- Broaden writable/cache ignore pattern to cover throttler cache file
* Update app/Language/ar-EG/Login.php
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* style(i18n): use single quotes for translation array keys and values
Convert double-quoted array keys and string values to single quotes
across all Login.php language files for style consistency.
* test(auth): update LoginTest to use throttler service directly
Replace clearThrottleState's Services::resetSingle('throttler') call
with Services::throttler() to reset state via the service instance.
Add usedKeys property to track throttle keys used across test cases.
* test(auth): skip login test assertion when migration required
LoginTest now check migration-required response state before
asserting HTTP 200. Prevent false failures when app force
pending-migration redirect during test run.
* Added missing return statements
* fix(items): validate item_number and skip receiving quantity default for temp items
- Validate item_number against alpha_numeric_punct rule, return JSON error on failure
- Add item_number_invalid language string
* i18n: reorder Items language keys and add item_number_invalid string
Add item_number_invalid translation across all locale files and
resort surrounding keys alphabetically to match key ordering
convention.
* PSR-12 refactoring.
- Change local variable to camelCase.
- Use single quote in language files.
* i18n: translate item_number_invalid string in ta, th, tl, zh-Hans
Item_number_invalid key had English placeholder text in Tamil,
Thai, Tagalog, Chinese Simplified language files. Translate to
match each locale.
* fix(items): use FormatRules for item_number validation
* refactor(items): use camelCase for variable names
* refactor(items): use camelCase for variable names in Items controller
* Reject item CSV imports whose header row is missing required columns
Signed-off-by: Sai Asish Y <say.apm35@gmail.com>
* Require all template columns when validating CSV import headers
Signed-off-by: Sai Asish Y <say.apm35@gmail.com>
* fix: derive required CSV import headers from the template generator
---------
Signed-off-by: Sai Asish Y <say.apm35@gmail.com>
Co-authored-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
* fix(xss): remove redundant escaping that double-encoded item attribute values
- Remove esc()/html_entity_decode() calls now that output is escaped
at render time by the framework, preventing double-encoding of
special characters in attribute names, units, and definition values
- Fix employee_name form_input value fields to stop pre-escaping
before form_input applies its own escaping
- Reorder Items.php use statements and add missing BaseConnection import
- Change items/manage.php start_date from let to plain assignment for
proper reassignment scope
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
* test(sales): add regression tests for permission checks on sales endpoints
- Ensure role-based permissions correctly restrict access to sensitive actions like price edits, receipt/invoice views, and report generation.
- Add tests for both granted and restricted user scenarios to validate the behavior.
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
* fix(attributes): validate `attribute_value` before processing
- Add checks to ensure `attribute_value` is a non-empty string in `postSaveAttributeValue` and `postDeleteDropdownAttributeValue` methods.
- Return error response if validation fails to prevent invalid data handling.
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
* fix(attributes): improve error handling and optimize affected items processing
- Use `array_column` for extracting item IDs to streamline logic.
- Add JSON validation with `JSON_THROW_ON_ERROR` and return proper error response for invalid `definition_values`.
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
* test(sales): enable database refresh for consistent test state
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
* test(sales): assert unauthorized message is displayed on restricted access
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
* refactor(attributes): use camelCase for `attributeValue` in controller methods
- Standardize variable naming in `postSaveAttributeValue` and `postDeleteDropdownAttributeValue` methods by switching to camelCase.
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
---------
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
Co-authored-by: Travis Garrison <travis@chiraqbookstore.com>
postSave() called $this->db which is not set on the Items controller, causing Undefined property errors when saving items. Match the CSV import path and obtain the connection via db_connect().
Fixes#4623
Renames the Attribute-specific definition methods from snake_case to
camelCase and updates every call site:
get_definition_by_name -> getDefinitionByName
get_definition_names -> getDefinitionNames
get_definition_values -> getDefinitionValues
get_definitions_by_type -> getDefinitionsByType
get_definitions_by_flags -> getDefinitionsByFlags
get_definition_flags -> getDefinitionFlags
Also documents getDefinitionByName()'s return contract: a single
definition row as an associative array, or [] when none matches,
matching the getRowArray() behaviour introduced in #4464.
get_found_rows() and get_total_rows() are deliberately left alone -
they are declared across 15 models and renaming them only here would
break that shared convention.
Refs #4622
Co-authored-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
* fix(auth): validate gcaptcha before password to prevent bypass
Move gcaptcha check before credential validation so a valid captcha
is required prior to any login attempt. Previously, password auth
ran first, allowing timing-based enumeration without captcha.
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
* test(auth): add regression tests for gcaptcha validation order in OSPOSRules
Guards fix from 5dea748b0: gcaptcha must be validated before
Employee::login() is attempted to prevent auth bypass.
Change gcaptcha_check visibility to protected to allow testing.
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
---------
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
Co-authored-by: Travis Garrison <travis@chiraqbookstore.com>
Wrap $quote_number output with esc() in quote_email.php. Raw
interpolation allowed injected HTML/JS via quote number field.
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
Co-authored-by: Travis Garrison <travis@chiraqbookstore.com>
Client-side \"change_price\" flag is UI-only, not trustworthy. Compare
submitted price against current cart price server-side and require
sales_change_price grant when they differ, else reject with
not_authorized error.
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
Co-authored-by: Travis Garrison <travis@chiraqbookstore.com>
* refactor: Replace var with let/const in JavaScript files
- Replace var with let for variables that are reassigned
- Replace var with const for variables that are never reassigned
- Modernize manage_tables.js and nominatim.autocomplete.js
- Skip third-party libraries (imgpreview.full.jquery.js, clipboard.min.js)
Closes#4491
* refactor: Replace var with let/const in inline JavaScript
- Fixed CodeRabbit review: changed enable_actions and load_success
from const to let in manage_tables.js (they are reassigned in init)
- Replaced all var declarations in inline JavaScript in Views with
let (for reassigned) or const (for never reassigned)
- Modernized 48 additional files with inline JavaScript
* refactor: Replace var with let/const in remaining JS files
- Modernized gulpfile.js: 3 var declarations replaced
- Modernized app/Views/errors/html/debug.js: all var declarations replaced
- Used const for never-reassigned, let for reassigned variables
* fix: Replace remaining var declarations in Views
- Changed var to const in sales/register.php
- Changed var to const in configs/receipt_config.php
These were missed in the initial pass.
* fix: Replace remaining var declarations in gulpfile.js
- Converted 12 remaining var declarations to const
- All variables are function-scoped and never reassigned
- Complete coverage for this file now
* fix: Address CodeRabbit review comments
- items/manage.php: Remove duplicate let declaration for start_date
(partial/daterangepicker already declares it)
- header_js.php: Escape CSRF hash in JavaScript context
- tax_jurisdictions.php: Fix mismatched selector (remove_tax_jurisdictions
-> remove_tax_jurisdiction)
* style(views): Replace var with let/const and fix comment casing
- Convert var to let in items/manage.php for JS modernization
- Capitalize \"Submit\" in validation comments across tax view files
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
---------
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
Co-authored-by: Ollama <ollama@steganos.dev>
Co-authored-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
Co-authored-by: Travis Garrison <travis@chiraqbookstore.com>
* fix: wrap postSave() in single transaction for atomicity
- Remove internal transaction from Item_taxes->save_value() to allow controller-level transaction
- Wrap entire save sequence (item, taxes, quantities, inventory, attributes) in single transaction
- Ensure all operations succeed or all fail together
- Prevents partial writes when saveItemAttributes() fails after item/tax/quantity saves succeed
Fixes#4474
* fix: Use explicit transBegin/transCommit/transRollback for atomicity
- Replace transStart/transComplete with transBegin/transCommit/transRollback
- Check all success conditions before committing
- Explicit rollback on failure
Address CodeRabbit review feedback
* refactor(items): rename postSave locals to camelCase per PSR-12
Convert snake_case variables to camelCase in postSave() and related
item-save logic to match project naming convention for new methods.
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
---------
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
Co-authored-by: Ollama <ollama@steganos.dev>
Co-authored-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
Co-authored-by: Travis Garrison <travis@chiraqbookstore.com>
* 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>
- Relocate PluginTestCase from App\\Libraries\\Plugins to Tests\\Support
- Update all references in AGENTS.md, README.md, and test files
- Add TestModulePlugin fixture for module registration tests
- Add PluginModuleRegistrationTest covering namespace registration
- Add plugin_data_helper.js for zero-JS plugin form field collection
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
- 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>
* feat(plugins): add sale document view hooks and a webhook CSRF exemption
Plugins can already inject buttons into the receipt via
view:sales_receipt_buttons, but the invoice, quote and work order views have
no hook point, so a plugin that delivers sale documents can only reach one of
the four. Add the matching hooks, keeping the same name/data shape:
view:sales_invoice_buttons
view:sales_quote_buttons
view:sales_work_order_buttons
Each passes ['saleId' => $sale_id_num] and sits in the same position as the
existing receipt hook, so one plugin callback can serve all four and branch on
the document type.
Also allow a plugin to expose an inbound provider webhook. A server-to-server
delivery carries no CSRF token and no session, so the global csrf filter now
excludes the pattern plugins/*/webhook. Plugins are responsible for
authenticating such requests themselves (typically by verifying the provider's
signature header against a shared secret) — this is documented alongside the
change.
While editing that line, the except list becomes an array. CodeIgniter matches
every entry as \A<pattern>\z, so the previous 'login|migrate' string produced a
single pattern whose inner alternatives were unanchored: 'login/anything' was
CSRF-exempt. Listing the entries separately anchors each one, which closes that
and keeps the new plugin pattern tight — plugins/whatsapp/webhook is exempt
while plugins/whatsapp/send is not.
* refactor(plugins): drop explanatory comments from csrf filter exceptions
---------
Co-authored-by: Joshua Fernandes <“joshua.1234511@yahoo.in”>
- Add Plugins testsuite to phpunit.xml.dist for auto-discovery
- Document PluginTestCase base class and test directory convention in README.md and AGENTS.md
- Tests resolve via existing App\\ PSR-4 mapping; no composer.json changes needed
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>