Update to CodeIgniter 4.7.2 (#4485)

- Merge Config and Core File Changes 4.6.3 > 4.6.4
- Merge Config and Core File Changes 4.6.4 > 4.7.0
- Added app\Config\WorkerMode.php
- Merge Config and Core File Changes Not previously merged
- Added app\Config\Hostnames.php
- Corrected incorrect CSS property used in invoice.php view.
- Corrected unknown CSS properties used in register.php view.
- Used shorthand CSS in debug.css
- Corrected indentation in barcode_sheet.php view.
- Corrected indentation in footer.php view.
- Corrected indentation in invoice_email.php view.
- Replaced obsolete attributes with CSS style attributes in barcode_sheet.php
- Replaced obsolete attribute in error_exception.php
- Replaced obsolete attribute in invoice_email.php
- Replaced obsolete attribute in quote_email.php
- Replaced obsolete attributes in work_order_email.php
- Fixed indentation in system_info.php
- Replaced <strong> tag outside <p> tags, which isn't allowed, with style attributes.
- Simplified js return logic and indentation fixes in tax_categories.php
- Simplified js return logic in tax_codes.php
- Simplified js return logic in tax_jurisdictions.php
- Removed unnecessary labels in manage views.
- Rewrite JavaScript function and PHP to be more readable in bar.php, hbar.php, line.php and pie.php
- Added type declarations, return types and an import to app\Config\Services
- Updated Attribute.php parameter type
- Updated Receiving_lib.php parameter type
- Updated Receivings.php parameter types and updated PHPdocs
- Updated tabular_helper.php parameter types and updated PHPdocs
- Added type declarations and corrected PHPdocs in url_helper.php
- Added return types to functions
- Revert $objectSrc value in ContentSecurityPolicy.php
- Correct return type in Customer->get_stats()
- Correct return type in Item->get_info_by_id_or_number()
- Correct misspelling in border-spacing
- Added missing css style semicolons
- Resolve operator precedence ambiguity.
- Resolve column mismatch.
- Added missing escaping in view.
- Updated requirement for PHP 8.2
- Resolve unresolved conflicts
- Added PHP 8.2 requirement to the README.md
- Fixed bugs in display of UI
- Fixed duplicated `>` in app\Views\Expenses\manage.php
- Removed excess whitespace at the end of some lines in table_filter_persistence.php
- Added missing `>` in app\Views\Expenses\manage.php
- Corrected grammar in PHPdoc in table_filter_persistence.php
- Remove bug causing `\` to be injected into the new giftcard value
- Fix bug causing DROPDOWN Attribute Values to not save correctly
- Added check for null in $normalizedItemId

- Removing < PHP 8.2 from linting and tests
- Update Linter to not include PHP 8.2 and 8.1
- Remove PHP 8.1 unit test cycle.
- Update Bug Report Template
- Update Composer files for CodeIgniter 4.7.2
- Updated INSTALL.md to reflect changes.

---------

Signed-off-by: objec <objecttothis@gmail.com>
This commit is contained in:
objecttothis
2026-04-14 01:05:10 +04:00
committed by GitHub
parent 332d8c8c69
commit 6fec2464f8
75 changed files with 1316 additions and 1013 deletions

View File

@@ -12,11 +12,11 @@ body:
attributes:
value: |
## Thanks for taking the time to fill out this bug report! 🐜
Bug reports help us identify and fix issues. Please provide as much detail as possible.
> ⚠️ **Important:** Submit a separate bug report for each problem you encounter.
>
>
> 🚫 Do not include personal identifying information such as email addresses or encryption keys.
# ─────────────────────────────────────────────────────────────────────────────
@@ -28,7 +28,7 @@ body:
label: 🐛 Bug Description
description: A clear and concise description of what the bug is.
placeholder: |
Example: When I try to print a receipt, the application crashes
Example: When I try to print a receipt, the application crashes
with an error message saying "Unable to connect to printer".
validations:
required: true
@@ -86,8 +86,7 @@ body:
- PHP 8.2
- PHP 8.1
- PHP 7.4
- PHP 7.3
- PHP 7.2
- Other
default: 0
validations:
required: true
@@ -141,7 +140,7 @@ body:
label: 📊 System Information Report
description: |
Copy and paste the system information from OSPOS:
**Navigation:** Configuration → Setup & Conf → System Info
placeholder: |
Paste the System Information Report here...
@@ -155,7 +154,7 @@ body:
label: 📜 Relevant Log Output
description: |
Please copy and paste any relevant log output.
**Log locations:**
- OSPOS logs: `writable/logs/`
- Web server logs: `/var/log/apache2/` or `/var/log/nginx/`
@@ -185,4 +184,4 @@ body:
- label: I have searched existing issues to ensure this bug has not already been reported
required: true
- label: I have provided all the information requested above
required: true
required: true

View File

@@ -28,7 +28,6 @@ jobs:
fail-fast: false
matrix:
php-version:
- '8.1'
- '8.2'
- '8.3'
- '8.4'

View File

@@ -12,14 +12,6 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: PHP Lint 8.0
uses: dbfx/github-phplint/8.0@master
with:
folder-to-exclude: "! -path \"./vendor/*\" ! -path \"./folder/excluded/*\""
- name: PHP Lint 8.1
uses: dbfx/github-phplint/8.1@master
with:
folder-to-exclude: "! -path \"./vendor/*\" ! -path \"./folder/excluded/*\""
- name: PHP Lint 8.2
uses: dbfx/github-phplint/8.2@master
with:

View File

@@ -34,7 +34,6 @@ jobs:
fail-fast: false
matrix:
php-version:
- '8.1'
- '8.2'
- '8.3'
- '8.4'
@@ -119,4 +118,4 @@ jobs:
- name: Stop MariaDB
if: always()
run: docker stop mysql && docker rm mysql
run: docker stop mysql && docker rm mysql

View File

@@ -1,6 +1,6 @@
## Server Requirements
- PHP version `8.1` to `8.4` are supported, PHP version `≤7.4` is NOT supported. Please note that PHP needs to have the extensions `php-json`, `php-gd`, `php-bcmath`, `php-intl`, `php-openssl`, `php-mbstring`, `php-curl` and `php-xml` installed and enabled. An unstable master build can be downloaded in the releases section.
- PHP version `8.2` to `8.4` are supported, PHP version `≤ 8.1` is NOT supported. Please note that PHP needs to have the extensions `php-json`, `php-gd`, `php-bcmath`, `php-intl`, `php-openssl`, `php-mbstring`, `php-curl` and `php-xml` installed and enabled. An unstable master build can be downloaded in the releases section.
- MySQL `5.7` is supported, also MariaDB replacement `10.x` is supported and might offer better performance.
- Apache `2.4` is supported. Nginx should work fine too, see [wiki page here](https://github.com/opensourcepos/opensourcepos/wiki/Local-Deployment-using-LEMP).
- Raspberry PI based installations proved to work, see [wiki page here](<https://github.com/opensourcepos/opensourcepos/wiki/Installing-on-Raspberry-PI---Orange-PI-(Headless-OSPOS)>).

View File

@@ -102,7 +102,7 @@ NOTE: If you're running non-release code, please make sure you always run the la
- If you have suhosin installed and face an issue with CSRF, please make sure you read [issue #1492](https://github.com/opensourcepos/opensourcepos/issues/1492).
- PHP `≥ 8.1` is required to run this app.
- PHP `≥ 8.2` is required to run this app.
## 🏃 Keep the Machine Running

View File

@@ -55,21 +55,13 @@ class App extends BaseConfig
public string $baseURL; // Defined in the constructor
/**
* Allowed Hostnames for the Site URL.
*
* Security: This is used to validate the HTTP Host header to prevent
* Host Header Injection attacks. If the Host header doesn't match
* an entry in this list, the request will use the first allowed hostname.
*
* IMPORTANT: This MUST be configured for production deployments.
* If empty in production, the application will fail to start.
* In development, it will fall back to 'localhost' with a warning.
*
* Configure via .env file (comma-separated list):
* app.allowedHostnames = 'example.com,www.example.com'
*
* For local development:
* app.allowedHostnames = 'localhost'
* Allowed Hostnames in the Site URL other than the hostname in the baseURL.
* If you want to accept multiple Hostnames, set this.
*
* E.g.,
* When your site URL ($baseURL) is 'http://example.com/', and your site
* also accepts 'http://media.example.com/' and 'http://accounts.example.com/':
* ['media.example.com', 'accounts.example.com']
*
* @var list<string>
*/
@@ -125,7 +117,7 @@ class App extends BaseConfig
| DO NOT CHANGE THIS UNLESS YOU FULLY UNDERSTAND THE REPERCUSSIONS!!
|
*/
public string $permittedURIChars = 'a-z 0-9~%.:_\-=';
public string $permittedURIChars = 'a-z 0-9~%.:_\-';
/**
* --------------------------------------------------------------------------
@@ -286,12 +278,12 @@ class App extends BaseConfig
* @see http://www.html5rocks.com/en/tutorials/security/content-security-policy/
* @see http://www.w3.org/TR/CSP/
*/
public bool $CSPEnabled = false; // TODO: Currently CSP3 tags are not supported so enabling this causes problems with script-src-elem, style-src-attr and style-src-elem
public bool $CSPEnabled = false;
public function __construct()
{
parent::__construct();
// Solution for CodeIgniter 4 limitation: arrays cannot be set from .env
// See: https://github.com/codeigniter4/CodeIgniter4/issues/7311
$envAllowedHostnames = getenv('app.allowedHostnames');
@@ -301,9 +293,9 @@ class App extends BaseConfig
static fn (string $hostname): bool => $hostname !== ''
));
}
$this->https_on = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') || (isset($_ENV['FORCE_HTTPS']) && $_ENV['FORCE_HTTPS'] == 'true');
$host = $this->getValidHost();
$this->baseURL = $this->https_on ? 'https' : 'http';
$this->baseURL .= '://' . $host . '/';
@@ -312,39 +304,39 @@ class App extends BaseConfig
/**
* Validates and returns a trusted hostname.
*
*
* Security: Prevents Host Header Injection attacks (GHSA-jchf-7hr6-h4f3)
* by validating the HTTP_HOST against a whitelist of allowed hostnames.
*
*
* In production: Fails fast if allowedHostnames is not configured.
* In development: Allows localhost fallback with an error log.
*
*
* @return string A validated hostname
* @throws \RuntimeException If allowedHostnames is not configured in production
*/
private function getValidHost(): string
{
$httpHost = $_SERVER['HTTP_HOST'] ?? 'localhost';
// Determine environment
// CodeIgniter's test bootstrap sets $_SERVER['CI_ENVIRONMENT'] = 'testing'
// Check $_SERVER first, then $_ENV, then fall back to 'production'
$environment = $_SERVER['CI_ENVIRONMENT'] ?? $_ENV['CI_ENVIRONMENT'] ?? getenv('CI_ENVIRONMENT') ?: 'production';
if (empty($this->allowedHostnames)) {
$errorMessage =
$errorMessage =
'Security: allowedHostnames is not configured. ' .
'Host header injection protection is disabled. ' .
'Set app.allowedHostnames in your .env file. ' .
'Example: app.allowedHostnames = "example.com,www.example.com" ' .
'Received Host: ' . $httpHost;
// Production: Fail explicitly to prevent silent security vulnerabilities
// Testing and development: Allow localhost fallback
if ($environment === 'production') {
throw new \RuntimeException($errorMessage);
}
log_message('error', $errorMessage . ' Using localhost fallback (development only).');
return 'localhost';
}
@@ -354,7 +346,7 @@ class App extends BaseConfig
}
// Host not in whitelist - use first configured hostname as fallback
log_message('warning',
log_message('warning',
'Security: Rejected HTTP_HOST "' . $httpHost . '" - not in allowedHostnames whitelist. ' .
'Using fallback: ' . $this->allowedHostnames[0]
);

View File

@@ -17,8 +17,6 @@ use CodeIgniter\Config\AutoloadConfig;
*
* NOTE: This class is required prior to Autoloader instantiation,
* and does not extend BaseConfig.
*
* @immutable
*/
class Autoload extends AutoloadConfig
{

View File

@@ -1,23 +1,38 @@
<?php
/*
* The environment testing is reserved for PHPUnit testing. It has special
* conditions built into the framework at various places to assist with that.
* You cant use it for your development.
*/
/*
|--------------------------------------------------------------------------
| ERROR DISPLAY
| ERROR DISPLAY
|--------------------------------------------------------------------------
*/
| In development, we want to show as many errors as possible to help
| make sure they don't make it to production. And save us hours of
| painful debugging.
*/
error_reporting(E_ALL);
ini_set('display_errors', '1');
/*
|--------------------------------------------------------------------------
| DEBUG BACKTRACES
| DEBUG BACKTRACES
|--------------------------------------------------------------------------
*/
| If true, this constant will tell the error screens to display debug
| backtraces along with the other error information. If you would
| prefer to not see this, set this value to false.
*/
defined('SHOW_DEBUG_BACKTRACE') || define('SHOW_DEBUG_BACKTRACE', true);
/*
|--------------------------------------------------------------------------
| DEBUG MODE
| DEBUG MODE
|--------------------------------------------------------------------------
*/
defined('CI_DEBUG') || define('CI_DEBUG', true);
| Debug mode is an experimental flag that can allow changes throughout
| the system. It's not widely used currently, and may not survive
| release of the framework.
*/
defined('CI_DEBUG') || define('CI_DEBUG', true);

View File

@@ -6,6 +6,22 @@ use CodeIgniter\Config\BaseConfig;
class CURLRequest extends BaseConfig
{
/**
* --------------------------------------------------------------------------
* CURLRequest Share Connection Options
* --------------------------------------------------------------------------
*
* Share connection options between requests.
*
* @var list<int>
*
* @see https://www.php.net/manual/en/curl.constants.php#constant.curl-lock-data-connect
*/
public array $shareConnectionOptions = [
CURL_LOCK_DATA_CONNECT,
CURL_LOCK_DATA_DNS,
];
/**
* --------------------------------------------------------------------------
* CURLRequest Share Options

View File

@@ -3,6 +3,7 @@
namespace Config;
use CodeIgniter\Cache\CacheInterface;
use CodeIgniter\Cache\Handlers\ApcuHandler;
use CodeIgniter\Cache\Handlers\DummyHandler;
use CodeIgniter\Cache\Handlers\FileHandler;
use CodeIgniter\Cache\Handlers\MemcachedHandler;
@@ -78,7 +79,7 @@ class Cache extends BaseConfig
* Your file storage preferences can be specified below, if you are using
* the File driver.
*
* @var array<string, int|string|null>
* @var array{storePath?: string, mode?: int}
*/
public array $file = [
'storePath' => WRITEPATH . 'cache/',
@@ -95,7 +96,7 @@ class Cache extends BaseConfig
*
* @see https://codeigniter.com/user_guide/libraries/caching.html#memcached
*
* @var array<string, bool|int|string>
* @var array{host?: string, port?: int, weight?: int, raw?: bool}
*/
public array $memcached = [
'host' => '127.0.0.1',
@@ -108,17 +109,28 @@ class Cache extends BaseConfig
* -------------------------------------------------------------------------
* Redis settings
* -------------------------------------------------------------------------
*
* Your Redis server can be specified below, if you are using
* the Redis or Predis drivers.
*
* @var array<string, int|string|null>
* @var array{
* host?: string,
* password?: string|null,
* port?: int,
* timeout?: int,
* async?: bool,
* persistent?: bool,
* database?: int
* }
*/
public array $redis = [
'host' => '127.0.0.1',
'password' => null,
'port' => 6379,
'timeout' => 0,
'database' => 0,
'host' => '127.0.0.1',
'password' => null,
'port' => 6379,
'timeout' => 0,
'async' => false, // specific to Predis and ignored by the native Redis extension
'persistent' => false,
'database' => 0,
];
/**
@@ -132,6 +144,7 @@ class Cache extends BaseConfig
* @var array<string, class-string<CacheInterface>>
*/
public array $validHandlers = [
'apcu' => ApcuHandler::class,
'dummy' => DummyHandler::class,
'file' => FileHandler::class,
'memcached' => MemcachedHandler::class,
@@ -158,4 +171,28 @@ class Cache extends BaseConfig
* @var bool|list<string>
*/
public $cacheQueryString = false;
/**
* --------------------------------------------------------------------------
* Web Page Caching: Cache Status Codes
* --------------------------------------------------------------------------
*
* HTTP status codes that are allowed to be cached. Only responses with
* these status codes will be cached by the PageCache filter.
*
* Default: [] - Cache all status codes (backward compatible)
*
* Recommended: [200] - Only cache successful responses
*
* You can also use status codes like:
* [200, 404, 410] - Cache successful responses and specific error codes
* [200, 201, 202, 203, 204] - All 2xx successful responses
*
* WARNING: Using [] may cache temporary error pages (404, 500, etc).
* Consider restricting to [200] for production applications to avoid
* caching errors that should be temporary.
*
* @var list<int>
*/
public array $cacheStatusCodes = [];
}

View File

@@ -30,6 +30,11 @@ class ContentSecurityPolicy extends BaseConfig
*/
public ?string $reportURI = null;
/**
* Specifies a reporting endpoint to which violation reports ought to be sent.
*/
public ?string $reportTo = null;
/**
* Instructs user agents to rewrite URL schemes, changing
* HTTP to HTTPS. This directive is for websites with
@@ -38,12 +43,12 @@ class ContentSecurityPolicy extends BaseConfig
public bool $upgradeInsecureRequests = false;
// -------------------------------------------------------------------------
// Sources allowed
// CSP DIRECTIVES SETTINGS
// NOTE: once you set a policy to 'none', it cannot be further restricted
// -------------------------------------------------------------------------
/**
* Will default to self if not overridden
* Will default to `'self'` if not overridden
*
* @var list<string>|string|null
*/
@@ -64,6 +69,21 @@ class ContentSecurityPolicy extends BaseConfig
'www.google.com www.gstatic.com'
];
/**
* Specifies valid sources for JavaScript <script> elements.
*
* @var list<string>|string
*/
public array|string $scriptSrcElem = 'self';
/**
* Specifies valid sources for JavaScript inline event
* handlers and JavaScript URLs.
*
* @var list<string>|string
*/
public array|string $scriptSrcAttr = 'self';
/**
* Lists allowed stylesheets' URLs.
*
@@ -76,6 +96,21 @@ class ContentSecurityPolicy extends BaseConfig
'https://fonts.googleapis.com',
];
/**
* Specifies valid sources for stylesheets <link> elements.
*
* @var list<string>|string
*/
public array|string $styleSrcElem = 'self';
/**
* Specifies valid sources for stylesheets inline
* style attributes and `<style>` elements.
*
* @var list<string>|string
*/
public array|string $styleSrcAttr = 'self';
/**
* Defines the origins from which images can be loaded.
*
@@ -169,6 +204,11 @@ class ContentSecurityPolicy extends BaseConfig
*/
public $manifestSrc;
/**
* @var list<string>|string
*/
public array|string $workerSrc = [];
/**
* Limits the kinds of plugins a page may invoke.
*
@@ -184,17 +224,17 @@ class ContentSecurityPolicy extends BaseConfig
public $sandbox;
/**
* Nonce tag for style
* Nonce placeholder for style tags.
*/
public string $styleNonceTag = '{csp-style-nonce}';
/**
* Nonce tag for script
* Nonce placeholder for script tags.
*/
public string $scriptNonceTag = '{csp-script-nonce}';
/**
* Replace nonce tag automatically
* Replace nonce tag automatically?
*/
public bool $autoNonce = true;
}

View File

@@ -85,7 +85,7 @@ class Cookie extends BaseConfig
* (empty string) means default SameSite attribute set by browsers (`Lax`)
* will be set on cookies. If set to `None`, `$secure` must also be set.
*
* @phpstan-var 'None'|'Lax'|'Strict'|''
* @var ''|'Lax'|'None'|'Strict'
*/
public string $samesite = 'Lax';

View File

@@ -42,6 +42,8 @@ class Database extends Config
'strictOn' => false,
'failover' => [],
'port' => 3306,
'numberNative' => false,
'foundRows' => false,
'dateFormat' => [
'date' => 'Y-m-d',
'datetime' => 'Y-m-d H:i:s',
@@ -55,26 +57,27 @@ class Database extends Config
* @var array<string, mixed>
*/
public array $tests = [
'DSN' => '',
'hostname' => 'localhost',
'username' => 'admin',
'password' => 'pointofsale',
'database' => 'ospos',
'DBDriver' => 'MySQLi',
'DBPrefix' => 'ospos_',
'pConnect' => false,
'DBDebug' => (ENVIRONMENT !== 'production'),
'charset' => 'utf8mb4',
'DBCollat' => 'utf8mb4_general_ci',
'swapPre' => '',
'encrypt' => false,
'compress' => false,
'strictOn' => false,
'failover' => [],
'port' => 3306,
'foreignKeys' => true,
'busyTimeout' => 1000,
'dateFormat' => [
'DSN' => '',
'hostname' => 'localhost',
'username' => 'admin',
'password' => 'pointofsale',
'database' => 'ospos',
'DBDriver' => 'MySQLi',
'DBPrefix' => 'ospos_',
'pConnect' => false,
'DBDebug' => (ENVIRONMENT !== 'production'),
'charset' => 'utf8mb4',
'DBCollat' => 'utf8mb4_general_ci',
'swapPre' => '',
'encrypt' => false,
'compress' => false,
'strictOn' => false,
'failover' => [],
'port' => 3306,
'foreignKeys' => true,
'busyTimeout' => 1000,
'synchronous' => null,
'dateFormat' => [
'date' => 'Y-m-d',
'datetime' => 'Y-m-d H:i:s',
'time' => 'H:i:s',

View File

@@ -2,9 +2,6 @@
namespace Config;
/**
* @immutable
*/
class DocTypes
{
/**

View File

@@ -30,6 +30,11 @@ class Email extends BaseConfig
*/
public string $SMTPHost = 'mail.mxserver.com';
/**
* Which SMTP authentication method to use: login, plain
*/
public string $SMTPAuthMethod = 'login';
/**
* SMTP Username
*/

View File

@@ -23,6 +23,23 @@ class Encryption extends BaseConfig
*/
public string $key = '';
/**
* --------------------------------------------------------------------------
* Previous Encryption Keys
* --------------------------------------------------------------------------
*
* When rotating encryption keys, add old keys here to maintain ability
* to decrypt data encrypted with previous keys. Encryption always uses
* the current $key. Decryption tries current key first, then falls back
* to previous keys if decryption fails.
*
* In .env file, use comma-separated string:
* encryption.previousKeys = hex2bin:9be8c64fcea509867...,hex2bin:3f5a1d8e9c2b7a4f6...
*
* @var list<string>|string
*/
public array|string $previousKeys = '';
/**
* --------------------------------------------------------------------------
* Encryption Driver to Use

View File

@@ -65,7 +65,10 @@ class Filters extends BaseFilters
* List of filter aliases that are always
* applied before and after every request.
*
* @var array<string, array<string, array<string, string>>>|array<string, list<string>>
* @var array{
* before: array<string, array{except: list<string>|string}>|list<string>,
* after: array<string, array{except: list<string>|string}>|list<string>
* }
*/
public array $globals = [
'before' => [
@@ -100,7 +103,7 @@ class Filters extends BaseFilters
* before or after URI patterns.
*
* Example:
* isLoggedIn' => ['before' => ['account/*', 'profiles/*']]
* 'isLoggedIn' => ['before' => ['account/*', 'profiles/*']]
*
* @var array<string, array<string, list<string>>>
*/

View File

@@ -61,4 +61,13 @@ class Format extends BaseConfig
'application/xml' => 0,
'text/xml' => 0,
];
/**
* --------------------------------------------------------------------------
* Maximum depth for JSON encoding.
* --------------------------------------------------------------------------
*
* This value determines how deep the JSON encoder will traverse nested structures.
*/
public int $jsonEncodeDepth = 512;
}

40
app/Config/Hostnames.php Normal file
View File

@@ -0,0 +1,40 @@
<?php
namespace Config;
class Hostnames
{
// List of known two-part TLDs for subdomain extraction
public const TWO_PART_TLDS = [
'co.uk', 'org.uk', 'gov.uk', 'ac.uk', 'sch.uk', 'ltd.uk', 'plc.uk',
'com.au', 'net.au', 'org.au', 'edu.au', 'gov.au', 'asn.au', 'id.au',
'co.jp', 'ac.jp', 'go.jp', 'or.jp', 'ne.jp', 'gr.jp',
'co.nz', 'org.nz', 'govt.nz', 'ac.nz', 'net.nz', 'geek.nz', 'maori.nz', 'school.nz',
'co.in', 'net.in', 'org.in', 'ind.in', 'ac.in', 'gov.in', 'res.in',
'com.cn', 'net.cn', 'org.cn', 'gov.cn', 'edu.cn',
'com.sg', 'net.sg', 'org.sg', 'gov.sg', 'edu.sg', 'per.sg',
'co.za', 'org.za', 'gov.za', 'ac.za', 'net.za',
'co.kr', 'or.kr', 'go.kr', 'ac.kr', 'ne.kr', 'pe.kr',
'co.th', 'or.th', 'go.th', 'ac.th', 'net.th', 'in.th',
'com.my', 'net.my', 'org.my', 'edu.my', 'gov.my', 'mil.my', 'name.my',
'com.mx', 'org.mx', 'net.mx', 'edu.mx', 'gob.mx',
'com.br', 'net.br', 'org.br', 'gov.br', 'edu.br', 'art.br', 'eng.br',
'co.il', 'org.il', 'ac.il', 'gov.il', 'net.il', 'muni.il',
'co.id', 'or.id', 'ac.id', 'go.id', 'net.id', 'web.id', 'my.id',
'com.hk', 'edu.hk', 'gov.hk', 'idv.hk', 'net.hk', 'org.hk',
'com.tw', 'net.tw', 'org.tw', 'edu.tw', 'gov.tw', 'idv.tw',
'com.sa', 'net.sa', 'org.sa', 'gov.sa', 'edu.sa', 'sch.sa', 'med.sa',
'co.ae', 'net.ae', 'org.ae', 'gov.ae', 'ac.ae', 'sch.ae',
'com.tr', 'net.tr', 'org.tr', 'gov.tr', 'edu.tr', 'av.tr', 'gen.tr',
'co.ke', 'or.ke', 'go.ke', 'ac.ke', 'sc.ke', 'me.ke', 'mobi.ke', 'info.ke',
'com.ng', 'org.ng', 'gov.ng', 'edu.ng', 'net.ng', 'sch.ng', 'name.ng',
'com.pk', 'net.pk', 'org.pk', 'gov.pk', 'edu.pk', 'fam.pk',
'com.eg', 'edu.eg', 'gov.eg', 'org.eg', 'net.eg',
'com.cy', 'net.cy', 'org.cy', 'gov.cy', 'ac.cy',
'com.lk', 'org.lk', 'edu.lk', 'gov.lk', 'net.lk', 'int.lk',
'com.bd', 'net.bd', 'org.bd', 'ac.bd', 'gov.bd', 'mil.bd',
'com.ar', 'net.ar', 'org.ar', 'gov.ar', 'edu.ar', 'mil.ar',
'gob.cl', 'com.pl', 'net.pl', 'org.pl', 'gov.pl', 'edu.pl',
'co.ir', 'ac.ir', 'org.ir', 'id.ir', 'gov.ir', 'sch.ir', 'net.ir',
];
}

View File

@@ -16,6 +16,8 @@ class Images extends BaseConfig
/**
* The path to the image library.
* Required for ImageMagick, GraphicsMagick, or NetPBM.
*
* @deprecated 4.7.0 No longer used.
*/
public string $libraryPath = '/usr/local/bin/convert';

View File

@@ -4,6 +4,7 @@ namespace Config;
use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Log\Handlers\FileHandler;
use CodeIgniter\Log\Handlers\HandlerInterface;
class Logger extends BaseConfig
{
@@ -73,7 +74,7 @@ class Logger extends BaseConfig
* Handlers are executed in the order defined in this array, starting with
* the handler on top and continuing down.
*
* @var array<class-string, array<string, int|list<string>|string>>
* @var array<class-string<HandlerInterface>, array<string, int|list<string>|string>>
*/
public array $handlers = [
/*

View File

@@ -47,4 +47,19 @@ class Migrations extends BaseConfig
* - Y_m_d_His_
*/
public string $timestampFormat = 'YmdHis_';
/**
* --------------------------------------------------------------------------
* Enable/Disable Migration Lock
* --------------------------------------------------------------------------
*
* Locking is disabled by default.
*
* When enabled, it will prevent multiple migration processes
* from running at the same time by using a lock mechanism.
*
* This is useful in production environments to avoid conflicts
* or race conditions during concurrent deployments.
*/
public bool $lock = false;
}

View File

@@ -3,8 +3,6 @@
namespace Config;
/**
* Mimes
*
* This file contains an array of mime types. It is used by the
* Upload class to help identify allowed file types.
*
@@ -15,8 +13,6 @@ namespace Config;
*
* When working with mime types, please make sure you have the ´fileinfo´
* extension enabled to reliably detect the media types.
*
* @immutable
*/
class Mimes
{
@@ -482,13 +478,16 @@ class Mimes
'application/sla',
'application/vnd.ms-pki.stl',
'application/x-navistyle',
'model/stl',
'application/octet-stream',
],
];
/**
* Attempts to determine the best mime type for the given file extension.
*
* @return string|null The mime type found, or none if unable to determine.
* @param string $extension
* @return array|string|null The mime type found, or none if unable to determine.
*/
public static function guessTypeFromExtension(string $extension): array|string|null
{
@@ -524,7 +523,7 @@ class Mimes
}
// Reverse check the mime type list if no extension was proposed.
// This search is order sensitive!
// This search is order-sensitive!
foreach (static::$mimes as $ext => $types) {
if (in_array($type, (array) $types, true)) {
return $ext;

View File

@@ -9,8 +9,6 @@ use CodeIgniter\Modules\Modules as BaseModules;
*
* NOTE: This class is required prior to Autoloader instantiation,
* and does not extend BaseConfig.
*
* @immutable
*/
class Modules extends BaseModules
{

View File

@@ -8,7 +8,7 @@ namespace Config;
* NOTE: This class does not extend BaseConfig for performance reasons.
* So you cannot replace the property values with Environment Variables.
*
* @immutable
* WARNING: Do not use these options when running the app in the Worker Mode.
*/
class Optimize
{

View File

@@ -15,8 +15,6 @@ namespace Config;
*
* NOTE: This class is required prior to Autoloader instantiation,
* and does not extend BaseConfig.
*
* @immutable
*/
class Paths
{
@@ -77,4 +75,16 @@ class Paths
* is used when no value is provided to `Services::renderer()`.
*/
public string $viewDirectory = __DIR__ . '/../Views';
/**
* ---------------------------------------------------------------
* ENVIRONMENT DIRECTORY NAME
* ---------------------------------------------------------------
*
* This variable must contain the name of the directory where
* the .env file is located.
* Please consider security implications when changing this
* value - the directory should not be publicly accessible.
*/
public string $envDirectory = __DIR__ . '/../../';
}

View File

@@ -96,6 +96,15 @@ class Routing extends BaseRouting
*/
public bool $autoRoute = true;
/**
* If TRUE, the system will look for attributes on controller
* class and methods that can run before and after the
* controller/method.
*
* If FALSE, will ignore any attributes.
*/
public bool $useControllerAttributes = true;
/**
* For Defined Routes.
* If TRUE, will enable the use of the 'prioritize' option

View File

@@ -13,9 +13,9 @@ class Security extends BaseConfig
*
* Protection Method for Cross Site Request Forgery protection.
*
* @var string|false 'cookie', 'session', or false
* @var string 'cookie' or 'session'
*/
public string|false $csrfProtection = 'session';
public string $csrfProtection = 'session';
/**
* --------------------------------------------------------------------------

View File

@@ -2,6 +2,7 @@
namespace Config;
use App\Libraries\MY_Language;
use Locale;
use HTMLPurifier;
use HTMLPurifier_Config;
@@ -38,9 +39,11 @@ class Services extends BaseService
/**
* Responsible for loading the language string translations.
*
* @param string|null $locale
* @param bool $getShared
* @return MY_Language
*/
public static function language(?string $locale = null, bool $getShared = true)
public static function language(?string $locale = null, bool $getShared = true): MY_Language
{
if ($getShared) {
return static::getSharedInstance('language', $locale)->setLocale($locale);
@@ -55,12 +58,12 @@ class Services extends BaseService
// Use '?:' for empty string check
$locale = $locale ?: $requestLocale;
return new \App\Libraries\MY_Language($locale);
return new MY_Language($locale);
}
private static $htmlPurifier;
private static HTMLPurifier $htmlPurifier;
public static function htmlPurifier($getShared = true)
public static function htmlPurifier($getShared = true): object
{
if ($getShared) {
return static::getSharedInstance('htmlPurifier');

View File

@@ -3,10 +3,10 @@
namespace Config;
use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Session\Handlers\BaseHandler;
use CodeIgniter\Session\Handlers\DatabaseHandler;
use CodeIgniter\Session\Handlers\FileHandler;
use Config\Database;
class Session extends BaseConfig
{
@@ -139,7 +139,7 @@ class Session extends BaseConfig
$this->driver = FileHandler::class;
$this->savePath = WRITEPATH . 'session';
}
} catch (\CodeIgniter\Database\Exceptions\DatabaseException $e) {
} catch (DatabaseException $e) {
$this->driver = FileHandler::class;
$this->savePath = WRITEPATH . 'session';
}

View File

@@ -119,4 +119,29 @@ class Toolbar extends BaseConfig
public array $watchedExtensions = [
'php', 'css', 'js', 'html', 'svg', 'json', 'env',
];
/**
* --------------------------------------------------------------------------
* Ignored HTTP Headers
* --------------------------------------------------------------------------
*
* CodeIgniter Debug Toolbar normally injects HTML and JavaScript into every
* HTML response. This is correct for full page loads, but it breaks requests
* that expect only a clean HTML fragment.
*
* Libraries like HTMX, Unpoly, and Hotwire (Turbo) update parts of the page or
* manage navigation on the client side. Injecting the Debug Toolbar into their
* responses can cause invalid HTML, duplicated scripts, or JavaScript errors
* (such as infinite loops or "Maximum call stack size exceeded").
*
* Any request containing one of the following headers is treated as a
* client-managed or partial request, and the Debug Toolbar injection is skipped.
*
* @var array<string, string|null>
*/
public array $disableOnHeaders = [
'X-Requested-With' => 'xmlhttprequest', // AJAX requests
'HX-Request' => 'true', // HTMX requests
'X-Up-Version' => null, // Unpoly partial requests
];
}

View File

@@ -230,9 +230,13 @@ class UserAgents extends BaseConfig
*/
public array $robots = [
'googlebot' => 'Googlebot',
'google-pagerenderer' => 'Google Page Renderer',
'google-read-aloud' => 'Google Read Aloud',
'google-safety' => 'Google Safety Bot',
'msnbot' => 'MSNBot',
'baiduspider' => 'Baiduspider',
'bingbot' => 'Bing',
'bingpreview' => 'BingPreview',
'slurp' => 'Inktomi Slurp',
'yahoo' => 'Yahoo',
'ask jeeves' => 'Ask Jeeves',
@@ -248,5 +252,11 @@ class UserAgents extends BaseConfig
'ia_archiver' => 'Alexa Crawler',
'MJ12bot' => 'Majestic-12',
'Uptimebot' => 'Uptimebot',
'duckduckbot' => 'DuckDuckBot',
'sogou' => 'Sogou Spider',
'exabot' => 'Exabot',
'bot' => 'Generic Bot',
'crawler' => 'Generic Crawler',
'spider' => 'Generic Spider',
];
}

View File

@@ -59,4 +59,21 @@ class View extends BaseView
* @var list<class-string<ViewDecoratorInterface>>
*/
public array $decorators = [];
/**
* Subdirectory within app/Views for namespaced view overrides.
*
* Namespaced views will be searched in:
*
* app/Views/{$appOverridesFolder}/{Namespace}/{view_path}.{php|html...}
*
* This allows application-level overrides for package or module views
* without modifying vendor source files.
*
* Examples:
* 'overrides' -> app/Views/overrides/Example/Blog/post/card.php
* 'vendor' -> app/Views/vendor/Example/Blog/post/card.php
* '' -> app/Views/Example/Blog/post/card.php (direct mapping)
*/
public string $appOverridesFolder = 'overrides';
}

62
app/Config/WorkerMode.php Normal file
View File

@@ -0,0 +1,62 @@
<?php
namespace Config;
/**
* This configuration controls how CodeIgniter behaves when running
* in worker mode (with FrankenPHP).
*/
class WorkerMode
{
/**
* Persistent Services
*
* List of service names that should persist across requests.
* These services will NOT be reset between requests.
*
* Services not in this list will be reset for each request to prevent
* state leakage.
*
* Recommended persistent services:
* - `autoloader`: PSR-4 autoloading configuration
* - `locator`: File locator
* - `exceptions`: Exception handler
* - `commands`: CLI commands registry
* - `codeigniter`: Main application instance
* - `superglobals`: Superglobals wrapper
* - `routes`: Router configuration
* - `cache`: Cache instance
*
* @var list<string>
*/
public array $persistentServices = [
'autoloader',
'locator',
'exceptions',
'commands',
'codeigniter',
'superglobals',
'routes',
'cache',
];
/**
* Reset Event Listeners
*
* List of event names whose listeners should be removed between requests.
* Use this if you register event listeners inside other event callbacks
* (rather than at the top level of Config/Events.php), which would cause
* them to accumulate across requests in worker mode.
*
* @var list<string>
*/
public array $resetEventListeners = [];
/**
* Force Garbage Collection
*
* Whether to force garbage collection after each request.
* Helps prevent memory leaks at a small performance cost.
*/
public bool $forceGarbageCollection = true;
}

View File

@@ -3,56 +3,46 @@
namespace App\Controllers;
use CodeIgniter\Controller;
use CodeIgniter\HTTP\CLIRequest;
use CodeIgniter\HTTP\IncomingRequest;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Psr\Log\LoggerInterface;
/**
* Class BaseController
*
* BaseController provides a convenient place for loading components
* and performing functions that are needed by all your controllers.
* Extend this class in any new controllers:
* class Home extends BaseController
*
* For security be sure to declare any new methods as protected or private.
* Extend this class in any new controllers:
* ```
* class Home extends BaseController
* ```
*
* For security, be sure to declare any new methods as protected or private.
*/
abstract class BaseController extends Controller
{
/**
* Instance of the main Request object.
*
* @var CLIRequest|IncomingRequest
*/
protected $request;
/**
* An array of helpers to be loaded automatically upon
* class instantiation. These helpers will be available
* to all other controllers that extend BaseController.
*
* @var list<string>
*/
protected $helpers = [];
/**
* Be sure to declare properties for any property fetch you initialized.
* The creation of dynamic property is deprecated in PHP 8.2.
*/
// protected $session;
/**
* @param RequestInterface $request
* @param ResponseInterface $response
* @param LoggerInterface $logger
* @return void
*/
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger): void
{
// Do Not Edit This Line
// Load here all helpers you want to be available in your controllers that extend BaseController.
// Caution: Do not put the this below the parent::initController() call below.
// $this->helpers = ['form', 'url'];
// Caution: Do not edit this line.
parent::initController($request, $response, $logger);
// Preload any models, libraries, etc, here.
// E.g.: $this->session = service('session');
// $this->session = service('session');
}
}

View File

@@ -35,12 +35,12 @@ class Home extends Secure_Controller
}
/**
* Load "change employee password" form
* Load the "change employee password" form
*
* @param int $employeeId
* @return ResponseInterface|string
* @noinspection PhpUnused
*/
public function getChangePassword(int $employeeId = NEW_ENTRY)
public function getChangePassword(int $employeeId = NEW_ENTRY): ResponseInterface|string
{
$loggedInEmployee = $this->employee->get_logged_in_employee_info();
$currentPersonId = $loggedInEmployee->person_id;

View File

@@ -190,11 +190,11 @@ class Receivings extends Secure_Controller
/**
* Edit line item in current receiving. Used in app/Views/receivings/receiving.php
*
* @param string|int|null $item_id
* @param int|string|null $item_id
* @return string
* @noinspection PhpUnused
*/
public function postEditItem($item_id): string
public function postEditItem(int|string|null $item_id): string
{
$data = [];
@@ -242,7 +242,7 @@ class Receivings extends Secure_Controller
}
$receiving_info = $this->receiving->get_info($receiving_id)->getRowArray();
$current_employee_id = $this->employee->get_logged_in_employee_info()->person_id;
$can_assign_employee = $this->employee->has_grant('employees', $current_employee_id);
@@ -280,8 +280,10 @@ class Receivings extends Secure_Controller
}
/**
* @throws ReflectionException
* @param int $receiving_id
* @param bool $update_inventory
* @return ResponseInterface
* @throws ReflectionException
*/
public function postDelete(int $receiving_id = -1, bool $update_inventory = true): ResponseInterface
{

View File

@@ -425,7 +425,7 @@ class Sales extends Secure_Controller
$new_giftcard_value = $giftcard->get_giftcard_value($giftcard_num) - $this->sale_lib->get_amount_due();
$new_giftcard_value = max($new_giftcard_value, 0);
$this->sale_lib->set_giftcard_remainder($new_giftcard_value);
$new_giftcard_value = str_replace('$', '\$', to_currency($new_giftcard_value));
$new_giftcard_value = to_currency($new_giftcard_value);
$data['warning'] = lang('Giftcards.remaining_balance', [$giftcard_num, $new_giftcard_value]);
$amount_tendered = min($this->sale_lib->get_amount_due(), $giftcard->get_giftcard_value($giftcard_num));

View File

@@ -5,6 +5,7 @@ use App\Models\Employee;
use App\Models\Item_taxes;
use App\Models\Tax_category;
use CodeIgniter\Database\ResultInterface;
use CodeIgniter\HTTP\IncomingRequest;
use CodeIgniter\Session\Session;
use Config\OSPOS;
use Config\Services;
@@ -577,8 +578,8 @@ function item_kit_headers(): array
['item_kit_number' => lang('Item_kits.item_kit_number')],
['name' => lang('Item_kits.name')],
['description' => lang('Item_kits.description')],
['total_cost_price' => lang('Items.cost_price'), 'sortable' => FALSE],
['total_unit_price' => lang('Items.unit_price'), 'sortable' => FALSE]
['total_cost_price' => lang('Items.cost_price'), 'sortable' => false],
['total_unit_price' => lang('Items.unit_price'), 'sortable' => false]
];
}
@@ -654,7 +655,7 @@ function expand_attribute_values(array $definition_names, array $row): array
foreach ($definition_names as $definition_id => $definitionInfo) {
if (isset($indexed_values[$definition_id])) {
$raw_value = $indexed_values[$definition_id];
// Format DECIMAL attributes according to locale
if (is_array($definitionInfo) && isset($definitionInfo['type']) && $definitionInfo['type'] === DECIMAL) {
$attribute_values["$definition_id"] = to_decimals($raw_value);
@@ -742,7 +743,7 @@ function get_expense_category_manage_table_headers(): string
}
/**
* Gets the html data row for the expenses category
* Gets the html data row for the expense category
*/
function get_expense_category_data_row(object $expense_category): array
{
@@ -841,7 +842,7 @@ function get_expenses_data_last_row(object $expense): array
}
/**
* Get the expenses payments summary
* Get the expense payments summary
*/
function get_expenses_manage_payments_summary(array $payments, ResultInterface $expenses): string // TODO: $expenses is passed but never used.
{
@@ -933,22 +934,22 @@ function get_controller(): string
}
/**
* Restores filter values from URL query string.
*
* @param CodeIgniter\HTTP\IncomingRequest $request The request object
* Restores filter values from the URL query string.
*
* @param IncomingRequest $request The request object
* @return array Array with 'start_date', 'end_date', and 'selected_filters' keys
*/
function restoreTableFilters($request): array
function restoreTableFilters(IncomingRequest $request): array
{
$startDate = $request->getGet('start_date', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$endDate = $request->getGet('end_date', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$urlFilters = $request->getGet('filters', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
return array_filter([
'start_date' => $startDate ?: null,
'end_date' => $endDate ?: null,
'selected_filters' => $urlFilters ?? []
], function($value) {
], function ($value) {
return $value !== null && $value !== [];
});
}

View File

@@ -7,7 +7,7 @@ if (!function_exists('base64url_encode')) {
* @param string $data
* @return string
*/
function base64url_encode($data)
function base64url_encode(string $data): string
{
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
@@ -20,7 +20,7 @@ if (!function_exists('base64url_decode')) {
* @param string $data
* @return string|false
*/
function base64url_decode($data)
function base64url_decode(string $data): false|string
{
$remainder = strlen($data) % 4;
if ($remainder) {
@@ -28,4 +28,4 @@ if (!function_exists('base64url_decode')) {
}
return base64_decode(strtr($data, '-_', '+/'));
}
}
}

View File

@@ -6,8 +6,7 @@ use CodeIgniter\Language\Language;
class MY_Language extends Language
{
public function getLine(string $line, array $args = [])
public function getLine(string $line, array $args = []): array|string
{
// If no file is given, just parse the line
if (! str_contains($line, '.')) {
@@ -20,7 +19,7 @@ class MY_Language extends Language
$output = $this->getTranslationOutput($this->locale, $file, $parsedLine);
if ($output === NULL && strpos($this->locale, '-')) {
if ($output === null && strpos($this->locale, '-')) {
[$locale] = explode('-', $this->locale, 2);
[$file, $parsedLine] = $this->parseLine($line, $locale);
@@ -29,7 +28,7 @@ class MY_Language extends Language
}
// If still not found, try English
if ($output === NULL || $output === "") {
if ($output === null || $output === "") {
[$file, $parsedLine] = $this->parseLine($line, 'en');
$output = $this->getTranslationOutput('en', $file, $parsedLine);

View File

@@ -394,7 +394,7 @@ class Receiving_lib
/**
* @param $line int|string The item_number or item_id of the item to be removed from the receiving.
*/
public function delete_item($line): void
public function delete_item(int|string $line): void
{
$items = $this->get_cart();
unset($items[$line]);

View File

@@ -575,10 +575,10 @@ class Attribute extends Model
/**
* @param string $definition_name
* @param $definition_type
* @param string|bool $definition_type
* @return array
*/
public function get_definition_by_name(string $definition_name, $definition_type = false): array
public function get_definition_by_name(string $definition_name, string|bool $definition_type = false): array
{
$builder = $this->db->table('attribute_definitions');
$builder->where('definition_name', $definition_name);
@@ -606,22 +606,43 @@ class Attribute extends Model
$this->db->transStart();
$definitionType = $this->getAttributeInfo($definitionId)->definition_type ?? '';
$builder = $this->db->table('attribute_links');
if ($this->attributeLinkExists($normalizedItemId, $definitionId)) {
$builder->set(['attribute_id' => $normalizedAttributeId]);
$builder->where('definition_id', $definitionId);
if ($definitionType === DROPDOWN && $normalizedItemId === null) {
$builder->where('item_id', $normalizedItemId);
$builder->where('definition_id', $definitionId);
$builder->where('attribute_id', $normalizedAttributeId);
$builder->where('sale_id', null);
$builder->where('receiving_id', null);
$builder->update();
$dropdownAttributeLinkExists = $builder->countAllResults(false) !== 0;
if (!$dropdownAttributeLinkExists) {
$data = [
'attribute_id' => $normalizedAttributeId,
'item_id' => $normalizedItemId,
'definition_id' => $definitionId
];
$builder->insert($data);
}
} else {
$data = [
'attribute_id' => $normalizedAttributeId,
'item_id' => $normalizedItemId,
'definition_id' => $definitionId
];
$builder->insert($data);
if ($this->attributeLinkExists($normalizedItemId, $definitionId)) {
$builder->set(['attribute_id' => $normalizedAttributeId]);
$builder->where('definition_id', $definitionId);
$builder->where('item_id', $normalizedItemId);
$builder->where('sale_id', null);
$builder->where('receiving_id', null);
$builder->update();
} else {
$data = [
'attribute_id' => $normalizedAttributeId,
'item_id' => $normalizedItemId,
'definition_id' => $definitionId
];
$builder->insert($data);
}
}
$this->db->transComplete();

View File

@@ -4,6 +4,7 @@ namespace App\Models;
use CodeIgniter\Database\ResultInterface;
use Config\OSPOS;
use stdClass;
/**
* Customer class
@@ -128,7 +129,7 @@ class Customer extends Person
/**
* Gets stats about a particular customer
*/
public function get_stats(int $customer_id)
public function get_stats(int $customer_id): ?stdClass
{
$db_prefix = $this->db->getPrefix();
$totals_decimals = totals_decimals();

View File

@@ -4,6 +4,7 @@ namespace App\Models;
use CodeIgniter\Database\ResultInterface;
use CodeIgniter\Session\Session;
use stdClass;
/**
* Employee class
@@ -407,7 +408,7 @@ class Employee extends Person
/**
* Gets information about the currently logged in employee.
*/
public function get_logged_in_employee_info()
public function get_logged_in_employee_info(): float|false|array|int|string|stdClass|null
{
if ($this->is_logged_in()) {
return $this->get_info($this->session->get('person_id'));

View File

@@ -352,7 +352,7 @@ class Item extends Model
/**
* Gets information about a particular item by item id or number
*/
public function get_info_by_id_or_number(string $item_id, bool $include_deleted = true)
public function get_info_by_id_or_number(string $item_id, bool $include_deleted = true): stdClass|string
{
$builder = $this->db->table('items');
$builder->groupStart();
@@ -547,9 +547,9 @@ class Item extends Model
public function get_search_suggestion_format(?string $seed = null): string
{
$config = config(OSPOS::class)->settings;
$suggestionsFirstColumn = $this->suggestionColumnIsAllowed($config['suggestions_first_column'])
? $config['suggestions_first_column']
? $config['suggestions_first_column']
: 'name';
$seed .= ',' . $suggestionsFirstColumn;
@@ -573,14 +573,14 @@ class Item extends Model
$config = config(OSPOS::class)->settings;
$label = '';
$label1 = $this->suggestionColumnIsAllowed($config['suggestions_first_column'])
? $config['suggestions_first_column']
$label1 = $this->suggestionColumnIsAllowed($config['suggestions_first_column'])
? $config['suggestions_first_column']
: 'name';
$label2 = $this->suggestionColumnIsAllowed($config['suggestions_second_column'])
? $config['suggestions_second_column']
$label2 = $this->suggestionColumnIsAllowed($config['suggestions_second_column'])
? $config['suggestions_second_column']
: '';
$label3 = $this->suggestionColumnIsAllowed($config['suggestions_third_column'])
? $config['suggestions_third_column']
$label3 = $this->suggestionColumnIsAllowed($config['suggestions_third_column'])
? $config['suggestions_third_column']
: '';
$this->format_result_numbers($result_row);

View File

@@ -11,34 +11,31 @@ $barcode_lib = new Barcode_lib();
<!doctype html>
<html lang="<?= current_language_code() ?>">
<head>
<meta charset="utf-8">
<title><?= lang('Items.generate_barcodes') ?></title>
<link rel="stylesheet" href="<?= base_url() ?>css/barcode_font.css">
<style>
.barcode svg {
height: <?= $barcode_config['barcode_height'] ?>px;
width: <?= $barcode_config['barcode_width'] ?>px;
}
</style>
</head>
<body class=<?= 'font_' . $barcode_lib->get_font_name($barcode_config['barcode_font']) ?> style="font-size: <?= $barcode_config['barcode_font_size'] ?>px;">
<table cellspacing="<?= $barcode_config['barcode_page_cellspacing'] ?>" width="<?= $barcode_config['barcode_page_width'] . '%' ?>">
<tr>
<?php
$count = 0;
foreach ($items as $item) {
if ($count % $barcode_config['barcode_num_in_row'] == 0 && $count != 0) {
echo '</tr><tr>';
}
echo '<td>' . $barcode_lib->display_barcode($item, $barcode_config) . '</td>';
$count++;
<head>
<meta charset="utf-8">
<title><?= lang('Items.generate_barcodes') ?></title>
<link rel="stylesheet" href="<?= base_url() ?>css/barcode_font.css">
<style>
.barcode svg {
height: <?= $barcode_config['barcode_height'] ?>px;
width: <?= $barcode_config['barcode_width'] ?>px;
}
?>
</tr>
</table>
</body>
</style>
</head>
<body class=<?= 'font_' . $barcode_lib->get_font_name($barcode_config['barcode_font']) ?> style="font-size: <?= $barcode_config['barcode_font_size'] ?>px;">
<table style="border-spacing: <?= $barcode_config['barcode_page_cellspacing'] ?>; width: <?= $barcode_config['barcode_page_width'] ?>%;">
<tr>
<?php
$count = 0;
foreach ($items as $item) {
if ($count % $barcode_config['barcode_num_in_row'] == 0 && $count != 0) {
echo '</tr><tr>';
}
echo '<td>' . $barcode_lib->display_barcode($item, $barcode_config) . '</td>';
$count++;
}
?>
</tr>
</table>
</body>
</html>

View File

@@ -47,7 +47,7 @@
<?= view('partial/print_receipt', ['print_after_sale' => false, 'selected_printer' => 'takings_printer']) ?>
<div id="title_bar" class="print_hide btn-toolbar">
<button onclick="javascript:printdoc()" class="btn btn-info btn-sm pull-right">
<button onclick="printdoc()" class="btn btn-info btn-sm pull-right">
<span class="glyphicon glyphicon-print">&nbsp;</span><?= lang('Common.print') ?>
</button>
<button class="btn btn-info btn-sm pull-right modal-dlg" data-btn-submit="<?= lang('Common.submit') ?>" data-href="<?= "$controller_name/view" ?>" title="<?= lang(ucfirst($controller_name) . ".new") ?>">

View File

@@ -25,11 +25,9 @@ use Config\OSPOS;
<div class="container">
<div class="row">
<div class="col-sm-2" style="text-align: left;"><br>
<strong>
<p style="min-height: 14.7em;">General Info</p>
<p style="min-height: 10.5em;">User Setup</p><br>
<p>Permissions</p>
</strong>
<p style="min-height: 14.7em; font-weight: bold;">General Info</p>
<p style="min-height: 10.5em; font-weight: bold;">User Setup</p><br>
<p style="font-weight: bold;">Permissions</p>
</div>
<div class="col-sm-8" id="issuetemplate" style="text-align: left;"><br>
<?= lang('Config.ospos_info') . ':' ?>
@@ -38,14 +36,14 @@ use Config\OSPOS;
<div id="TimeError"></div>
Extensions & Modules:<br>
<?php
echo "&#187; GD: ", extension_loaded('gd') ? '<span style="color: green;">Enabled &#x2713</span>' : '<span style="color: red;">Disabled &#x2717</span>', '<br>';
echo "&#187; BC Math: ", extension_loaded('bcmath') ? '<span style="color: green;">Enabled &#x2713</span>' : '<span style="color: red">Disabled &#x2717</span>', '<br>';
echo "&#187; INTL: ", extension_loaded('intl') ? '<span style="color: green;">Enabled &#x2713</span>' : '<span style="color: red">Disabled &#x2717</span>', '<br>';
echo "&#187; OpenSSL: ", extension_loaded('openssl') ? '<span style="color: green;">Enabled &#x2713</span>' : '<span style="color: red">Disabled &#x2717</span>', '<br>';
echo "&#187; MBString: ", extension_loaded('mbstring') ? '<span style="color: green;">Enabled &#x2713</span>' : '<span style="color: red">Disabled &#x2717</span>', '<br>';
echo "&#187; Curl: ", extension_loaded('curl') ? '<span style="color: green;">Enabled &#x2713</span>' : '<span style="color: red">Disabled &#x2717</span>', '<br>';
echo "&#187; Json: ", extension_loaded('json') ? '<span style="color: green;">Enabled &#x2713</span>' : '<span style="color: red">Disabled &#x2717</span>', '<br><br>';
echo "&#187; Xml: ", extension_loaded('xml') ? '<span style="color: green;">Enabled &#x2713</span>' : '<span style="color: red">Disabled &#x2717</span>', '<br><br>';
echo "&#187; GD: ", extension_loaded('gd') ? '<span style="color: green;">Enabled &#x2713</span>' : '<span style="color: red;">Disabled &#x2717</span>', '<br>';
echo "&#187; BC Math: ", extension_loaded('bcmath') ? '<span style="color: green;">Enabled &#x2713</span>' : '<span style="color: red;">Disabled &#x2717</span>', '<br>';
echo "&#187; INTL: ", extension_loaded('intl') ? '<span style="color: green;">Enabled &#x2713</span>' : '<span style="color: red;">Disabled &#x2717</span>', '<br>';
echo "&#187; OpenSSL: ", extension_loaded('openssl') ? '<span style="color: green;">Enabled &#x2713</span>' : '<span style="color: red;">Disabled &#x2717</span>', '<br>';
echo "&#187; MBString: ", extension_loaded('mbstring') ? '<span style="color: green;">Enabled &#x2713</span>' : '<span style="color: red;">Disabled &#x2717</span>', '<br>';
echo "&#187; Curl: ", extension_loaded('curl') ? '<span style="color: green;">Enabled &#x2713</span>' : '<span style="color: red;">Disabled &#x2717</span>', '<br>';
echo "&#187; Json: ", extension_loaded('json') ? '<span style="color: green;">Enabled &#x2713</span>' : '<span style="color: red;">Disabled &#x2717</span>', '<br><br>';
echo "&#187; Xml: ", extension_loaded('xml') ? '<span style="color: green;">Enabled &#x2713</span>' : '<span style="color: red;">Disabled &#x2717</span>', '<br><br>';
?>
User Configuration:<br>
.Browser:

View File

@@ -57,7 +57,7 @@ if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE) {
$args = implode(', ', array_map(static fn ($value): string => match (true) {
is_object($value) => 'Object(' . $value::class . ')',
is_array($value) => $value !== [] ? '[...]' : '[]',
$value === null => 'null', // Return the lowercased version
$value === null => 'null', // return the lowercased version
default => var_export($value, true),
}, array_values($error['args'] ?? [])));

View File

@@ -3,7 +3,7 @@
--main-text-color: #555;
--dark-text-color: #222;
--light-text-color: #c7c7c7;
--brand-primary-color: #E06E3F;
--brand-primary-color: #DC4814;
--light-bg-color: #ededee;
--dark-bg-color: #404040;
}
@@ -71,7 +71,7 @@ p.lead {
text-align: center;
padding: calc(4px + 0.2083vw);
width: 100%;
margin-top: -2.14rem;
top: 0;
position: fixed;
}
@@ -101,11 +101,9 @@ p.lead {
}
.tabs {
list-style: none;
list-style-position: inside;
margin: 0;
list-style: none inside none;
padding: 0;
margin-bottom: -1px;
margin: 0 0 -1px;
}
.tabs li {
display: inline;

View File

@@ -1,6 +1,5 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title><?= lang('Errors.badRequest') ?></title>
@@ -16,7 +15,6 @@
left: 50%;
margin-left: -73px;
}
body {
height: 100%;
background: #fafafa;
@@ -24,7 +22,6 @@
color: #777;
font-weight: 300;
}
h1 {
font-weight: lighter;
letter-spacing: normal;
@@ -33,7 +30,6 @@
margin-bottom: 0;
color: #222;
}
.wrap {
max-width: 1024px;
margin: 5rem auto;
@@ -44,12 +40,10 @@
border-radius: 0.5rem;
position: relative;
}
pre {
white-space: normal;
margin-top: 1.5rem;
}
code {
background: #fafafa;
border: 1px solid #efefef;
@@ -57,11 +51,9 @@
border-radius: 5px;
display: block;
}
p {
margin-top: 1.5rem;
}
.footer {
margin-top: 2rem;
border-top: 1px solid #efefef;
@@ -69,7 +61,6 @@
font-size: 85%;
color: #999;
}
a:active,
a:link,
a:visited {
@@ -77,19 +68,17 @@
}
</style>
</head>
<body>
<div class="wrap">
<h1>400</h1>
<div class="wrap">
<h1>400</h1>
<p>
<?php if (ENVIRONMENT !== 'production') : ?>
<?= nl2br(esc($message)) ?>
<?php else : ?>
<?= lang('Errors.sorryBadRequest') ?>
<?php endif; ?>
</p>
</div>
<p>
<?php if (ENVIRONMENT !== 'production') : ?>
<?= nl2br(esc($message)) ?>
<?php else : ?>
<?= lang('Errors.sorryBadRequest') ?>
<?php endif; ?>
</p>
</div>
</body>
</html>

View File

@@ -1,10 +1,4 @@
<?php
/**
* @var string $message
*/
?>
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">

View File

@@ -14,421 +14,424 @@ $errorId = uniqid('error', true);
?>
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta name="robots" content="noindex">
<head>
<meta charset="UTF-8">
<meta name="robots" content="noindex">
<title><?= esc($title) ?></title>
<style>
<?= preg_replace('#[\r\n\t ]+#', ' ', file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'debug.css')) ?>
</style>
<title><?= esc($title) ?></title>
<style>
<?= preg_replace('#[\r\n\t ]+#', ' ', file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'debug.css')) ?>
</style>
<script type="text/javascript">
<?= file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'debug.js') ?>
</script>
</head>
<body onload="init()">
<script type="text/javascript">
<?= file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'debug.js') ?>
</script>
</head>
<body onload="init()">
<!-- Header -->
<div class="header">
<div class="environment">
Displayed at <?= esc(date('H:i:sa')) ?> &mdash;
PHP: <?= esc(PHP_VERSION) ?> &mdash;
CodeIgniter: <?= esc(CodeIgniter::CI_VERSION) ?> --
Environment: <?= ENVIRONMENT ?>
<!-- Header -->
<div class="header">
<div class="environment">
Displayed at <?= esc(date('H:i:s')) ?> &mdash;
PHP: <?= esc(PHP_VERSION) ?> &mdash;
CodeIgniter: <?= esc(CodeIgniter::CI_VERSION) ?> --
Environment: <?= ENVIRONMENT ?>
</div>
<div class="container">
<h1><?= esc($title), esc($exception->getCode() ? ' #' . $exception->getCode() : '') ?></h1>
<p>
<?= nl2br(esc($exception->getMessage())) ?>
<a href="https://www.duckduckgo.com/?q=<?= urlencode($title . ' ' . preg_replace('#\'.*\'|".*"#Us', '', $exception->getMessage())) ?>"
rel="noreferrer" target="_blank">search &rarr;</a>
</p>
</div>
</div>
<!-- Source -->
<div class="container">
<h1><?= esc($title), esc($exception->getCode() ? ' #' . $exception->getCode() : '') ?></h1>
<p>
<?= nl2br(esc($exception->getMessage())) ?>
<a href="https://www.duckduckgo.com/?q=<?= urlencode($title . ' ' . preg_replace('#\'.*\'|".*"#Us', '', $exception->getMessage())) ?>"
rel="noreferrer" target="_blank">search &rarr;</a>
</p>
<p><b><?= esc(clean_path($file)) ?></b> at line <b><?= esc($line) ?></b></p>
<?php if (is_file($file)) : ?>
<div class="source">
<?= static::highlightFile($file, $line, 15); ?>
</div>
<?php endif; ?>
</div>
</div>
<!-- Source -->
<div class="container">
<p><b><?= esc(clean_path($file)) ?></b> at line <b><?= esc($line) ?></b></p>
<?php if (is_file($file)) : ?>
<div class="source">
<?= static::highlightFile($file, $line, 15); ?>
</div>
<?php endif; ?>
</div>
<div class="container">
<?php
$last = $exception;
while ($prevException = $last->getPrevious()) {
$last = $prevException;
?>
<pre>
Caused by:
<?= esc($prevException::class), esc($prevException->getCode() ? ' #' . $prevException->getCode() : '') ?>
<?= nl2br(esc($prevException->getMessage())) ?>
<a href="https://www.duckduckgo.com/?q=<?= urlencode($prevException::class . ' ' . preg_replace('#\'.*\'|".*"#Us', '', $prevException->getMessage())) ?>" rel="noreferrer" target="_blank">search &rarr;</a>
<?= esc(clean_path($prevException->getFile()) . ':' . $prevException->getLine()) ?>
</pre>
<?php } ?>
</div>
<?php if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE) : ?>
<div class="container">
<ul class="tabs" id="tabs">
<li><a href="#backtrace">Backtrace</a></li>
<li><a href="#server">Server</a></li>
<li><a href="#request">Request</a></li>
<li><a href="#response">Response</a></li>
<li><a href="#files">Files</a></li>
<li><a href="#memory">Memory</a></li>
</ul>
<div class="tab-content">
<!-- Backtrace -->
<div class="content" id="backtrace">
<ol class="trace">
<?php foreach ($trace as $index => $row) : ?>
<li>
<p>
<!-- Trace info -->
<?php if (isset($row['file']) && is_file($row['file'])) : ?>
<?php
if (isset($row['function']) && in_array($row['function'], ['include', 'include_once', 'require', 'require_once'], true)) {
echo esc($row['function'] . ' ' . clean_path($row['file']));
} else {
echo esc(clean_path($row['file']) . ' : ' . $row['line']);
}
?>
<?php else: ?>
{PHP internal code}
<?php endif; ?>
<!-- Class/Method -->
<?php if (isset($row['class'])) : ?>
&nbsp;&nbsp;&mdash;&nbsp;&nbsp;<?= esc($row['class'] . $row['type'] . $row['function']) ?>
<?php if (! empty($row['args'])) : ?>
<?php $argsId = $errorId . 'args' . $index ?>
( <a href="#" onclick="return toggle('<?= esc($argsId, 'attr') ?>');">arguments</a> )
<div class="args" id="<?= esc($argsId, 'attr') ?>">
<table cellspacing="0">
<?php
$params = null;
// Reflection by name is not available for closure function
if (! str_ends_with($row['function'], '}')) {
$mirror = isset($row['class']) ? new ReflectionMethod($row['class'], $row['function']) : new ReflectionFunction($row['function']);
$params = $mirror->getParameters();
}
foreach ($row['args'] as $key => $value) : ?>
<tr>
<td><code><?= esc(isset($params[$key]) ? '$' . $params[$key]->name : "#{$key}") ?></code></td>
<td><pre><?= esc(print_r($value, true)) ?></pre></td>
</tr>
<?php endforeach ?>
</table>
</div>
<?php else : ?>
()
<?php endif; ?>
<?php endif; ?>
<?php if (! isset($row['class']) && isset($row['function'])) : ?>
&nbsp;&nbsp;&mdash;&nbsp;&nbsp; <?= esc($row['function']) ?>()
<?php endif; ?>
</p>
<!-- Source? -->
<?php if (isset($row['file']) && is_file($row['file']) && isset($row['class'])) : ?>
<div class="source">
<?= static::highlightFile($row['file'], $row['line']) ?>
</div>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ol>
</div>
<!-- Server -->
<div class="content" id="server">
<?php foreach (['_SERVER', '_SESSION'] as $var) : ?>
<?php
if (empty($GLOBALS[$var]) || ! is_array($GLOBALS[$var])) {
continue;
} ?>
<h3>$<?= esc($var) ?></h3>
<table>
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<?php foreach ($GLOBALS[$var] as $key => $value) : ?>
<tr>
<td><?= esc($key) ?></td>
<td>
<?php if (is_string($value)) : ?>
<?= esc($value) ?>
<?php else: ?>
<pre><?= esc(print_r($value, true)) ?></pre>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endforeach ?>
<!-- Constants -->
<?php $constants = get_defined_constants(true); ?>
<?php if (! empty($constants['user'])) : ?>
<h3>Constants</h3>
<table>
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<?php foreach ($constants['user'] as $key => $value) : ?>
<tr>
<td><?= esc($key) ?></td>
<td>
<?php if (is_string($value)) : ?>
<?= esc($value) ?>
<?php else: ?>
<pre><?= esc(print_r($value, true)) ?></pre>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<!-- Request -->
<div class="content" id="request">
<?php $request = service('request'); ?>
<table>
<tbody>
<tr>
<td style="width: 10em">Path</td>
<td><?= esc($request->getUri()) ?></td>
</tr>
<tr>
<td>HTTP Method</td>
<td><?= esc($request->getMethod()) ?></td>
</tr>
<tr>
<td>IP Address</td>
<td><?= esc($request->getIPAddress()) ?></td>
</tr>
<tr>
<td style="width: 10em">Is AJAX Request?</td>
<td><?= $request->isAJAX() ? 'yes' : 'no' ?></td>
</tr>
<tr>
<td>Is CLI Request?</td>
<td><?= $request->isCLI() ? 'yes' : 'no' ?></td>
</tr>
<tr>
<td>Is Secure Request?</td>
<td><?= $request->isSecure() ? 'yes' : 'no' ?></td>
</tr>
<tr>
<td>User Agent</td>
<td><?= esc($request->getUserAgent()->getAgentString()) ?></td>
</tr>
</tbody>
</table>
<?php $empty = true; ?>
<?php foreach (['_GET', '_POST', '_COOKIE'] as $var) : ?>
<?php
if (empty($GLOBALS[$var]) || ! is_array($GLOBALS[$var])) {
continue;
} ?>
<?php $empty = false; ?>
<h3>$<?= esc($var) ?></h3>
<table style="width: 100%">
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<?php foreach ($GLOBALS[$var] as $key => $value) : ?>
<tr>
<td><?= esc($key) ?></td>
<td>
<?php if (is_string($value)) : ?>
<?= esc($value) ?>
<?php else: ?>
<pre><?= esc(print_r($value, true)) ?></pre>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endforeach ?>
<?php if ($empty) : ?>
<div class="alert">
No $_GET, $_POST, or $_COOKIE Information to show.
</div>
<?php endif; ?>
<?php $headers = $request->headers(); ?>
<?php if (! empty($headers)) : ?>
<h3>Headers</h3>
<table>
<thead>
<tr>
<th>Header</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<?php foreach ($headers as $name => $value) : ?>
<tr>
<td><?= esc($name, 'html') ?></td>
<td>
<?php
if ($value instanceof Header) {
echo esc($value->getValueLine(), 'html');
} else {
foreach ($value as $i => $header) {
echo ' ('. $i+1 . ') ' . esc($header->getValueLine(), 'html');
}
}
?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<!-- Response -->
<div class="container">
<?php
$response = service('response');
$response->setStatusCode(http_response_code());
?>
<div class="content" id="response">
<table>
<tr>
<td style="width: 15em">Response Status</td>
<td><?= esc($response->getStatusCode() . ' - ' . $response->getReasonPhrase()) ?></td>
</tr>
</table>
$last = $exception;
<?php $headers = $response->headers(); ?>
<?php if (! empty($headers)) : ?>
<h3>Headers</h3>
while ($prevException = $last->getPrevious()) {
$last = $prevException;
?>
<pre>
Caused by:
<?= esc($prevException::class), esc($prevException->getCode() ? ' #' . $prevException->getCode() : '') ?>
<?= nl2br(esc($prevException->getMessage())) ?>
<a href="https://www.duckduckgo.com/?q=<?= urlencode($prevException::class . ' ' . preg_replace('#\'.*\'|".*"#Us', '', $prevException->getMessage())) ?>"
rel="noreferrer" target="_blank">search &rarr;</a>
<?= esc(clean_path($prevException->getFile()) . ':' . $prevException->getLine()) ?>
</pre>
<?php
}
?>
</div>
<?php if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE) : ?>
<div class="container">
<ul class="tabs" id="tabs">
<li><a href="#backtrace">Backtrace</a></li>
<li><a href="#server">Server</a></li>
<li><a href="#request">Request</a></li>
<li><a href="#response">Response</a></li>
<li><a href="#files">Files</a></li>
<li><a href="#memory">Memory</a></li>
</ul>
<div class="tab-content">
<!-- Backtrace -->
<div class="content" id="backtrace">
<ol class="trace">
<?php foreach ($trace as $index => $row) : ?>
<li>
<p>
<!-- Trace info -->
<?php if (isset($row['file']) && is_file($row['file'])) : ?>
<?php
if (isset($row['function']) && in_array($row['function'], ['include', 'include_once', 'require', 'require_once'], true)) {
echo esc($row['function'] . ' ' . clean_path($row['file']));
} else {
echo esc(clean_path($row['file']) . ' : ' . $row['line']);
}
?>
<?php else: ?>
{PHP internal code}
<?php endif; ?>
<!-- Class/Method -->
<?php if (isset($row['class'])) : ?>
&nbsp;&nbsp;&mdash;&nbsp;&nbsp;<?= esc($row['class'] . $row['type'] . $row['function']) ?>
<?php if (! empty($row['args'])) : ?>
<?php $argsId = $errorId . 'args' . $index ?>
( <a href="#" onclick="return toggle('<?= esc($argsId, 'attr') ?>');">arguments</a> )
<div class="args" id="<?= esc($argsId, 'attr') ?>">
<table style="border-spacing: 0;">
<?php
$params = null;
// Reflection by name is not available for closure function
if (! str_ends_with($row['function'], '}')) {
$mirror = isset($row['class']) ? new ReflectionMethod($row['class'], $row['function']) : new ReflectionFunction($row['function']);
$params = $mirror->getParameters();
}
foreach ($row['args'] as $key => $value) : ?>
<tr>
<td><code><?= esc(isset($params[$key]) ? '$' . $params[$key]->name : "#{$key}") ?></code></td>
<td><pre><?= esc(print_r($value, true)) ?></pre></td>
</tr>
<?php endforeach ?>
</table>
</div>
<?php else : ?>
()
<?php endif; ?>
<?php endif; ?>
<?php if (! isset($row['class']) && isset($row['function'])) : ?>
&nbsp;&nbsp;&mdash;&nbsp;&nbsp; <?= esc($row['function']) ?>()
<?php endif; ?>
</p>
<!-- Source? -->
<?php if (isset($row['file']) && is_file($row['file']) && isset($row['class'])) : ?>
<div class="source">
<?= static::highlightFile($row['file'], $row['line']) ?>
</div>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ol>
</div>
<!-- Server -->
<div class="content" id="server">
<?php foreach (['_SERVER', '_SESSION'] as $var) : ?>
<?php
if (empty($GLOBALS[$var]) || ! is_array($GLOBALS[$var])) {
continue;
} ?>
<h3>$<?= esc($var) ?></h3>
<table>
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<?php foreach ($GLOBALS[$var] as $key => $value) : ?>
<tr>
<td><?= esc($key) ?></td>
<td>
<?php if (is_string($value)) : ?>
<?= esc($value) ?>
<?php else: ?>
<pre><?= esc(print_r($value, true)) ?></pre>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endforeach ?>
<!-- Constants -->
<?php $constants = get_defined_constants(true); ?>
<?php if (! empty($constants['user'])) : ?>
<h3>Constants</h3>
<table>
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<?php foreach ($constants['user'] as $key => $value) : ?>
<tr>
<td><?= esc($key) ?></td>
<td>
<?php if (is_string($value)) : ?>
<?= esc($value) ?>
<?php else: ?>
<pre><?= esc(print_r($value, true)) ?></pre>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<!-- Request -->
<div class="content" id="request">
<?php $request = service('request'); ?>
<table>
<thead>
<tr>
<th>Header</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<?php foreach ($headers as $name => $value) : ?>
<tr>
<td><?= esc($name, 'html') ?></td>
<td>
<?php
if ($value instanceof Header) {
echo esc($response->getHeaderLine($name), 'html');
} else {
foreach ($value as $i => $header) {
echo ' ('. $i+1 . ') ' . esc($header->getValueLine(), 'html');
}
}
?>
</td>
<td style="width: 10em">Path</td>
<td><?= esc($request->getUri()) ?></td>
</tr>
<?php endforeach; ?>
<tr>
<td>HTTP Method</td>
<td><?= esc($request->getMethod()) ?></td>
</tr>
<tr>
<td>IP Address</td>
<td><?= esc($request->getIPAddress()) ?></td>
</tr>
<tr>
<td style="width: 10em">Is AJAX Request?</td>
<td><?= $request->isAJAX() ? 'yes' : 'no' ?></td>
</tr>
<tr>
<td>Is CLI Request?</td>
<td><?= $request->isCLI() ? 'yes' : 'no' ?></td>
</tr>
<tr>
<td>Is Secure Request?</td>
<td><?= $request->isSecure() ? 'yes' : 'no' ?></td>
</tr>
<tr>
<td>User Agent</td>
<td><?= esc($request->getUserAgent()->getAgentString()) ?></td>
</tr>
</tbody>
</table>
<?php endif; ?>
</div>
<!-- Files -->
<div class="content" id="files">
<?php $files = get_included_files(); ?>
<?php $empty = true; ?>
<?php foreach (['_GET', '_POST', '_COOKIE'] as $var) : ?>
<?php
if (empty($GLOBALS[$var]) || ! is_array($GLOBALS[$var])) {
continue;
} ?>
<ol>
<?php foreach ($files as $file) :?>
<li><?= esc(clean_path($file)) ?></li>
<?php endforeach ?>
</ol>
</div>
<?php $empty = false; ?>
<!-- Memory -->
<div class="content" id="memory">
<h3>$<?= esc($var) ?></h3>
<table>
<tbody>
<table style="width: 100%">
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<?php foreach ($GLOBALS[$var] as $key => $value) : ?>
<tr>
<td><?= esc($key) ?></td>
<td>
<?php if (is_string($value)) : ?>
<?= esc($value) ?>
<?php else: ?>
<pre><?= esc(print_r($value, true)) ?></pre>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endforeach ?>
<?php if ($empty) : ?>
<div class="alert">
No $_GET, $_POST, or $_COOKIE Information to show.
</div>
<?php endif; ?>
<?php $headers = $request->headers(); ?>
<?php if (! empty($headers)) : ?>
<h3>Headers</h3>
<table>
<thead>
<tr>
<th>Header</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<?php foreach ($headers as $name => $value) : ?>
<tr>
<td><?= esc($name, 'html') ?></td>
<td>
<?php
if ($value instanceof Header) {
echo esc($value->getValueLine(), 'html');
} else {
foreach ($value as $i => $header) {
echo ' (' . ($i + 1) . ') ' . esc($header->getValueLine(), 'html');
}
}
?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<!-- Response -->
<?php
$response = service('response');
$response->setStatusCode(http_response_code());
?>
<div class="content" id="response">
<table>
<tr>
<td>Memory Usage</td>
<td><?= esc(static::describeMemory(memory_get_usage(true))) ?></td>
<td style="width: 15em">Response Status</td>
<td><?= esc($response->getStatusCode() . ' - ' . $response->getReasonPhrase()) ?></td>
</tr>
<tr>
<td style="width: 12em">Peak Memory Usage:</td>
<td><?= esc(static::describeMemory(memory_get_peak_usage(true))) ?></td>
</tr>
<tr>
<td>Memory Limit:</td>
<td><?= esc(ini_get('memory_limit')) ?></td>
</tr>
</tbody>
</table>
</table>
</div>
<?php $headers = $response->headers(); ?>
<?php if (! empty($headers)) : ?>
<h3>Headers</h3>
</div> <!-- /tab-content -->
<table>
<thead>
<tr>
<th>Header</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<?php foreach ($headers as $name => $value) : ?>
<tr>
<td><?= esc($name, 'html') ?></td>
<td>
<?php
if ($value instanceof Header) {
echo esc($response->getHeaderLine($name), 'html');
} else {
foreach ($value as $i => $header) {
echo ' (' . ($i + 1) . ') ' . esc($header->getValueLine(), 'html');
}
}
?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div> <!-- /container -->
<?php endif; ?>
<?php endif; ?>
</div>
</body>
<!-- Files -->
<div class="content" id="files">
<?php $files = get_included_files(); ?>
<ol>
<?php foreach ($files as $file) :?>
<li><?= esc(clean_path($file)) ?></li>
<?php endforeach ?>
</ol>
</div>
<!-- Memory -->
<div class="content" id="memory">
<table>
<tbody>
<tr>
<td>Memory Usage</td>
<td><?= esc(static::describeMemory(memory_get_usage(true))) ?></td>
</tr>
<tr>
<td style="width: 12em">Peak Memory Usage:</td>
<td><?= esc(static::describeMemory(memory_get_peak_usage(true))) ?></td>
</tr>
<tr>
<td>Memory Limit:</td>
<td><?= esc(ini_get('memory_limit')) ?></td>
</tr>
</tbody>
</table>
</div>
</div> <!-- /tab-content -->
</div> <!-- /container -->
<?php endif; ?>
</body>
</html>

View File

@@ -49,13 +49,13 @@
});
});
</script
<?= view('partial/table_filter_persistence') ?>>
</script>
<?= view('partial/table_filter_persistence') ?>
<?= view('partial/print_receipt', ['print_after_sale' => false, 'selected_printer' => 'takings_printer']) ?>
<div id="title_bar" class="print_hide btn-toolbar">
<button onclick="javascript:printdoc()" class="btn btn-info btn-sm pull-right">
<button onclick="printdoc()" class="btn btn-info btn-sm pull-right">
<span class="glyphicon glyphicon-print">&nbsp;</span><?= lang('Common.print') ?>
</button>
<button class="btn btn-info btn-sm pull-right modal-dlg" data-btn-submit="<?= lang('Common.submit') ?>" data-href="<?= "$controller_name/view" ?>" title="<?= lang(ucfirst($controller_name) . '.new') ?>">

View File

@@ -4,21 +4,20 @@ use Config\OSPOS;
?>
</div>
</div>
</div>
<div id="footer">
<div class="jumbotron push-spaces">
<strong>
<?= lang('Common.copyrights', [date('Y')]) ?> ·
<a href="https://opensourcepos.org" target="_blank"><?= lang('Common.website') ?></a> ·
<?= esc(config('App')->application_version) ?> -
<a target="_blank" href="https://github.com/opensourcepos/opensourcepos/commit/<?= esc(config(OSPOS::class)->commit_sha1) ?>">
<?= esc(substr(config(OSPOS::class)->commit_sha1, 0, 6)); ?>
</a>
</strong>.
<div id="footer">
<div class="jumbotron push-spaces">
<strong>
<?= lang('Common.copyrights', [date('Y')]) ?> ·
<a href="https://opensourcepos.org" target="_blank"><?= lang('Common.website') ?></a> ·
<?= esc(config('App')->application_version) ?> -
<a target="_blank" href="https://github.com/opensourcepos/opensourcepos/commit/<?= esc(config(OSPOS::class)->commit_sha1) ?>">
<?= esc(substr(config(OSPOS::class)->commit_sha1, 0, 6)); ?>
</a>
</strong>.
</div>
</div>
</div>
</body>
</body>
</html>

View File

@@ -1,12 +1,12 @@
<?php
/**
* Table Filter Persistence
*
* This partial updates the URL when filters change, allowing users to
*
* This partially updates the URL when filters change, allowing users to
* share/bookmark filtered views and maintain state on back navigation.
*
*
* Filter restoration from URL is handled server-side in the controller.
*
*
* @param array $options Additional filter options
* - 'additional_params': Array of additional parameter names to track (e.g., ['stock_location'])
* - 'filter_select_id': Filter multiselect element ID (default: 'filters')
@@ -20,10 +20,10 @@ $filter_select_id = $options['filter_select_id'] ?? 'filters';
$(document).ready(function() {
var additional_params = <?= json_encode($additional_params) ?>;
var filter_select_id = '<?= esc($filter_select_id) ?>';
function update_url() {
var params = new URLSearchParams();
// Add dates
if (typeof start_date !== 'undefined') {
params.set('start_date', start_date);
@@ -31,7 +31,7 @@ $filter_select_id = $options['filter_select_id'] ?? 'filters';
if (typeof end_date !== 'undefined') {
params.set('end_date', end_date);
}
// Add filters
var filters = $('#' + filter_select_id).val();
if (filters) {
@@ -39,7 +39,7 @@ $filter_select_id = $options['filter_select_id'] ?? 'filters';
params.append('filters[]', filter);
});
}
// Add additional params
additional_params.forEach(function(param) {
var element = $('#' + param);
@@ -54,7 +54,7 @@ $filter_select_id = $options['filter_select_id'] ?? 'filters';
}
}
});
// Update URL without page reload
var new_url = window.location.pathname;
var params_str = params.toString();
@@ -63,22 +63,22 @@ $filter_select_id = $options['filter_select_id'] ?? 'filters';
}
window.history.replaceState({}, '', new_url);
}
// Update URL when filter dropdown changes
$('#' + filter_select_id).on('hidden.bs.select', function(e) {
update_url();
});
// Update URL when stock location changes (if exists)
if ($('#stock_location').length) {
$("#stock_location").change(function() {
update_url();
});
}
// Update URL when daterangepicker changes
$("#daterangepicker").on('apply.daterangepicker', function(ev, picker) {
update_url();
});
});
</script>
</script>

View File

@@ -47,24 +47,26 @@
offset: 60,
// The label interpolation function enables you to modify the values
// used for the labels on each axis.
labelInterpolationFnc: function(value) {
<?php
<?php
$currency_symbol = esc($config['currency_symbol'], 'js');
$currency_prefix = '';
$currency_suffix = '';
if ($show_currency) {
if (is_right_side_currency_symbol()) {
?>
return value + '<?= esc($config['currency_symbol'], 'js') ?>';
<?php } else { ?>
return '<?= esc($config['currency_symbol'], 'js') ?>' + value;
<?php
$currency_suffix = $currency_symbol;
} else {
$currency_prefix = $currency_symbol;
}
} else {
?>
return value;
<?php } ?>
}
?>
labelInterpolationFnc: function(value) {
return '<?= $currency_prefix ?>' + value + '<?= $currency_suffix ?>';
}
},
// Plugins configuration
// Plugin configuration
plugins: [
Chartist.plugins.ctAxisTitle({
axisX: {

View File

@@ -44,30 +44,32 @@
position: 'end',
// The label interpolation function enables you to modify the values
// used for the labels on each axis.
labelInterpolationFnc: function(value) {
<?php
<?php
$currency_symbol = esc($config['currency_symbol'], 'js');
$currency_prefix = '';
$currency_suffix = '';
if ($show_currency) {
if (is_right_side_currency_symbol()) {
?>
return value + '<?= esc($config['currency_symbol'], 'js') ?>';
<?php } else { ?>
return '<?= esc($config['currency_symbol'], 'js') ?>' + value;
<?php
$currency_suffix = $currency_symbol;
} else {
$currency_prefix = $currency_symbol;
}
} else {
?>
return value;
<?php } ?>
}
?>
labelInterpolationFnc: function(value) {
return '<?= $currency_prefix ?>' + value + '<?= $currency_suffix ?>';
}
},
// Y-Axis specific configuration
axisY: {
// Lets offset the chart a bit from the labels
// Let's offset the chart a bit from the labels
offset: 120
},
// Plugins configuration
// Plugin configuration
plugins: [
Chartist.plugins.ctAxisTitle({
axisX: {

View File

@@ -63,24 +63,26 @@
},
// The label interpolation function enables you to modify the values
// used for the labels on each axis.
labelInterpolationFnc: function(value) {
<?php
<?php
$currency_symbol = esc($config['currency_symbol'], 'js');
$currency_prefix = '';
$currency_suffix = '';
if ($show_currency) {
if (is_right_side_currency_symbol()) {
?>
return value + '<?= esc($config['currency_symbol'], 'js') ?>';
<?php } else { ?>
return '<?= esc($config['currency_symbol'], 'js') ?>' + value;
<?php
$currency_suffix = $currency_symbol;
} else {
$currency_prefix = $currency_symbol;
}
} else {
?>
return value;
<?php } ?>
}
?>
labelInterpolationFnc: function(value) {
return '<?= $currency_prefix ?>' + value + '<?= $currency_suffix ?>';
}
},
// Plugins configuration
// Plugin configuration
plugins: [
Chartist.plugins.ctAxisTitle({
axisX: {
@@ -104,41 +106,45 @@
}
}),
<?php
$currency_symbol = esc($config['currency_symbol'], 'js');
$currency_prefix = '';
$currency_suffix = '';
if ($show_currency) {
if (is_right_side_currency_symbol()) {
$currency_suffix = $currency_symbol;
} else {
$currency_prefix = $currency_symbol;
}
}
?>
Chartist.plugins.ctPointLabels({
textAnchor: 'middle',
labelInterpolationFnc: function(value) {
<?php
if ($show_currency) {
if (is_right_side_currency_symbol()) {
?>
return value + '<?= esc($config['currency_symbol'], 'js') ?>';
<?php } else { ?>
return '<?= esc($config['currency_symbol'], 'js') ?>' + value;
<?php
}
} else {
?>
return value;
<?php } ?>
return '<?= $currency_prefix ?>' + value + '<?= $currency_suffix ?>';
}
}),
<?php
$currency_symbol = esc($config['currency_symbol'], 'js');
$currency_prefix = '';
$currency_suffix = '';
if ($show_currency) {
if (is_right_side_currency_symbol()) {
$currency_suffix = $currency_symbol;
} else {
$currency_prefix = $currency_symbol;
}
}
?>
Chartist.plugins.tooltip({
pointClass: 'ct-tooltip-point',
transformTooltipTextFnc: function(value) {
<?php
if ($show_currency) {
if (is_right_side_currency_symbol()) {
?>
return value + '<?= esc($config['currency_symbol'], 'js') ?>';
<?php } else { ?>
return '<?= esc($config['currency_symbol'], 'js') ?>' + value;
<?php
}
} else {
?>
return value;
<?php } ?>
return '<?= $currency_prefix ?>' + value + '<?= $currency_suffix ?>';
}
})
]

View File

@@ -31,26 +31,27 @@
labelPosition: 'outside',
labelDirection: 'explode',
<?php
$currency_symbol = esc($config['currency_symbol'], 'js');
$currency_prefix = '';
$currency_suffix = '';
if ($show_currency) {
if (is_right_side_currency_symbol()) {
$currency_suffix = $currency_symbol;
} else {
$currency_prefix = $currency_symbol;
}
}
?>
plugins: [
Chartist.plugins.tooltip({
transformTooltipTextFnc: function(value) {
<?php
if ($show_currency) {
if (is_right_side_currency_symbol()) {
?>
return value + '<?= esc($config['currency_symbol'], 'js') ?>';
<?php } else { ?>
return '<?= esc($config['currency_symbol'], 'js') ?>' + value;
<?php
}
} else {
?>
return value;
<?php } ?>
return '<?= $currency_prefix ?>' + value + '<?= $currency_suffix ?>';
}
})
]
};
] };
var responsiveOptions = [
['screen and (min-width: 640px)', {

View File

@@ -63,7 +63,7 @@ if (isset($error_message)) {
/* This line will allow to print and go back to sales automatically.
* echo anchor('sales', '<span class="glyphicon glyphicon-print">&nbsp;</span>' . lang('Common.print'), ['class' => 'btn btn-info btn-sm', 'id' => 'show_print_button', 'onclick' => 'window.print();'));
*/
?>
?>
<?php if (isset($customer_email) && !empty($customer_email)): ?>
<a href="javascript:void(0);">
<div class="btn btn-info btn-sm" id="show_email_button"><?= '<span class="glyphicon glyphicon-envelope">&nbsp;</span>' . lang('Sales.send_invoice') ?></div>
@@ -115,10 +115,10 @@ if (isset($error_message)) {
<tr>
<th><?= lang('Sales.item_number') ?></th>
<?php
$invoice_columns = 6;
if ($include_hsn) {
$invoice_columns += 1;
?>
$invoice_columns = 6;
if ($include_hsn) {
$invoice_columns += 1;
?>
<th><?= lang('Sales.hsn') ?></th>
<?php } ?>
<th><?= lang('Sales.item_name') ?></th>
@@ -126,9 +126,9 @@ if (isset($error_message)) {
<th><?= lang('Sales.price') ?></th>
<th><?= lang('Sales.discount') ?></th>
<?php
if ($discount > 0) {
$invoice_columns += 1;
?>
if ($discount > 0) {
$invoice_columns += 1;
?>
<th><?= lang('Sales.customer_discount') ?></th>
<?php } ?>
<th><?= lang('Sales.total') ?></th>
@@ -137,7 +137,7 @@ if (isset($error_message)) {
<?php
foreach ($cart as $line => $item) {
if ($item['print_option'] == PRINT_YES) {
?>
?>
<tr class="item-row">
<td><?= esc($item['item_number']) ?></td>
<?php if ($include_hsn): ?>
@@ -146,7 +146,7 @@ if (isset($error_message)) {
<td class="item-name"><?= ($item['is_serialized'] || $item['allow_alt_description']) && !empty($item['description']) ? esc($item['description']) : esc($item['name'] . ' ' . $item['attribute_values']) ?></td>
<td style="text-align: center;"><?= to_quantity_decimals($item['quantity']) ?></td>
<td><?= to_currency($item['price']) ?></td>
<td style="height: center;"><?= ($item['discount_type'] == FIXED) ? to_currency($item['discount']) : to_decimals($item['discount']) . '%' ?></td>
<td style="text-align: center;"><?= ($item['discount_type'] == FIXED) ? to_currency($item['discount']) : to_decimals($item['discount']) . '%' ?></td>
<?php if ($discount > 0): ?>
<td style="text-align: center;"><?= to_currency($item['discounted_total'] / $item['quantity']) ?></td>
<?php endif; ?>
@@ -155,13 +155,13 @@ if (isset($error_message)) {
<?php if ($item['is_serialized']) { ?>
<tr class="item-row">
<td class="item-description" colspan="<?= $invoice_columns - 1 ?>"></td>
<td style="text-align: center;"><?= esc($item['serialnumber']) // TODO: serialnumber does not match variable naming conventions for this project ?></td>
<td style="text-align: center;"><?= esc($item['serialnumber']) // TODO: `serialnumber` does not match variable naming conventions for this project. Should be `serialNumber`?></td>
</tr>
<?php
}
}
}
?>
?>
<tr>
<td class="blank" colspan="<?= $invoice_columns ?>" style="text-align: center;"><?= '&nbsp;' ?></td>
@@ -188,13 +188,13 @@ if (isset($error_message)) {
</tr>
<?php
$only_sale_check = false;
$show_giftcard_remainder = false;
foreach ($payments as $payment_id => $payment) {
$only_sale_check |= $payment['payment_type'] == lang('Sales.check');
$splitpayment = explode(':', $payment['payment_type']); // TODO: $splitpayment does not meet variable naming standards for this project
$show_giftcard_remainder |= $splitpayment[0] == lang('Sales.giftcard');
?>
$only_sale_check = false;
$show_giftcard_remainder = false;
foreach ($payments as $payment_id => $payment) {
$only_sale_check |= $payment['payment_type'] == lang('Sales.check');
$splitpayment = explode(':', $payment['payment_type']); // TODO: $splitpayment does not meet variable naming standards for this project
$show_giftcard_remainder |= $splitpayment[0] == lang('Sales.giftcard');
?>
<tr>
<td colspan="<?= $invoice_columns - 3 ?>" class="blank"> </td>
<td colspan="2" class="total-line"><?= esc($splitpayment[0]) ?></td>

View File

@@ -20,171 +20,167 @@
?>
<!doctype html>
<head>
<meta charset="utf-8">
<title><?= lang('Sales.email_receipt') ?></title>
<link rel="stylesheet" href="<?= base_url('css/invoice_email.css') ?>">
</head>
<body>
<?php
if (isset($error_message)) {
echo '<div class="alert alert-dismissible alert-danger">' . esc($error_message) . '</div>';
exit;
}
?>
<div id="page-wrap">
<div id="header"><?= lang('Sales.invoice') ?></div>
<table id="info">
<tr>
<td id="logo">
<?php if ($config['company_logo'] != '') { ?>
<img id="image" src="data:<?= esc($mimetype, 'attr') ?>;base64,<?= base64_encode(file_get_contents('uploads/' . esc($config['company_logo']))) ?>" alt="company_logo">
<?php } ?>
</td>
<td id="customer-title" id="customer">
<?php if (isset($customer)) {
echo nl2br(esc($customer_info));
} ?>
</td>
</tr>
<tr>
<td id="company-title" id="company">
<?= esc($config['company']) ?><br>
<?= nl2br(esc($company_info)) ?>
</td>
<td id="meta">
<table id="meta-content" align="right">
<tr>
<td class="meta-head"><?= lang('Sales.invoice_number') ?></td>
<td><?= esc($invoice_number) ?></td>
</tr>
<tr>
<td class="meta-head"><?= lang('Common.date') ?></td>
<td><?= esc($transaction_date) ?></td>
</tr>
<?php if ($amount_due > 0) { ?>
<tr>
<td class="meta-head"><?= lang('Sales.amount_due') ?></td>
<td class="due"><?= to_currency($total) ?></td>
</tr>
<?php } ?>
</table>
</td>
</tr>
</table>
<table id="items">
<tr>
<th><?= lang('Sales.item_number') ?></th>
<th><?= lang('Sales.item_name') ?></th>
<th><?= lang('Sales.quantity') ?></th>
<th><?= lang('Sales.price') ?></th>
<th><?= lang('Sales.discount') ?></th>
<?php
$invoice_columns = 6;
if ($discount > 0) {
$invoice_columns = $invoice_columns + 1;
?>
<th><?= lang('Sales.customer_discount') ?></th>
<?php } ?>
<th><?= lang('Sales.total') ?></th>
</tr>
<?php
foreach ($cart as $line => $item) {
if ($item['print_option'] == PRINT_YES) {
?>
<tr class="item-row">
<td><?= esc($item['item_number']) ?></td>
<td class="item-name"><?= esc($item['name']) ?></td>
<td><?= to_quantity_decimals($item['quantity']) ?></td>
<td><?= to_currency($item['price']) ?></td>
<td><?= ($item['discount_type'] == FIXED) ? to_currency($item['discount']) : to_decimals($item['discount']) . '%' ?></td>
<?php if ($item['discount'] > 0): ?>
<td><?= to_currency($item['discounted_total'] / $item['quantity']) ?></td>
<?php endif; ?>
<td class="total-line"><?= to_currency($item['discounted_total']) ?></td>
</tr>
<?php
}
<head>
<meta charset="utf-8">
<title><?= lang('Sales.email_receipt') ?></title>
<link rel="stylesheet" href="<?= base_url('css/invoice_email.css') ?>">
</head>
<body>
<?php
if (isset($error_message)) {
echo '<div class="alert alert-dismissible alert-danger">' . esc($error_message) . '</div>';
exit;
}
?>
?>
<tr>
<td colspan="<?= $invoice_columns ?>" align="center"><?= '&nbsp;' ?></td>
</tr>
<div id="page-wrap">
<div id="header"><?= lang('Sales.invoice') ?></div>
<table id="info">
<tr>
<td id="logo">
<?php if ($config['company_logo'] != '') { ?>
<img id="image" src="data:<?= esc($mimetype, 'attr') ?>;base64,<?= base64_encode(file_get_contents('uploads/' . esc($config['company_logo']))) ?>" alt="company_logo">
<?php } ?>
</td>
<td id="customer-title" id="customer">
<?php if (isset($customer)) {
echo nl2br(esc($customer_info));
} ?>
</td>
</tr>
<tr>
<td id="company-title" id="company">
<?= esc($config['company']) ?><br>
<?= nl2br(esc($company_info)) ?>
</td>
<td id="meta">
<table id="meta-content" style="text-align: right;">
<tr>
<td class="meta-head"><?= lang('Sales.invoice_number') ?></td>
<td><?= esc($invoice_number) ?></td>
</tr>
<tr>
<td class="meta-head"><?= lang('Common.date') ?></td>
<td><?= esc($transaction_date) ?></td>
</tr>
<?php if ($amount_due > 0) { ?>
<tr>
<td class="meta-head"><?= lang('Sales.amount_due') ?></td>
<td class="due"><?= to_currency($total) ?></td>
</tr>
<?php } ?>
</table>
</td>
</tr>
</table>
<tr>
<td colspan="<?= $invoice_columns - 3 ?>" class="blank"> </td>
<td colspan="2" class="total-line"><?= lang('Sales.sub_total') ?></td>
<td id="subtotal" class="total-value"><?= to_currency($subtotal) ?></td>
</tr>
<table id="items">
<tr>
<th><?= lang('Sales.item_number') ?></th>
<th><?= lang('Sales.item_name') ?></th>
<th><?= lang('Sales.quantity') ?></th>
<th><?= lang('Sales.price') ?></th>
<th><?= lang('Sales.discount') ?></th>
<?php
$invoice_columns = 6;
if ($discount > 0) {
$invoice_columns = $invoice_columns + 1;
?>
<th><?= lang('Sales.customer_discount') ?></th>
<?php } ?>
<th><?= lang('Sales.total') ?></th>
</tr>
<?php
foreach ($cart as $line => $item) {
if ($item['print_option'] == PRINT_YES) {
?>
<tr class="item-row">
<td><?= esc($item['item_number']) ?></td>
<td class="item-name"><?= esc($item['name']) ?></td>
<td><?= to_quantity_decimals($item['quantity']) ?></td>
<td><?= to_currency($item['price']) ?></td>
<td><?= ($item['discount_type'] == FIXED) ? to_currency($item['discount']) : to_decimals($item['discount']) . '%' ?></td>
<?php if ($discount > 0): ?>
<td><?= to_currency($item['discounted_total'] / $item['quantity']) ?></td>
<?php endif; ?>
<td class="total-line"><?= to_currency($item['discounted_total']) ?></td>
</tr>
<?php
}
}
?>
<tr>
<td colspan="<?= $invoice_columns ?>" style="text-align: center;"><?= '&nbsp;' ?></td>
</tr>
<?php foreach ($taxes as $tax_group_index => $tax) { ?>
<tr>
<td colspan="<?= $invoice_columns - 3 ?>" class="blank"> </td>
<td colspan="2" class="total-line"><?= (float)$tax['tax_rate'] . '% ' . esc($tax['tax_group']) ?></td>
<td id="taxes" class="total-value"><?= to_currency_tax($tax['sale_tax_amount']) ?></td>
<td colspan="2" class="total-line"><?= lang('Sales.sub_total') ?></td>
<td id="subtotal" class="total-value"><?= to_currency($subtotal) ?></td>
</tr>
<?php } ?>
<tr>
<td colspan="<?= $invoice_columns - 3 ?>" class="blank"> </td>
<td colspan="2" class="total-line"><?= lang('Sales.total') ?></td>
<td id="total" class="total-value"><?= to_currency($total) ?></td>
</tr>
<?php foreach ($taxes as $tax_group_index => $tax) { ?>
<tr>
<td colspan="<?= $invoice_columns - 3 ?>" class="blank"> </td>
<td colspan="2" class="total-line"><?= (float)$tax['tax_rate'] . '% ' . esc($tax['tax_group']) ?></td>
<td id="taxes" class="total-value"><?= to_currency_tax($tax['sale_tax_amount']) ?></td>
</tr>
<?php } ?>
<?php
$only_sale_check = false;
$show_giftcard_remainder = false;
foreach ($payments as $payment_id => $payment) {
$only_sale_check |= $payment['payment_type'] == lang('Sales.check');
$splitpayment = explode(':', $payment['payment_type']); // TODO: $splitpayment does not meet the variable naming conventions for this project
$show_giftcard_remainder |= $splitpayment[0] == lang('Sales.giftcard');
?>
<tr>
<td colspan="<?= $invoice_columns - 3 ?>" class="blank"> </td>
<td colspan="2" class="total-line"><?= esc($splitpayment[0]) ?></td>
<td class="total-value"><?= to_currency(-$payment['payment_amount']) ?></td>
<td colspan="2" class="total-line"><?= lang('Sales.total') ?></td>
<td id="total" class="total-value"><?= to_currency($total) ?></td>
</tr>
<?php } ?>
<?php if (isset($cur_giftcard_value) && $show_giftcard_remainder) { ?>
<tr>
<td colspan="<?= $invoice_columns - 3 ?>" class="blank"> </td>
<td colspan="2" class="total-line"><?= lang('Sales.giftcard_balance') ?></td>
<td class="total-value" id="giftcard"><?= to_currency($cur_giftcard_value) ?></td>
</tr>
<?php } ?>
<?php
$only_sale_check = false;
$show_giftcard_remainder = false;
<?php if (!empty($payments)) { ?>
<tr>
<td colspan="<?= $invoice_columns - 3 ?>" class="blank"> </td>
<td colspan="2" class="total-line"><?= lang($amount_change >= 0 ? ($only_sale_check ? 'Sales.check_balance' : 'Sales.change_due') : 'Sales.amount_due') ?></td>
<td class="total-value"><?= to_currency($amount_change) ?></td>
</tr>
<?php } ?>
</table>
foreach ($payments as $payment_id => $payment) {
$only_sale_check |= $payment['payment_type'] == lang('Sales.check');
$splitpayment = explode(':', $payment['payment_type']); // TODO: $splitpayment does not meet the variable naming conventions for this project
$show_giftcard_remainder |= $splitpayment[0] == lang('Sales.giftcard');
?>
<tr>
<td colspan="<?= $invoice_columns - 3 ?>" class="blank"> </td>
<td colspan="2" class="total-line"><?= esc($splitpayment[0]) ?></td>
<td class="total-value"><?= to_currency(-$payment['payment_amount']) ?></td>
</tr>
<?php } ?>
<div id="terms">
<div id="sale_return_policy">
<h5>
<span><?= nl2br($config['payment_message']) ?></span>
<span><?= lang('Sales.comments') . ': ' . (empty($comments) ? esc($config['invoice_default_comments']) : esc($comments)) ?></span>
</h5>
<?= nl2br(esc($config['return_policy'])) ?>
</div>
<div id="barcode">
<img alt="<?= esc($sale_id) ?>" src="data:image/svg+xml;base64,<?= base64_encode($barcode) ?>"><br>
<?= esc($sale_id) ?>
<?php if (isset($cur_giftcard_value) && $show_giftcard_remainder) { ?>
<tr>
<td colspan="<?= $invoice_columns - 3 ?>" class="blank"> </td>
<td colspan="2" class="total-line"><?= lang('Sales.giftcard_balance') ?></td>
<td class="total-value" id="giftcard"><?= to_currency($cur_giftcard_value) ?></td>
</tr>
<?php } ?>
<?php if (!empty($payments)) { ?>
<tr>
<td colspan="<?= $invoice_columns - 3 ?>" class="blank"> </td>
<td colspan="2" class="total-line"><?= lang($amount_change >= 0 ? ($only_sale_check ? 'Sales.check_balance' : 'Sales.change_due') : 'Sales.amount_due') ?></td>
<td class="total-value"><?= to_currency($amount_change) ?></td>
</tr>
<?php } ?>
</table>
<div id="terms">
<div id="sale_return_policy">
<h5>
<span><?= nl2br(esc($config['payment_message'])) ?></span>
<span><?= lang('Sales.comments') . ': ' . (empty($comments) ? esc($config['invoice_default_comments']) : esc($comments)) ?></span>
</h5>
<?= nl2br(esc($config['return_policy'])) ?>
</div>
<div id="barcode">
<img alt="<?= esc($sale_id) ?>" src="data:image/svg+xml;base64,<?= base64_encode($barcode) ?>"><br>
<?= esc($sale_id) ?>
</div>
</div>
</div>
</div>
</body>
</body>
</html>

View File

@@ -73,7 +73,7 @@
<?= view('partial/print_receipt', ['print_after_sale' => false, 'selected_printer' => 'takings_printer']) ?>
<div id="title_bar" class="print_hide btn-toolbar">
<button onclick="javascript:printdoc()" class="btn btn-info btn-sm pull-right">
<button onclick="printdoc()" class="btn btn-info btn-sm pull-right">
<span class="glyphicon glyphicon-print">&nbsp;</span><?= lang('Common.print') ?>
</button>
<?= anchor("sales", '<span class="glyphicon glyphicon-shopping-cart">&nbsp;</span>' . lang('Sales.register'), ['class' => 'btn btn-info btn-sm pull-right', 'id' => 'show_sales_button']) ?>

View File

@@ -58,7 +58,7 @@
</div>
</td>
<td id="meta">
<table id="meta-content" align="right">
<table id="meta-content" style="text-align: right;">
<tr>
<td class="meta-head"><?= lang('Sales.quote_number') ?> </td>
<td><?= esc($quote_number) ?></td>
@@ -116,7 +116,7 @@
?>
<tr>
<td colspan="<?= $quote_columns ?>" align="center"><?= '&nbsp;' //TODO: Replace the php echo for nbsp with just straight html? ?></td>
<td colspan="<?= $quote_columns ?>" style="text-align: center;"><?= '&nbsp;' //TODO: Replace the php echo for nbsp with just straight html? ?></td>
</tr>
<tr>

View File

@@ -173,12 +173,12 @@ helper('url');
</td>
<?php if ($item['item_type'] == ITEM_TEMP) { ?>
<td><?= form_input(['name' => 'item_number', 'id' => 'item_number', 'class' => 'form-control input-sm', 'value' => $item['item_number'], 'tabindex' => ++$tabindex]) ?></td>
<td style="align: center;">
<td style="text-align: center;">
<?= form_input(['name' => 'name', 'id' => 'name', 'class' => 'form-control input-sm', 'value' => $item['name'], 'tabindex' => ++$tabindex]) ?>
</td>
<?php } else { ?>
<td><?= esc($item['item_number']) ?></td>
<td style="align: center;">
<td style="text-align: center;">
<?= esc($item['name']) . ' ' . implode(' ', [$item['attribute_values'], $item['attribute_dtvalues']]) ?>
<br>
<?php if ($item['stock_type'] == '0'): echo '[' . to_quantity_decimals($item['in_stock']) . ' in ' . esc($item['stock_name']) . ']';
@@ -236,7 +236,7 @@ helper('url');
<tr>
<?php if ($item['item_type'] == ITEM_TEMP) { ?>
<td><?= form_input(['type' => 'hidden', 'name' => 'item_id', 'value' => $item['item_id']]) ?></td>
<td style="align: center;" colspan="6">
<td style="text-align: center;" colspan="6">
<?= form_input(['name' => 'item_description', 'id' => 'item_description', 'class' => 'form-control input-sm', 'value' => $item['description'], 'tabindex' => ++$tabindex]) ?>
</td>
<td> </td>

View File

@@ -55,7 +55,7 @@
<pre><?= esc($company_info) ?></pre>
</td>
<td id="meta">
<table align="right">
<table style="text-align: right;">
<tr>
<td class="meta-head"><?= lang('Sales.work_order_number') ?> </td>
<td><?= esc($work_order_number) ?></td>
@@ -103,7 +103,7 @@
?>
<tr>
<td colspan="6" align="center"><?= '&nbsp;' ?></td>
<td colspan="6" style="text-align: center;"><?= '&nbsp;' ?></td>
</tr>
<tr>

View File

@@ -79,24 +79,18 @@
$('input[name="tax_category[]"]').each(function() {
value_count = $(this).val() == value ? value_count + 1 : value_count;
});
if (value_count > 1) {
return false;
}
return true;
return value_count <= 1;
}, "<?= lang('Taxes.tax_category_duplicate') ?>");
$.validator.addMethod('validateTaxCategoryCharacters', function(value, element) {
if ((value.indexOf('_') != -1)) {
return false;
}
return true;
return (value.indexOf('_') == -1);
}, "<?= lang('Taxes.tax_category_invalid_chars') ?>");
$.validator.addMethod('requireTaxCategory', function(value, element) {
if (value.trim() == '') {
return false;
}
return true;
return value.trim() != '';
}, "<?= lang('Taxes.tax_category_required') ?>");
$('#tax_categories_form').validate($.extend(form_support.handler, {
@@ -120,8 +114,8 @@
}));
<?php
$i = 0;
foreach ($tax_categories as $tax_category => $tax_category_data) {
$i = 0;
foreach ($tax_categories as $tax_category => $tax_category_data) {
?>
$('<?= '#tax_category_' . ++$i ?>').rules("add", {
requireTaxCategory: true,

View File

@@ -79,24 +79,18 @@
$("input[name='tax_code[]']").each(function() {
value_count = $(this).val() == value ? value_count + 1 : value_count;
});
if (value_count > 1) {
return false;
}
return true;
return value_count <= 1;
}, "<?= lang('Taxes.tax_code_duplicate') ?>");
$.validator.addMethod('validateTaxCodeCharacters', function(value, element) {
if ((value.indexOf('_') != -1)) {
return false;
}
return true;
return (value.indexOf('_') == -1);
}, "<?= lang('Taxes.tax_code_invalid_chars') ?>");
$.validator.addMethod('requireTaxCode', function(value, element) {
if (value.trim() == '') {
return false;
}
return true;
return value.trim() != '';
}, "<?= lang('Taxes.tax_code_required') ?>");
$('#tax_codes_form').validate($.extend(form_support.handler, {

View File

@@ -83,24 +83,18 @@
$("input[name='jurisdiction_name[]']").each(function() {
value_count = $(this).val() == value ? value_count + 1 : value_count;
});
if (value_count > 1) {
return false;
}
return true;
return value_count <= 1;
}, "<?= lang('Taxes.tax_jurisdiction_duplicate') ?>");
$.validator.addMethod('validateTaxJurisdictionCharacters', function(value, element) {
if ((value.indexOf('_') != -1)) {
return false;
}
return true;
return (value.indexOf('_') == -1);
}, "<?= lang('Taxes.tax_jurisdiction_invalid_chars') ?>");
$.validator.addMethod('requireTaxJurisdiction', function(value, element) {
if (value.trim() == '') {
return false;
}
return true;
return value.trim() != '';
}, "<?= lang('Taxes.tax_jurisdiction_required') ?>");
$('#tax_jurisdictions_form').validate($.extend(form_support.handler, {

View File

@@ -32,11 +32,11 @@
},
"require": {
"ext-intl": "*",
"php": "^8.1",
"codeigniter4/framework": "4.6.3",
"php": "^8.2",
"codeigniter4/framework": "4.7.2",
"dompdf/dompdf": "^2.0.3",
"ezyang/htmlpurifier": "^4.17",
"laminas/laminas-escaper": "2.17.0",
"laminas/laminas-escaper": "2.18.0",
"paragonie/random_compat": "^2.0.21",
"picqer/php-barcode-generator": "^2.4.0",
"tamtamchik/namecase": "^3.0.0"

43
composer.lock generated
View File

@@ -4,40 +4,41 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "9cf76605c1b45f81c547fefc5a29f101",
"content-hash": "e95f6e5e86d323370ddb0df57c4d3fb3",
"packages": [
{
"name": "codeigniter4/framework",
"version": "v4.6.3",
"version": "v4.7.2",
"source": {
"type": "git",
"url": "https://github.com/codeigniter4/framework.git",
"reference": "68d1a5896106f869452dd369a690dd5bc75160fb"
"reference": "b3359be849be29394660c3aed909aa32b6c45cf6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/codeigniter4/framework/zipball/68d1a5896106f869452dd369a690dd5bc75160fb",
"reference": "68d1a5896106f869452dd369a690dd5bc75160fb",
"url": "https://api.github.com/repos/codeigniter4/framework/zipball/b3359be849be29394660c3aed909aa32b6c45cf6",
"reference": "b3359be849be29394660c3aed909aa32b6c45cf6",
"shasum": ""
},
"require": {
"ext-intl": "*",
"ext-mbstring": "*",
"laminas/laminas-escaper": "^2.17",
"php": "^8.1",
"laminas/laminas-escaper": "^2.18",
"php": "^8.2",
"psr/log": "^3.0"
},
"require-dev": {
"codeigniter/coding-standard": "^1.7",
"fakerphp/faker": "^1.24",
"friendsofphp/php-cs-fixer": "^3.47.1",
"kint-php/kint": "^6.0",
"kint-php/kint": "^6.1",
"mikey179/vfsstream": "^1.6.12",
"nexusphp/cs-config": "^3.6",
"phpunit/phpunit": "^10.5.16 || ^11.2",
"predis/predis": "^3.0"
},
"suggest": {
"ext-apcu": "If you use Cache class ApcuHandler",
"ext-curl": "If you use CURLRequest class",
"ext-dom": "If you use TestResponse",
"ext-exif": "If you run Image class tests",
@@ -49,7 +50,9 @@
"ext-memcached": "If you use Cache class MemcachedHandler with Memcached",
"ext-mysqli": "If you use MySQL",
"ext-oci8": "If you use Oracle Database",
"ext-pcntl": "If you use Signals",
"ext-pgsql": "If you use PostgreSQL",
"ext-posix": "If you use Signals",
"ext-readline": "Improves CLI::input() usability",
"ext-redis": "If you use Cache class RedisHandler",
"ext-simplexml": "If you format XML",
@@ -78,7 +81,7 @@
"slack": "https://codeigniterchat.slack.com",
"source": "https://github.com/codeigniter4/CodeIgniter4"
},
"time": "2025-08-02T13:36:13+00:00"
"time": "2026-03-24T18:26:09+00:00"
},
{
"name": "dompdf/dompdf",
@@ -205,32 +208,32 @@
},
{
"name": "laminas/laminas-escaper",
"version": "2.17.0",
"version": "2.18.0",
"source": {
"type": "git",
"url": "https://github.com/laminas/laminas-escaper.git",
"reference": "df1ef9503299a8e3920079a16263b578eaf7c3ba"
"reference": "06f211dfffff18d91844c1f55250d5d13c007e18"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/df1ef9503299a8e3920079a16263b578eaf7c3ba",
"reference": "df1ef9503299a8e3920079a16263b578eaf7c3ba",
"url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/06f211dfffff18d91844c1f55250d5d13c007e18",
"reference": "06f211dfffff18d91844c1f55250d5d13c007e18",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"ext-mbstring": "*",
"php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
"php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0"
},
"conflict": {
"zendframework/zend-escaper": "*"
},
"require-dev": {
"infection/infection": "^0.29.8",
"laminas/laminas-coding-standard": "~3.0.1",
"phpunit/phpunit": "^10.5.45",
"psalm/plugin-phpunit": "^0.19.2",
"vimeo/psalm": "^6.6.2"
"infection/infection": "^0.31.0",
"laminas/laminas-coding-standard": "~3.1.0",
"phpunit/phpunit": "^11.5.42",
"psalm/plugin-phpunit": "^0.19.5",
"vimeo/psalm": "^6.13.1"
},
"type": "library",
"autoload": {
@@ -262,7 +265,7 @@
"type": "community_bridge"
}
],
"time": "2025-05-06T19:29:36+00:00"
"time": "2025-10-14T18:31:13+00:00"
},
{
"name": "masterminds/html5",

View File

@@ -9,6 +9,9 @@
* the LICENSE file that was distributed with this source code.
*/
use CodeIgniter\Boot;
use Config\Paths;
/*
*---------------------------------------------------------------
* Sample file for Preloading
@@ -54,6 +57,7 @@ class preload
'/system/Config/Routes.php',
'/system/Language/',
'/system/bootstrap.php',
'/system/util_bootstrap.php',
'/system/rewrite.php',
'/Views/',
// Errors occur.
@@ -69,10 +73,10 @@ class preload
private function loadAutoloader(): void
{
$paths = new Config\Paths();
$paths = new Paths();
require rtrim($paths->systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'Boot.php';
CodeIgniter\Boot::preload($paths);
Boot::preload($paths);
}
/**
@@ -97,7 +101,9 @@ class preload
}
require_once $file[0];
echo 'Loaded: ' . $file[0] . "\n";
// Uncomment only for debugging (to inspect which files are included).
// Never use this in production - preload scripts must not generate output.
// echo 'Loaded: ' . $file[0] . "\n";
}
}
}

View File