* 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 `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>
- Add `$pluginData` parameter to `Events::trigger` calls for key events (e.g., `sale_completed`, `customer_saved`)
- Update core views to include `data-plugin-form` attribute for plugin-field serialization
- Bundle `plugin_data_helper.js` to handle form field extraction for plugins
- Extend README.md with comprehensive documentation on using `$pluginData`
- Refactor language files to support `install` translations and streamline UI labels
- Add new view hooks for sales receipt and register UI extension
Signed-off-by: Travis Garrison <travis@chiraqbookstore.com>
- reinstated 'update-licenses' task in gulp (accidentally removed in 3e844f2f89)
- updated bootstrap, bootswatch, and various dev dependencies
- refinded text across UI
- applied consistency fixes
- added 'number' and 'tel' input types to relevant settings
- improved system info layout (still room for improvement, but better)
- updated and fixed changelog
- 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>
This commit adds URL-based filter persistence for table views, allowing
users to navigate away from a filtered view (e.g., clicking into sale
details) and return without losing their filter settings.
The solution uses history.replaceState() to update the URL without
triggering a page reload, providing a seamless user experience while
maintaining shareable/bookmarkable URLs.
Fixes navigation issue where filters are lost when viewing details or
navigating away from table views.
* Move filter restoration to server-side for cleaner architecture
Changes:
- Controllers now restore filters from URL query string on initial page load:
* Sales.php: Reads start_date, end_date, and filters[] from GET
* Items.php: Reads start_date, end_date, filters[], and stock_location
* Expenses.php: Reads start_date, end_date, and filters[]
* Cashups.php: Reads start_date, end_date, and filters[]
- Views now receive restored filter values from controllers:
* Server-side date override via JavaScript variables
* form_multiselect() receives $selected_filters from controller
* Removed setTimeout hack from table_filter_persistence.php
- Simplified table_filter_persistence.php:
* Now only handles URL updates on filter changes
* No longer responsible for restoring state
* Cleaner, single responsibility (client-side URL management)
Benefits:
- Works without JavaScript for initial render
- Cleaner architecture (server controls initial state)
- Client-side JS only handles "live" filter updates
- Filters persist across navigation via URL query string
- Shareable/bookmarkable URLs
How it works:
1. User visits /sales/manage?start_date=2024-01-01&filters[]=only_cash
2. Controller reads GET params and passes to view
3. View renders with correct initial filter values
4. User changes filter → JavaScript updates URL via replaceState()
5. User navigates away and back → Controller restores from URL again
* Refactor filter restoration into helper function and use PSR-12 naming
* Use array_merge with helper to reduce code duplication
---------
Co-authored-by: Ollama <ollama@steganos.dev>
When localization uses dot (.) as thousands separator (e.g., it_IT, es_ES, pt_PT),
the payment_amount value was displayed as raw float (e.g., '10.50') but parsed
using parse_decimals() which expects locale-formatted numbers.
In these locales, '.' is thousands separator and ',' is decimal separator.
parse_decimals('10.50') would return false, causing the condition
!= 0 to evaluate incorrectly (false == 0 in PHP),
resulting in the payment being deleted instead of updated.
Fix: Use to_currency_no_money() to format payment_amount and cash_refund
values according to locale before displaying in the form, so parse_decimals()
can correctly parse them on submission.
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.
* 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
- Added @noinspection PhpUnused to AJAX-called functions to remove weak warning that the function is unused. This will be needed for the linter.
- Referenced where the function is called in the PHPdocs.
- Removed redundant transaction. batch_save() is already being run in a transaction.
- Fixed function name in controller and view.
- Removed form helper load because it's autoloaded.
- Corrected variable reference in Secure_Controller.php
Signed-off-by: objecttothis <objecttothis@gmail.com>
- 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>
- Added missing ; to "nbsp"
- Remove filtering from checkbox items in controller
- Added null check to checkboxes in controller
- Fixed function naming to avoid 404
- Removed escaping from fixed urls
- Removed esc() wrapping around site_url() which already returns escaped urls.
- Made view CI form helper function call format uniform.
- replaced calls to array() with []
- Placed { on its own line
- Removed empty lines where there shouldn't be any.
- Replaced text/javascript with application/javascript as the former is deprecated
In Views/sales/register.php two button labels did not show the correct language strings, because the variable '$controller_name' was passed to lang(), but this needed to be given literally as 'Sales'. In Views/receivings/receiving.php, the suggested items autocomplete function was called as stock_item_search and needed to be changed to stockItemSearch. There is also an almost identical function itemSearch, which may indicate that one of the two is redundant and should be pruned, but without documentation to guide me I am unwilling to do that at this time.