mirror of
https://github.com/opensourcepos/opensourcepos.git
synced 2026-05-29 10:47:53 -04:00
Compare commits
431 Commits
fix-requis
...
WebShells-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a6cfffc27 | ||
|
|
5e0541c53e | ||
|
|
b0dddc22a3 | ||
|
|
8d6b166673 | ||
|
|
093ec7fb13 | ||
|
|
9c89a2e2cb | ||
|
|
2f51c4ef52 | ||
|
|
def0c27a0e | ||
|
|
90c981b6b7 | ||
|
|
6ff28d8a4d | ||
|
|
70fb347fc4 | ||
|
|
2f5c0130f4 | ||
|
|
fdd6a408ec | ||
|
|
ef91e6a9df | ||
|
|
144e73eba6 | ||
|
|
42ba39d290 | ||
|
|
81213f0434 | ||
|
|
7edefe8ee1 | ||
|
|
68e14191f9 | ||
|
|
a381c3ca54 | ||
|
|
058e12244e | ||
|
|
f1c6fe2981 | ||
|
|
ff7a8d2e88 | ||
|
|
e602eddb47 | ||
|
|
0a313aa09d | ||
|
|
12e3c7e31f | ||
|
|
de62e9f3bd | ||
|
|
97ca738b2d | ||
|
|
c714dd6f68 | ||
|
|
b6f28da058 | ||
|
|
165c3351eb | ||
|
|
905b58ca6e | ||
|
|
609b206375 | ||
|
|
6fec2464f8 | ||
|
|
332d8c8c69 | ||
|
|
577cf55b6a | ||
|
|
e70395bb85 | ||
|
|
7f9321eca0 | ||
|
|
71056d9b03 | ||
|
|
e17944d883 | ||
|
|
0ac427b2b1 | ||
|
|
3038f83a4a | ||
|
|
75f6ce3140 | ||
|
|
ce7a3ce341 | ||
|
|
d99d2855ec | ||
|
|
96b4b24d9b | ||
|
|
871231e406 | ||
|
|
e62477ed4e | ||
|
|
2a0997f267 | ||
|
|
1ca8effe08 | ||
|
|
ed2c975ad5 | ||
|
|
403feed3e5 | ||
|
|
7f6f36210c | ||
|
|
1121ced532 | ||
|
|
632a18212d | ||
|
|
3208f15244 | ||
|
|
079b809622 | ||
|
|
d685e09c29 | ||
|
|
149c27d60f | ||
|
|
57b7705cd4 | ||
|
|
e8951422c0 | ||
|
|
8afc57fcf4 | ||
|
|
7af64a9a21 | ||
|
|
46d5781498 | ||
|
|
66b61c0554 | ||
|
|
6b97131c48 | ||
|
|
a4c19a3c2c | ||
|
|
7ca8c9561a | ||
|
|
4fac5d9198 | ||
|
|
221995b6db | ||
|
|
91dbe5b869 | ||
|
|
afd908327b | ||
|
|
cfde66481d | ||
|
|
80f00c8552 | ||
|
|
dbdf4db4fb | ||
|
|
64004db271 | ||
|
|
7f20a5dd4c | ||
|
|
d7a276b488 | ||
|
|
57dbe43313 | ||
|
|
6f1c39d99e | ||
|
|
45902caa67 | ||
|
|
1fe865a100 | ||
|
|
90da63cb13 | ||
|
|
8da4aff262 | ||
|
|
0e9f4a998d | ||
|
|
85c7ce2da4 | ||
|
|
e723e2ddf4 | ||
|
|
71eb8de7fe | ||
|
|
9d5019e12e | ||
|
|
56670271d6 | ||
|
|
cef103445e | ||
|
|
68e9a56632 | ||
|
|
ba05536317 | ||
|
|
f74f286a51 | ||
|
|
7180ec33e8 | ||
|
|
496c8a8262 | ||
|
|
493d9cc9c1 | ||
|
|
f761e1464f | ||
|
|
a5bbb2bcc5 | ||
|
|
92ec321d08 | ||
|
|
e046e74c79 | ||
|
|
e0cd0f6129 | ||
|
|
3b102adf3f | ||
|
|
260358d611 | ||
|
|
e615200466 | ||
|
|
56cead478a | ||
|
|
7030f6bac3 | ||
|
|
299f62669a | ||
|
|
072865620a | ||
|
|
3bbd4c4c95 | ||
|
|
0253bf85b8 | ||
|
|
92c1be8bb1 | ||
|
|
23829eab35 | ||
|
|
c81c6506cb | ||
|
|
840d9ccc81 | ||
|
|
e763ee2acc | ||
|
|
8ef109efbc | ||
|
|
9a544096c2 | ||
|
|
3e4ac0b24d | ||
|
|
3c9c592ca3 | ||
|
|
a4d8bedbf3 | ||
|
|
c4304fd0a9 | ||
|
|
44fe2c087a | ||
|
|
985c1c55ce | ||
|
|
8029e5538f | ||
|
|
1a7683a8ac | ||
|
|
e4b92b58c3 | ||
|
|
dc1e448bc3 | ||
|
|
24b2825b31 | ||
|
|
38d672592b | ||
|
|
6f7e06e986 | ||
|
|
fda40d9340 | ||
|
|
b49186ec7c | ||
|
|
8b56f61b8a | ||
|
|
9820beb0e1 | ||
|
|
e01dad728f | ||
|
|
234f930079 | ||
|
|
3001dc0e17 | ||
|
|
3ba207e8b9 | ||
|
|
d684c49ebd | ||
|
|
071e641f95 | ||
|
|
48af67bd00 | ||
|
|
7cb1d95da7 | ||
|
|
bafe3ddf1b | ||
|
|
c482e75304 | ||
|
|
afc2f82dc6 | ||
|
|
ce411707b4 | ||
|
|
37c6e22fc4 | ||
|
|
3c7ece5c33 | ||
|
|
02fccaf43f | ||
|
|
ee4d44ed39 | ||
|
|
fa3f257e7b | ||
|
|
431a9951e9 | ||
|
|
f7e8d6e427 | ||
|
|
85889b6e65 | ||
|
|
6818f02ef9 | ||
|
|
436696b11b | ||
|
|
9a2b308647 | ||
|
|
1f55d96580 | ||
|
|
b2fadea44a | ||
|
|
0fdb3ba37b | ||
|
|
d7b2264ac1 | ||
|
|
a229bf6031 | ||
|
|
977fa5647b | ||
|
|
52b0a83190 | ||
|
|
f25a0f5b09 | ||
|
|
f0f288797a | ||
|
|
63083a0946 | ||
|
|
3a33098776 | ||
|
|
ca6a1b35af | ||
|
|
418580a52d | ||
|
|
31d25e06dc | ||
|
|
b1819b3b36 | ||
|
|
6705420373 | ||
|
|
d6b767c80a | ||
|
|
19eb43270a | ||
|
|
df4549bb0b | ||
|
|
bdc965be23 | ||
|
|
5c8905aa1b | ||
|
|
690f43578d | ||
|
|
0858a1c23c | ||
|
|
3c217bbddd | ||
|
|
87a0606141 | ||
|
|
b6a90f7880 | ||
|
|
b93359bcaf | ||
|
|
79427481b3 | ||
|
|
b23351a45c | ||
|
|
bee0c8e364 | ||
|
|
849439c71e | ||
|
|
25680f05db | ||
|
|
a11fb099e2 | ||
|
|
aee5f31cf5 | ||
|
|
643b0ac499 | ||
|
|
3e844f2f89 | ||
|
|
2acdec431f | ||
|
|
f245f585da | ||
|
|
e48ab45094 | ||
|
|
46e31b1c16 | ||
|
|
bea69c7aa1 | ||
|
|
30da69a382 | ||
|
|
6dd5a9162f | ||
|
|
26a398f7d2 | ||
|
|
ce73d9bb31 | ||
|
|
83af580d40 | ||
|
|
ca7adf76c1 | ||
|
|
832db664e5 | ||
|
|
36e73a84af | ||
|
|
bcddf482fe | ||
|
|
759356288b | ||
|
|
d1e5575ac1 | ||
|
|
b3f67a5e0f | ||
|
|
41b349134a | ||
|
|
b1f6ae6d35 | ||
|
|
4153c69ccd | ||
|
|
87fbd72478 | ||
|
|
a4ac42b4ad | ||
|
|
2eff79a8b6 | ||
|
|
880fb8faef | ||
|
|
4d2347173b | ||
|
|
82d36d01fb | ||
|
|
13314b7da1 | ||
|
|
43808c5970 | ||
|
|
1615ef3832 | ||
|
|
e089dc5e2c | ||
|
|
4cf70a95e6 | ||
|
|
e08367aaae | ||
|
|
9cd2f685ff | ||
|
|
6800f338e7 | ||
|
|
d4ab56b742 | ||
|
|
1eb75d6e05 | ||
|
|
8833420917 | ||
|
|
0d1f4efe3c | ||
|
|
b9e17daac7 | ||
|
|
5f395d987b | ||
|
|
6f587498e6 | ||
|
|
29c3c55fcc | ||
|
|
e1fedab9b7 | ||
|
|
3c846e6324 | ||
|
|
85120fa4be | ||
|
|
7ba60ba58b | ||
|
|
64f34933c4 | ||
|
|
1c0442c4f6 | ||
|
|
8bc4ee3792 | ||
|
|
c200561eb5 | ||
|
|
a55d5b415e | ||
|
|
f31d004fb7 | ||
|
|
40e4ad3d38 | ||
|
|
7658ca8dd2 | ||
|
|
f38272cb59 | ||
|
|
dca3cdeaf5 | ||
|
|
41eb07caec | ||
|
|
766c9bb0f2 | ||
|
|
7113e1167c | ||
|
|
eaeb9cb426 | ||
|
|
1971519629 | ||
|
|
b4e010dab8 | ||
|
|
75e709d0b5 | ||
|
|
605f550666 | ||
|
|
bc55908af2 | ||
|
|
707339f3b5 | ||
|
|
d0bb7998a9 | ||
|
|
c47ea659bc | ||
|
|
9b8d6acb79 | ||
|
|
640bdfd0f9 | ||
|
|
0ea4fcd474 | ||
|
|
056add7979 | ||
|
|
4577525566 | ||
|
|
75d4d894a4 | ||
|
|
e4b07125d6 | ||
|
|
2d35346d16 | ||
|
|
e0969a8c2b | ||
|
|
965f3706da | ||
|
|
e83c23cf0c | ||
|
|
1456feae58 | ||
|
|
32c0b74e0a | ||
|
|
9ecbe5770c | ||
|
|
cedcbf459e | ||
|
|
73df6db4f8 | ||
|
|
b0e0b5b429 | ||
|
|
36f41db6aa | ||
|
|
a6c9011954 | ||
|
|
9f19a15845 | ||
|
|
c33bd9a868 | ||
|
|
d4e775d252 | ||
|
|
aeda461743 | ||
|
|
c1c74279f1 | ||
|
|
aecb4deac0 | ||
|
|
fb2d61fc49 | ||
|
|
ea21abf7a7 | ||
|
|
0acd52cfdd | ||
|
|
e2cfcc07a4 | ||
|
|
fc676091c3 | ||
|
|
bcf17ae4c3 | ||
|
|
2c598c6e3c | ||
|
|
6139659c94 | ||
|
|
17c14c8a41 | ||
|
|
c7223e4b75 | ||
|
|
7e1895d06c | ||
|
|
3b959bb1e8 | ||
|
|
33b8fc1607 | ||
|
|
0f5718e53e | ||
|
|
42feed19a0 | ||
|
|
bbab34e6ba | ||
|
|
d6bf2d11a0 | ||
|
|
f38661bd76 | ||
|
|
1fe6cf67f6 | ||
|
|
45b39cf8c5 | ||
|
|
6056ebf9d4 | ||
|
|
9726b46b15 | ||
|
|
16307105a4 | ||
|
|
86325263bc | ||
|
|
0fd1bd9b50 | ||
|
|
0339ed8292 | ||
|
|
2b6d5eae77 | ||
|
|
b0c71621a9 | ||
|
|
b9c97324fa | ||
|
|
546b90e5f7 | ||
|
|
205346ff90 | ||
|
|
edb0bcf206 | ||
|
|
6987e14147 | ||
|
|
d5910f2e75 | ||
|
|
7fb75dbea9 | ||
|
|
febe5109f0 | ||
|
|
a32519fe4a | ||
|
|
e0cb950083 | ||
|
|
9c963814dd | ||
|
|
2fec49e7df | ||
|
|
1bdc19f14f | ||
|
|
02d63fe067 | ||
|
|
3e996b7818 | ||
|
|
fc37848fa7 | ||
|
|
477942beea | ||
|
|
f7e12d6ba1 | ||
|
|
a0f49d70b1 | ||
|
|
66502af0ad | ||
|
|
b099161dd1 | ||
|
|
2e2bbf35b9 | ||
|
|
bc8c42ee0d | ||
|
|
2b361aaaed | ||
|
|
82f0e75bf0 | ||
|
|
4d8403eb2b | ||
|
|
d89cf3c9ad | ||
|
|
adfd708613 | ||
|
|
4166ee96d5 | ||
|
|
123606e842 | ||
|
|
9d02e288e7 | ||
|
|
c7f379f8a4 | ||
|
|
229685f8e0 | ||
|
|
d10b38a03b | ||
|
|
264a449496 | ||
|
|
12a57d5701 | ||
|
|
27f769e3f4 | ||
|
|
fc60a09f28 | ||
|
|
59798cae28 | ||
|
|
7a170b7f7f | ||
|
|
9c6023e7f0 | ||
|
|
70352ba954 | ||
|
|
01d0555586 | ||
|
|
22203a83d7 | ||
|
|
2d99655400 | ||
|
|
b8be47d4ef | ||
|
|
fd86e08e7e | ||
|
|
a1d2d19a5b | ||
|
|
766b3b967e | ||
|
|
a62bef53b4 | ||
|
|
eb643cc74c | ||
|
|
a0fb5f317c | ||
|
|
1f7da93189 | ||
|
|
ed00395243 | ||
|
|
f47f474335 | ||
|
|
e0cebb86bd | ||
|
|
78d0193121 | ||
|
|
3d5d2ebb89 | ||
|
|
075d261758 | ||
|
|
8e9c3d7df5 | ||
|
|
1428ad2789 | ||
|
|
89919c88a2 | ||
|
|
31edc87348 | ||
|
|
8565e73f0c | ||
|
|
942ea19fe4 | ||
|
|
c4fbdb1231 | ||
|
|
fd441d57a1 | ||
|
|
2080f5b187 | ||
|
|
ad2902cb19 | ||
|
|
606b9461d2 | ||
|
|
d37016a9f5 | ||
|
|
09530c1609 | ||
|
|
2c9ae36247 | ||
|
|
69a507f879 | ||
|
|
e1e3a30fc0 | ||
|
|
c1906727ec | ||
|
|
8dde4c3425 | ||
|
|
f399714dc3 | ||
|
|
e90b5b87da | ||
|
|
69bcd84699 | ||
|
|
f3fae110d6 | ||
|
|
e9e82e4e50 | ||
|
|
2bd38737e1 | ||
|
|
2a789bb583 | ||
|
|
e8a79910fe | ||
|
|
9bfe6c7c4e | ||
|
|
bc0e2c6833 | ||
|
|
196375d594 | ||
|
|
fafba87894 | ||
|
|
66a097d9f2 | ||
|
|
f3931577be | ||
|
|
f125960fe2 | ||
|
|
787977ed3e | ||
|
|
502b5fd6b9 | ||
|
|
ec2b941f3f | ||
|
|
8723274418 | ||
|
|
cf73ffa825 | ||
|
|
eeaa693ede | ||
|
|
1378794e7e | ||
|
|
d1d8aa0401 | ||
|
|
882f3b4522 | ||
|
|
19974bc8e0 | ||
|
|
d0b2b3e80b | ||
|
|
57c36e7ba7 | ||
|
|
8516ffe216 | ||
|
|
534f7361d6 | ||
|
|
5609859fdf | ||
|
|
c6c5fcac26 | ||
|
|
4d9cd80f8b | ||
|
|
2924a889c7 | ||
|
|
beb18ff96b | ||
|
|
7ad1bfa0fb | ||
|
|
9cc24f0c70 | ||
|
|
b86e5ca6ef | ||
|
|
4879fe2cf3 | ||
|
|
a5b2b5f771 |
@@ -1,24 +1,56 @@
|
|||||||
node_modules
|
# Version control
|
||||||
tmp
|
.git
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# Sensitive config (user may mount their own)
|
||||||
app/Config/Email.php
|
app/Config/Email.php
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
tmp/
|
||||||
*.patch
|
*.patch
|
||||||
patches/
|
patches/
|
||||||
|
|
||||||
|
# IDE and editor files
|
||||||
.idea/
|
.idea/
|
||||||
git-svn-diff.py
|
.vscode/
|
||||||
*.bash
|
|
||||||
.swp
|
.swp
|
||||||
|
*.swp
|
||||||
.buildpath
|
.buildpath
|
||||||
.project
|
.project
|
||||||
.settings/*
|
.settings/
|
||||||
.git
|
|
||||||
dist/
|
# Development tools and configs
|
||||||
node_modules/
|
tests/
|
||||||
*.swp
|
phpunit.xml
|
||||||
|
.php-cs-fixer.*
|
||||||
|
phpstan.neon
|
||||||
|
*.bash
|
||||||
|
git-svn-diff.py
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
*.md
|
||||||
|
!LICENSE
|
||||||
|
branding/
|
||||||
|
|
||||||
|
# Build configs (not needed at runtime)
|
||||||
|
composer.json
|
||||||
|
composer.lock
|
||||||
|
package.json
|
||||||
|
package-lock.json
|
||||||
|
gulpfile.js
|
||||||
|
.env.example
|
||||||
|
.dockerignore
|
||||||
|
|
||||||
|
# Temporary and backup files
|
||||||
*.rej
|
*.rej
|
||||||
*.orig
|
*.orig
|
||||||
*~
|
*~
|
||||||
*.~
|
*.~
|
||||||
*.log
|
*.log
|
||||||
app/writable/session/*
|
|
||||||
!app/writable/session/index.html
|
|
||||||
|
|
||||||
|
# CI
|
||||||
|
.github/
|
||||||
|
.github/workflows/
|
||||||
|
build/
|
||||||
|
|||||||
@@ -1,22 +1,15 @@
|
|||||||
|
# editorconfig.org
|
||||||
|
|
||||||
root = true
|
root = true
|
||||||
|
|
||||||
[*]
|
[*]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
indent_style = tab
|
indent_style = space
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
max_line_length = 120
|
max_line_length = 120
|
||||||
tab_width = 4
|
|
||||||
|
|
||||||
[{*.cjs,*.js}]
|
[*.md]
|
||||||
indent_style = tab
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
[{*.ctp,*.hphp,*.inc,*.module,*.php,*.php4,*.php5,*.phtml}]
|
|
||||||
indent_style = tab
|
|
||||||
|
|
||||||
[{*.har,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.prettierrc,.stylelintrc,bowerrc,composer.lock,jest.config}]
|
|
||||||
indent_style = tab
|
|
||||||
|
|
||||||
[{*.htm,*.html,*.ng,*.sht,*.shtm,*.shtml}]
|
|
||||||
indent_style = tab
|
|
||||||
63
.env-example
63
.env-example
@@ -1,63 +0,0 @@
|
|||||||
#--------------------------------------------------------------------
|
|
||||||
# ENVIRONMENT
|
|
||||||
#--------------------------------------------------------------------
|
|
||||||
|
|
||||||
CI_ENVIRONMENT = production
|
|
||||||
|
|
||||||
#--------------------------------------------------------------------
|
|
||||||
# DATABASE
|
|
||||||
#--------------------------------------------------------------------
|
|
||||||
|
|
||||||
database.default.hostname = 'localhost'
|
|
||||||
database.default.database = 'ospos'
|
|
||||||
database.default.username = 'admin'
|
|
||||||
database.default.password = 'pointofsale'
|
|
||||||
database.default.DBDriver = 'MySQLi'
|
|
||||||
database.default.DBPrefix = 'ospos_'
|
|
||||||
|
|
||||||
database.development.hostname = 'localhost'
|
|
||||||
database.development.database = 'ospos'
|
|
||||||
database.development.username = 'admin'
|
|
||||||
database.development.password = 'pointofsale'
|
|
||||||
database.development.DBDriver = 'MySQLi'
|
|
||||||
database.development.DBPrefix = 'ospos_'
|
|
||||||
|
|
||||||
database.tests.hostname = 'localhost'
|
|
||||||
database.tests.database = 'ospos'
|
|
||||||
database.tests.username = 'admin'
|
|
||||||
database.tests.password = 'pointofsale'
|
|
||||||
database.tests.DBDriver = 'MySQLi'
|
|
||||||
database.tests.DBPrefix = 'ospos_'
|
|
||||||
|
|
||||||
#--------------------------------------------------------------------
|
|
||||||
# ENCRYPTION
|
|
||||||
#--------------------------------------------------------------------
|
|
||||||
|
|
||||||
encryption.key = ''
|
|
||||||
|
|
||||||
#--------------------------------------------------------------------
|
|
||||||
# LOGGER
|
|
||||||
# - 0 = Disables logging, Error logging TURNED OFF
|
|
||||||
# - 1 = Emergency Messages - System is unusable
|
|
||||||
# - 2 = Alert Messages - Action Must Be Taken Immediately
|
|
||||||
# - 3 = Critical Messages - Application component unavailable, unexpected exception.
|
|
||||||
# - 4 = Runtime Errors - Don't need immediate action, but should be monitored.
|
|
||||||
# - 5 = Warnings - Exceptional occurrences that are not errors.
|
|
||||||
# - 6 = Notices - Normal but significant events.
|
|
||||||
# - 7 = Info - Interesting events, like user logging in, etc.
|
|
||||||
# - 8 = Debug - Detailed debug information.
|
|
||||||
# - 9 = All Messages
|
|
||||||
#--------------------------------------------------------------------
|
|
||||||
|
|
||||||
logger.threshold = 0
|
|
||||||
app.db_log_enabled = false
|
|
||||||
|
|
||||||
#--------------------------------------------------------------------
|
|
||||||
# HONEYPOT
|
|
||||||
#--------------------------------------------------------------------
|
|
||||||
|
|
||||||
honeypot.hidden = true
|
|
||||||
honeypot.label = 'Fill This Field'
|
|
||||||
honeypot.name = 'honeypot'
|
|
||||||
honeypot.template = '<label>{label}</label><input type="text" name="{name}" value=""/>'
|
|
||||||
honeypot.container = '<div style="display:none">{template}</div>'
|
|
||||||
@@ -3,13 +3,27 @@
|
|||||||
#--------------------------------------------------------------------
|
#--------------------------------------------------------------------
|
||||||
|
|
||||||
CI_ENVIRONMENT = production
|
CI_ENVIRONMENT = production
|
||||||
CI_DEBUG = false
|
|
||||||
|
|
||||||
|
|
||||||
#--------------------------------------------------------------------
|
#--------------------------------------------------------------------
|
||||||
# APP
|
# SECURITY: ALLOWED HOSTNAMES
|
||||||
#--------------------------------------------------------------------
|
#--------------------------------------------------------------------
|
||||||
app.appTimezone = 'UTC'
|
# CRITICAL: Whitelist of allowed hostnames to prevent Host Header
|
||||||
|
# Injection attacks (GHSA-jchf-7hr6-h4f3).
|
||||||
|
#
|
||||||
|
# REQUIRED IN PRODUCTION: Application will fail to start if not configured.
|
||||||
|
# In development, falls back to 'localhost' with an error log.
|
||||||
|
#
|
||||||
|
# Configure with comma-separated list of domains/subdomains:
|
||||||
|
# app.allowedHostnames = 'yourdomain.com,www.yourdomain.com'
|
||||||
|
#
|
||||||
|
# Or via environment variable (useful for Docker/Compose):
|
||||||
|
# ALLOWED_HOSTNAMES=yourdomain.com,www.yourdomain.com
|
||||||
|
#
|
||||||
|
# For local development:
|
||||||
|
# app.allowedHostnames = 'localhost'
|
||||||
|
#
|
||||||
|
# Note: Do not include protocol (http/https) or port numbers.
|
||||||
|
app.allowedHostnames = ''
|
||||||
|
|
||||||
#--------------------------------------------------------------------
|
#--------------------------------------------------------------------
|
||||||
# DATABASE
|
# DATABASE
|
||||||
@@ -21,7 +35,6 @@ database.default.username = 'admin'
|
|||||||
database.default.password = 'pointofsale'
|
database.default.password = 'pointofsale'
|
||||||
database.default.DBDriver = 'MySQLi'
|
database.default.DBDriver = 'MySQLi'
|
||||||
database.default.DBPrefix = 'ospos_'
|
database.default.DBPrefix = 'ospos_'
|
||||||
database.default.port = 3306
|
|
||||||
|
|
||||||
database.development.hostname = 'localhost'
|
database.development.hostname = 'localhost'
|
||||||
database.development.database = 'ospos'
|
database.development.database = 'ospos'
|
||||||
@@ -29,7 +42,6 @@ database.development.username = 'admin'
|
|||||||
database.development.password = 'pointofsale'
|
database.development.password = 'pointofsale'
|
||||||
database.development.DBDriver = 'MySQLi'
|
database.development.DBDriver = 'MySQLi'
|
||||||
database.development.DBPrefix = 'ospos_'
|
database.development.DBPrefix = 'ospos_'
|
||||||
database.development.port = 3306
|
|
||||||
|
|
||||||
database.tests.hostname = 'localhost'
|
database.tests.hostname = 'localhost'
|
||||||
database.tests.database = 'ospos'
|
database.tests.database = 'ospos'
|
||||||
@@ -37,19 +49,6 @@ database.tests.username = 'admin'
|
|||||||
database.tests.password = 'pointofsale'
|
database.tests.password = 'pointofsale'
|
||||||
database.tests.DBDriver = 'MySQLi'
|
database.tests.DBDriver = 'MySQLi'
|
||||||
database.tests.DBPrefix = 'ospos_'
|
database.tests.DBPrefix = 'ospos_'
|
||||||
database.tests.charset = utf8mb4
|
|
||||||
database.tests.DBCollat = utf8mb4_general_ci
|
|
||||||
database.tests.port = 3306
|
|
||||||
|
|
||||||
#--------------------------------------------------------------------
|
|
||||||
# EMAIL
|
|
||||||
#--------------------------------------------------------------------
|
|
||||||
email.SMTPHost = ''
|
|
||||||
email.SMTPUser = ''
|
|
||||||
email.SMTPPass = ''
|
|
||||||
email.SMTPPort =
|
|
||||||
email.SMTPTimeout = 5
|
|
||||||
email.SMTPCrypto = 'tls'
|
|
||||||
|
|
||||||
#--------------------------------------------------------------------
|
#--------------------------------------------------------------------
|
||||||
# ENCRYPTION
|
# ENCRYPTION
|
||||||
@@ -57,6 +56,23 @@ email.SMTPCrypto = 'tls'
|
|||||||
|
|
||||||
encryption.key = ''
|
encryption.key = ''
|
||||||
|
|
||||||
|
#--------------------------------------------------------------------
|
||||||
|
# LOGGER
|
||||||
|
# - 0 = Disables logging, Error logging TURNED OFF
|
||||||
|
# - 1 = Emergency Messages - System is unusable
|
||||||
|
# - 2 = Alert Messages - Action Must Be Taken Immediately
|
||||||
|
# - 3 = Critical Messages - Application component unavailable, unexpected exception.
|
||||||
|
# - 4 = Runtime Errors - Don't need immediate action, but should be monitored.
|
||||||
|
# - 5 = Warnings - Exceptional occurrences that are not errors.
|
||||||
|
# - 6 = Notices - Normal but significant events.
|
||||||
|
# - 7 = Info - Interesting events, like user logging in, etc.
|
||||||
|
# - 8 = Debug - Detailed debug information.
|
||||||
|
# - 9 = All Messages
|
||||||
|
#--------------------------------------------------------------------
|
||||||
|
|
||||||
|
logger.threshold = 0
|
||||||
|
app.db_log_enabled = false
|
||||||
|
|
||||||
#--------------------------------------------------------------------
|
#--------------------------------------------------------------------
|
||||||
# HONEYPOT
|
# HONEYPOT
|
||||||
#--------------------------------------------------------------------
|
#--------------------------------------------------------------------
|
||||||
@@ -64,23 +80,5 @@ encryption.key = ''
|
|||||||
honeypot.hidden = true
|
honeypot.hidden = true
|
||||||
honeypot.label = 'Fill This Field'
|
honeypot.label = 'Fill This Field'
|
||||||
honeypot.name = 'honeypot'
|
honeypot.name = 'honeypot'
|
||||||
honeypot.template = '<label>{label}</label><input type="text" name="{name}" value=""/>'
|
honeypot.template = '<label>{label}</label><input type="text" name="{name}" value="">'
|
||||||
honeypot.container = '<div style="display:none">{template}</div>'
|
honeypot.container = '<div style="display:none">{template}</div>'
|
||||||
|
|
||||||
#--------------------------------------------------------------------
|
|
||||||
# LOGGER
|
|
||||||
# - 0 = Disables logging, Error logging TURNED OFF
|
|
||||||
# - 1 = Emergency Messages - System is unusable
|
|
||||||
# - 2 = Alert Messages - Action Must Be Taken Immediately
|
|
||||||
# - 3 = Critical Messages - Application component unavailable, unexpected exception.
|
|
||||||
# - 4 = Runtime Errors - Don't need immediate action, but should be monitored.
|
|
||||||
# - 5 = Warnings - Exceptional occurrences that are not errors.
|
|
||||||
# - 6 = Notices - Normal but significant events.
|
|
||||||
# - 7 = Info - Interesting events, like user logging in, etc.
|
|
||||||
# - 8 = Debug - Detailed debug information.
|
|
||||||
# - 9 = All Messages
|
|
||||||
#--------------------------------------------------------------------
|
|
||||||
|
|
||||||
logger.threshold = 0
|
|
||||||
app.db_log_enabled = false
|
|
||||||
app.db_log_only_long = false
|
|
||||||
306
.github/ISSUE_TEMPLATE/bug report.yml
vendored
306
.github/ISSUE_TEMPLATE/bug report.yml
vendored
@@ -1,119 +1,187 @@
|
|||||||
name: Bug Report
|
name: 🐛 Bug Report
|
||||||
description: File a bug report
|
description: File a bug report to help us improve
|
||||||
title: "[Bug]: "
|
title: "[Bug]: "
|
||||||
labels: ["bug", "triage"]
|
labels: ["bug", "triage"]
|
||||||
projects: ["ospos/3", "ospos/4"]
|
projects: ["ospos/3", "ospos/4"]
|
||||||
assignees:
|
assignees: []
|
||||||
- none
|
body:
|
||||||
body:
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
- type: markdown
|
# INTRODUCTION
|
||||||
attributes:
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
value: |
|
- type: markdown
|
||||||
Bug reports indicate that something is not working as intended.
|
attributes:
|
||||||
Please include as much detail as possible and submit a separate bug report for each problem.
|
value: |
|
||||||
Do not include personal identifying information such as email addresses or encryption keys.
|
## Thanks for taking the time to fill out this bug report! 🐜
|
||||||
- type: textarea
|
|
||||||
id: bug-description
|
Bug reports help us identify and fix issues. Please provide as much detail as possible.
|
||||||
attributes:
|
|
||||||
label: Bug Description?
|
> ⚠️ **Important:** Submit a separate bug report for each problem you encounter.
|
||||||
description: Describe the problem that you are seeing
|
>
|
||||||
placeholder: "Describe the problem that you are seeing"
|
> 🚫 Do not include personal identifying information such as email addresses or encryption keys.
|
||||||
validations:
|
|
||||||
required: true
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
- type: textarea
|
# PROBLEM DESCRIPTION
|
||||||
id: steps-reproduce
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
attributes:
|
- type: textarea
|
||||||
label: Steps to Reproduce?
|
id: bug-description
|
||||||
description: List the steps to reproduce this issue
|
attributes:
|
||||||
placeholder: "Steps to Reproduce"
|
label: 🐛 Bug Description
|
||||||
validations:
|
description: A clear and concise description of what the bug is.
|
||||||
required: true
|
placeholder: |
|
||||||
- type: textarea
|
Example: When I try to print a receipt, the application crashes
|
||||||
id: expected-behavior
|
with an error message saying "Unable to connect to printer".
|
||||||
attributes:
|
validations:
|
||||||
label: Expected Behavior?
|
required: true
|
||||||
description: Tell us what did you expect to happen?
|
|
||||||
placeholder: "Expected Behavior"
|
- type: textarea
|
||||||
validations:
|
id: steps-reproduce
|
||||||
required: true
|
attributes:
|
||||||
- type: dropdown
|
label: 📋 Steps to Reproduce
|
||||||
id: ospos-version
|
description: Detailed steps to reproduce the behavior.
|
||||||
attributes:
|
placeholder: |
|
||||||
label: OpensourcePOS Version
|
1. Go to '...'
|
||||||
description: What version of our software are you running?
|
2. Click on '...'
|
||||||
options:
|
3. Scroll down to '...'
|
||||||
- opensourcepos 3.3.9
|
4. See error
|
||||||
- opensourcepos 3.3.8
|
validations:
|
||||||
- opensourcepos 3.3.7
|
required: true
|
||||||
- development (unreleased)
|
|
||||||
default: 0
|
- type: textarea
|
||||||
validations:
|
id: expected-behavior
|
||||||
required: true
|
attributes:
|
||||||
- type: dropdown
|
label: ✅ Expected Behavior
|
||||||
id: php-version
|
description: A clear and concise description of what you expected to happen.
|
||||||
attributes:
|
placeholder: |
|
||||||
label: Php version
|
Example: The receipt should print successfully without any errors.
|
||||||
description: What version of Php?
|
validations:
|
||||||
options:
|
required: true
|
||||||
- Php 7.2
|
|
||||||
- Php 7.3
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
- Php 7.4
|
# ENVIRONMENT DETAILS
|
||||||
- Php 8.1
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
- Php 8.2
|
- type: dropdown
|
||||||
- Php 8.3
|
id: ospos-version
|
||||||
- Php 8.4
|
attributes:
|
||||||
default: 0
|
label: 📦 OpenSourcePOS Version
|
||||||
validations:
|
description: What version of our software are you running?
|
||||||
required: true
|
options:
|
||||||
- type: dropdown
|
- development (unreleased)
|
||||||
id: browsers
|
- OpenSourcePOS 3.4.2
|
||||||
attributes:
|
- OpenSourcePOS 3.4.1
|
||||||
label: What browsers are you seeing the problem on?
|
- OpenSourcePOS 3.4.0
|
||||||
multiple: true
|
- OpenSourcePOS 3.3.9
|
||||||
options:
|
- OpenSourcePOS 3.3.8
|
||||||
- Firefox
|
default: 0
|
||||||
- Chrome
|
validations:
|
||||||
- Safari
|
required: true
|
||||||
- Microsoft Edge
|
|
||||||
- Other
|
- type: dropdown
|
||||||
- type: input
|
id: php-version
|
||||||
id: server
|
attributes:
|
||||||
attributes:
|
label: 🔧 PHP Version
|
||||||
label: Server Operating System and version
|
description: What version of PHP are you running?
|
||||||
description: "Server Operating System "
|
options:
|
||||||
placeholder: "Server Operating System "
|
- PHP 8.4
|
||||||
validations:
|
- PHP 8.3
|
||||||
required: true
|
- PHP 8.2
|
||||||
- type: input
|
- PHP 8.1
|
||||||
id: database
|
- PHP 7.4
|
||||||
attributes:
|
- Other
|
||||||
label: Database Management System and version
|
default: 0
|
||||||
description: "Database Management System"
|
validations:
|
||||||
placeholder: "Database Management"
|
required: true
|
||||||
validations:
|
|
||||||
required: true
|
- type: dropdown
|
||||||
- type: input
|
id: browsers
|
||||||
id: webserver
|
attributes:
|
||||||
attributes:
|
label: 🌐 Browser(s)
|
||||||
label: Web Server and version
|
description: What browser(s) are you seeing the problem on?
|
||||||
description: "Web Server and version "
|
multiple: true
|
||||||
placeholder: "Web Server and version "
|
options:
|
||||||
validations:
|
- Firefox
|
||||||
required: true
|
- Chrome
|
||||||
- type: textarea
|
- Safari
|
||||||
id: servers
|
- Microsoft Edge
|
||||||
attributes:
|
- Other
|
||||||
label: System Information Report (optional)
|
|
||||||
description: Copy and paste from OSPOS > Configuration > Setup & Conf > Setup & Conf?
|
- type: input
|
||||||
placeholder: System Information Report
|
id: server
|
||||||
value: "System Information Report"
|
attributes:
|
||||||
validations:
|
label: 🖥️ Server Operating System
|
||||||
required: true
|
description: What server OS and version are you running?
|
||||||
- type: checkboxes
|
placeholder: "e.g., Ubuntu 22.04, CentOS 7, Windows Server 2022"
|
||||||
id: terms
|
validations:
|
||||||
attributes:
|
required: true
|
||||||
label: Unmodified copy of OpensourcePOS
|
|
||||||
description: By submitting this issue you agree this copy has not been modified
|
- type: input
|
||||||
options:
|
id: database
|
||||||
- label: I agree this copy has not been modified
|
attributes:
|
||||||
required: true
|
label: 🗄️ Database
|
||||||
|
description: What database management system and version are you using?
|
||||||
|
placeholder: "e.g., MySQL 8.0, MariaDB 10.11, Percona 8.0"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: webserver
|
||||||
|
attributes:
|
||||||
|
label: 🌍 Web Server
|
||||||
|
description: What web server and version are you using?
|
||||||
|
placeholder: "e.g., Apache 2.4, Nginx 1.24, Caddy 2.7"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# ADDITIONAL INFORMATION
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
- type: textarea
|
||||||
|
id: system-info
|
||||||
|
attributes:
|
||||||
|
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...
|
||||||
|
render: text
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
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/`
|
||||||
|
- PHP logs: Check your `php.ini` for `error_log` location
|
||||||
|
placeholder: |
|
||||||
|
Paste log output here...
|
||||||
|
render: shell
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: screenshots
|
||||||
|
attributes:
|
||||||
|
label: 📸 Screenshots
|
||||||
|
description: If applicable, add screenshots to help explain your problem.
|
||||||
|
placeholder: Drag and drop images here...
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# CONFIRMATION
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
- type: checkboxes
|
||||||
|
id: terms
|
||||||
|
attributes:
|
||||||
|
label: ✓ Confirmation
|
||||||
|
description: Please confirm the following before submitting
|
||||||
|
options:
|
||||||
|
- label: I certify that this is an unmodified copy of OpenSourcePOS
|
||||||
|
required: true
|
||||||
|
- 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
|
||||||
|
|||||||
199
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
199
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -1,63 +1,136 @@
|
|||||||
name: ✨ Feature Request
|
name: ✨ Feature Request
|
||||||
description: Suggest an idea for this project
|
description: Suggest an idea or enhancement for this project
|
||||||
title: "[Feature]: "
|
title: "[Feature]: "
|
||||||
labels: ["enhancement"]
|
labels: ["enhancement"]
|
||||||
assignees: ["none"]
|
assignees: []
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
attributes:
|
# INTRODUCTION
|
||||||
value: |
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
Thanks for taking the time to fill out this feature request! 🤗
|
- type: markdown
|
||||||
Please make sure this feature request hasn't been already submitted by someone by looking through other open/closed issues. 😃
|
attributes:
|
||||||
|
value: |
|
||||||
- type: dropdown
|
## Thanks for suggesting a new feature! 💡
|
||||||
attributes:
|
|
||||||
multiple: false
|
We appreciate you taking the time to help improve OpenSourcePOS.
|
||||||
label: Type of Feature
|
|
||||||
description: Select the type of feature request.
|
> 📋 **Before submitting:** Please search [existing feature requests](https://github.com/opensourcepos/opensourcepos/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement) to ensure your idea hasn't already been suggested.
|
||||||
options:
|
|
||||||
- "✨ New Feature"
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
- "📝 Documentation"
|
# FEATURE DETAILS
|
||||||
- "🎨 Style and UI"
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
- "🔨 Code Refactor"
|
- type: dropdown
|
||||||
- "⚡ Performance Improvements"
|
id: feature-type
|
||||||
- "✅ New Test"
|
attributes:
|
||||||
validations:
|
label: 🏷️ Feature Type
|
||||||
required: true
|
description: What type of feature are you requesting?
|
||||||
|
options:
|
||||||
- type: dropdown
|
- "✨ New Feature"
|
||||||
id: ospos-version
|
- "📝 Documentation Improvement"
|
||||||
attributes:
|
- "🎨 UI/UX Enhancement"
|
||||||
label: OpensourcePOS Version
|
- "🔨 Code Refactoring"
|
||||||
description: What version of our software are you running?
|
- "⚡ Performance Improvement"
|
||||||
options:
|
- "✅ New Test Coverage"
|
||||||
- opensourcepos 3.3.9
|
- "🔌 Plugin/Integration"
|
||||||
- opensourcepos 3.3.8
|
default: 0
|
||||||
- opensourcepos 3.3.7
|
validations:
|
||||||
default: 0
|
required: true
|
||||||
validations:
|
|
||||||
required: true
|
- type: dropdown
|
||||||
|
id: ospos-version
|
||||||
- type: textarea
|
attributes:
|
||||||
id: description
|
label: 📦 OpenSourcePOS Version
|
||||||
attributes:
|
description: What version are you currently running?
|
||||||
label: Description
|
options:
|
||||||
description: Give us a brief description of the feature or enhancement you would like
|
- development (unreleased)
|
||||||
validations:
|
- OpenSourcePOS 3.4.2
|
||||||
required: true
|
- OpenSourcePOS 3.4.1
|
||||||
|
- OpenSourcePOS 3.4.0
|
||||||
- type: textarea
|
- OpenSourcePOS 3.3.9
|
||||||
id: additional-information
|
- OpenSourcePOS 3.3.8
|
||||||
attributes:
|
default: 0
|
||||||
label: Additional Information
|
validations:
|
||||||
description: Give us some additional information on the feature request like proposed solutions, links, screenshots, etc.
|
required: true
|
||||||
|
|
||||||
- type: checkboxes
|
- type: textarea
|
||||||
id: terms
|
id: problem-statement
|
||||||
attributes:
|
attributes:
|
||||||
label: Verify you searched open requests in OpensourcePOS
|
label: 🎯 Problem Statement
|
||||||
description: By submitting this request you agree that you have searched Open Requests in the Tracker
|
description: |
|
||||||
options:
|
Is your feature request related to a problem? Please describe.
|
||||||
- label: I agree I have searched Open Requests
|
|
||||||
required: true
|
A clear description of what the problem is. Ex: I'm always frustrated when [...]
|
||||||
|
placeholder: |
|
||||||
|
Example: I always have to manually calculate taxes for different regions,
|
||||||
|
which is time-consuming and error-prone.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: proposed-solution
|
||||||
|
attributes:
|
||||||
|
label: 💡 Proposed Solution
|
||||||
|
description: A clear and concise description of what you want to happen.
|
||||||
|
placeholder: |
|
||||||
|
Example: Add an automatic tax calculation feature that:
|
||||||
|
- Detects the customer's region
|
||||||
|
- Applies the correct tax rate
|
||||||
|
- Generates a tax report automatically
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: alternatives
|
||||||
|
attributes:
|
||||||
|
label: 🔄 Alternatives Considered
|
||||||
|
description: A clear description of any alternative solutions or features you've considered.
|
||||||
|
placeholder: |
|
||||||
|
Example: I considered using an external tax service, but it would be
|
||||||
|
better to have this integrated directly into OpenSourcePOS.
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# ADDITIONAL INFORMATION
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
- type: textarea
|
||||||
|
id: additional-context
|
||||||
|
attributes:
|
||||||
|
label: 📎 Additional Context
|
||||||
|
description: |
|
||||||
|
Add any other context, screenshots, mockups, or references about the feature request here.
|
||||||
|
|
||||||
|
**Helpful additions:**
|
||||||
|
- Links to similar features in other software
|
||||||
|
- Mockups or diagrams
|
||||||
|
- Code examples
|
||||||
|
- Documentation references
|
||||||
|
placeholder: |
|
||||||
|
Any other relevant information, links, or screenshots...
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: acceptance-criteria
|
||||||
|
attributes:
|
||||||
|
label: ✅ Acceptance Criteria
|
||||||
|
description: |
|
||||||
|
(Optional) Define what "done" looks like for this feature.
|
||||||
|
|
||||||
|
Format: **Given** [context], **When** [action], **Then** [outcome]
|
||||||
|
placeholder: |
|
||||||
|
Given a customer is selected from region X
|
||||||
|
When the sale is completed
|
||||||
|
Then the tax rate for region X is automatically applied
|
||||||
|
And the tax amount is correctly calculated
|
||||||
|
And a tax entry is logged in the report
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# CONFIRMATION
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
- type: checkboxes
|
||||||
|
id: terms
|
||||||
|
attributes:
|
||||||
|
label: ✓ Confirmation
|
||||||
|
description: Please confirm before submitting
|
||||||
|
options:
|
||||||
|
- label: I have searched existing feature requests to ensure this is not a duplicate
|
||||||
|
required: true
|
||||||
|
- label: I have provided a clear problem statement and proposed solution
|
||||||
|
required: true
|
||||||
61
.github/workflows/README.md
vendored
Normal file
61
.github/workflows/README.md
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# GitHub Actions
|
||||||
|
|
||||||
|
This document describes the CI/CD workflows for OSPOS.
|
||||||
|
|
||||||
|
## Build and Release Workflow (`.github/workflows/build-release.yml`)
|
||||||
|
|
||||||
|
### Build Process
|
||||||
|
- Setup PHP 8.2 with required extensions
|
||||||
|
- Setup Node.js 20
|
||||||
|
- Install composer dependencies
|
||||||
|
- Install npm dependencies
|
||||||
|
- Build frontend assets with Gulp
|
||||||
|
|
||||||
|
### Docker Images
|
||||||
|
- Build and push `opensourcepos` Docker image for multiple architectures (linux/amd64, linux/arm64)
|
||||||
|
- On master: tagged with version and `latest`
|
||||||
|
- On other branches: tagged with version only
|
||||||
|
- Pushed to Docker Hub
|
||||||
|
|
||||||
|
### Releases
|
||||||
|
- Create distribution archives (tar.gz, zip)
|
||||||
|
- Create/update GitHub "unstable" release on master branch only
|
||||||
|
|
||||||
|
## Required Secrets
|
||||||
|
|
||||||
|
To use this workflow, you need to add the following secrets to your repository:
|
||||||
|
|
||||||
|
1. **DOCKER_USERNAME** - Docker Hub username for pushing images
|
||||||
|
2. **DOCKER_PASSWORD** - Docker Hub password/token for pushing images
|
||||||
|
|
||||||
|
### How to add secrets
|
||||||
|
|
||||||
|
1. Go to your repository on GitHub
|
||||||
|
2. Click **Settings** → **Secrets and variables** → **Actions**
|
||||||
|
3. Click **New repository secret**
|
||||||
|
4. Add `DOCKER_USERNAME` and `DOCKER_PASSWORD`
|
||||||
|
|
||||||
|
The `GITHUB_TOKEN` is automatically provided by GitHub Actions.
|
||||||
|
|
||||||
|
## Workflow Triggers
|
||||||
|
|
||||||
|
- **Push to master** - Runs build, Docker push (with `latest` tag), and release
|
||||||
|
- **Push to other branches** - Runs build and Docker push (version tag only)
|
||||||
|
- **Push tags** - Runs build and Docker push (version tag only)
|
||||||
|
- **Pull requests** - Runs build only (PHPUnit tests run in parallel via phpunit.yml)
|
||||||
|
|
||||||
|
## Existing Workflows
|
||||||
|
|
||||||
|
This repository also has these workflows:
|
||||||
|
- `.github/workflows/main.yml` - PHP linting with PHP-CS-Fixer
|
||||||
|
- `.github/workflows/phpunit.yml` - PHPUnit tests (runs on all PHP versions 8.1-8.4)
|
||||||
|
- `.github/workflows/php-linter.yml` - PHP linting
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
PHPUnit tests are run separately via `.github/workflows/phpunit.yml` on every push and pull request, testing against PHP 8.1, 8.2, 8.3, and 8.4.
|
||||||
|
|
||||||
|
To test the build workflow:
|
||||||
|
1. Add the required secrets
|
||||||
|
2. Push to master or create a PR
|
||||||
|
3. Monitor the Actions tab in GitHub
|
||||||
215
.github/workflows/build-release.yml
vendored
Normal file
215
.github/workflows/build-release.yml
vendored
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
name: Build and Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
version: ${{ steps.version.outputs.version }}
|
||||||
|
version-tag: ${{ steps.version.outputs.version-tag }}
|
||||||
|
short-sha: ${{ steps.version.outputs.short-sha }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: '8.2'
|
||||||
|
extensions: intl, mbstring, mysqli, gd, bcmath, zip
|
||||||
|
coverage: none
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
|
- name: Get composer cache directory
|
||||||
|
run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Cache composer dependencies
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ${{ env.COMPOSER_CACHE_FILES_DIR }}
|
||||||
|
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-composer-
|
||||||
|
|
||||||
|
- name: Get npm cache directory
|
||||||
|
run: echo "NPM_CACHE_DIR=$(npm config get cache)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Cache npm dependencies
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ${{ env.NPM_CACHE_DIR }}
|
||||||
|
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-node-
|
||||||
|
|
||||||
|
- name: Install composer dependencies
|
||||||
|
run: composer install --no-dev --optimize-autoloader
|
||||||
|
|
||||||
|
- name: Install npm dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Install gulp globally
|
||||||
|
run: npm install -g gulp-cli
|
||||||
|
|
||||||
|
- name: Get version info
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
VERSION=$(grep "application_version" app/Config/App.php | sed "s/.*= '\(.*\)';/\1/g")
|
||||||
|
BRANCH=$(echo "${GITHUB_REF#refs/heads/}" | sed 's/feature\///' | tr '/' '_')
|
||||||
|
TAG=$(echo "${GITHUB_TAG:-$BRANCH}" | tr '/' '_')
|
||||||
|
SHORT_SHA=$(git rev-parse --short=6 HEAD)
|
||||||
|
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||||
|
echo "version-tag=$VERSION-$BRANCH-$SHORT_SHA" >> $GITHUB_OUTPUT
|
||||||
|
echo "short-sha=$SHORT_SHA" >> $GITHUB_OUTPUT
|
||||||
|
echo "branch=$BRANCH" >> $GITHUB_OUTPUT
|
||||||
|
env:
|
||||||
|
GITHUB_TAG: ${{ github.ref_name }}
|
||||||
|
|
||||||
|
- name: Create .env file
|
||||||
|
run: |
|
||||||
|
cp .env.example .env
|
||||||
|
sed -i 's/production/development/g' .env
|
||||||
|
|
||||||
|
- name: Update commit hash
|
||||||
|
run: |
|
||||||
|
SHORT_SHA="${{ steps.version.outputs.short-sha }}"
|
||||||
|
sed -i "s/commit_sha1 = 'dev'/commit_sha1 = '$SHORT_SHA'/g" app/Config/OSPOS.php
|
||||||
|
|
||||||
|
- name: Build frontend assets
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Create distribution archives
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
gulp compress
|
||||||
|
VERSION="${{ steps.version.outputs.version }}"
|
||||||
|
SHORT_SHA="${{ steps.version.outputs.short-sha }}"
|
||||||
|
mv dist/opensourcepos.tar "dist/opensourcepos.$VERSION.$SHORT_SHA.tar"
|
||||||
|
mv dist/opensourcepos.zip "dist/opensourcepos.$VERSION.$SHORT_SHA.zip"
|
||||||
|
|
||||||
|
- name: Upload build artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: dist-${{ steps.version.outputs.short-sha }}
|
||||||
|
path: dist/
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
- name: Upload build context for Docker
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: build-context-${{ steps.version.outputs.short-sha }}
|
||||||
|
path: |
|
||||||
|
.
|
||||||
|
!.git
|
||||||
|
!node_modules
|
||||||
|
include-hidden-files: true
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
docker:
|
||||||
|
name: Build Docker Image
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
needs: build
|
||||||
|
if: github.event_name == 'push'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Download build context
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: build-context-${{ needs.build.outputs.short-sha }}
|
||||||
|
path: .
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Determine Docker tags
|
||||||
|
id: tags
|
||||||
|
run: |
|
||||||
|
BRANCH=$(echo "${GITHUB_REF#refs/heads/}" | tr '/' '_')
|
||||||
|
if [ "$BRANCH" = "master" ]; then
|
||||||
|
echo "tags=${{ secrets.DOCKER_USERNAME }}/opensourcepos:${{ needs.build.outputs.version-tag }},${{ secrets.DOCKER_USERNAME }}/opensourcepos:master" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "tags=${{ secrets.DOCKER_USERNAME }}/opensourcepos:${{ needs.build.outputs.version-tag }}" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
env:
|
||||||
|
GITHUB_REF: ${{ github.ref }}
|
||||||
|
|
||||||
|
- name: Build and push Docker images
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
target: ospos
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.tags.outputs.tags }}
|
||||||
|
|
||||||
|
release:
|
||||||
|
name: Create Release
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Download build artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: dist-${{ needs.build.outputs.short-sha }}
|
||||||
|
path: dist/
|
||||||
|
|
||||||
|
- name: Get version info
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
VERSION="${{ needs.build.outputs.version }}"
|
||||||
|
SHORT_SHA=$(git rev-parse --short=6 HEAD)
|
||||||
|
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||||
|
echo "short-sha=$SHORT_SHA" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Create/Update unstable release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
tag_name: unstable
|
||||||
|
name: Unstable OpenSourcePOS
|
||||||
|
body: |
|
||||||
|
This is a build of the latest master which might contain bugs. Use at your own risk.
|
||||||
|
|
||||||
|
Check the releases section for the latest official release.
|
||||||
|
files: |
|
||||||
|
dist/opensourcepos.${{ steps.version.outputs.version }}.${{ steps.version.outputs.short-sha }}.zip
|
||||||
|
prerelease: true
|
||||||
|
draft: false
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
71
.github/workflows/codeql-analysis.yml
vendored
71
.github/workflows/codeql-analysis.yml
vendored
@@ -1,71 +0,0 @@
|
|||||||
# For most projects, this workflow file will not need changing; you simply need
|
|
||||||
# to commit it to your repository.
|
|
||||||
#
|
|
||||||
# You may wish to alter this file to override the set of languages analyzed,
|
|
||||||
# or to provide custom queries or build logic.
|
|
||||||
#
|
|
||||||
# ******** NOTE ********
|
|
||||||
# We have attempted to detect the languages in your repository. Please check
|
|
||||||
# the `language` matrix defined below to confirm you have the correct set of
|
|
||||||
# supported CodeQL languages.
|
|
||||||
#
|
|
||||||
name: "CodeQL"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ master ]
|
|
||||||
pull_request:
|
|
||||||
# The branches below must be a subset of the branches above
|
|
||||||
branches: [ master ]
|
|
||||||
schedule:
|
|
||||||
- cron: '21 12 * * 3'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
analyze:
|
|
||||||
name: Analyze
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
security-events: write
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
language: [ 'javascript' ]
|
|
||||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
|
||||||
# Learn more:
|
|
||||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
|
||||||
- name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@v1
|
|
||||||
with:
|
|
||||||
languages: ${{ matrix.language }}
|
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
|
||||||
# By default, queries listed here will override any specified in a config file.
|
|
||||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
|
||||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
|
||||||
|
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
|
||||||
- name: Autobuild
|
|
||||||
uses: github/codeql-action/autobuild@v1
|
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
|
||||||
# 📚 https://git.io/JvXDl
|
|
||||||
|
|
||||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
|
||||||
# and modify them (or add more) to build your code if your project
|
|
||||||
# uses a compiled language
|
|
||||||
|
|
||||||
#- run: |
|
|
||||||
# make bootstrap
|
|
||||||
# make release
|
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
|
||||||
uses: github/codeql-action/analyze@v1
|
|
||||||
219
.github/workflows/deploy-core.yml
vendored
Normal file
219
.github/workflows/deploy-core.yml
vendored
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
name: Deploy Core
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
image_tag:
|
||||||
|
description: 'Docker image tag to deploy'
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
sha:
|
||||||
|
description: 'Git commit SHA to deploy'
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description:
|
||||||
|
description: 'Deployment description'
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
pr_number:
|
||||||
|
description: 'Pull request number (optional)'
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
outputs:
|
||||||
|
deployment_id:
|
||||||
|
description: 'GitHub deployment ID'
|
||||||
|
value: ${{ jobs.deploy.outputs.deployment_id }}
|
||||||
|
status:
|
||||||
|
description: 'Deployment status (success/failure)'
|
||||||
|
value: ${{ jobs.deploy.outputs.status }}
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: deploy-staging
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
deployments: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
name: Deploy to staging
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
environment:
|
||||||
|
name: staging
|
||||||
|
url: ${{ vars.DEPLOY_URL || 'https://dev.opensourcepos.org' }}
|
||||||
|
deployment: false
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
deployment_id: ${{ steps.deployment.outputs.deployment_id }}
|
||||||
|
status: ${{ steps.webhook.outputs.status }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Create GitHub Deployment
|
||||||
|
id: deployment
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
IMAGE_TAG: ${{ inputs.image_tag }}
|
||||||
|
REF_SHA: ${{ inputs.sha }}
|
||||||
|
DESCRIPTION: ${{ inputs.description }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
DEPLOYMENT_ID=$(gh api "repos/${GITHUB_REPOSITORY}/deployments" \
|
||||||
|
-X POST \
|
||||||
|
-f ref="${REF_SHA}" \
|
||||||
|
-f environment="staging" \
|
||||||
|
-f description="${DESCRIPTION}" \
|
||||||
|
-F auto_merge=false \
|
||||||
|
-F required_contexts[] \
|
||||||
|
--jq '.id')
|
||||||
|
|
||||||
|
if [ -z "$DEPLOYMENT_ID" ]; then
|
||||||
|
echo "::error::Failed to create deployment"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "deployment_id=$DEPLOYMENT_ID" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Created deployment: $DEPLOYMENT_ID"
|
||||||
|
|
||||||
|
- name: Set deployment status to in_progress
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
gh api "repos/${GITHUB_REPOSITORY}/deployments/${{ steps.deployment.outputs.deployment_id }}/statuses" \
|
||||||
|
-X POST \
|
||||||
|
-f state="in_progress" \
|
||||||
|
-f description="Deployment in progress..." \
|
||||||
|
-f log_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
|
||||||
|
|
||||||
|
- name: Trigger deployment webhook
|
||||||
|
id: webhook
|
||||||
|
env:
|
||||||
|
DEPLOY_WEBHOOK_URL: ${{ secrets.DEPLOY_WEBHOOK_URL }}
|
||||||
|
DEPLOY_WEBHOOK_SECRET: ${{ secrets.DEPLOY_WEBHOOK_SECRET }}
|
||||||
|
DOCKER_REPO_NAME: ${{ secrets.DOCKER_REPO_NAME }}
|
||||||
|
IMAGE_TAG: ${{ inputs.image_tag }}
|
||||||
|
REF_SHA: ${{ inputs.sha }}
|
||||||
|
DEPLOYMENT_ID: ${{ steps.deployment.outputs.deployment_id }}
|
||||||
|
PR_NUMBER: ${{ inputs.pr_number }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [ -z "$DEPLOY_WEBHOOK_URL" ]; then
|
||||||
|
echo "::error::DEPLOY_WEBHOOK_URL secret is not configured"
|
||||||
|
echo "Please add the DEPLOY_WEBHOOK_URL secret in your repository settings"
|
||||||
|
echo "status=failure" >> "$GITHUB_OUTPUT"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
REPO_NAME="${DOCKER_REPO_NAME:-opensourcepos/opensourcepos}"
|
||||||
|
REPO_NAMESPACE="${REPO_NAME%%/*}"
|
||||||
|
REPO_SHORT_NAME="${REPO_NAME#*/}"
|
||||||
|
PUSHED_AT=$(date +%s)
|
||||||
|
|
||||||
|
if [ -n "$PR_NUMBER" ]; then
|
||||||
|
PAYLOAD=$(jq -n \
|
||||||
|
--arg callback_url "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
|
||||||
|
--argjson pushed_at "$PUSHED_AT" \
|
||||||
|
--arg pusher "$GITHUB_ACTOR" \
|
||||||
|
--arg tag "$IMAGE_TAG" \
|
||||||
|
--arg repo_name "$REPO_NAME" \
|
||||||
|
--arg name "$REPO_SHORT_NAME" \
|
||||||
|
--arg namespace "$REPO_NAMESPACE" \
|
||||||
|
--arg repo_url "https://hub.docker.com/r/${REPO_NAME}/" \
|
||||||
|
--arg deployment_id "$DEPLOYMENT_ID" \
|
||||||
|
--arg repository "$GITHUB_REPOSITORY" \
|
||||||
|
--arg sha "$REF_SHA" \
|
||||||
|
--arg run_id "$GITHUB_RUN_ID" \
|
||||||
|
--arg actor "$GITHUB_ACTOR" \
|
||||||
|
--argjson pr_number "$PR_NUMBER" \
|
||||||
|
'{
|
||||||
|
callback_url: $callback_url,
|
||||||
|
push_data: {pushed_at: $pushed_at, pusher: $pusher, tag: $tag},
|
||||||
|
repository: {repo_name: $repo_name, name: $name, namespace: $namespace, repo_url: $repo_url, status: "Active"},
|
||||||
|
github_deployment: {id: $deployment_id, environment: "staging", repository: $repository, sha: $sha, run_id: $run_id, actor: $actor, pull_request: $pr_number}
|
||||||
|
}')
|
||||||
|
else
|
||||||
|
PAYLOAD=$(jq -n \
|
||||||
|
--arg callback_url "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
|
||||||
|
--argjson pushed_at "$PUSHED_AT" \
|
||||||
|
--arg pusher "$GITHUB_ACTOR" \
|
||||||
|
--arg tag "$IMAGE_TAG" \
|
||||||
|
--arg repo_name "$REPO_NAME" \
|
||||||
|
--arg name "$REPO_SHORT_NAME" \
|
||||||
|
--arg namespace "$REPO_NAMESPACE" \
|
||||||
|
--arg repo_url "https://hub.docker.com/r/${REPO_NAME}/" \
|
||||||
|
--arg deployment_id "$DEPLOYMENT_ID" \
|
||||||
|
--arg repository "$GITHUB_REPOSITORY" \
|
||||||
|
--arg sha "$REF_SHA" \
|
||||||
|
--arg run_id "$GITHUB_RUN_ID" \
|
||||||
|
--arg actor "$GITHUB_ACTOR" \
|
||||||
|
'{
|
||||||
|
callback_url: $callback_url,
|
||||||
|
push_data: {pushed_at: $pushed_at, pusher: $pusher, tag: $tag},
|
||||||
|
repository: {repo_name: $repo_name, name: $name, namespace: $namespace, repo_url: $repo_url, status: "Active"},
|
||||||
|
github_deployment: {id: $deployment_id, environment: "staging", repository: $repository, sha: $sha, run_id: $run_id, actor: $actor}
|
||||||
|
}')
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Sending webhook..."
|
||||||
|
echo "Image: ${IMAGE_TAG}"
|
||||||
|
echo "Environment: staging"
|
||||||
|
|
||||||
|
HEADERS=(-H "Content-Type: application/json")
|
||||||
|
|
||||||
|
if [ -n "$DEPLOY_WEBHOOK_SECRET" ]; then
|
||||||
|
SIGNATURE=$(printf '%s' "$PAYLOAD" | openssl dgst -sha256 -hmac "$DEPLOY_WEBHOOK_SECRET" | sed 's/.*= //')
|
||||||
|
HEADERS+=(-H "X-Hub-Signature-256: sha256=$SIGNATURE")
|
||||||
|
echo "Using HMAC-SHA256 signature verification"
|
||||||
|
else
|
||||||
|
echo "::warning::DEPLOY_WEBHOOK_SECRET not set - webhook calls will not be signed"
|
||||||
|
echo "For security, configure DEPLOY_WEBHOOK_SECRET in your repository settings"
|
||||||
|
fi
|
||||||
|
|
||||||
|
HTTP_CODE=$(curl -sS --connect-timeout 10 --max-time 120 \
|
||||||
|
-o response.txt -w "%{http_code}" \
|
||||||
|
-X POST \
|
||||||
|
"${HEADERS[@]}" \
|
||||||
|
-d "$PAYLOAD" \
|
||||||
|
"$DEPLOY_WEBHOOK_URL") || HTTP_CODE="000"
|
||||||
|
|
||||||
|
echo "Response code: $HTTP_CODE"
|
||||||
|
if [ -s response.txt ]; then
|
||||||
|
cat response.txt
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
|
||||||
|
echo "status=success" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "status=failure" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Set deployment status
|
||||||
|
if: always()
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
IMAGE_TAG: ${{ inputs.image_tag }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
STATE="${{ steps.webhook.outputs.status }}"
|
||||||
|
|
||||||
|
if [ "$STATE" = "success" ]; then
|
||||||
|
DESCRIPTION=$(jq -nr --arg tag "$IMAGE_TAG" \
|
||||||
|
'"Deployed image \($tag) to staging"')
|
||||||
|
|
||||||
|
gh api "repos/${GITHUB_REPOSITORY}/deployments/${{ steps.deployment.outputs.deployment_id }}/statuses" \
|
||||||
|
-X POST \
|
||||||
|
-f state="success" \
|
||||||
|
-f description="$DESCRIPTION"
|
||||||
|
else
|
||||||
|
gh api "repos/${GITHUB_REPOSITORY}/deployments/${{ steps.deployment.outputs.deployment_id }}/statuses" \
|
||||||
|
-X POST \
|
||||||
|
-f state="failure" \
|
||||||
|
-f description="Deployment failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
79
.github/workflows/deploy-pr.yml
vendored
Normal file
79
.github/workflows/deploy-pr.yml
vendored
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
name: PR Deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_review:
|
||||||
|
types: [submitted]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: staging-deploy
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
deployments: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
prepare:
|
||||||
|
name: Prepare deployment
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: >
|
||||||
|
github.event.review.state == 'approved' &&
|
||||||
|
github.event.pull_request.head.repo.full_name == github.repository
|
||||||
|
outputs:
|
||||||
|
image_tag: ${{ steps.image.outputs.tag }}
|
||||||
|
sha: ${{ github.event.pull_request.head.sha }}
|
||||||
|
pr_number: ${{ github.event.pull_request.number }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout PR
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
|
||||||
|
- name: Get image tag
|
||||||
|
id: image
|
||||||
|
env:
|
||||||
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
|
PR_SHA: ${{ github.event.pull_request.head.sha }}
|
||||||
|
run: |
|
||||||
|
IMAGE_TAG="pr-${PR_NUMBER}-${PR_SHA:0:7}"
|
||||||
|
echo "tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
name: Deploy to staging
|
||||||
|
needs: prepare
|
||||||
|
uses: ./.github/workflows/deploy-core.yml
|
||||||
|
with:
|
||||||
|
image_tag: ${{ needs.prepare.outputs.image_tag }}
|
||||||
|
sha: ${{ needs.prepare.outputs.sha }}
|
||||||
|
description: Deploy PR #${{ needs.prepare.outputs.pr_number }} to staging
|
||||||
|
pr_number: ${{ needs.prepare.outputs.pr_number }}
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
comment:
|
||||||
|
name: Comment deployment status
|
||||||
|
needs: [prepare, deploy]
|
||||||
|
if: always()
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
IMAGE_TAG: ${{ needs.prepare.outputs.image_tag }}
|
||||||
|
PR_NUMBER: ${{ needs.prepare.outputs.pr_number }}
|
||||||
|
REF_SHA: ${{ needs.prepare.outputs.sha }}
|
||||||
|
STATUS: ${{ needs.deploy.outputs.status }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Comment on PR
|
||||||
|
run: |
|
||||||
|
if [ "$STATUS" = "success" ]; then
|
||||||
|
BODY=$(jq -nr --arg tag "$IMAGE_TAG" --arg sha "$REF_SHA" --arg url "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
|
||||||
|
'"✅ **Staging deployment completed**\n\n🔗 **URL**: https://dev.opensourcepos.org\n📦 **Image Tag**: `\($tag)`\n🔨 **Commit**: \($sha)\n\nView logs: \($url)"')
|
||||||
|
else
|
||||||
|
BODY=$(jq -nr --arg url "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
|
||||||
|
'"❌ **Staging deployment failed**\n\nCheck the [workflow logs](\($url)) for details."')
|
||||||
|
fi
|
||||||
|
|
||||||
|
gh api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" \
|
||||||
|
-X POST \
|
||||||
|
-f body="$BODY"
|
||||||
23
.github/workflows/deploy.yml
vendored
Normal file
23
.github/workflows/deploy.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
name: Deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
image_tag:
|
||||||
|
description: 'Docker image tag to deploy (e.g., v3.4.0, latest)'
|
||||||
|
required: true
|
||||||
|
default: 'latest'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
deployments: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
name: Deploy to staging
|
||||||
|
uses: ./.github/workflows/deploy-core.yml
|
||||||
|
with:
|
||||||
|
image_tag: ${{ inputs.image_tag }}
|
||||||
|
sha: ${{ github.sha }}
|
||||||
|
description: Deploy image ${{ inputs.image_tag }}
|
||||||
|
secrets: inherit
|
||||||
62
.github/workflows/main.yml
vendored
Normal file
62
.github/workflows/main.yml
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
name: Coding Standards
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- '**.php'
|
||||||
|
- 'spark'
|
||||||
|
- '.github/workflows/test-coding-standards.yml'
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- '**.php'
|
||||||
|
- 'spark'
|
||||||
|
- '.github/workflows/test-coding-standards.yml'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
name: PHP ${{ matrix.php-version }} Lint with PHP CS Fixer
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
php-version:
|
||||||
|
- '8.2'
|
||||||
|
- '8.3'
|
||||||
|
- '8.4'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-version }}
|
||||||
|
extensions: tokenizer
|
||||||
|
coverage: none
|
||||||
|
|
||||||
|
- name: Get composer cache directory
|
||||||
|
run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Cache dependencies
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ${{ env.COMPOSER_CACHE_FILES_DIR }}
|
||||||
|
key: ${{ runner.os }}-${{ matrix.php-version }}-${{ hashFiles('**/composer.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-${{ matrix.php-version }}-
|
||||||
|
${{ runner.os }}-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: composer update --ansi --no-interaction
|
||||||
|
|
||||||
|
- name: Run lint on `app/`, `public/`
|
||||||
|
run: vendor/bin/php-cs-fixer fix --verbose --ansi --dry-run --config=.php-cs-fixer.no-header.php --using-cache=no --diff
|
||||||
12
.github/workflows/php-linter.yml
vendored
12
.github/workflows/php-linter.yml
vendored
@@ -12,14 +12,6 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
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
|
- name: PHP Lint 8.2
|
||||||
uses: dbfx/github-phplint/8.2@master
|
uses: dbfx/github-phplint/8.2@master
|
||||||
with:
|
with:
|
||||||
@@ -28,3 +20,7 @@ jobs:
|
|||||||
uses: dbfx/github-phplint/8.3@master
|
uses: dbfx/github-phplint/8.3@master
|
||||||
with:
|
with:
|
||||||
folder-to-exclude: "! -path \"./vendor/*\" ! -path \"./folder/excluded/*\""
|
folder-to-exclude: "! -path \"./vendor/*\" ! -path \"./folder/excluded/*\""
|
||||||
|
- name: PHP Lint 8.4
|
||||||
|
uses: dbfx/github-phplint/8.4@master
|
||||||
|
with:
|
||||||
|
folder-to-exclude: "! -path \"./vendor/*\" ! -path \"./folder/excluded/*\""
|
||||||
|
|||||||
121
.github/workflows/phpunit.yml
vendored
Normal file
121
.github/workflows/phpunit.yml
vendored
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
name: PHPUnit Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- '**.php'
|
||||||
|
- 'spark'
|
||||||
|
- 'tests/**'
|
||||||
|
- '.github/workflows/phpunit.yml'
|
||||||
|
- 'gulpfile.js'
|
||||||
|
- 'app/Database/**'
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- '**.php'
|
||||||
|
- 'spark'
|
||||||
|
- 'tests/**'
|
||||||
|
- '.github/workflows/phpunit.yml'
|
||||||
|
- 'gulpfile.js'
|
||||||
|
- 'app/Database/**'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: PHP ${{ matrix.php-version }} Tests
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
php-version:
|
||||||
|
- '8.2'
|
||||||
|
- '8.3'
|
||||||
|
- '8.4'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-version }}
|
||||||
|
extensions: intl, mbstring, mysqli
|
||||||
|
coverage: none
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
|
- name: Get npm cache directory
|
||||||
|
run: echo "NPM_CACHE_DIR=$(npm config get cache)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Cache npm dependencies
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ${{ env.NPM_CACHE_DIR }}
|
||||||
|
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-node-
|
||||||
|
|
||||||
|
- name: Install npm dependencies
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
- name: Start MariaDB
|
||||||
|
run: |
|
||||||
|
docker run -d --name mysql \
|
||||||
|
-e MYSQL_ROOT_PASSWORD=root \
|
||||||
|
-e MYSQL_DATABASE=ospos \
|
||||||
|
-e MYSQL_USER=admin \
|
||||||
|
-e MYSQL_PASSWORD=pointofsale \
|
||||||
|
-p 3306:3306 \
|
||||||
|
mariadb:10.5
|
||||||
|
# Wait for MariaDB to be ready
|
||||||
|
until docker exec mysql mysqladmin ping -h 127.0.0.1 -u root -proot --silent; do
|
||||||
|
echo "Waiting for MariaDB..."
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
echo "MariaDB is ready!"
|
||||||
|
|
||||||
|
- name: Get composer cache directory
|
||||||
|
run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Cache dependencies
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ${{ env.COMPOSER_CACHE_FILES_DIR }}
|
||||||
|
key: ${{ runner.os }}-${{ matrix.php-version }}-${{ hashFiles('**/composer.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-${{ matrix.php-version }}-
|
||||||
|
${{ runner.os }}-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: composer update --ansi --no-interaction
|
||||||
|
|
||||||
|
- name: Create .env file
|
||||||
|
run: cp .env.example .env
|
||||||
|
|
||||||
|
- name: Run PHPUnit tests
|
||||||
|
env:
|
||||||
|
CI_ENVIRONMENT: testing
|
||||||
|
MYSQL_HOST_NAME: 127.0.0.1
|
||||||
|
run: composer test -- --log-junit test-results/junit.xml
|
||||||
|
|
||||||
|
- name: Upload test results
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: test-results-php-${{ matrix.php-version }}
|
||||||
|
path: test-results/
|
||||||
|
retention-days: 30
|
||||||
|
|
||||||
|
- name: Stop MariaDB
|
||||||
|
if: always()
|
||||||
|
run: docker stop mysql && docker rm mysql
|
||||||
172
.github/workflows/release.yml
vendored
Normal file
172
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
name: Release Version Bump
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version_type:
|
||||||
|
description: 'Version bump type'
|
||||||
|
required: true
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- minor
|
||||||
|
- major
|
||||||
|
- patch
|
||||||
|
default: 'minor'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
prepare-release:
|
||||||
|
name: Prepare Release
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Get current version
|
||||||
|
id: current_version
|
||||||
|
run: |
|
||||||
|
CURRENT_VERSION=$(grep "application_version" app/Config/App.php | sed "s/.*= '\(.*\)';/\1/g")
|
||||||
|
echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
|
||||||
|
echo "Current version: $CURRENT_VERSION"
|
||||||
|
|
||||||
|
- name: Calculate new version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
CURRENT_VERSION="${{ steps.current_version.outputs.current_version }}"
|
||||||
|
VERSION_TYPE="${{ github.event.inputs.version_type }}"
|
||||||
|
|
||||||
|
# Parse current version
|
||||||
|
MAJOR=$(echo $CURRENT_VERSION | cut -d. -f1)
|
||||||
|
MINOR=$(echo $CURRENT_VERSION | cut -d. -f2)
|
||||||
|
PATCH=$(echo $CURRENT_VERSION | cut -d. -f3)
|
||||||
|
|
||||||
|
# Bump version based on type
|
||||||
|
case $VERSION_TYPE in
|
||||||
|
major)
|
||||||
|
MAJOR=$((MAJOR + 1))
|
||||||
|
MINOR=0
|
||||||
|
PATCH=0
|
||||||
|
;;
|
||||||
|
minor)
|
||||||
|
MINOR=$((MINOR + 1))
|
||||||
|
PATCH=0
|
||||||
|
;;
|
||||||
|
patch)
|
||||||
|
PATCH=$((PATCH + 1))
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}"
|
||||||
|
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
||||||
|
echo "previous_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
|
||||||
|
echo "New version: $NEW_VERSION (was: $CURRENT_VERSION, type: $VERSION_TYPE)"
|
||||||
|
|
||||||
|
- name: Update version in App.php
|
||||||
|
run: |
|
||||||
|
NEW_VERSION="${{ steps.version.outputs.new_version }}"
|
||||||
|
sed -i "s/public string \\\$application_version = '[^']*';/public string \\\$application_version = '$NEW_VERSION';/" app/Config/App.php
|
||||||
|
echo "Updated app/Config/App.php"
|
||||||
|
|
||||||
|
- name: Update version in package.json
|
||||||
|
run: |
|
||||||
|
NEW_VERSION="${{ steps.version.outputs.new_version }}"
|
||||||
|
sed -i "s/\"version\": \"[^\"]*\",/\"version\": \"$NEW_VERSION\",/" package.json
|
||||||
|
echo "Updated package.json"
|
||||||
|
|
||||||
|
- name: Update version in docker-compose.nginx.yml
|
||||||
|
run: |
|
||||||
|
NEW_VERSION="${{ steps.version.outputs.new_version }}"
|
||||||
|
sed -i "s/jekkos\/opensourcepos:[^ ]*/jekkos\/opensourcepos:$NEW_VERSION/" docker-compose.nginx.yml
|
||||||
|
echo "Updated docker-compose.nginx.yml"
|
||||||
|
|
||||||
|
- name: Update version in README.md
|
||||||
|
run: |
|
||||||
|
NEW_VERSION="${{ steps.version.outputs.new_version }}"
|
||||||
|
# Extract major.minor for the "latest X.Y version" text
|
||||||
|
MAJOR_MINOR=$(echo "$NEW_VERSION" | cut -d. -f1,2)
|
||||||
|
sed -i "s/The latest \`[0-9]*\.[0-9]*\` version/The latest \`${MAJOR_MINOR}\` version/" README.md
|
||||||
|
echo "Updated README.md with version ${MAJOR_MINOR}"
|
||||||
|
|
||||||
|
- name: Generate changelog
|
||||||
|
id: changelog
|
||||||
|
run: |
|
||||||
|
PREVIOUS_VERSION="${{ steps.version.outputs.previous_version }}"
|
||||||
|
NEW_VERSION="${{ steps.version.outputs.new_version }}"
|
||||||
|
|
||||||
|
# Get commits since last version
|
||||||
|
if git rev-parse "$PREVIOUS_VERSION" >/dev/null 2>&1; then
|
||||||
|
COMMITS=$(git log "$PREVIOUS_VERSION"..HEAD --pretty=format:"- %s" --no-merges)
|
||||||
|
else
|
||||||
|
COMMITS=$(git log --pretty=format:"- %s" --no-merges -50)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create changelog entry
|
||||||
|
CHANGELOG_FILE="CHANGELOG.md"
|
||||||
|
|
||||||
|
# Create the new version comparison link
|
||||||
|
NEW_LINK="[${NEW_VERSION}]: https://github.com/opensourcepos/opensourcepos/compare/${PREVIOUS_VERSION}...${NEW_VERSION}"
|
||||||
|
|
||||||
|
# Insert new link after [unreleased] line
|
||||||
|
sed -i "/^\[unreleased\]/a $NEW_LINK" "$CHANGELOG_FILE"
|
||||||
|
|
||||||
|
# Update [unreleased] link to start from new version
|
||||||
|
sed -i "s|^\[unreleased\]: .*|\[unreleased\]: https://github.com/opensourcepos/opensourcepos/compare/${NEW_VERSION}...HEAD|" "$CHANGELOG_FILE"
|
||||||
|
|
||||||
|
# Create version header and content using temp file to avoid sed issues with special characters
|
||||||
|
VERSION_DATE=$(date +%Y-%m-%d)
|
||||||
|
VERSION_HEADER="## [$NEW_VERSION] - $VERSION_DATE"
|
||||||
|
|
||||||
|
# Create temp file with changelog entry
|
||||||
|
TMP_FILE=$(mktemp)
|
||||||
|
{
|
||||||
|
echo ""
|
||||||
|
echo "$VERSION_HEADER"
|
||||||
|
echo ""
|
||||||
|
echo "$COMMITS"
|
||||||
|
} > "$TMP_FILE"
|
||||||
|
|
||||||
|
# Insert after Unreleased header
|
||||||
|
sed -i "/^## \[Unreleased\]/r $TMP_FILE" "$CHANGELOG_FILE"
|
||||||
|
rm "$TMP_FILE"
|
||||||
|
|
||||||
|
echo "Updated CHANGELOG.md"
|
||||||
|
echo "Changelog entries:"
|
||||||
|
echo "$COMMITS"
|
||||||
|
|
||||||
|
- name: Update version in issue templates
|
||||||
|
run: |
|
||||||
|
NEW_VERSION="${{ steps.version.outputs.new_version }}"
|
||||||
|
|
||||||
|
# Calculate version to remove (keep 5 versions)
|
||||||
|
PREVIOUS_VERSION="${{ steps.version.outputs.previous_version }}"
|
||||||
|
|
||||||
|
# Bug report template - insert new version after development (unreleased)
|
||||||
|
BUG_TEMPLATE=".github/ISSUE_TEMPLATE/bug report.yml"
|
||||||
|
sed -i "/- development (unreleased)/a\\ - OpenSourcePOS ${NEW_VERSION}" "$BUG_TEMPLATE"
|
||||||
|
# Remove the oldest version (5th version from the end)
|
||||||
|
sed -i "/OpenSourcePOS 3\\.3\\.7/d" "$BUG_TEMPLATE"
|
||||||
|
echo "Updated $BUG_TEMPLATE"
|
||||||
|
|
||||||
|
# Feature request template - insert new version after development (unreleased)
|
||||||
|
FEATURE_TEMPLATE=".github/ISSUE_TEMPLATE/feature_request.yml"
|
||||||
|
sed -i "/- development (unreleased)/a\\ - OpenSourcePOS ${NEW_VERSION}" "$FEATURE_TEMPLATE"
|
||||||
|
# Remove the oldest version (5th version from the end)
|
||||||
|
sed -i "/OpenSourcePOS 3\\.3\\.7/d" "$FEATURE_TEMPLATE"
|
||||||
|
echo "Updated $FEATURE_TEMPLATE"
|
||||||
|
|
||||||
|
- name: Commit version bump
|
||||||
|
run: |
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
|
||||||
|
NEW_VERSION="${{ steps.version.outputs.new_version }}"
|
||||||
|
|
||||||
|
git add app/Config/App.php package.json docker-compose.nginx.yml CHANGELOG.md README.md .github/ISSUE_TEMPLATE/
|
||||||
|
git commit -m "chore: release version $NEW_VERSION"
|
||||||
|
git push origin HEAD
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -2,8 +2,13 @@
|
|||||||
node_modules
|
node_modules
|
||||||
vendor
|
vendor
|
||||||
public/resources
|
public/resources
|
||||||
|
public/images/menubar/*
|
||||||
|
!public/images/menubar/.gitkeep
|
||||||
|
public/license/*
|
||||||
|
!public/license/.gitkeep
|
||||||
app/Config/email.php
|
app/Config/email.php
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
|
.vscode
|
||||||
|
|
||||||
# Docker
|
# Docker
|
||||||
!docker/.env
|
!docker/.env
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ IndexIgnore *
|
|||||||
</Files>
|
</Files>
|
||||||
|
|
||||||
# prevent access to csv, txt and md files
|
# prevent access to csv, txt and md files
|
||||||
<FilesMatch "\.(csv|txt|md|yml|json|lock)$">
|
<FilesMatch "\.(csv|txt|md|yml|json|lock|env)$">
|
||||||
Require all denied
|
Require all denied
|
||||||
</FilesMatch>
|
</FilesMatch>
|
||||||
</IfModule>
|
</IfModule>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ $finder = Finder::create()
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$overrides = [
|
$overrides = [
|
||||||
// for updating to coding-standard
|
// For updating to coding-standard
|
||||||
'modernize_strpos' => true,
|
'modernize_strpos' => true,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
70
.travis.yml
70
.travis.yml
@@ -1,70 +0,0 @@
|
|||||||
sudo: required
|
|
||||||
|
|
||||||
branches:
|
|
||||||
except:
|
|
||||||
- unstable
|
|
||||||
- weblate
|
|
||||||
services:
|
|
||||||
- docker
|
|
||||||
|
|
||||||
dist: jammy
|
|
||||||
language: node_js
|
|
||||||
node_js:
|
|
||||||
- 20
|
|
||||||
script:
|
|
||||||
- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
|
|
||||||
- docker run --rm -u $(id -u) -v $(pwd):/app opensourcepos/composer:ci4 composer install
|
|
||||||
- version=$(grep application_version app/Config/App.php | sed "s/.*=\s'\(.*\)';/\1/g")
|
|
||||||
- sed -i "s/commit_sha1 = 'dev'/commit_sha1 = '$rev'/g" app/Config/OSPOS.php
|
|
||||||
- echo "$version-$branch-$rev"
|
|
||||||
- npm version "$version-$branch-$rev" --force || true
|
|
||||||
- sed -i 's/opensourcepos.tar.gz/opensourcepos.$version.tgz/g' package.json
|
|
||||||
- npm ci && npm install -g gulp && npm run build
|
|
||||||
- docker build . --target ospos -t ospos
|
|
||||||
- docker build app/Database/ -t "jekkos/opensourcepos:sql-$TAG"
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
- BRANCH=$(echo ${TRAVIS_BRANCH} | sed s/feature\\///)
|
|
||||||
- TAG=${TRAVIS_TAG:-$BRANCH}
|
|
||||||
- date=`date +%Y%m%d%H%M%S` && branch=${TRAVIS_BRANCH} && rev=`git rev-parse --short=6 HEAD`
|
|
||||||
after_success:
|
|
||||||
- docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" && docker tag "ospos:latest"
|
|
||||||
"jekkos/opensourcepos:$TAG" && docker push "jekkos/opensourcepos:$TAG" && docker push "jekkos/opensourcepos:sql-$TAG"
|
|
||||||
- gulp compress
|
|
||||||
- mv dist/opensourcepos.tar.gz "dist/opensourcepos.$version.$rev.tgz"
|
|
||||||
- mv dist/opensourcepos.zip "dist/opensourcepos.$version.$rev.zip"
|
|
||||||
deploy:
|
|
||||||
- provider: releases
|
|
||||||
edge: true
|
|
||||||
file: dist/opensourcepos.$version.$rev.zip
|
|
||||||
name: "Unstable OpensourcePos"
|
|
||||||
overwrite: true
|
|
||||||
release_notes: "This is a build of the latest master which might contain bugs. Use at your own risk. Check releases section for the latest official release"
|
|
||||||
prerelease: true
|
|
||||||
tag_name: unstable
|
|
||||||
user: jekkos
|
|
||||||
|
|
||||||
api_key:
|
|
||||||
secure: "KOukL8IFf/uL/BjMyCSKjf2vylydjcWqgEx0eMqFCg3nZ4ybMaOwPORRthIfyT72/FvGX/aoxxEn0uR/AEtb+hYQXHmNS+kZdX72JCe8LpGuZ7FJ5X+Eo9mhJcsmS+smd1sC95DySSc/GolKPo+0WtJYONY/xGCLxm+9Ay4HREg="
|
|
||||||
|
|
||||||
on:
|
|
||||||
branch: master
|
|
||||||
- provider: releases
|
|
||||||
edge: true
|
|
||||||
file: dist/opensourcepos.$version.$rev.zip
|
|
||||||
name: "OpensourcePos $version"
|
|
||||||
release_notes_file: CHANGELOG.md
|
|
||||||
prerelease: true
|
|
||||||
|
|
||||||
user: jekkos
|
|
||||||
overwrite: true
|
|
||||||
api_key:
|
|
||||||
secure: "KOukL8IFf/uL/BjMyCSKjf2vylydjcWqgEx0eMqFCg3nZ4ybMaOwPORRthIfyT72/FvGX/aoxxEn0uR/AEtb+hYQXHmNS+kZdX72JCe8LpGuZ7FJ5X+Eo9mhJcsmS+smd1sC95DySSc/GolKPo+0WtJYONY/xGCLxm+9Ay4HREg="
|
|
||||||
|
|
||||||
branches:
|
|
||||||
except:
|
|
||||||
- unstable
|
|
||||||
- /unstable.*/
|
|
||||||
on:
|
|
||||||
tags: true
|
|
||||||
branch: master
|
|
||||||
40
AGENTS.md
Normal file
40
AGENTS.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# Agent Instructions
|
||||||
|
|
||||||
|
This document provides guidance for AI agents working on the Open Source Point of Sale (OSPOS) codebase.
|
||||||
|
|
||||||
|
## Code Style
|
||||||
|
|
||||||
|
- Follow PHP CodeIgniter 4 coding standards
|
||||||
|
- Run PHP-CS-Fixer before committing: `vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.no-header.php`
|
||||||
|
- Write PHP 8.1+ compatible code with proper type declarations
|
||||||
|
- Use PSR-12 naming conventions: `camelCase` for variables and functions, `PascalCase` for classes, `UPPER_CASE` for constants
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
- Create a new git worktree for each issue, based on the latest state of `origin/master`
|
||||||
|
- Commit fixes to the worktree and push to the remote
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
- Run PHPUnit tests: `composer test`
|
||||||
|
- Tests must pass before submitting changes
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
- Install dependencies: `composer install && npm install`
|
||||||
|
- Build assets: `npm run build` or `gulp`
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
- Controllers go in `app/Controllers/`
|
||||||
|
- Models go in `app/Models/`
|
||||||
|
- Views go in `app/Views/`
|
||||||
|
- Database migrations in `app/Database/Migrations/`
|
||||||
|
- Use CodeIgniter 4 framework patterns and helpers
|
||||||
|
- Sanitize user input; escape output using `esc()` helper
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
- Never commit secrets, credentials, or `.env` files
|
||||||
|
- Use parameterized queries to prevent SQL injection
|
||||||
|
- Validate and sanitize all user input
|
||||||
6
BUILD.md
6
BUILD.md
@@ -23,9 +23,9 @@ The build process uses the build tools "npm" and "gulp" to piece everything toge
|
|||||||
2. Unzip it and copy the contents into the working folder.
|
2. Unzip it and copy the contents into the working folder.
|
||||||
3. Start a terminal session from the root of your working folder. For example, I normally open up the working folder in PHPStorm and run the commands from the Terminal provided by the IDE.
|
3. Start a terminal session from the root of your working folder. For example, I normally open up the working folder in PHPStorm and run the commands from the Terminal provided by the IDE.
|
||||||
4. Enter the following three commands in sequence:
|
4. Enter the following three commands in sequence:
|
||||||
- `composer install`
|
- `composer install`
|
||||||
- `npm install`
|
- `npm install`
|
||||||
- `npm run build`
|
- `npm run build`
|
||||||
|
|
||||||
That's all there is to it.
|
That's all there is to it.
|
||||||
|
|
||||||
|
|||||||
40
CHANGELOG.md
40
CHANGELOG.md
@@ -1,4 +1,6 @@
|
|||||||
[unreleased]: https://github.com/opensourcepos/opensourcepos/compare/3.3.9...HEAD
|
[unreleased]: https://github.com/opensourcepos/opensourcepos/compare/3.4.1...HEAD
|
||||||
|
[3.4.1]: https://github.com/opensourcepos/opensourcepos/compare/3.4.0...3.4.1
|
||||||
|
[3.4.0]: https://github.com/opensourcepos/opensourcepos/compare/3.3.9...3.4.0
|
||||||
[3.3.9]: https://github.com/opensourcepos/opensourcepos/compare/3.3.8...3.3.9
|
[3.3.9]: https://github.com/opensourcepos/opensourcepos/compare/3.3.8...3.3.9
|
||||||
[3.3.8]: https://github.com/opensourcepos/opensourcepos/compare/3.3.7...3.3.8
|
[3.3.8]: https://github.com/opensourcepos/opensourcepos/compare/3.3.7...3.3.8
|
||||||
[3.3.7]: https://github.com/opensourcepos/opensourcepos/compare/3.3.6...3.3.7
|
[3.3.7]: https://github.com/opensourcepos/opensourcepos/compare/3.3.6...3.3.7
|
||||||
@@ -31,6 +33,42 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [3.4.1] - 2025-06-05
|
||||||
|
- Feature: PSR-12 Compliant Indentation by @objecttothis in ([#4196](https://github.com/opensourcepos/opensourcepos/pull/4196))
|
||||||
|
- Add .env to dist zip by @jekkos in ([#4199](https://github.com/opensourcepos/opensourcepos/pull/4199))
|
||||||
|
- Add CI4 coding standards linter ([#3708](https://github.com/opensourcepos/opensourcepos/issues/3708)) by @jekkos in ([#4198](https://github.com/opensourcepos/opensourcepos/pull/4198))
|
||||||
|
- Bump canvg from 3.0.10 to 3.0.11 by @dependabot in ([#4189](https://github.com/opensourcepos/opensourcepos/pull/4189))
|
||||||
|
- Bump jspdf and jspdf-autotable by @dependabot in ([#4190](https://github.com/opensourcepos/opensourcepos/pull/4190))
|
||||||
|
- Feature bump ci to 4.6.0 by @objecttothis in ([#4197](https://github.com/opensourcepos/opensourcepos/pull/4197))
|
||||||
|
- Add Kurdish language option to UI by @BudsieBuds in ([#4210](https://github.com/opensourcepos/opensourcepos/pull/4210))
|
||||||
|
- Convert language ku to ckb by @BudsieBuds in ([#4211](https://github.com/opensourcepos/opensourcepos/pull/4211))
|
||||||
|
- Fix PHP 8.4 errors by @BudsieBuds in ([#4215](https://github.com/opensourcepos/opensourcepos/pull/4215))
|
||||||
|
- Add default bootstrap to themes by @BudsieBuds in ([#4219](https://github.com/opensourcepos/opensourcepos/pull/4219))
|
||||||
|
- Update language names by @BudsieBuds in ([#4218](https://github.com/opensourcepos/opensourcepos/pull/4218))
|
||||||
|
- Update install docs by @BudsieBuds in ([#4217](https://github.com/opensourcepos/opensourcepos/pull/4217))
|
||||||
|
- Convert menu icons to SVG by @BudsieBuds in ([#4220](https://github.com/opensourcepos/opensourcepos/pull/4220))
|
||||||
|
- Enhance license handling by @BudsieBuds in ([#4223](https://github.com/opensourcepos/opensourcepos/pull/4223))
|
||||||
|
- Fix datetime rendering ([#4226](https://github.com/opensourcepos/opensourcepos/issues/4226)) by @jekkos in ([#4227](https://github.com/opensourcepos/opensourcepos/pull/4227))
|
||||||
|
- Fix datetime rendering by @jekkos in ([#4228](https://github.com/opensourcepos/opensourcepos/pull/4228))
|
||||||
|
- Fix null error when sending by email a receipt of a sale that has no invoice by @diego-ramos in ([#4229](https://github.com/opensourcepos/opensourcepos/pull/4229))
|
||||||
|
- Update Receivings.php to save form. by @odiea in ([#4231](https://github.com/opensourcepos/opensourcepos/pull/4231))
|
||||||
|
- Update Cashups.php for ajax cashup total to work. by @odiea in ([#4238](https://github.com/opensourcepos/opensourcepos/pull/4238))
|
||||||
|
- Coding style updates for PSR-12 compliance & improved readability by @BudsieBuds in ([#4204](https://github.com/opensourcepos/opensourcepos/pull/4204))
|
||||||
|
- Fix Codeigniter disallowed characters error with payment types that have accents by @diego-ramos in ([#4232](https://github.com/opensourcepos/opensourcepos/pull/4232))
|
||||||
|
- Fixed broken escape string for success & warning messages by @Franchovy in ([#4253](https://github.com/opensourcepos/opensourcepos/pull/4253))
|
||||||
|
- Bugfix constraint migration fix by @objecttothis in ([#4230](https://github.com/opensourcepos/opensourcepos/pull/4230))
|
||||||
|
- Fix item number lookup in sales/receivings ([#4212](https://github.com/opensourcepos/opensourcepos/issues/4212)) by @jekkos in ([#4250](https://github.com/opensourcepos/opensourcepos/pull/4250))
|
||||||
|
|
||||||
|
## [3.4.0] - 2025-03-23
|
||||||
|
|
||||||
|
- Translation updates (Spanish, Indonesian, Swedish, Urdu, Chinese, Thai, French, Dutch)
|
||||||
|
- PHP `8.x` support
|
||||||
|
- Security fixes (XSS, SQLi)
|
||||||
|
- Migration to Gulp as buildsystem
|
||||||
|
- Decimal validation fix
|
||||||
|
- Sticky header fix
|
||||||
|
- Receipt sent as attachment
|
||||||
|
- Barcode generation library upgrade
|
||||||
- Bump framework to CodeIgniter `4.x.x`
|
- Bump framework to CodeIgniter `4.x.x`
|
||||||
- Improve security performance against bots
|
- Improve security performance against bots
|
||||||
|
|
||||||
|
|||||||
@@ -1,98 +1,85 @@
|
|||||||
Contributor Covenant Code of Conduct
|
[comment]: # (Contributor Covenant 2.1 - from https://www.contributor-covenant.org/version/2/1/code_of_conduct/code_of_conduct.md)
|
||||||
Our Pledge
|
|
||||||
We as members, contributors, and leaders pledge to make participation in our
|
# Contributor Covenant Code of Conduct
|
||||||
community a harassment-free experience for everyone, regardless of age, body
|
|
||||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
## Our Pledge
|
||||||
identity and expression, level of experience, education, socio-economic status,
|
|
||||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
|
||||||
identity and orientation.
|
|
||||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
|
||||||
diverse, inclusive, and healthy community.
|
|
||||||
Our Standards
|
## Our Standards
|
||||||
Examples of behavior that contributes to a positive environment for our
|
|
||||||
community include:
|
Examples of behavior that contributes to a positive environment for our community include:
|
||||||
|
|
||||||
* Demonstrating empathy and kindness toward other people
|
* Demonstrating empathy and kindness toward other people
|
||||||
* Being respectful of differing opinions, viewpoints, and experiences
|
* Being respectful of differing opinions, viewpoints, and experiences
|
||||||
* Giving and gracefully accepting constructive feedback
|
* Giving and gracefully accepting constructive feedback
|
||||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
||||||
and learning from the experience
|
* Focusing on what is best not just for us as individuals, but for the overall community
|
||||||
* Focusing on what is best not just for us as individuals, but for the overall
|
|
||||||
community
|
|
||||||
|
|
||||||
Examples of unacceptable behavior include:
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
* The use of sexualized language or imagery, and sexual attention or advances of
|
* The use of sexualized language or imagery, and sexual attention or advances of any kind
|
||||||
any kind
|
|
||||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
* Public or private harassment
|
* Public or private harassment
|
||||||
* Publishing others’ private information, such as a physical or email address,
|
* Publishing others' private information, such as a physical or email address, without their explicit permission
|
||||||
without their explicit permission
|
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||||
* Other conduct which could reasonably be considered inappropriate in a
|
|
||||||
professional setting
|
|
||||||
|
|
||||||
Enforcement Responsibilities
|
## Enforcement Responsibilities
|
||||||
Community leaders are responsible for clarifying and enforcing our standards of
|
|
||||||
acceptable behavior and will take appropriate and fair corrective action in
|
|
||||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
|
||||||
or harmful.
|
|
||||||
Community leaders have the right and responsibility to remove, edit, or reject
|
|
||||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
|
||||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
|
||||||
decisions when appropriate.
|
|
||||||
Scope
|
|
||||||
This Code of Conduct applies within all community spaces, and also applies when
|
|
||||||
an individual is officially representing the community in public spaces.
|
|
||||||
Examples of representing our community include using an official email address,
|
|
||||||
posting via an official social media account, or acting as an appointed
|
|
||||||
representative at an online or offline event.
|
|
||||||
Enforcement
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
||||||
reported to the community leaders responsible for enforcement at
|
|
||||||
[INSERT CONTACT METHOD].
|
|
||||||
All complaints will be reviewed and investigated promptly and fairly.
|
|
||||||
All community leaders are obligated to respect the privacy and security of the
|
|
||||||
reporter of any incident.
|
|
||||||
Enforcement Guidelines
|
|
||||||
Community leaders will follow these Community Impact Guidelines in determining
|
|
||||||
the consequences for any action they deem in violation of this Code of Conduct:
|
|
||||||
1. Correction
|
|
||||||
Community Impact: Use of inappropriate language or other behavior deemed
|
|
||||||
unprofessional or unwelcome in the community.
|
|
||||||
Consequence: A private, written warning from community leaders, providing
|
|
||||||
clarity around the nature of the violation and an explanation of why the
|
|
||||||
behavior was inappropriate. A public apology may be requested.
|
|
||||||
2. Warning
|
|
||||||
Community Impact: A violation through a single incident or series of
|
|
||||||
actions.
|
|
||||||
Consequence: A warning with consequences for continued behavior. No
|
|
||||||
interaction with the people involved, including unsolicited interaction with
|
|
||||||
those enforcing the Code of Conduct, for a specified period of time. This
|
|
||||||
includes avoiding interactions in community spaces as well as external channels
|
|
||||||
like social media. Violating these terms may lead to a temporary or permanent
|
|
||||||
ban.
|
|
||||||
3. Temporary Ban
|
|
||||||
Community Impact: A serious violation of community standards, including
|
|
||||||
sustained inappropriate behavior.
|
|
||||||
Consequence: A temporary ban from any sort of interaction or public
|
|
||||||
communication with the community for a specified period of time. No public or
|
|
||||||
private interaction with the people involved, including unsolicited interaction
|
|
||||||
with those enforcing the Code of Conduct, is allowed during this period.
|
|
||||||
Violating these terms may lead to a permanent ban.
|
|
||||||
4. Permanent Ban
|
|
||||||
Community Impact: Demonstrating a pattern of violation of community
|
|
||||||
standards, including sustained inappropriate behavior, harassment of an
|
|
||||||
individual, or aggression toward or disparagement of classes of individuals.
|
|
||||||
Consequence: A permanent ban from any sort of public interaction within the
|
|
||||||
community.
|
|
||||||
Attribution
|
|
||||||
This Code of Conduct is adapted from the Contributor Covenant,
|
|
||||||
version 2.1, available at
|
|
||||||
https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.
|
|
||||||
Community Impact Guidelines were inspired by
|
|
||||||
Mozilla’s code of conduct enforcement ladder.
|
|
||||||
For answers to common questions about this code of conduct, see the FAQ at
|
|
||||||
https://www.contributor-covenant.org/faq. Translations are available at
|
|
||||||
https://www.contributor-covenant.org/translations.
|
|
||||||
|
|
||||||
|
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [INSERT CONTACT METHOD]. All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
|
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
||||||
|
|
||||||
|
## Enforcement Guidelines
|
||||||
|
|
||||||
|
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
|
||||||
|
|
||||||
|
### 1. Correction
|
||||||
|
|
||||||
|
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
|
||||||
|
|
||||||
|
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
|
||||||
|
|
||||||
|
### 2. Warning
|
||||||
|
|
||||||
|
**Community Impact**: A violation through a single incident or series of actions.
|
||||||
|
|
||||||
|
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
|
||||||
|
|
||||||
|
### 3. Temporary Ban
|
||||||
|
|
||||||
|
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
|
||||||
|
|
||||||
|
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
|
||||||
|
|
||||||
|
### 4. Permanent Ban
|
||||||
|
|
||||||
|
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
|
**Consequence**: A permanent ban from any sort of public interaction within the community.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||||
|
|
||||||
|
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations].
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||||
|
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||||
|
[FAQ]: https://www.contributor-covenant.org/faq
|
||||||
|
[translations]: https://www.contributor-covenant.org/translations
|
||||||
|
|||||||
33
Dockerfile
33
Dockerfile
@@ -1,28 +1,23 @@
|
|||||||
FROM php:8.2-apache AS ospos
|
FROM php:8.2-apache AS ospos
|
||||||
LABEL maintainer="jekkos"
|
LABEL maintainer="jekkos"
|
||||||
|
|
||||||
RUN apt update && apt-get install -y libicu-dev libgd-dev
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
RUN a2enmod rewrite
|
libicu-dev \
|
||||||
RUN docker-php-ext-install mysqli bcmath intl gd
|
libgd-dev \
|
||||||
|
&& docker-php-ext-install mysqli bcmath intl gd \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
|
&& a2enmod rewrite
|
||||||
|
|
||||||
RUN echo "date.timezone = \"\${PHP_TIMEZONE}\"" > /usr/local/etc/php/conf.d/timezone.ini
|
RUN echo "date.timezone = \"\${PHP_TIMEZONE}\"" > /usr/local/etc/php/conf.d/timezone.ini
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY . /app
|
COPY --chown=www-data:www-data . /app
|
||||||
RUN ln -s /app/*[^public] /var/www && rm -rf /var/www/html && ln -nsf /app/public /var/www/html
|
RUN chmod 750 /app/writable/logs /app/writable/uploads /app/writable/cache /app/public/uploads /app/public/uploads/item_pics \
|
||||||
RUN chmod -R 770 /app/writable/uploads /app/writable/logs /app/writable/cache && chown -R www-data:www-data /app
|
&& chmod 640 /app/writable/uploads/importCustomers.csv \
|
||||||
|
&& ln -s /app/*[^public] /var/www \
|
||||||
FROM ospos AS ospos_test
|
&& rm -rf /var/www/html \
|
||||||
|
&& ln -nsf /app/public /var/www/html
|
||||||
COPY --from=composer /usr/bin/composer /usr/bin/composer
|
|
||||||
|
|
||||||
RUN apt-get install -y libzip-dev wget git
|
|
||||||
RUN wget https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh -O /bin/wait-for-it.sh && chmod +x /bin/wait-for-it.sh
|
|
||||||
RUN docker-php-ext-install zip
|
|
||||||
RUN composer install -d/app
|
|
||||||
#RUN sed -i 's/backupGlobals="true"/backupGlobals="false"/g' /app/tests/phpunit.xml
|
|
||||||
WORKDIR /app/tests
|
|
||||||
|
|
||||||
CMD ["/app/vendor/phpunit/phpunit/phpunit"]
|
|
||||||
|
|
||||||
FROM ospos AS ospos_dev
|
FROM ospos AS ospos_dev
|
||||||
|
|
||||||
|
|||||||
61
INSTALL.md
61
INSTALL.md
@@ -1,27 +1,68 @@
|
|||||||
## Server Requirements
|
## Server Requirements
|
||||||
|
|
||||||
- PHP version `7.4` is supported, PHP version `≤7.3` is NOT supported. Please note that PHP needs to have the extensions `php-gd`, `php-bcmath`, `php-intl`, `php-openssl`, `php-mbstring` , `php-curl` and `php-xml` installed and enabled. PHP version `8.x` is only supported by the code that is still under development in master branch. An unstable 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.6` and `5.7` are supported, also MariaDB replacement `10.x` is supported and might offer better performance.
|
- 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).
|
- 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)>).
|
- 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)>).
|
||||||
- For Windows based installations please read [the wiki](https://github.com/opensourcepos/opensourcepos/wiki). There are closed issues about this subject, as this topic has been covered a lot.
|
- For Windows based installations please read [the wiki](https://github.com/opensourcepos/opensourcepos/wiki). There are closed issues about this subject, as this topic has been covered a lot.
|
||||||
|
|
||||||
|
## Security Configuration
|
||||||
|
|
||||||
|
### Allowed Hostnames (REQUIRED for Production)
|
||||||
|
|
||||||
|
⚠️ **CRITICAL**: OpenSourcePOS validates the Host header to prevent Host Header Injection attacks (GHSA-jchf-7hr6-h4f3). **You MUST configure `app.allowedHostnames` for production deployments. If not configured, the application will fail to start.**
|
||||||
|
|
||||||
|
**Add to your `.env` file:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Comma-separated list of allowed hostnames (no protocols or ports)
|
||||||
|
app.allowedHostnames = 'yourdomain.com,www.yourdomain.com'
|
||||||
|
```
|
||||||
|
|
||||||
|
**For local development:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
app.allowedHostnames = 'localhost'
|
||||||
|
```
|
||||||
|
|
||||||
|
**If you see this error at startup:**
|
||||||
|
|
||||||
|
```text
|
||||||
|
RuntimeException: Security: allowedHostnames is not configured.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution**: Add `app.allowedHostnames` to your `.env` file with your domain(s).
|
||||||
|
|
||||||
|
**Why this matters:**
|
||||||
|
- Prevents Host Header Injection attacks (GHSA-jchf-7hr6-h4f3)
|
||||||
|
- Ensures URLs are generated with the correct domain
|
||||||
|
- Security advisory: https://github.com/opensourcepos/opensourcepos/security/advisories/GHSA-jchf-7hr6-h4f3
|
||||||
|
- Fixes issue #4480: .env configuration now works via comma-separated values
|
||||||
|
|
||||||
|
### HTTPS Behind Proxy
|
||||||
|
|
||||||
|
If your installation is behind a proxy with SSL offloading, set:
|
||||||
|
```
|
||||||
|
FORCE_HTTPS = true
|
||||||
|
```
|
||||||
|
|
||||||
## Local install
|
## Local install
|
||||||
|
|
||||||
First of all, if you're seeing the message `system folder missing` after launching your browser, or cannot find `database.php`, that most likely means you have cloned the repository and have not built the project. To build the project from a source commit point instead of from an official release check out [Building OSPOS](BUILD.md). Otherwise, continue with the following steps.
|
First of all, if you're seeing the message `system folder missing` after launching your browser, that most likely means you have cloned the repository and have not built the project. To build the project from a source commit point instead of from an official release check out [Building OSPOS](BUILD.md). Otherwise, continue with the following steps.
|
||||||
|
|
||||||
1. Download the a [pre-release for a specific branch](https://github.com/opensourcepos/opensourcepos/packages/1047637) or the latest stable [from GitHub here](https://github.com/opensourcepos/opensourcepos/releases). A repository clone will not work unless know how to build the project.
|
1. Download the a [pre-release for a specific branch](https://github.com/opensourcepos/opensourcepos/releases) or the latest stable [from GitHub here](https://github.com/opensourcepos/opensourcepos/releases). A repository clone will not work unless know how to build the project.
|
||||||
2. Create/locate a new MySQL database to install Open Source Point of Sale into.
|
2. Create/locate a new MySQL database to install Open Source Point of Sale into.
|
||||||
3. Execute the file `database/database.sql` to create the tables needed.
|
3. Unzip and upload Open Source Point of Sale files to the web-server.
|
||||||
4. Unzip and upload Open Source Point of Sale files to the web-server.
|
4. If `.env` does not exist, copy `.env.example` to `.env`.
|
||||||
5. Open `app/Config/database.php` and modify credentials to connect to your database if needed.
|
5. Open `.env` and modify credentials to connect to your database if needed.
|
||||||
6. Open `app/Config/config.php` and swap the encryption key with your own.
|
6. The database schema will be automatically created when you first access the application. Migrations run automatically on fresh installs.
|
||||||
7. Go to your install `public` dir via the browser.
|
7. Go to your install `public` dir via the browser.
|
||||||
8. Log in using
|
8. Log in using
|
||||||
- Username: admin
|
- Username: admin
|
||||||
- Password: pointofsale
|
- Password: pointofsale
|
||||||
9. Enjoy!
|
9. If everything works, then set the `CI_ENVIRONMENT` variable to `production` in the .env file
|
||||||
10. Oops, an issue? Please make sure you read the FAQ, wiki page, and you checked open and closed issues on GitHub. PHP `display_errors` is disabled by default. Create an` app/Config/.env` file from the `.env.example` to enable it in a development environment.
|
10. Enjoy!
|
||||||
|
11. Oops, an issue? Please make sure you read the FAQ, wiki page, and you checked open and closed issues on GitHub. PHP `display_errors` is disabled by default. Create an` app/Config/.env` file from the `.env.example` to enable it in a development environment.
|
||||||
|
|
||||||
## Local install using Docker
|
## Local install using Docker
|
||||||
|
|
||||||
|
|||||||
26
LICENSE
26
LICENSE
@@ -1,30 +1,30 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2013-2023 jekkos
|
Copyright (c) 2013-2025 jekkos
|
||||||
|
Copyright (c) 2017-2025 objecttothis
|
||||||
|
Copyright (c) 2017-2025 odiea
|
||||||
|
Copyright (c) 2021-2025 BudsieBuds
|
||||||
|
Copyright (c) 2017-2024 Steve Ireland
|
||||||
|
Copyright (c) 2018-2024 WebShells
|
||||||
Copyright (c) 2015-2023 FrancescoUK (aka daN4cat)
|
Copyright (c) 2015-2023 FrancescoUK (aka daN4cat)
|
||||||
Copyright (c) 2017-2023 Steve Ireland
|
Copyright (c) 2015-2022 Aamir Shahzad (aka asakpke), RoshanTech, eSite.pk
|
||||||
Copyright (c) 2017-2023 objecttothis
|
Copyright (c) 2019-2020 Andriux1990
|
||||||
Copyright (c) 2017-2023 odiea
|
Copyright (c) 2018-2019 Erasto Marroquin (aka Erastus)
|
||||||
Copyright (c) 2017-2023 WebShells
|
|
||||||
Copyright (c) 2020-2021 Andriux1990
|
|
||||||
Copyright (c) 2021 BudsieBuds
|
|
||||||
Copyright (c) 2019 Loyd Jayme (aka loydjayme25)
|
Copyright (c) 2019 Loyd Jayme (aka loydjayme25)
|
||||||
Copyright (c) 2018 Erasto Marroquin (aka Erastus)
|
|
||||||
Copyright (c) 2018 Nathan Sas (aka nathanzky)
|
Copyright (c) 2018 Nathan Sas (aka nathanzky)
|
||||||
Copyright (c) 2018 Emilio Silva (aka emi-silva)
|
Copyright (c) 2018 Emilio Silva (aka emi-silva)
|
||||||
Copyright (c) 2017 Jesus Guerrero Botella (aka i92guboj)
|
Copyright (c) 2016-2017 Ramkrishna Mondal (aka RamkrishnaMondal)
|
||||||
|
Copyright (c) 2016-2017 Jorge Colmenarez (aka jlctmaster), frontuari.com
|
||||||
|
Copyright (c) 2016-2017 Jesus Guerrero Botella (aka i92guboj)
|
||||||
Copyright (c) 2017 Deep Shah (aka deepshah)
|
Copyright (c) 2017 Deep Shah (aka deepshah)
|
||||||
Copyright (c) 2017 Joshua Fernandez (aka joshua1234511)
|
Copyright (c) 2017 Joshua Fernandez (aka joshua1234511)
|
||||||
Copyright (c) 2017 asadjaved63
|
Copyright (c) 2017 asadjaved63
|
||||||
Copyright (c) 2016-2017 Ramkrishna Mondal (aka RamkrishnaMondal)
|
|
||||||
Copyright (c) 2016-2017 Jorge Colmenarez (aka jlctmaster), frontuari.com
|
|
||||||
Copyright (c) 2016 Rinaldy@dbarber (aka rnld26)
|
Copyright (c) 2016 Rinaldy@dbarber (aka rnld26)
|
||||||
Copyright (c) 2015-2022 Aamir Shahzad (aka asakpke), RoshanTech, eSite.pk
|
|
||||||
Copyright (c) 2015 Toni Haryanto (aka yllumi)
|
Copyright (c) 2015 Toni Haryanto (aka yllumi)
|
||||||
|
Copyright (c) 2012-2014 pappastech
|
||||||
Copyright (c) 2013 Rob Garrison
|
Copyright (c) 2013 Rob Garrison
|
||||||
Copyright (c) 2013 Parq
|
Copyright (c) 2013 Parq
|
||||||
Copyright (c) 2013 Ramel
|
Copyright (c) 2013 Ramel
|
||||||
Copyright (c) 2012-2014 pappastech
|
|
||||||
Copyright (c) 2012 Alain
|
Copyright (c) 2012 Alain
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
|||||||
18
README.md
18
README.md
@@ -8,7 +8,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://app.travis-ci.com/opensourcepos/opensourcepos" target="_blank"><img src="https://api.travis-ci.com/opensourcepos/opensourcepos.svg?branch=master" alt="Build Status"></a>
|
<a href="https://github.com/opensourcepos/opensourcepos/actions/workflows/build-release.yml" target="_blank"><img src="https://github.com/opensourcepos/opensourcepos/actions/workflows/build-release.yml/badge.svg" alt="Build Status"></a>
|
||||||
<a href="https://app.gitter.im/#/room/#opensourcepos_Lobby:gitter.im?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge" target="_blank"><img src="https://badges.gitter.im/jekkos/opensourcepos.svg" alt="Join the chat at https://app.gitter.im"></a>
|
<a href="https://app.gitter.im/#/room/#opensourcepos_Lobby:gitter.im?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge" target="_blank"><img src="https://badges.gitter.im/jekkos/opensourcepos.svg" alt="Join the chat at https://app.gitter.im"></a>
|
||||||
<a href="https://badge.fury.io/gh/opensourcepos%2Fopensourcepos" target="_blank"><img src="https://badge.fury.io/gh/opensourcepos%2Fopensourcepos.svg" alt="Project Version"></a>
|
<a href="https://badge.fury.io/gh/opensourcepos%2Fopensourcepos" target="_blank"><img src="https://badge.fury.io/gh/opensourcepos%2Fopensourcepos.svg" alt="Project Version"></a>
|
||||||
<a href="https://translate.opensourcepos.org/engage/opensourcepos/?utm_source=widget" target="_blank"><img src="https://translate.opensourcepos.org/widgets/opensourcepos/-/svg-badge.svg" alt="Translation Status"></a>
|
<a href="https://translate.opensourcepos.org/engage/opensourcepos/?utm_source=widget" target="_blank"><img src="https://translate.opensourcepos.org/widgets/opensourcepos/-/svg-badge.svg" alt="Translation Status"></a>
|
||||||
@@ -94,19 +94,19 @@ NOTE: If you're running non-release code, please make sure you always run the la
|
|||||||
|
|
||||||
- Apache server configurations are SysAdmin issues and not strictly related to OSPOS. Please make sure you can show a "Hello world" HTML page before pointing to OSPOS public directory. Make sure `.htaccess` is correctly configured.
|
- Apache server configurations are SysAdmin issues and not strictly related to OSPOS. Please make sure you can show a "Hello world" HTML page before pointing to OSPOS public directory. Make sure `.htaccess` is correctly configured.
|
||||||
|
|
||||||
- If the avatar pictures are not shown in items or at item save you get an error, please make sure your `public` and subdirs are assigned to the correct owner and the access permission is set to `750`.
|
- If the avatar pictures are not shown in items or at item save you get an error, please make sure your `writable` and subdirs are assigned to the correct owner and the access permission is set to `750`.
|
||||||
|
|
||||||
- If you install OSPOS in Docker behind a proxy that performs `ssloffloading`, you can enable the URL generated to be HTTPS instead of HTTP, by activating the environment variable `FORCE_HTTPS = 1`.
|
- If you install OSPOS in Docker behind a proxy that performs `ssloffloading`, you can enable the URL generated to be HTTPS instead of HTTP, by activating the environment variable `FORCE_HTTPS = 1`.
|
||||||
|
|
||||||
- If you install OSPOS behind a proxy and OSPOS constantly drops your session, consider whitelisting the proxy IP address by setting `$config['proxy_ips'] = '<proxy ip>';` in the [main PHP config file](https://github.com/opensourcepos/opensourcepos/blob/master/application/config/config.php). In extreme instances, changing `$config['sess_match_ip'] = true;` to `FALSE` may also help.
|
- If you install OSPOS behind a proxy and OSPOS constantly drops your session, consider whitelisting the proxy IP address by setting `public array $proxyIPs = [];` in the [main PHP config file](https://github.com/opensourcepos/opensourcepos/blob/master/app/Config/App.php).
|
||||||
|
|
||||||
- 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).
|
- 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
|
## 🏃 Keep the Machine Running
|
||||||
|
|
||||||
If you like our project, please consider buying us a coffee through the button below so we can keep adding features.
|
If you like our project, please consider buying us a coffee through the button below so we can keep adding features. Please star the project if you like it!
|
||||||
|
|
||||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=MUN6AEG7NY6H8)\
|
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=MUN6AEG7NY6H8)\
|
||||||
Or refer to the [FUNDING.yml](.github/FUNDING.yml) file.
|
Or refer to the [FUNDING.yml](.github/FUNDING.yml) file.
|
||||||
@@ -137,7 +137,7 @@ Any person or company found breaching the license agreement might find a bunch o
|
|||||||
|
|
||||||
## 🙏 Credits
|
## 🙏 Credits
|
||||||
|
|
||||||
| <div align="center">DigitalOcean</div> | <div align="center">JetBrains</div> | <div align="center">Travis CI</div> |
|
| <div align="center">DigitalOcean</div> | <div align="center">JetBrains</div> | <div align="center">GitHub</div> |
|
||||||
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| --- | --- |
|
| --- | --- | --- |
|
||||||
| <div align="center"><a href="https://www.digitalocean.com?utm_medium=opensource&utm_source=opensourcepos" target="_blank"><img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" alt="DigitalOcean Logo" height="50"></a></div> | <div align="center"><a href="https://www.jetbrains.com/idea/" target="_blank"><img src="https://github.com/opensourcepos/opensourcepos/assets/12870258/187f9bbe-4484-475c-9b58-5e5d5f931f09" alt="IntelliJ IDEA Logo" height="50"></a></div> | <div align="center"><a href="https://www.travis-ci.com/" target="_blank"><img src="https://github.com/opensourcepos/opensourcepos/assets/12870258/71cc2b44-83af-4510-a543-6358285f43c6" alt="Travis CI Logo" height="50"></a></div> |
|
| <div align="center"><a href="https://www.digitalocean.com?utm_medium=opensource&utm_source=opensourcepos" target="_blank"><img src="https://github.com/user-attachments/assets/fbbf7433-ed35-407d-8946-fd03d236d350" alt="DigitalOcean Logo" height="50"></a></div> | <div align="center"><a href="https://www.jetbrains.com/idea/" target="_blank"><img src="https://github.com/opensourcepos/opensourcepos/assets/12870258/187f9bbe-4484-475c-9b58-5e5d5f931f09" alt="IntelliJ IDEA Logo" height="50"></a></div> | <div align="center"><a href="https://github.com/features/actions" target="_blank"><img src="https://github.githubassets.com/images/modules/site/icons/eyebrow-panel/actions-icon.svg" alt="GitHub Actions Logo" height="50"></a></div> |
|
||||||
| Many thanks to [Digital Ocean](https://www.digitalocean.com) for providing the project with hosting credits | Many thanks to [JetBrains](https://www.jetbrains.com/) for providing a free license of [IntelliJ IDEA](https://www.jetbrains.com/idea/) to kindly support the development of OSPOS. | Many thanks to [Travis CI](https://www.travis-ci.com/) for providing a free continuous integration service for open source projects. |
|
| Many thanks to [DigitalOcean](https://www.digitalocean.com) for providing the project with hosting credits. | Many thanks to [JetBrains](https://www.jetbrains.com/) for providing a free license of [IntelliJ IDEA](https://www.jetbrains.com/idea/) to kindly support the development of OSPOS. | Many thanks to [GitHub](https://github.com) for providing free continuous integration via GitHub Actions for open-source projects. |
|
||||||
|
|||||||
129
SECURITY.md
129
SECURITY.md
@@ -1,24 +1,137 @@
|
|||||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||||
|
|
||||||
|
|
||||||
- [Security Policy](#security-policy)
|
- [Security Policy](#security-policy)
|
||||||
- [Supported Versions](#supported-versions)
|
- [Supported Versions](#supported-versions)
|
||||||
|
- [Security Advisories](#security-advisories)
|
||||||
- [Reporting a Vulnerability](#reporting-a-vulnerability)
|
- [Reporting a Vulnerability](#reporting-a-vulnerability)
|
||||||
|
- [Disclosure Process](#disclosure-process)
|
||||||
|
|
||||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
<!-- END doctoc generated TOC please keep comment here to allow update -->
|
||||||
|
|
||||||
# Security Policy
|
# Security Policy
|
||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
We release patches for security vulnerabilities. Which versions are eligible to receive such patches depend on the CVSS v3.0 Rating:
|
We release patches for security vulnerabilities.
|
||||||
|
|
||||||
| CVSS v3.0 | Supported Versions |
|
| Version | Supported |
|
||||||
| --------- | -------------------------------------------------- |
|
| --------- | ------------------ |
|
||||||
| 7.3 | 3.3.5 |
|
| >= 3.4.2 | :white_check_mark: |
|
||||||
| 9.8 | 3.3.6 |
|
| < 3.4.2 | :x: |
|
||||||
|
|
||||||
|
## Security Advisories
|
||||||
|
|
||||||
|
For a complete list of published and draft security advisories with CVE details, see our [GitHub Security Advisories page](https://github.com/opensourcepos/opensourcepos/security/advisories).
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
Please report (suspected) security vulnerabilities to **[jekkos@opensourcepos.org](mailto:jekkos@opensourcepos.org)**. You will receive a response from us within 48 hours. If the issue is confirmed, we will release a patch as soon as possible depending on complexity but historically within a few days.
|
**Option 1: GitHub Security Advisory (Preferred)**
|
||||||
|
|
||||||
|
1. Create a draft security advisory directly on GitHub:
|
||||||
|
- Go to https://github.com/opensourcepos/opensourcepos/security/advisories
|
||||||
|
- Click "New draft security advisory"
|
||||||
|
- Fill in the vulnerability details using our [template below](#vulnerability-template)
|
||||||
|
- Submit as **draft** (not published)
|
||||||
|
|
||||||
|
2. Notify us for triage:
|
||||||
|
- Send an email to **[jeroen@steganos.dev](mailto:jeroen@steganos.dev)** with:
|
||||||
|
- Subject: `[GHSA] Brief description of vulnerability`
|
||||||
|
- Link to the draft advisory
|
||||||
|
- Brief summary
|
||||||
|
|
||||||
|
**Option 2: Email Report**
|
||||||
|
|
||||||
|
Send vulnerability details to **[jeroen@steganos.dev](mailto:jeroen@steganos.dev)**.
|
||||||
|
|
||||||
|
You will receive a response within 48 hours. Confirmed vulnerabilities will be patched within a few days depending on complexity.
|
||||||
|
|
||||||
|
## Disclosure Process
|
||||||
|
|
||||||
|
### Timeline
|
||||||
|
|
||||||
|
| Step | Timeline | Action |
|
||||||
|
|------|----------|--------|
|
||||||
|
| 1. Report received | Day 0 | We acknowledge within 48 hours |
|
||||||
|
| 2. Triage & confirmation | Day 1-3 | We validate the vulnerability |
|
||||||
|
| 3. Fix development | Day 3-7 | We develop and test the fix |
|
||||||
|
| 4. Patch release | Day 7-10 | We release a security patch |
|
||||||
|
| 5. CVE request | Day 7-14 | We request CVE from GitHub (if applicable) |
|
||||||
|
| 6. Advisory published | Day 14 | We publish the advisory with credit |
|
||||||
|
| 7. Public disclosure | Day 14+ | Full disclosure after patch release |
|
||||||
|
|
||||||
|
### CVE Process
|
||||||
|
|
||||||
|
**We request CVE identifiers through GitHub's security advisory system.** This is the preferred and easiest method:
|
||||||
|
|
||||||
|
1. After we confirm and fix the vulnerability, we'll request a CVE through GitHub
|
||||||
|
2. GitHub coordinates with MITRE on our behalf
|
||||||
|
3. The CVE is automatically linked to the advisory
|
||||||
|
4. You'll be credited as the reporter in the published advisory
|
||||||
|
|
||||||
|
**Already have a CVE?** If you've already obtained a CVE from another source (e.g., VulDB, CVE.MITRE.ORG), please include it in your report or advisory. We'll update our advisory to reference the existing CVE.
|
||||||
|
|
||||||
|
### No Bug Bounty Program
|
||||||
|
|
||||||
|
**Important:** Open Source Point of Sale does not offer a bug bounty program.
|
||||||
|
|
||||||
|
- All security research and vulnerability triage is done on a **voluntary basis** in our free time
|
||||||
|
- We do not offer monetary rewards for vulnerability reports
|
||||||
|
- We do credit reporters in published advisories (unless anonymity is requested)
|
||||||
|
- We greatly appreciate the security research community's efforts to help improve project security
|
||||||
|
|
||||||
|
### Security Best Practices for Researchers
|
||||||
|
|
||||||
|
- **Do not** access, modify, or delete data that doesn't belong to you
|
||||||
|
- **Do not** perform denial of service attacks
|
||||||
|
- **Do not** publicly disclose vulnerabilities before we've had time to fix them
|
||||||
|
- **Do** provide sufficient information to reproduce the vulnerability
|
||||||
|
- **Do** allow us reasonable time to fix before public disclosure
|
||||||
|
- **Do** report through official channels (GitHub advisories or email)
|
||||||
|
|
||||||
|
### Vulnerability Template
|
||||||
|
|
||||||
|
When creating a draft advisory, please include:
|
||||||
|
|
||||||
|
```
|
||||||
|
## Summary
|
||||||
|
[Brief description of the vulnerability]
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
- **Confidentiality:** [High/Medium/Low - what data can be exposed]
|
||||||
|
- **Integrity:** [High/Medium/Low - what can be modified]
|
||||||
|
- **Availability:** [High/Medium/Low - service disruption potential]
|
||||||
|
- **Privilege Required:** [None/Low/High - authentication level needed]
|
||||||
|
- **CVSS v3.1:** [Score] ([Vector string])
|
||||||
|
|
||||||
|
## Details
|
||||||
|
[Technical details about the vulnerability]
|
||||||
|
|
||||||
|
**Affected Code:**
|
||||||
|
```php
|
||||||
|
// Path to affected file and vulnerable code
|
||||||
|
```
|
||||||
|
|
||||||
|
**Attack Vector:**
|
||||||
|
[How an attacker can exploit this]
|
||||||
|
|
||||||
|
## Proof of Concept
|
||||||
|
```bash
|
||||||
|
# Steps to reproduce
|
||||||
|
```
|
||||||
|
|
||||||
|
## Patch
|
||||||
|
[Suggested fix or approach]
|
||||||
|
|
||||||
|
## Affected Versions
|
||||||
|
- OpenSourcePOS X.Y.Z and earlier
|
||||||
|
|
||||||
|
## Credit
|
||||||
|
[Your GitHub username or preferred name]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Thank you to all security researchers who have contributed to making Open Source Point of Sale more secure.** Your voluntary efforts help protect thousands of users worldwide and contribute to a safer, more trustworthy free and open-source software ecosystem. We deeply appreciate your responsible disclosure and the time you invest in improving our project.
|
||||||
|
|
||||||
|
If you've reported a vulnerability and would like to discuss CVE coordination or have questions about the process, please reach out to us at [jeroen@steganos.dev](mailto:jeroen@steganos.dev).
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
## How to Upgrade
|
## How to Upgrade
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> Not updated for upcoming CodeIgniter4 release (3.4.0 and subsequent versions).
|
||||||
|
|
||||||
1. Back up all your current database and OSPOS code.
|
1. Back up all your current database and OSPOS code.
|
||||||
2. Make sure you have a copy of `application/config/config.php` and `application/config/database.php`.
|
2. Make sure you have a copy of `application/config/config.php` and `application/config/database.php`.
|
||||||
3. Remove all directories.
|
3. Remove all directories.
|
||||||
|
|||||||
@@ -7,60 +7,60 @@ use CodeIgniter\Session\Handlers\DatabaseHandler;
|
|||||||
|
|
||||||
class App extends BaseConfig
|
class App extends BaseConfig
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* This is the code version of the Open Source Point of Sale you're running.
|
* This is the code version of the Open Source Point of Sale you're running.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
public string $application_version = '3.4.0-dev';
|
public string $application_version = '3.4.2';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the commit hash for the version you are currently using.
|
* This is the commit hash for the version you are currently using.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
public string $commit_sha1 = 'dev';
|
public string $commit_sha1 = 'dev';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs are stored in writable/logs
|
* Logs are stored in writable/logs
|
||||||
*
|
*
|
||||||
* @var bool
|
* @var bool
|
||||||
*/
|
*/
|
||||||
public bool $db_log_enabled = false;
|
public bool $db_log_enabled = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DB Query Log only long-running queries
|
* DB Query Log only long-running queries
|
||||||
*
|
*
|
||||||
* @var bool
|
* @var bool
|
||||||
*/
|
*/
|
||||||
public bool $db_log_only_long = false;
|
public bool $db_log_only_long = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines whether to require/reroute to HTTPS
|
* Defines whether to require/reroute to HTTPS
|
||||||
*
|
*
|
||||||
* @var bool
|
* @var bool
|
||||||
*/
|
*/
|
||||||
public bool $https_on; //Set in the constructor
|
public bool $https_on; // Set in the constructor
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
* Base Site URL
|
* Base Site URL
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
*
|
*
|
||||||
* URL to your CodeIgniter root. Typically, this will be your base URL,
|
* URL to your CodeIgniter root. Typically, this will be your base URL,
|
||||||
* WITH a trailing slash:
|
* WITH a trailing slash:
|
||||||
*
|
*
|
||||||
* E.g., http://example.com/
|
* E.g., http://example.com/
|
||||||
*/
|
*/
|
||||||
public string $baseURL; //Defined in the constructor
|
public string $baseURL; // Defined in the constructor
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allowed Hostnames in the Site URL other than the hostname in the baseURL.
|
* Allowed Hostnames in the Site URL other than the hostname in the baseURL.
|
||||||
* If you want to accept multiple Hostnames, set this.
|
* If you want to accept multiple Hostnames, set this.
|
||||||
*
|
*
|
||||||
* E.g.,
|
* Or via environment variable (useful for Docker/Compose):
|
||||||
* When your site URL ($baseURL) is 'http://example.com/', and your site
|
* ALLOWED_HOSTNAMES=example.com,www.example.com
|
||||||
* also accepts 'http://media.example.com/' and 'http://accounts.example.com/':
|
*
|
||||||
* ['media.example.com', 'accounts.example.com']
|
* ['media.example.com', 'accounts.example.com']
|
||||||
*
|
*
|
||||||
* @var list<string>
|
* @var list<string>
|
||||||
@@ -143,63 +143,64 @@ class App extends BaseConfig
|
|||||||
*/
|
*/
|
||||||
public bool $negotiateLocale = true;
|
public bool $negotiateLocale = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
* Supported Locales
|
* Supported Locales
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
*
|
*
|
||||||
* If $negotiateLocale is true, this array lists the locales supported
|
* If $negotiateLocale is true, this array lists the locales supported
|
||||||
* by the application in descending order of priority. If no match is
|
* by the application in descending order of priority. If no match is
|
||||||
* found, the first locale will be used.
|
* found, the first locale will be used.
|
||||||
*
|
*
|
||||||
* IncomingRequest::setLocale() also uses this list.
|
* IncomingRequest::setLocale() also uses this list.
|
||||||
*
|
*
|
||||||
* @var list<string>
|
* @var list<string>
|
||||||
*/
|
*/
|
||||||
public array $supportedLocales = [
|
public array $supportedLocales = [
|
||||||
'ar-EG',
|
'ar-EG',
|
||||||
'ar-LB',
|
'ar-LB',
|
||||||
'az',
|
'az',
|
||||||
'bg',
|
'bg',
|
||||||
'bs',
|
'bs',
|
||||||
'cs',
|
'ckb',
|
||||||
'da',
|
'cs',
|
||||||
'de-CH',
|
'da',
|
||||||
'de-DE',
|
'de-CH',
|
||||||
'el',
|
'de-DE',
|
||||||
|
'el',
|
||||||
'en',
|
'en',
|
||||||
'en-GB',
|
'en-GB',
|
||||||
'es-ES',
|
'es-ES',
|
||||||
'es-MX',
|
'es-MX',
|
||||||
'fa',
|
'fa',
|
||||||
'fr',
|
'fr',
|
||||||
'he',
|
'he',
|
||||||
'hr-HR',
|
'hr-HR',
|
||||||
'hu',
|
'hu',
|
||||||
'hy',
|
'hy',
|
||||||
'id',
|
'id',
|
||||||
'it',
|
'it',
|
||||||
'km',
|
'km',
|
||||||
'lo',
|
'lo',
|
||||||
'ml',
|
'ml',
|
||||||
'nb',
|
'nb',
|
||||||
'nl-BE',
|
'nl-BE',
|
||||||
'nl-NL',
|
'nl-NL',
|
||||||
'pl',
|
'pl',
|
||||||
'pt-BR',
|
'pt-BR',
|
||||||
'ro',
|
'ro',
|
||||||
'ru',
|
'ru',
|
||||||
'sv',
|
'sv',
|
||||||
'ta',
|
'ta',
|
||||||
'th',
|
'th',
|
||||||
'tl',
|
'tl',
|
||||||
'tr',
|
'tr',
|
||||||
'uk',
|
'uk',
|
||||||
'ur',
|
'ur',
|
||||||
'vi',
|
'vi',
|
||||||
'zh-Hans',
|
'zh-Hans',
|
||||||
'zh-Hant',
|
'zh-Hant',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
@@ -261,30 +262,99 @@ class App extends BaseConfig
|
|||||||
*/
|
*/
|
||||||
public array $proxyIPs = [];
|
public array $proxyIPs = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
* Content Security Policy
|
* Content Security Policy
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
*
|
*
|
||||||
* Enables the Response's Content Secure Policy to restrict the sources that
|
* Enables the Response's Content Secure Policy to restrict the sources that
|
||||||
* can be used for images, scripts, CSS files, audio, video, etc. If enabled,
|
* can be used for images, scripts, CSS files, audio, video, etc. If enabled,
|
||||||
* the Response object will populate default values for the policy from the
|
* the Response object will populate default values for the policy from the
|
||||||
* `ContentSecurityPolicy.php` file. Controllers can always add to those
|
* `ContentSecurityPolicy.php` file. Controllers can always add to those
|
||||||
* restrictions at run time.
|
* restrictions at run time.
|
||||||
*
|
*
|
||||||
* For a better understanding of CSP, see these documents:
|
* For a better understanding of CSP, see these documents:
|
||||||
*
|
*
|
||||||
* @see http://www.html5rocks.com/en/tutorials/security/content-security-policy/
|
* @see http://www.html5rocks.com/en/tutorials/security/content-security-policy/
|
||||||
* @see http://www.w3.org/TR/CSP/
|
* @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()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
$this->https_on = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') || (isset($_ENV['FORCE_HTTPS']) && $_ENV['FORCE_HTTPS'] == 'true');
|
|
||||||
$this->baseURL = $this->https_on ? 'https' : 'http';
|
// Solution for CodeIgniter 4 limitation: arrays cannot be set from .env
|
||||||
$this->baseURL .= '://' . ((isset($_SERVER['HTTP_HOST'])) ? $_SERVER['HTTP_HOST'] : 'localhost') . '/';
|
// See: https://github.com/codeigniter4/CodeIgniter4/issues/7311
|
||||||
$this->baseURL .= str_replace(basename($_SERVER['SCRIPT_NAME']), '', $_SERVER['SCRIPT_NAME']);
|
// Support both: app.allowedHostnames (from .env) and ALLOWED_HOSTNAMES (from environment/Docker)
|
||||||
}
|
$envAllowedHostnames = getenv('ALLOWED_HOSTNAMES');
|
||||||
|
if ($envAllowedHostnames === false || trim($envAllowedHostnames) === '') {
|
||||||
|
$envAllowedHostnames = getenv('app.allowedHostnames');
|
||||||
|
}
|
||||||
|
if ($envAllowedHostnames !== false && trim($envAllowedHostnames) !== '') {
|
||||||
|
$this->allowedHostnames = array_values(array_filter(
|
||||||
|
array_map('trim', explode(',', $envAllowedHostnames)),
|
||||||
|
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 . '/';
|
||||||
|
$this->baseURL .= str_replace(basename($_SERVER['SCRIPT_NAME']), '', $_SERVER['SCRIPT_NAME']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 =
|
||||||
|
'Security: allowedHostnames is not configured. ' .
|
||||||
|
'Host header injection protection is disabled. ' .
|
||||||
|
'Set app.allowedHostnames in your .env file or ALLOWED_HOSTNAMES environment variable. ' .
|
||||||
|
'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';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array($httpHost, $this->allowedHostnames, true)) {
|
||||||
|
return $httpHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Host not in whitelist - use first configured hostname as fallback
|
||||||
|
log_message('warning',
|
||||||
|
'Security: Rejected HTTP_HOST "' . $httpHost . '" - not in allowedHostnames whitelist. ' .
|
||||||
|
'Using fallback: ' . $this->allowedHostnames[0]
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->allowedHostnames[0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ use CodeIgniter\Config\AutoloadConfig;
|
|||||||
*
|
*
|
||||||
* NOTE: This class is required prior to Autoloader instantiation,
|
* NOTE: This class is required prior to Autoloader instantiation,
|
||||||
* and does not extend BaseConfig.
|
* and does not extend BaseConfig.
|
||||||
*
|
|
||||||
* @immutable
|
|
||||||
*/
|
*/
|
||||||
class Autoload extends AutoloadConfig
|
class Autoload extends AutoloadConfig
|
||||||
{
|
{
|
||||||
@@ -42,7 +40,7 @@ class Autoload extends AutoloadConfig
|
|||||||
public $psr4 = [
|
public $psr4 = [
|
||||||
APP_NAMESPACE => APPPATH,
|
APP_NAMESPACE => APPPATH,
|
||||||
'Config' => APPPATH . 'Config',
|
'Config' => APPPATH . 'Config',
|
||||||
'dompdf' => APPPATH . 'ThirdParty/dompdf/src'
|
'dompdf' => APPPATH . 'ThirdParty/dompdf/src'
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,115 +60,115 @@ class Autoload extends AutoloadConfig
|
|||||||
*
|
*
|
||||||
* @var array<string, string>
|
* @var array<string, string>
|
||||||
*/
|
*/
|
||||||
public $classmap = [
|
public $classmap = [
|
||||||
//Controllers
|
// Controllers
|
||||||
'Attributes' => '/App/Controllers/Attributes.php',
|
'Attributes' => '/App/Controllers/Attributes.php',
|
||||||
'Cashups' => '/App/Controllers/Cashups.php',
|
'Cashups' => '/App/Controllers/Cashups.php',
|
||||||
'Config' => '/App/Controllers/Config.php',
|
'Config' => '/App/Controllers/Config.php',
|
||||||
'Customers' => '/App/Controllers/Customers.php',
|
'Customers' => '/App/Controllers/Customers.php',
|
||||||
'Employees' => '/App/Controllers/Employees.php',
|
'Employees' => '/App/Controllers/Employees.php',
|
||||||
'Expenses' => '/App/Controllers/Expenses.php',
|
'Expenses' => '/App/Controllers/Expenses.php',
|
||||||
'Expenses_categories' => '/App/Controllers/Expenses_categories.php',
|
'Expenses_categories' => '/App/Controllers/Expenses_categories.php',
|
||||||
'Giftcards' => '/App/Controllers/Giftcards.php',
|
'Giftcards' => '/App/Controllers/Giftcards.php',
|
||||||
'Home' => '/App/Controllers/Home.php',
|
'Home' => '/App/Controllers/Home.php',
|
||||||
'Item_kits' => '/App/Controllers/Item_kits.php',
|
'Item_kits' => '/App/Controllers/Item_kits.php',
|
||||||
'Items' => '/App/Controllers/Items.php',
|
'Items' => '/App/Controllers/Items.php',
|
||||||
'Login' => '/App/Controllers/Login.php',
|
'Login' => '/App/Controllers/Login.php',
|
||||||
'Messages' => '/App/Controllers/Messages.php',
|
'Messages' => '/App/Controllers/Messages.php',
|
||||||
'No_access' => '/App/Controllers/No_access.php',
|
'No_access' => '/App/Controllers/No_access.php',
|
||||||
'Office' => '/App/Controllers/Office.php',
|
'Office' => '/App/Controllers/Office.php',
|
||||||
'Persons' => '/App/Controllers/Persons.php',
|
'Persons' => '/App/Controllers/Persons.php',
|
||||||
'Receivings' => '/App/Controllers/Receivings.php',
|
'Receivings' => '/App/Controllers/Receivings.php',
|
||||||
'Reports' => '/App/Controllers/Reports.php',
|
'Reports' => '/App/Controllers/Reports.php',
|
||||||
'Sales' => '/App/Controllers/Sales.php',
|
'Sales' => '/App/Controllers/Sales.php',
|
||||||
'Secure_Controller' => '/App/Controllers/Secure_Controller.php',
|
'Secure_Controller' => '/App/Controllers/Secure_Controller.php',
|
||||||
'Suppliers' => '/App/Controllers/Suppliers.php',
|
'Suppliers' => '/App/Controllers/Suppliers.php',
|
||||||
'Tax_categories' => '/App/Controllers/Tax_categories.php',
|
'Tax_categories' => '/App/Controllers/Tax_categories.php',
|
||||||
'Tax_codes' => '/App/Controllers/Tax_codes.php',
|
'Tax_codes' => '/App/Controllers/Tax_codes.php',
|
||||||
'Tax_jurisdictions' => '/App/Controllers/Tax_jurisdictions.php',
|
'Tax_jurisdictions' => '/App/Controllers/Tax_jurisdictions.php',
|
||||||
'Taxes' => '/App/Controllers/Taxes.php',
|
'Taxes' => '/App/Controllers/Taxes.php',
|
||||||
|
|
||||||
//Models
|
// Models
|
||||||
'Appconfig' => '/App/Models/Appconfig.php',
|
'Appconfig' => '/App/Models/Appconfig.php',
|
||||||
'Attribute' => '/App/Models/Attribute.php',
|
'Attribute' => '/App/Models/Attribute.php',
|
||||||
'Cashup' => '/App/Models/Cashup.php',
|
'Cashup' => '/App/Models/Cashup.php',
|
||||||
'Customer' => '/App/Models/Customer.php',
|
'Customer' => '/App/Models/Customer.php',
|
||||||
'Customer_rewards' => '/App/Models/Customer_rewards.php',
|
'Customer_rewards' => '/App/Models/Customer_rewards.php',
|
||||||
'Dinner_table' => '/App/Models/Dinner_table.php',
|
'Dinner_table' => '/App/Models/Dinner_table.php',
|
||||||
'Employee' => '/App/Models/Employee.php',
|
'Employee' => '/App/Models/Employee.php',
|
||||||
'Expense' => '/App/Models/Expense.php',
|
'Expense' => '/App/Models/Expense.php',
|
||||||
'Expense_category' => '/App/Models/Expense_category.php',
|
'Expense_category' => '/App/Models/Expense_category.php',
|
||||||
'Giftcard' => '/App/Models/Giftcard.php',
|
'Giftcard' => '/App/Models/Giftcard.php',
|
||||||
'Inventory' => '/App/Models/Inventory.php',
|
'Inventory' => '/App/Models/Inventory.php',
|
||||||
'Item_kit' => '/App/Models/Item_kit.php',
|
'Item_kit' => '/App/Models/Item_kit.php',
|
||||||
'Item_kit_items' => '/App/Models/Item_kit_items.php',
|
'Item_kit_items' => '/App/Models/Item_kit_items.php',
|
||||||
'Item_quantity' => '/App/Models/Item_quantity.php',
|
'Item_quantity' => '/App/Models/Item_quantity.php',
|
||||||
'Item_taxes' => '/App/Models/Item_taxes.php',
|
'Item_taxes' => '/App/Models/Item_taxes.php',
|
||||||
'Module' => '/App/Models/Module.php',
|
'Module' => '/App/Models/Module.php',
|
||||||
'Person' => '/App/Models/Person.php',
|
'Person' => '/App/Models/Person.php',
|
||||||
'Receiving' => '/App/Models/Receiving.php',
|
'Receiving' => '/App/Models/Receiving.php',
|
||||||
'Rewards' => '/App/Models/Rewards.php',
|
'Rewards' => '/App/Models/Rewards.php',
|
||||||
'Sale' => '/App/Models/Sale.php',
|
'Sale' => '/App/Models/Sale.php',
|
||||||
'Stock_location' => '/App/Models/Stock_location.php',
|
'Stock_location' => '/App/Models/Stock_location.php',
|
||||||
'Supplier' => '/App/Models/Supplier.php',
|
'Supplier' => '/App/Models/Supplier.php',
|
||||||
'Tax' => '/App/Models/Tax.php',
|
'Tax' => '/App/Models/Tax.php',
|
||||||
'Tax_category' => '/App/Models/Tax_category.php',
|
'Tax_category' => '/App/Models/Tax_category.php',
|
||||||
'Tax_code' => '/App/Models/Tax_code.php',
|
'Tax_code' => '/App/Models/Tax_code.php',
|
||||||
'Tax_jurisdiction' => '/App/Models/Tax_jurisdiction.php',
|
'Tax_jurisdiction' => '/App/Models/Tax_jurisdiction.php',
|
||||||
|
|
||||||
//Reports
|
// Reports
|
||||||
'Report' => '/App/Models/Reports/Report.php',
|
'Report' => '/App/Models/Reports/Report.php',
|
||||||
'Detailed_receiving' => '/App/Models/Reports/Detailed_receiving.php',
|
'Detailed_receiving' => '/App/Models/Reports/Detailed_receiving.php',
|
||||||
'Detailed_sales' => '/App/Models/Reports/Detailed_sales.php',
|
'Detailed_sales' => '/App/Models/Reports/Detailed_sales.php',
|
||||||
'Inventory_low' => '/App/Models/Reports/Inventory_low.php',
|
'Inventory_low' => '/App/Models/Reports/Inventory_low.php',
|
||||||
'Inventory_summary' => '/App/Models/Reports/Inventory_summary.php',
|
'Inventory_summary' => '/App/Models/Reports/Inventory_summary.php',
|
||||||
'Specific_customer' => '/App/Models/Reports/Specific_customer.php',
|
'Specific_customer' => '/App/Models/Reports/Specific_customer.php',
|
||||||
'Specific_discount' => '/App/Models/Reports/Specific_discount.php',
|
'Specific_discount' => '/App/Models/Reports/Specific_discount.php',
|
||||||
'Specific_employee' => '/App/Models/Reports/Specific_employee.php',
|
'Specific_employee' => '/App/Models/Reports/Specific_employee.php',
|
||||||
'Specific_supplier' => '/App/Models/Reports/Specific_supplier.php',
|
'Specific_supplier' => '/App/Models/Reports/Specific_supplier.php',
|
||||||
'Summary_categories' => '/App/Models/Reports/Summary_categories.php',
|
'Summary_categories' => '/App/Models/Reports/Summary_categories.php',
|
||||||
'Summary_customers' => '/App/Models/Reports/Summary_customers.php',
|
'Summary_customers' => '/App/Models/Reports/Summary_customers.php',
|
||||||
'Summary_discounts' => '/App/Models/Reports/Summary_discounts.php',
|
'Summary_discounts' => '/App/Models/Reports/Summary_discounts.php',
|
||||||
'Summary_employees' => '/App/Models/Reports/Summary_employees.php',
|
'Summary_employees' => '/App/Models/Reports/Summary_employees.php',
|
||||||
'Summary_expenses_categories' => '/App/Models/Reports/Summary_expenses_categories.php',
|
'Summary_expenses_categories' => '/App/Models/Reports/Summary_expenses_categories.php',
|
||||||
'Summary_items' => '/App/Models/Reports/Summary_items.php',
|
'Summary_items' => '/App/Models/Reports/Summary_items.php',
|
||||||
'Summary_payments' => '/App/Models/Reports/Summary_payments.php',
|
'Summary_payments' => '/App/Models/Reports/Summary_payments.php',
|
||||||
'Summary_report' => '/App/Models/Reports/Summary_report.php',
|
'Summary_report' => '/App/Models/Reports/Summary_report.php',
|
||||||
'Summary_sales' => '/App/Models/Reports/Summary_sales.php',
|
'Summary_sales' => '/App/Models/Reports/Summary_sales.php',
|
||||||
'Summary_sales_taxes' => '/App/Models/Reports/Summary_sales_taxes.php',
|
'Summary_sales_taxes' => '/App/Models/Reports/Summary_sales_taxes.php',
|
||||||
'Summary_suppliers' => '/App/Models/Reports/Summary_suppliers.php',
|
'Summary_suppliers' => '/App/Models/Reports/Summary_suppliers.php',
|
||||||
'Summary_taxes' => '/App/Models/Reports/Summary_taxes.php',
|
'Summary_taxes' => '/App/Models/Reports/Summary_taxes.php',
|
||||||
|
|
||||||
//Tokens
|
// Tokens
|
||||||
'Token' => '/App/Models/Tokens/Token.php',
|
'Token' => '/App/Models/Tokens/Token.php',
|
||||||
'Token_barcode_ean' => '/App/Models/Tokens/Token_barcode_ean.php',
|
'Token_barcode_ean' => '/App/Models/Tokens/Token_barcode_ean.php',
|
||||||
'Token_barcode_price' => '/App/Models/Tokens/Token_barcode_price.php',
|
'Token_barcode_price' => '/App/Models/Tokens/Token_barcode_price.php',
|
||||||
'Token_barcode_weight' => '/App/Models/Tokens/Token_barcode_weight.php',
|
'Token_barcode_weight' => '/App/Models/Tokens/Token_barcode_weight.php',
|
||||||
'Token_customer' => '/App/Models/Tokens/Token_customer.php',
|
'Token_customer' => '/App/Models/Tokens/Token_customer.php',
|
||||||
'Token_invoice_count' => '/App/Models/Tokens/Token_invoice_count.php',
|
'Token_invoice_count' => '/App/Models/Tokens/Token_invoice_count.php',
|
||||||
'Token_invoice_sequence' => '/App/Models/Tokens/Token_invoice_sequence.php',
|
'Token_invoice_sequence' => '/App/Models/Tokens/Token_invoice_sequence.php',
|
||||||
'Token_quote_sequence' => '/App/Models/Tokens/Token_quote_sequence.php',
|
'Token_quote_sequence' => '/App/Models/Tokens/Token_quote_sequence.php',
|
||||||
'Token_suspended_invoice_count' => '/App/Models/Tokens/Token_suspended_invoice_count.php',
|
'Token_suspended_invoice_count' => '/App/Models/Tokens/Token_suspended_invoice_count.php',
|
||||||
'Token_work_order_sequence' => '/App/Models/Tokens/Token_work_order_sequence.php',
|
'Token_work_order_sequence' => '/App/Models/Tokens/Token_work_order_sequence.php',
|
||||||
'Token_year_invoice_count' => '/App/Models/Tokens/Token_year_invoice_count.php',
|
'Token_year_invoice_count' => '/App/Models/Tokens/Token_year_invoice_count.php',
|
||||||
'Token_year_quote_count' => '/App/Models/Tokens/Token_year_quote_count.php',
|
'Token_year_quote_count' => '/App/Models/Tokens/Token_year_quote_count.php',
|
||||||
|
|
||||||
//Libraries
|
// Libraries
|
||||||
'Barcode_lib' => '/App/Libraries/Barcode_lib.php',
|
'Barcode_lib' => '/App/Libraries/Barcode_lib.php',
|
||||||
'Email_lib' => '/App/Libraries/Email_lib.php',
|
'Email_lib' => '/App/Libraries/Email_lib.php',
|
||||||
'Item_lib' => '/App/Libraries/Item_lib.php',
|
'Item_lib' => '/App/Libraries/Item_lib.php',
|
||||||
'Mailchimp_lib' => '/App/Libraries/Mailchimp_lib.php',
|
'Mailchimp_lib' => '/App/Libraries/Mailchimp_lib.php',
|
||||||
'MY_Email' => '/App/Libraries/MY_Email.php',
|
'MY_Email' => '/App/Libraries/MY_Email.php',
|
||||||
'MY_Migration' => '/App/Libraries/MY_Migration.php',
|
'MY_Migration' => '/App/Libraries/MY_Migration.php',
|
||||||
'Receving_lib' => '/App/Libraries/Receiving_lib.php',
|
'Receving_lib' => '/App/Libraries/Receiving_lib.php',
|
||||||
'Sale_lib' => '/App/Libraries/Sale_lib.php',
|
'Sale_lib' => '/App/Libraries/Sale_lib.php',
|
||||||
'Sms_lib' => '/App/Libraries/Sms_lib.php',
|
'Sms_lib' => '/App/Libraries/Sms_lib.php',
|
||||||
'Tax_lib' => '/App/Libraries/Tax_lib.php',
|
'Tax_lib' => '/App/Libraries/Tax_lib.php',
|
||||||
'Token_lib' => '/App/Libraries/Token_lib.php',
|
'Token_lib' => '/App/Libraries/Token_lib.php',
|
||||||
|
|
||||||
//Miscellaneous
|
// Miscellaneous
|
||||||
'Rounding_mode' => '/App/Models/Enums/Rounding_mode.php'
|
'Rounding_mode' => '/App/Models/Enums/Rounding_mode.php'
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* -------------------------------------------------------------------
|
* -------------------------------------------------------------------
|
||||||
@@ -201,10 +199,10 @@ class Autoload extends AutoloadConfig
|
|||||||
* @var list<string>
|
* @var list<string>
|
||||||
*/
|
*/
|
||||||
public $helpers = [
|
public $helpers = [
|
||||||
'form',
|
'form',
|
||||||
'cookie',
|
'cookie',
|
||||||
'tabular',
|
'tabular',
|
||||||
'locale',
|
'locale',
|
||||||
'security'
|
'security'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,22 @@ use CodeIgniter\Config\BaseConfig;
|
|||||||
|
|
||||||
class CURLRequest extends 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
|
* CURLRequest Share Options
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace Config;
|
namespace Config;
|
||||||
|
|
||||||
use CodeIgniter\Cache\CacheInterface;
|
use CodeIgniter\Cache\CacheInterface;
|
||||||
|
use CodeIgniter\Cache\Handlers\ApcuHandler;
|
||||||
use CodeIgniter\Cache\Handlers\DummyHandler;
|
use CodeIgniter\Cache\Handlers\DummyHandler;
|
||||||
use CodeIgniter\Cache\Handlers\FileHandler;
|
use CodeIgniter\Cache\Handlers\FileHandler;
|
||||||
use CodeIgniter\Cache\Handlers\MemcachedHandler;
|
use CodeIgniter\Cache\Handlers\MemcachedHandler;
|
||||||
@@ -34,18 +35,6 @@ class Cache extends BaseConfig
|
|||||||
*/
|
*/
|
||||||
public string $backupHandler = 'dummy';
|
public string $backupHandler = 'dummy';
|
||||||
|
|
||||||
/**
|
|
||||||
* --------------------------------------------------------------------------
|
|
||||||
* Cache Directory Path
|
|
||||||
* --------------------------------------------------------------------------
|
|
||||||
*
|
|
||||||
* The path to where cache files should be stored, if using a file-based
|
|
||||||
* system.
|
|
||||||
*
|
|
||||||
* @deprecated Use the driver-specific variant under $file
|
|
||||||
*/
|
|
||||||
public string $storePath = WRITEPATH . 'cache/';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
* Key Prefix
|
* Key Prefix
|
||||||
@@ -86,10 +75,11 @@ class Cache extends BaseConfig
|
|||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
* File settings
|
* File settings
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
|
*
|
||||||
* Your file storage preferences can be specified below, if you are using
|
* Your file storage preferences can be specified below, if you are using
|
||||||
* the File driver.
|
* the File driver.
|
||||||
*
|
*
|
||||||
* @var array<string, int|string|null>
|
* @var array{storePath?: string, mode?: int}
|
||||||
*/
|
*/
|
||||||
public array $file = [
|
public array $file = [
|
||||||
'storePath' => WRITEPATH . 'cache/',
|
'storePath' => WRITEPATH . 'cache/',
|
||||||
@@ -100,12 +90,13 @@ class Cache extends BaseConfig
|
|||||||
* -------------------------------------------------------------------------
|
* -------------------------------------------------------------------------
|
||||||
* Memcached settings
|
* Memcached settings
|
||||||
* -------------------------------------------------------------------------
|
* -------------------------------------------------------------------------
|
||||||
|
*
|
||||||
* Your Memcached servers can be specified below, if you are using
|
* Your Memcached servers can be specified below, if you are using
|
||||||
* the Memcached drivers.
|
* the Memcached drivers.
|
||||||
*
|
*
|
||||||
* @see https://codeigniter.com/user_guide/libraries/caching.html#memcached
|
* @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 = [
|
public array $memcached = [
|
||||||
'host' => '127.0.0.1',
|
'host' => '127.0.0.1',
|
||||||
@@ -118,17 +109,28 @@ class Cache extends BaseConfig
|
|||||||
* -------------------------------------------------------------------------
|
* -------------------------------------------------------------------------
|
||||||
* Redis settings
|
* Redis settings
|
||||||
* -------------------------------------------------------------------------
|
* -------------------------------------------------------------------------
|
||||||
|
*
|
||||||
* Your Redis server can be specified below, if you are using
|
* Your Redis server can be specified below, if you are using
|
||||||
* the Redis or Predis drivers.
|
* 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 = [
|
public array $redis = [
|
||||||
'host' => '127.0.0.1',
|
'host' => '127.0.0.1',
|
||||||
'password' => null,
|
'password' => null,
|
||||||
'port' => 6379,
|
'port' => 6379,
|
||||||
'timeout' => 0,
|
'timeout' => 0,
|
||||||
'database' => 0,
|
'async' => false, // specific to Predis and ignored by the native Redis extension
|
||||||
|
'persistent' => false,
|
||||||
|
'database' => 0,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -142,6 +144,7 @@ class Cache extends BaseConfig
|
|||||||
* @var array<string, class-string<CacheInterface>>
|
* @var array<string, class-string<CacheInterface>>
|
||||||
*/
|
*/
|
||||||
public array $validHandlers = [
|
public array $validHandlers = [
|
||||||
|
'apcu' => ApcuHandler::class,
|
||||||
'dummy' => DummyHandler::class,
|
'dummy' => DummyHandler::class,
|
||||||
'file' => FileHandler::class,
|
'file' => FileHandler::class,
|
||||||
'memcached' => MemcachedHandler::class,
|
'memcached' => MemcachedHandler::class,
|
||||||
@@ -168,4 +171,28 @@ class Cache extends BaseConfig
|
|||||||
* @var bool|list<string>
|
* @var bool|list<string>
|
||||||
*/
|
*/
|
||||||
public $cacheQueryString = false;
|
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 = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,10 +41,9 @@ defined('WEEK') || define('WEEK', 604800);
|
|||||||
defined('MONTH') || define('MONTH', 2_592_000);
|
defined('MONTH') || define('MONTH', 2_592_000);
|
||||||
defined('YEAR') || define('YEAR', 31_536_000);
|
defined('YEAR') || define('YEAR', 31_536_000);
|
||||||
defined('DECADE') || define('DECADE', 315_360_000);
|
defined('DECADE') || define('DECADE', 315_360_000);
|
||||||
defined('DEFAULT_DATE') || define('DEFAULT_DATE', mktime(0, 0, 0, 1, 1, 2010));
|
defined('DEFAULT_DATE') || define('DEFAULT_DATE', mktime(0, 0, 0, 1, 1, 2010));
|
||||||
defined('DEFAULT_DATETIME') || define('DEFAULT_DATETIME', mktime(0, 0, 0, 1, 1, 2010));
|
defined('DEFAULT_DATETIME') || define('DEFAULT_DATETIME', mktime(0, 0, 0, 1, 1, 2010));
|
||||||
defined('NOW') || define('NOW', time());
|
defined('NOW') || define('NOW', time());
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
| --------------------------------------------------------------------------
|
| --------------------------------------------------------------------------
|
||||||
@@ -82,21 +81,6 @@ defined('EXIT_DATABASE') || define('EXIT_DATABASE', 8); // database
|
|||||||
defined('EXIT__AUTO_MIN') || define('EXIT__AUTO_MIN', 9); // lowest automatically-assigned error code
|
defined('EXIT__AUTO_MIN') || define('EXIT__AUTO_MIN', 9); // lowest automatically-assigned error code
|
||||||
defined('EXIT__AUTO_MAX') || define('EXIT__AUTO_MAX', 125); // highest automatically-assigned error code
|
defined('EXIT__AUTO_MAX') || define('EXIT__AUTO_MAX', 125); // highest automatically-assigned error code
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use \CodeIgniter\Events\Events::PRIORITY_LOW instead.
|
|
||||||
*/
|
|
||||||
define('EVENT_PRIORITY_LOW', 200);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use \CodeIgniter\Events\Events::PRIORITY_NORMAL instead.
|
|
||||||
*/
|
|
||||||
define('EVENT_PRIORITY_NORMAL', 100);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use \CodeIgniter\Events\Events::PRIORITY_HIGH instead.
|
|
||||||
*/
|
|
||||||
define('EVENT_PRIORITY_HIGH', 10);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Global Constants.
|
* Global Constants.
|
||||||
*/
|
*/
|
||||||
@@ -185,3 +169,8 @@ const MAX_PRECISION = 1e14;
|
|||||||
const DEFAULT_PRECISION = 2;
|
const DEFAULT_PRECISION = 2;
|
||||||
const DEFAULT_LANGUAGE = 'english';
|
const DEFAULT_LANGUAGE = 'english';
|
||||||
const DEFAULT_LANGUAGE_CODE = 'en';
|
const DEFAULT_LANGUAGE_CODE = 'en';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Admin modules - list of modules required for admin privileges
|
||||||
|
*/
|
||||||
|
const ADMIN_MODULES = ['customers', 'employees', 'giftcards', 'items', 'item_kits', 'messages', 'receivings', 'reports', 'sales', 'config', 'suppliers'];
|
||||||
|
|||||||
@@ -30,6 +30,11 @@ class ContentSecurityPolicy extends BaseConfig
|
|||||||
*/
|
*/
|
||||||
public ?string $reportURI = null;
|
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
|
* Instructs user agents to rewrite URL schemes, changing
|
||||||
* HTTP to HTTPS. This directive is for websites with
|
* HTTP to HTTPS. This directive is for websites with
|
||||||
@@ -38,19 +43,19 @@ class ContentSecurityPolicy extends BaseConfig
|
|||||||
public bool $upgradeInsecureRequests = false;
|
public bool $upgradeInsecureRequests = false;
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Sources allowed
|
// CSP DIRECTIVES SETTINGS
|
||||||
// NOTE: once you set a policy to 'none', it cannot be further restricted
|
// 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
|
* @var list<string>|string|null
|
||||||
*/
|
*/
|
||||||
public $defaultSrc = [
|
public $defaultSrc = [
|
||||||
'self',
|
'self',
|
||||||
'www.google.com',
|
'www.google.com',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lists allowed scripts' URLs.
|
* Lists allowed scripts' URLs.
|
||||||
@@ -58,23 +63,53 @@ class ContentSecurityPolicy extends BaseConfig
|
|||||||
* @var list<string>|string
|
* @var list<string>|string
|
||||||
*/
|
*/
|
||||||
public $scriptSrc = [
|
public $scriptSrc = [
|
||||||
'self',
|
'self',
|
||||||
'unsafe-inline',
|
'unsafe-inline',
|
||||||
'unsafe-eval',
|
'unsafe-eval',
|
||||||
'www.google.com www.gstatic.com'
|
'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.
|
* Lists allowed stylesheets' URLs.
|
||||||
*
|
*
|
||||||
* @var list<string>|string
|
* @var list<string>|string
|
||||||
*/
|
*/
|
||||||
public $styleSrc = [
|
public $styleSrc = [
|
||||||
'self',
|
'self',
|
||||||
'unsafe-inline',
|
'unsafe-inline',
|
||||||
'nonce-{csp-style-nonce}',
|
'nonce-{csp-style-nonce}',
|
||||||
'https://fonts.googleapis.com',
|
'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.
|
* Defines the origins from which images can be loaded.
|
||||||
@@ -82,10 +117,10 @@ class ContentSecurityPolicy extends BaseConfig
|
|||||||
* @var list<string>|string
|
* @var list<string>|string
|
||||||
*/
|
*/
|
||||||
public $imageSrc = [
|
public $imageSrc = [
|
||||||
'self',
|
'self',
|
||||||
'data:',
|
'data:',
|
||||||
'blob:',
|
'blob:',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restricts the URLs that can appear in a page's `<base>` element.
|
* Restricts the URLs that can appear in a page's `<base>` element.
|
||||||
@@ -110,9 +145,9 @@ class ContentSecurityPolicy extends BaseConfig
|
|||||||
* @var list<string>|string
|
* @var list<string>|string
|
||||||
*/
|
*/
|
||||||
public $connectSrc = [
|
public $connectSrc = [
|
||||||
'self',
|
'self',
|
||||||
'nominatim.openstreetmap.org',
|
'nominatim.openstreetmap.org',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies the origins that can serve web fonts.
|
* Specifies the origins that can serve web fonts.
|
||||||
@@ -120,10 +155,10 @@ class ContentSecurityPolicy extends BaseConfig
|
|||||||
* @var list<string>|string
|
* @var list<string>|string
|
||||||
*/
|
*/
|
||||||
public $fontSrc = [
|
public $fontSrc = [
|
||||||
'self',
|
'self',
|
||||||
'fonts.googleapis.com',
|
'fonts.googleapis.com',
|
||||||
'fonts.gstatic.com',
|
'fonts.gstatic.com',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lists valid endpoints for submission from `<form>` tags.
|
* Lists valid endpoints for submission from `<form>` tags.
|
||||||
@@ -169,6 +204,11 @@ class ContentSecurityPolicy extends BaseConfig
|
|||||||
*/
|
*/
|
||||||
public $manifestSrc;
|
public $manifestSrc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var list<string>|string
|
||||||
|
*/
|
||||||
|
public array|string $workerSrc = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Limits the kinds of plugins a page may invoke.
|
* Limits the kinds of plugins a page may invoke.
|
||||||
*
|
*
|
||||||
@@ -184,17 +224,17 @@ class ContentSecurityPolicy extends BaseConfig
|
|||||||
public $sandbox;
|
public $sandbox;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Nonce tag for style
|
* Nonce placeholder for style tags.
|
||||||
*/
|
*/
|
||||||
public string $styleNonceTag = '{csp-style-nonce}';
|
public string $styleNonceTag = '{csp-style-nonce}';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Nonce tag for script
|
* Nonce placeholder for script tags.
|
||||||
*/
|
*/
|
||||||
public string $scriptNonceTag = '{csp-script-nonce}';
|
public string $scriptNonceTag = '{csp-script-nonce}';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replace nonce tag automatically
|
* Replace nonce tag automatically?
|
||||||
*/
|
*/
|
||||||
public bool $autoNonce = true;
|
public bool $autoNonce = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ class Cookie extends BaseConfig
|
|||||||
* (empty string) means default SameSite attribute set by browsers (`Lax`)
|
* (empty string) means default SameSite attribute set by browsers (`Lax`)
|
||||||
* will be set on cookies. If set to `None`, `$secure` must also be set.
|
* 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';
|
public string $samesite = 'Lax';
|
||||||
|
|
||||||
|
|||||||
@@ -19,104 +19,102 @@ class Database extends Config
|
|||||||
*/
|
*/
|
||||||
public string $defaultGroup = 'default';
|
public string $defaultGroup = 'default';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default database connection.
|
* The default database connection.
|
||||||
*
|
*
|
||||||
* @var array<string, mixed>
|
* @var array<string, mixed>
|
||||||
*/
|
*/
|
||||||
public array $default = [
|
public array $default = [
|
||||||
'DSN' => '',
|
'DSN' => '',
|
||||||
'hostname' => 'localhost',
|
'hostname' => 'localhost',
|
||||||
'username' => 'admin',
|
'username' => 'admin',
|
||||||
'password' => 'pointofsale',
|
'password' => 'pointofsale',
|
||||||
'database' => 'ospos',
|
'database' => 'ospos',
|
||||||
'DBDriver' => 'MySQLi',
|
'DBDriver' => 'MySQLi',
|
||||||
'DBPrefix' => 'ospos_',
|
'DBPrefix' => 'ospos_',
|
||||||
'pConnect' => false,
|
'pConnect' => false,
|
||||||
'DBDebug' => (ENVIRONMENT !== 'production'),
|
'DBDebug' => (ENVIRONMENT !== 'production'),
|
||||||
'charset' => 'utf8mb4',
|
'charset' => 'utf8mb4',
|
||||||
'DBCollat' => 'utf8mb4_general_ci',
|
'DBCollat' => 'utf8mb4_general_ci',
|
||||||
'swapPre' => '',
|
'swapPre' => '',
|
||||||
'encrypt' => false,
|
'encrypt' => false,
|
||||||
'compress' => false,
|
'compress' => false,
|
||||||
'strictOn' => false,
|
'strictOn' => false,
|
||||||
'failover' => [],
|
'failover' => [],
|
||||||
'port' => 3306,
|
'port' => 3306,
|
||||||
'numberNative' => false,
|
'numberNative' => false,
|
||||||
'dateFormat' => [
|
'foundRows' => false,
|
||||||
'date' => 'Y-m-d',
|
'dateFormat' => [
|
||||||
'datetime' => 'Y-m-d H:i:s',
|
'date' => 'Y-m-d',
|
||||||
'time' => 'H:i:s',
|
'datetime' => 'Y-m-d H:i:s',
|
||||||
],
|
'time' => 'H:i:s',
|
||||||
];
|
],
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This database connection is used when
|
* This database connection is used when running PHPUnit database tests.
|
||||||
* running PHPUnit database tests.
|
*
|
||||||
*
|
* @var array<string, mixed>
|
||||||
* @var array<string, mixed>
|
*/
|
||||||
*/
|
public array $tests = [
|
||||||
public array $tests = [
|
'DSN' => '',
|
||||||
'DSN' => '',
|
'hostname' => 'localhost',
|
||||||
'hostname' => 'localhost',
|
'username' => 'admin',
|
||||||
'username' => 'admin',
|
'password' => 'pointofsale',
|
||||||
'password' => 'pointofsale',
|
'database' => 'ospos',
|
||||||
'database' => 'ospos',
|
'DBDriver' => 'MySQLi',
|
||||||
'DBDriver' => 'MySQLi',
|
'DBPrefix' => 'ospos_',
|
||||||
'DBPrefix' => 'ospos_',
|
'pConnect' => false,
|
||||||
'pConnect' => false,
|
'DBDebug' => (ENVIRONMENT !== 'production'),
|
||||||
'DBDebug' => (ENVIRONMENT !== 'production'),
|
'charset' => 'utf8mb4',
|
||||||
'charset' => 'utf8mb4',
|
'DBCollat' => 'utf8mb4_general_ci',
|
||||||
'DBCollat' => 'utf8mb4_general_ci',
|
'swapPre' => '',
|
||||||
'swapPre' => '',
|
'encrypt' => false,
|
||||||
'encrypt' => false,
|
'compress' => false,
|
||||||
'compress' => false,
|
'strictOn' => false,
|
||||||
'strictOn' => false,
|
'failover' => [],
|
||||||
'failover' => [],
|
'port' => 3306,
|
||||||
'port' => 3306,
|
'foreignKeys' => true,
|
||||||
'foreignKeys' => true,
|
'busyTimeout' => 1000,
|
||||||
'busyTimeout' => 1000,
|
'synchronous' => null,
|
||||||
'numberNative' => false,
|
'dateFormat' => [
|
||||||
'dateFormat' => [
|
'date' => 'Y-m-d',
|
||||||
'date' => 'Y-m-d',
|
'datetime' => 'Y-m-d H:i:s',
|
||||||
'datetime' => 'Y-m-d H:i:s',
|
'time' => 'H:i:s',
|
||||||
'time' => 'H:i:s',
|
],
|
||||||
],
|
];
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This database connection is used when
|
* This database connection is used when developing against non-production data.
|
||||||
* developing against non-production data.
|
*
|
||||||
*
|
* @var array
|
||||||
* @var array
|
*/
|
||||||
*/
|
public $development = [
|
||||||
public $development = [
|
'DSN' => '',
|
||||||
'DSN' => '',
|
'hostname' => 'localhost',
|
||||||
'hostname' => 'localhost',
|
'username' => 'admin',
|
||||||
'username' => 'admin',
|
'password' => 'pointofsale',
|
||||||
'password' => 'pointofsale',
|
'database' => 'ospos',
|
||||||
'database' => 'ospos',
|
'DBDriver' => 'MySQLi',
|
||||||
'DBDriver' => 'MySQLi',
|
'DBPrefix' => 'ospos_',
|
||||||
'DBPrefix' => 'ospos_',
|
'pConnect' => false,
|
||||||
'pConnect' => false,
|
'DBDebug' => (ENVIRONMENT !== 'production'),
|
||||||
'DBDebug' => (ENVIRONMENT !== 'production'),
|
'charset' => 'utf8mb4',
|
||||||
'charset' => 'utf8mb4',
|
'DBCollat' => 'utf8mb4_general_ci',
|
||||||
'DBCollat' => 'utf8mb4_general_ci',
|
'swapPre' => '',
|
||||||
'swapPre' => '',
|
'encrypt' => false,
|
||||||
'encrypt' => false,
|
'compress' => false,
|
||||||
'compress' => false,
|
'strictOn' => false,
|
||||||
'strictOn' => false,
|
'failover' => [],
|
||||||
'failover' => [],
|
'port' => 3306,
|
||||||
'port' => 3306,
|
'foreignKeys' => true,
|
||||||
'foreignKeys' => true,
|
'busyTimeout' => 1000,
|
||||||
'busyTimeout' => 1000,
|
'dateFormat' => [
|
||||||
'numberNative' => false,
|
'date' => 'Y-m-d',
|
||||||
'dateFormat' => [
|
'datetime' => 'Y-m-d H:i:s',
|
||||||
'date' => 'Y-m-d',
|
'time' => 'H:i:s',
|
||||||
'datetime' => 'Y-m-d H:i:s',
|
],
|
||||||
'time' => 'H:i:s',
|
];
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
@@ -125,22 +123,20 @@ class Database extends Config
|
|||||||
// Ensure that we always set the database group to 'tests' if
|
// Ensure that we always set the database group to 'tests' if
|
||||||
// we are currently running an automated test suite, so that
|
// we are currently running an automated test suite, so that
|
||||||
// we don't overwrite live data on accident.
|
// we don't overwrite live data on accident.
|
||||||
switch(ENVIRONMENT)
|
switch (ENVIRONMENT) {
|
||||||
{
|
case 'testing':
|
||||||
case 'testing':
|
$this->defaultGroup = 'tests';
|
||||||
$this->defaultGroup = 'tests';
|
break;
|
||||||
break;
|
case 'development';
|
||||||
case 'development';
|
$this->defaultGroup = 'development';
|
||||||
$this->defaultGroup = 'development';
|
break;
|
||||||
break;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
foreach ([&$this->development, &$this->tests, &$this->default] as &$config)
|
foreach ([&$this->development, &$this->tests, &$this->default] as &$config) {
|
||||||
{
|
$config['hostname'] = !getenv('MYSQL_HOST_NAME') ? $config['hostname'] : getenv('MYSQL_HOST_NAME');
|
||||||
$config['hostname'] = !getenv('MYSQL_HOST_NAME') ? $config['hostname'] : getenv('MYSQL_HOST_NAME');
|
$config['username'] = !getenv('MYSQL_USERNAME') ? $config['username'] : getenv('MYSQL_USERNAME');
|
||||||
$config['username'] = !getenv('MYSQL_USERNAME') ? $config['username'] : getenv('MYSQL_USERNAME');
|
$config['password'] = !getenv('MYSQL_PASSWORD') ? $config['password'] : getenv('MYSQL_PASSWORD');
|
||||||
$config['password'] = !getenv('MYSQL_PASSWORD') ? $config['password'] : getenv('MYSQL_PASSWORD');
|
$config['database'] = !getenv('MYSQL_DB_NAME') ? $config['database'] : getenv('MYSQL_DB_NAME');
|
||||||
$config['database'] = !getenv('MYSQL_DB_NAME') ? $config['database'] : getenv('MYSQL_DB_NAME');
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,6 @@
|
|||||||
|
|
||||||
namespace Config;
|
namespace Config;
|
||||||
|
|
||||||
/**
|
|
||||||
* @immutable
|
|
||||||
*/
|
|
||||||
class DocTypes
|
class DocTypes
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -30,6 +30,11 @@ class Email extends BaseConfig
|
|||||||
*/
|
*/
|
||||||
public string $SMTPHost = 'mail.mxserver.com';
|
public string $SMTPHost = 'mail.mxserver.com';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which SMTP authentication method to use: login, plain
|
||||||
|
*/
|
||||||
|
public string $SMTPAuthMethod = 'login';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SMTP Username
|
* SMTP Username
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -23,6 +23,23 @@ class Encryption extends BaseConfig
|
|||||||
*/
|
*/
|
||||||
public string $key = '';
|
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
|
* Encryption Driver to Use
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
namespace Config;
|
namespace Config;
|
||||||
|
|
||||||
use App\Events\Db_log;
|
|
||||||
use App\Events\Load_config;
|
|
||||||
use App\Events\Method;
|
|
||||||
use CodeIgniter\Events\Events;
|
use CodeIgniter\Events\Events;
|
||||||
use CodeIgniter\Exceptions\FrameworkException;
|
use CodeIgniter\Exceptions\FrameworkException;
|
||||||
use CodeIgniter\HotReloader\HotReloader;
|
use CodeIgniter\HotReloader\HotReloader;
|
||||||
|
use App\Events\Db_log;
|
||||||
|
use App\Events\Load_config;
|
||||||
|
use App\Events\Method;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* --------------------------------------------------------------------
|
* --------------------------------------------------------------------
|
||||||
@@ -47,10 +47,10 @@ Events::on('pre_system', static function (): void {
|
|||||||
*/
|
*/
|
||||||
if (CI_DEBUG && ! is_cli()) {
|
if (CI_DEBUG && ! is_cli()) {
|
||||||
Events::on('DBQuery', 'CodeIgniter\Debug\Toolbar\Collectors\Database::collect');
|
Events::on('DBQuery', 'CodeIgniter\Debug\Toolbar\Collectors\Database::collect');
|
||||||
Services::toolbar()->respond();
|
service('toolbar')->respond();
|
||||||
// Hot Reload route - for framework use on the hot reloader.
|
// Hot Reload route - for framework use on the hot reloader.
|
||||||
if (ENVIRONMENT === 'development') {
|
if (ENVIRONMENT === 'development') {
|
||||||
Services::routes()->get('__hot-reload', static function (): void {
|
service('routes')->get('__hot-reload', static function (): void {
|
||||||
(new HotReloader())->run();
|
(new HotReloader())->run();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ class Exceptions extends BaseConfig
|
|||||||
*/
|
*/
|
||||||
public string $deprecationLogLevel = LogLevel::WARNING;
|
public string $deprecationLogLevel = LogLevel::WARNING;
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* DEFINE THE HANDLERS USED
|
* DEFINE THE HANDLERS USED
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
* Given the HTTP status code, returns exception handler that
|
* Given the HTTP status code, returns exception handler that
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use CodeIgniter\Config\BaseConfig;
|
|||||||
class Feature extends BaseConfig
|
class Feature extends BaseConfig
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Use improved new auto routing instead of the default legacy version.
|
* Use improved new auto routing instead of the legacy version.
|
||||||
*/
|
*/
|
||||||
public bool $autoRoutesImproved = true;
|
public bool $autoRoutesImproved = true;
|
||||||
|
|
||||||
@@ -26,4 +26,12 @@ class Feature extends BaseConfig
|
|||||||
* If false, `limit(0)` returns no records. (the behavior of 3.1.9 or later in version 3.x.)
|
* If false, `limit(0)` returns no records. (the behavior of 3.1.9 or later in version 3.x.)
|
||||||
*/
|
*/
|
||||||
public bool $limitZeroAsAll = true;
|
public bool $limitZeroAsAll = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use strict location negotiation.
|
||||||
|
*
|
||||||
|
* By default, the locale is selected based on a loose comparison of the language code (ISO 639-1)
|
||||||
|
* Enabling strict comparison will also consider the region code (ISO 3166-1 alpha-2).
|
||||||
|
*/
|
||||||
|
public bool $strictLocaleNegotiation = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,12 +65,15 @@ class Filters extends BaseFilters
|
|||||||
* List of filter aliases that are always
|
* List of filter aliases that are always
|
||||||
* applied before and after every request.
|
* 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 = [
|
public array $globals = [
|
||||||
'before' => [
|
'before' => [
|
||||||
'honeypot',
|
'honeypot',
|
||||||
//'csrf' => ['except' => 'login'], //TODO: Temporarily disable CSRF until we get everything sorted.
|
'csrf' => ['except' => 'login|migrate'],
|
||||||
'invalidchars',
|
'invalidchars',
|
||||||
],
|
],
|
||||||
'after' => [
|
'after' => [
|
||||||
@@ -105,4 +108,20 @@ class Filters extends BaseFilters
|
|||||||
* @var array<string, array<string, list<string>>>
|
* @var array<string, array<string, list<string>>>
|
||||||
*/
|
*/
|
||||||
public array $filters = [];
|
public array $filters = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor to conditionally disable CSRF filter in testing environment
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
// Check for testing environment via env variable or constant
|
||||||
|
$isTesting = ($_ENV['CI_ENVIRONMENT'] ?? $_SERVER['CI_ENVIRONMENT'] ?? getenv('CI_ENVIRONMENT')) === 'testing'
|
||||||
|
|| (defined('ENVIRONMENT') && ENVIRONMENT === 'testing');
|
||||||
|
|
||||||
|
// Remove CSRF filter from globals in testing environment
|
||||||
|
if ($isTesting) {
|
||||||
|
// Remove the 'csrf' key from $globals['before'] while preserving array structure
|
||||||
|
$this->globals['before'] = array_filter($this->globals['before'], static fn($key) => $key !== 'csrf', ARRAY_FILTER_USE_KEY);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace Config;
|
namespace Config;
|
||||||
|
|
||||||
use CodeIgniter\Config\BaseConfig;
|
use CodeIgniter\Config\BaseConfig;
|
||||||
use CodeIgniter\Format\FormatterInterface;
|
|
||||||
use CodeIgniter\Format\JSONFormatter;
|
use CodeIgniter\Format\JSONFormatter;
|
||||||
use CodeIgniter\Format\XMLFormatter;
|
use CodeIgniter\Format\XMLFormatter;
|
||||||
|
|
||||||
@@ -64,14 +63,11 @@ class Format extends BaseConfig
|
|||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Factory method to return the appropriate formatter for the given mime type.
|
* --------------------------------------------------------------------------
|
||||||
|
* Maximum depth for JSON encoding.
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
*
|
*
|
||||||
* @return FormatterInterface
|
* This value determines how deep the JSON encoder will traverse nested structures.
|
||||||
*
|
|
||||||
* @deprecated This is an alias of `\CodeIgniter\Format\Format::getFormatter`. Use that instead.
|
|
||||||
*/
|
*/
|
||||||
public function getFormatter(string $mime)
|
public int $jsonEncodeDepth = 512;
|
||||||
{
|
|
||||||
return Services::format()->getFormatter($mime);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
40
app/Config/Hostnames.php
Normal file
40
app/Config/Hostnames.php
Normal 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',
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -16,6 +16,8 @@ class Images extends BaseConfig
|
|||||||
/**
|
/**
|
||||||
* The path to the image library.
|
* The path to the image library.
|
||||||
* Required for ImageMagick, GraphicsMagick, or NetPBM.
|
* Required for ImageMagick, GraphicsMagick, or NetPBM.
|
||||||
|
*
|
||||||
|
* @deprecated 4.7.0 No longer used.
|
||||||
*/
|
*/
|
||||||
public string $libraryPath = '/usr/local/bin/convert';
|
public string $libraryPath = '/usr/local/bin/convert';
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace Config;
|
namespace Config;
|
||||||
|
|
||||||
use Kint\Parser\ConstructablePluginInterface;
|
use Kint\Parser\ConstructablePluginInterface;
|
||||||
use Kint\Renderer\AbstractRenderer;
|
|
||||||
use Kint\Renderer\Rich\TabPluginInterface;
|
use Kint\Renderer\Rich\TabPluginInterface;
|
||||||
use Kint\Renderer\Rich\ValuePluginInterface;
|
use Kint\Renderer\Rich\ValuePluginInterface;
|
||||||
|
|
||||||
@@ -41,7 +40,6 @@ class Kint
|
|||||||
*/
|
*/
|
||||||
public string $richTheme = 'aante-light.css';
|
public string $richTheme = 'aante-light.css';
|
||||||
public bool $richFolder = false;
|
public bool $richFolder = false;
|
||||||
public int $richSort = AbstractRenderer::SORT_FULL;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array<string, class-string<ValuePluginInterface>>|null
|
* @var array<string, class-string<ValuePluginInterface>>|null
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace Config;
|
|||||||
|
|
||||||
use CodeIgniter\Config\BaseConfig;
|
use CodeIgniter\Config\BaseConfig;
|
||||||
use CodeIgniter\Log\Handlers\FileHandler;
|
use CodeIgniter\Log\Handlers\FileHandler;
|
||||||
|
use CodeIgniter\Log\Handlers\HandlerInterface;
|
||||||
|
|
||||||
class Logger extends BaseConfig
|
class Logger extends BaseConfig
|
||||||
{
|
{
|
||||||
@@ -73,7 +74,7 @@ class Logger extends BaseConfig
|
|||||||
* Handlers are executed in the order defined in this array, starting with
|
* Handlers are executed in the order defined in this array, starting with
|
||||||
* the handler on top and continuing down.
|
* 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 = [
|
public array $handlers = [
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -47,4 +47,19 @@ class Migrations extends BaseConfig
|
|||||||
* - Y_m_d_His_
|
* - Y_m_d_His_
|
||||||
*/
|
*/
|
||||||
public string $timestampFormat = 'YmdHis_';
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
namespace Config;
|
namespace Config;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mimes
|
|
||||||
*
|
|
||||||
* This file contains an array of mime types. It is used by the
|
* This file contains an array of mime types. It is used by the
|
||||||
* Upload class to help identify allowed file types.
|
* 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´
|
* When working with mime types, please make sure you have the ´fileinfo´
|
||||||
* extension enabled to reliably detect the media types.
|
* extension enabled to reliably detect the media types.
|
||||||
*
|
|
||||||
* @immutable
|
|
||||||
*/
|
*/
|
||||||
class Mimes
|
class Mimes
|
||||||
{
|
{
|
||||||
@@ -482,6 +478,8 @@ class Mimes
|
|||||||
'application/sla',
|
'application/sla',
|
||||||
'application/vnd.ms-pki.stl',
|
'application/vnd.ms-pki.stl',
|
||||||
'application/x-navistyle',
|
'application/x-navistyle',
|
||||||
|
'model/stl',
|
||||||
|
'application/octet-stream',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -490,12 +488,11 @@ class Mimes
|
|||||||
*
|
*
|
||||||
* @return string|null The mime type found, or none if unable to determine.
|
* @return string|null The mime type found, or none if unable to determine.
|
||||||
*/
|
*/
|
||||||
public static function guessTypeFromExtension(string $extension): array|string|null
|
public static function guessTypeFromExtension(string $extension)
|
||||||
{
|
{
|
||||||
$extension = trim(strtolower($extension), '. ');
|
$extension = trim(strtolower($extension), '. ');
|
||||||
|
|
||||||
if (!array_key_exists($extension, static::$mimes))
|
if (! array_key_exists($extension, static::$mimes)) {
|
||||||
{
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -509,8 +506,8 @@ class Mimes
|
|||||||
*
|
*
|
||||||
* @return string|null The extension determined, or null if unable to match.
|
* @return string|null The extension determined, or null if unable to match.
|
||||||
*/
|
*/
|
||||||
public static function guessExtensionFromType(string $type, ?string $proposedExtension = null): ?string
|
public static function guessExtensionFromType(string $type, ?string $proposedExtension = null)
|
||||||
{
|
{
|
||||||
$type = trim(strtolower($type), '. ');
|
$type = trim(strtolower($type), '. ');
|
||||||
|
|
||||||
$proposedExtension = trim(strtolower($proposedExtension ?? ''));
|
$proposedExtension = trim(strtolower($proposedExtension ?? ''));
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ use CodeIgniter\Modules\Modules as BaseModules;
|
|||||||
*
|
*
|
||||||
* NOTE: This class is required prior to Autoloader instantiation,
|
* NOTE: This class is required prior to Autoloader instantiation,
|
||||||
* and does not extend BaseConfig.
|
* and does not extend BaseConfig.
|
||||||
*
|
|
||||||
* @immutable
|
|
||||||
*/
|
*/
|
||||||
class Modules extends BaseModules
|
class Modules extends BaseModules
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace Config;
|
|||||||
use App\Models\Appconfig;
|
use App\Models\Appconfig;
|
||||||
use CodeIgniter\Cache\CacheInterface;
|
use CodeIgniter\Cache\CacheInterface;
|
||||||
use CodeIgniter\Config\BaseConfig;
|
use CodeIgniter\Config\BaseConfig;
|
||||||
|
use Config\Database;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class holds the configuration options stored from the database so that on launch those settings can be cached
|
* This class holds the configuration options stored from the database so that on launch those settings can be cached
|
||||||
@@ -13,45 +14,63 @@ use CodeIgniter\Config\BaseConfig;
|
|||||||
*/
|
*/
|
||||||
class OSPOS extends BaseConfig
|
class OSPOS extends BaseConfig
|
||||||
{
|
{
|
||||||
public array $settings;
|
public array $settings = [];
|
||||||
public string $commit_sha1 = 'dev'; //TODO: Travis scripts need to be updated to replace this with the commit hash on build
|
public string $commit_sha1 = 'dev'; // TODO: Travis scripts need to be updated to replace this with the commit hash on build
|
||||||
private CacheInterface $cache;
|
private CacheInterface $cache;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
$this->cache = Services::cache();
|
$this->cache = Services::cache();
|
||||||
$this->set_settings();
|
$this->set_settings();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function set_settings(): void
|
public function set_settings(): void
|
||||||
{
|
{
|
||||||
$cache = $this->cache->get('settings');
|
$cache = $this->cache->get('settings');
|
||||||
|
|
||||||
if($cache)
|
if ($cache) {
|
||||||
{
|
$this->settings = decode_array($cache);
|
||||||
$this->settings = decode_array($cache);
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
$appconfig = model(Appconfig::class);
|
|
||||||
foreach($appconfig->get_all()->getResult() as $app_config)
|
|
||||||
{
|
|
||||||
$this->settings[$app_config->key] = $app_config->value;
|
|
||||||
}
|
|
||||||
$this->cache->save('settings', encode_array($this->settings));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
try {
|
||||||
* @return void
|
$db = Database::connect();
|
||||||
*/
|
|
||||||
public function update_settings(): void
|
if (!$db->tableExists('app_config')) {
|
||||||
{
|
$this->settings = $this->getDefaultSettings();
|
||||||
$this->cache->delete('settings');
|
return;
|
||||||
$this->set_settings();
|
}
|
||||||
}
|
|
||||||
|
$appconfig = model(Appconfig::class);
|
||||||
|
foreach ($appconfig->get_all()->getResult() as $app_config) {
|
||||||
|
$this->settings[$app_config->key] = $app_config->value;
|
||||||
|
}
|
||||||
|
$this->cache->save('settings', encode_array($this->settings));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->settings = $this->getDefaultSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getDefaultSettings(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'language' => 'english',
|
||||||
|
'language_code' => 'en',
|
||||||
|
'company' => 'Home',
|
||||||
|
'barcode_type' => 'Code39'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function update_settings(): void
|
||||||
|
{
|
||||||
|
$this->cache->delete('settings');
|
||||||
|
$this->set_settings();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace Config;
|
|||||||
* NOTE: This class does not extend BaseConfig for performance reasons.
|
* NOTE: This class does not extend BaseConfig for performance reasons.
|
||||||
* So you cannot replace the property values with Environment Variables.
|
* 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
|
class Optimize
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -35,27 +35,27 @@ class Pager extends BaseConfig
|
|||||||
*/
|
*/
|
||||||
public int $perPage = 20;
|
public int $perPage = 20;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
* Bootstrap 3 pagination links styling
|
* Bootstrap 3 pagination links styling
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
*
|
*
|
||||||
* Source code from http://stackoverflow.com/questions/20088779/bootstrap-3-pagination-with-codeigniter
|
* Source code from http://stackoverflow.com/questions/20088779/bootstrap-3-pagination-with-codeigniter
|
||||||
*/
|
*/
|
||||||
public $config = [
|
public $config = [
|
||||||
'full_tag_open' => "<ul class='pagination pagination-sm'>",
|
'full_tag_open' => '<ul class="pagination pagination-sm">',
|
||||||
'full_tag_close' => '</ul>',
|
'full_tag_close' => '</ul>',
|
||||||
'num_tag_open' => '<li>',
|
'num_tag_open' => '<li>',
|
||||||
'num_tag_close' => '</li>',
|
'num_tag_close' => '</li>',
|
||||||
'cur_tag_open' => "<li class='disabled'><li class='active'><a href='#'>",
|
'cur_tag_open' => '<li class="disabled"><li class="active"><a href="#">',
|
||||||
'cur_tag_close' => "<span class='sr-only'></span></a></li>",
|
'cur_tag_close' => '<span class="sr-only"></span></a></li>',
|
||||||
'next_tag_open' => "<li>",
|
'next_tag_open' => '<li>',
|
||||||
'next_tagl_close' => "</li>",
|
'next_tagl_close' => '</li>',
|
||||||
'prev_tag_open' => "<li>",
|
'prev_tag_open' => '<li>',
|
||||||
'prev_tagl_close' => "</li>",
|
'prev_tagl_close' => '</li>',
|
||||||
'first_tag_open' => "<li>",
|
'first_tag_open' => '<li>',
|
||||||
'first_tagl_close' => "</li>",
|
'first_tagl_close' => '</li>',
|
||||||
'last_tag_open' => "<li>",
|
'last_tag_open' => '<li>',
|
||||||
'last_tagl_close' => "</li>"
|
'last_tagl_close' => '</li>'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ namespace Config;
|
|||||||
*
|
*
|
||||||
* NOTE: This class is required prior to Autoloader instantiation,
|
* NOTE: This class is required prior to Autoloader instantiation,
|
||||||
* and does not extend BaseConfig.
|
* and does not extend BaseConfig.
|
||||||
*
|
|
||||||
* @immutable
|
|
||||||
*/
|
*/
|
||||||
class Paths
|
class Paths
|
||||||
{
|
{
|
||||||
@@ -77,4 +75,16 @@ class Paths
|
|||||||
* is used when no value is provided to `Services::renderer()`.
|
* is used when no value is provided to `Services::renderer()`.
|
||||||
*/
|
*/
|
||||||
public string $viewDirectory = __DIR__ . '/../Views';
|
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__ . '/../../';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use CodeIgniter\Router\RouteCollection;
|
use CodeIgniter\Router\RouteCollection;
|
||||||
|
|
||||||
@@ -10,6 +10,41 @@ $routes->setDefaultController('Login');
|
|||||||
$routes->get('/', 'Login::index');
|
$routes->get('/', 'Login::index');
|
||||||
$routes->get('login', 'Login::index');
|
$routes->get('login', 'Login::index');
|
||||||
$routes->post('login', 'Login::index');
|
$routes->post('login', 'Login::index');
|
||||||
|
$routes->post('migrate', 'Login::migrate');
|
||||||
|
|
||||||
|
$routes->get('sales', 'Sales::getIndex');
|
||||||
|
$routes->get('sales/customerDisplay', 'Sales::getCustomerDisplay');
|
||||||
|
$routes->get('sales/itemSearch', 'Sales::getItemSearch');
|
||||||
|
$routes->post('sales/selectCustomer', 'Sales::postSelectCustomer');
|
||||||
|
$routes->post('sales/changeMode', 'Sales::postChangeMode');
|
||||||
|
$routes->post('sales/setComment', 'Sales::postSetComment');
|
||||||
|
$routes->post('sales/setInvoiceNumber', 'Sales::postSetInvoiceNumber');
|
||||||
|
$routes->post('sales/setPaymentType', 'Sales::postSetPaymentType');
|
||||||
|
$routes->post('sales/setPrintAfterSale', 'Sales::postSetPrintAfterSale');
|
||||||
|
$routes->post('sales/setPriceWorkOrders', 'Sales::postSetPriceWorkOrders');
|
||||||
|
$routes->post('sales/setEmailReceipt', 'Sales::postSetEmailReceipt');
|
||||||
|
$routes->post('sales/addPayment', 'Sales::postAddPayment');
|
||||||
|
$routes->post('sales/add', 'Sales::postAdd');
|
||||||
|
$routes->post('sales/editItem/(:segment)', 'Sales::postEditItem/$1');
|
||||||
|
$routes->post('sales/deleteItem/(:segment)', 'Sales::getDeleteItem/$1');
|
||||||
|
$routes->post('sales/deletePayment/(:segment)', 'Sales::getDeletePayment/$1');
|
||||||
|
$routes->post('sales/removeCustomer', 'Sales::getRemoveCustomer');
|
||||||
|
$routes->post('sales/complete', 'Sales::postComplete');
|
||||||
|
$routes->post('sales/cancel', 'Sales::postCancel');
|
||||||
|
$routes->post('sales/suspend', 'Sales::postSuspend');
|
||||||
|
$routes->post('sales/unsuspend', 'Sales::postUnsuspend');
|
||||||
|
$routes->post('sales/checkInvoiceNumber', 'Sales::postCheckInvoiceNumber');
|
||||||
|
$routes->post('sales/changeItemNumber', 'Sales::postChangeItemNumber');
|
||||||
|
$routes->post('sales/changeItemName', 'Sales::postChangeItemName');
|
||||||
|
$routes->post('sales/changeItemDescription', 'Sales::postChangeItemDescription');
|
||||||
|
$routes->get('sales/suspended', 'Sales::getSuspended');
|
||||||
|
$routes->get('sales/discardSuspendedSale', 'Sales::getDiscardSuspendedSale');
|
||||||
|
$routes->get('sales/sales_keyboard_help', 'Sales::getSalesKeyboardHelp');
|
||||||
|
$routes->get('sales/receipt/(:num)', 'Sales::getReceipt/$1');
|
||||||
|
$routes->get('sales/invoice/(:num)', 'Sales::getInvoice/$1');
|
||||||
|
$routes->get('sales/edit/(:num)', 'Sales::getEdit/$1');
|
||||||
|
$routes->post('sales/delete/(:num)', 'Sales::postDelete/$1');
|
||||||
|
$routes->post('sales/save/(:num)', 'Sales::postSave/$1');
|
||||||
|
|
||||||
$routes->add('no_access/index/(:segment)', 'No_access::index/$1');
|
$routes->add('no_access/index/(:segment)', 'No_access::index/$1');
|
||||||
$routes->add('no_access/index/(:segment)/(:segment)', 'No_access::index/$1/$2');
|
$routes->add('no_access/index/(:segment)/(:segment)', 'No_access::index/$1/$2');
|
||||||
@@ -38,4 +73,4 @@ $routes->add('reports/specific_(:any)/(:any)/(:any)/(:any)', 'Reports::Specific_
|
|||||||
$routes->add('reports/specific_customers', 'Reports::specific_customer_input');
|
$routes->add('reports/specific_customers', 'Reports::specific_customer_input');
|
||||||
$routes->add('reports/specific_employees', 'Reports::specific_employee_input');
|
$routes->add('reports/specific_employees', 'Reports::specific_employee_input');
|
||||||
$routes->add('reports/specific_discounts', 'Reports::specific_discount_input');
|
$routes->add('reports/specific_discounts', 'Reports::specific_discount_input');
|
||||||
$routes->add('reports/specific_suppliers', 'Reports::specific_supplier_input');
|
$routes->add('reports/specific_suppliers', 'Reports::specific_supplier_input');
|
||||||
|
|||||||
@@ -96,6 +96,15 @@ class Routing extends BaseRouting
|
|||||||
*/
|
*/
|
||||||
public bool $autoRoute = true;
|
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.
|
* For Defined Routes.
|
||||||
* If TRUE, will enable the use of the 'prioritize' option
|
* If TRUE, will enable the use of the 'prioritize' option
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class Security extends BaseConfig
|
|||||||
*
|
*
|
||||||
* @var string 'cookie' or 'session'
|
* @var string 'cookie' or 'session'
|
||||||
*/
|
*/
|
||||||
public string $csrfProtection = 'cookie';
|
public string $csrfProtection = 'session';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
@@ -71,7 +71,7 @@ class Security extends BaseConfig
|
|||||||
*
|
*
|
||||||
* Regenerate CSRF Token on every submission.
|
* Regenerate CSRF Token on every submission.
|
||||||
*/
|
*/
|
||||||
public bool $regenerate = true;
|
public bool $regenerate = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* --------------------------------------------------------------------------
|
* --------------------------------------------------------------------------
|
||||||
@@ -83,21 +83,4 @@ class Security extends BaseConfig
|
|||||||
* @see https://codeigniter4.github.io/userguide/libraries/security.html#redirection-on-failure
|
* @see https://codeigniter4.github.io/userguide/libraries/security.html#redirection-on-failure
|
||||||
*/
|
*/
|
||||||
public bool $redirect = (ENVIRONMENT === 'production');
|
public bool $redirect = (ENVIRONMENT === 'production');
|
||||||
|
|
||||||
/**
|
|
||||||
* --------------------------------------------------------------------------
|
|
||||||
* CSRF SameSite
|
|
||||||
* --------------------------------------------------------------------------
|
|
||||||
*
|
|
||||||
* Setting for CSRF SameSite cookie token.
|
|
||||||
*
|
|
||||||
* Allowed values are: None - Lax - Strict - ''.
|
|
||||||
*
|
|
||||||
* Defaults to `Lax` as recommended in this link:
|
|
||||||
*
|
|
||||||
* @see https://portswigger.net/web-security/csrf/samesite-cookies
|
|
||||||
*
|
|
||||||
* @deprecated `Config\Cookie` $samesite property is used.
|
|
||||||
*/
|
|
||||||
public string $samesite = 'Lax';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,13 @@
|
|||||||
|
|
||||||
namespace Config;
|
namespace Config;
|
||||||
|
|
||||||
use CodeIgniter\Config\BaseService;
|
use App\Libraries\MY_Language;
|
||||||
use CodeIgniter\HTTP\IncomingRequest;
|
use Locale;
|
||||||
use Config\Services as AppServices;
|
|
||||||
use HTMLPurifier;
|
use HTMLPurifier;
|
||||||
use HTMLPurifier_Config;
|
use HTMLPurifier_Config;
|
||||||
|
use CodeIgniter\Config\BaseService;
|
||||||
|
use Config\Services as AppServices;
|
||||||
|
use CodeIgniter\HTTP\IncomingRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Services Configuration file.
|
* Services Configuration file.
|
||||||
@@ -34,44 +36,44 @@ class Services extends BaseService
|
|||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Responsible for loading the language string translations.
|
* Responsible for loading the language string translations.
|
||||||
*
|
*
|
||||||
* @return MY_Language
|
* @param string|null $locale
|
||||||
*/
|
* @param bool $getShared
|
||||||
public static function language(?string $locale = null, bool $getShared = true)
|
* @return MY_Language
|
||||||
{
|
*/
|
||||||
if ($getShared) {
|
public static function language(?string $locale = null, bool $getShared = true): MY_Language
|
||||||
return static::getSharedInstance('language', $locale)->setLocale($locale);
|
{
|
||||||
}
|
if ($getShared) {
|
||||||
|
return static::getSharedInstance('language', $locale)->setLocale($locale);
|
||||||
|
}
|
||||||
|
|
||||||
if (AppServices::get('request') instanceof IncomingRequest) {
|
if (AppServices::get('request') instanceof IncomingRequest) {
|
||||||
$requestLocale = AppServices::get('request')->getLocale();
|
$requestLocale = AppServices::get('request')->getLocale();
|
||||||
} else {
|
} else {
|
||||||
$requestLocale = Locale::getDefault();
|
$requestLocale = Locale::getDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use '?:' for empty string check
|
// Use '?:' for empty string check
|
||||||
$locale = $locale ?: $requestLocale;
|
$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)
|
if ($getShared) {
|
||||||
{
|
return static::getSharedInstance('htmlPurifier');
|
||||||
return static::getSharedInstance('htmlPurifier');
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!isset(static::$htmlPurifier))
|
if (!isset(static::$htmlPurifier)) {
|
||||||
{
|
$config = HTMLPurifier_Config::createDefault();
|
||||||
$config = HTMLPurifier_Config::createDefault();
|
static::$htmlPurifier = new HTMLPurifier($config);
|
||||||
static::$htmlPurifier = new HTMLPurifier($config);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return static::$htmlPurifier;
|
return static::$htmlPurifier;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace Config;
|
|||||||
use CodeIgniter\Config\BaseConfig;
|
use CodeIgniter\Config\BaseConfig;
|
||||||
use CodeIgniter\Session\Handlers\BaseHandler;
|
use CodeIgniter\Session\Handlers\BaseHandler;
|
||||||
use CodeIgniter\Session\Handlers\DatabaseHandler;
|
use CodeIgniter\Session\Handlers\DatabaseHandler;
|
||||||
|
use CodeIgniter\Session\Handlers\FileHandler;
|
||||||
|
|
||||||
class Session extends BaseConfig
|
class Session extends BaseConfig
|
||||||
{
|
{
|
||||||
@@ -124,4 +125,27 @@ class Session extends BaseConfig
|
|||||||
* seconds.
|
* seconds.
|
||||||
*/
|
*/
|
||||||
public int $lockMaxRetries = 300;
|
public int $lockMaxRetries = 300;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
if ($this->driver === DatabaseHandler::class) {
|
||||||
|
try {
|
||||||
|
$db = Database::connect();
|
||||||
|
|
||||||
|
if (!$db->tableExists($this->savePath)) {
|
||||||
|
$this->driver = FileHandler::class;
|
||||||
|
$this->savePath = WRITEPATH . 'session';
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// Database not available yet (e.g. fresh install before migrations).
|
||||||
|
// Fall back to file-based sessions so the login/migration page
|
||||||
|
// can still be served. Catches mysqli_sql_exception which is
|
||||||
|
// not a subclass of DatabaseException but is a RuntimeException.
|
||||||
|
$this->driver = FileHandler::class;
|
||||||
|
$this->savePath = WRITEPATH . 'session';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,4 +119,29 @@ class Toolbar extends BaseConfig
|
|||||||
public array $watchedExtensions = [
|
public array $watchedExtensions = [
|
||||||
'php', 'css', 'js', 'html', 'svg', 'json', 'env',
|
'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
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -230,9 +230,13 @@ class UserAgents extends BaseConfig
|
|||||||
*/
|
*/
|
||||||
public array $robots = [
|
public array $robots = [
|
||||||
'googlebot' => 'Googlebot',
|
'googlebot' => 'Googlebot',
|
||||||
|
'google-pagerenderer' => 'Google Page Renderer',
|
||||||
|
'google-read-aloud' => 'Google Read Aloud',
|
||||||
|
'google-safety' => 'Google Safety Bot',
|
||||||
'msnbot' => 'MSNBot',
|
'msnbot' => 'MSNBot',
|
||||||
'baiduspider' => 'Baiduspider',
|
'baiduspider' => 'Baiduspider',
|
||||||
'bingbot' => 'Bing',
|
'bingbot' => 'Bing',
|
||||||
|
'bingpreview' => 'BingPreview',
|
||||||
'slurp' => 'Inktomi Slurp',
|
'slurp' => 'Inktomi Slurp',
|
||||||
'yahoo' => 'Yahoo',
|
'yahoo' => 'Yahoo',
|
||||||
'ask jeeves' => 'Ask Jeeves',
|
'ask jeeves' => 'Ask Jeeves',
|
||||||
@@ -248,5 +252,11 @@ class UserAgents extends BaseConfig
|
|||||||
'ia_archiver' => 'Alexa Crawler',
|
'ia_archiver' => 'Alexa Crawler',
|
||||||
'MJ12bot' => 'Majestic-12',
|
'MJ12bot' => 'Majestic-12',
|
||||||
'Uptimebot' => 'Uptimebot',
|
'Uptimebot' => 'Uptimebot',
|
||||||
|
'duckduckbot' => 'DuckDuckBot',
|
||||||
|
'sogou' => 'Sogou Spider',
|
||||||
|
'exabot' => 'Exabot',
|
||||||
|
'bot' => 'Generic Bot',
|
||||||
|
'crawler' => 'Generic Crawler',
|
||||||
|
'spider' => 'Generic Spider',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
namespace Config;
|
namespace Config;
|
||||||
|
|
||||||
use App\Config\Validation\OSPOSRules;
|
|
||||||
use CodeIgniter\Config\BaseConfig;
|
use CodeIgniter\Config\BaseConfig;
|
||||||
use CodeIgniter\Validation\StrictRules\CreditCardRules;
|
use CodeIgniter\Validation\StrictRules\CreditCardRules;
|
||||||
use CodeIgniter\Validation\StrictRules\FileRules;
|
use CodeIgniter\Validation\StrictRules\FileRules;
|
||||||
use CodeIgniter\Validation\StrictRules\FormatRules;
|
use CodeIgniter\Validation\StrictRules\FormatRules;
|
||||||
use CodeIgniter\Validation\StrictRules\Rules;
|
use CodeIgniter\Validation\StrictRules\Rules;
|
||||||
|
use App\Config\Validation\OSPOSRules;
|
||||||
|
|
||||||
class Validation extends BaseConfig
|
class Validation extends BaseConfig
|
||||||
{
|
{
|
||||||
@@ -26,8 +26,8 @@ class Validation extends BaseConfig
|
|||||||
FormatRules::class,
|
FormatRules::class,
|
||||||
FileRules::class,
|
FileRules::class,
|
||||||
CreditCardRules::class,
|
CreditCardRules::class,
|
||||||
OSPOSRules::class
|
OSPOSRules::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies the views that are used to display the
|
* Specifies the views that are used to display the
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Config\Validation;
|
namespace App\Config\Validation;
|
||||||
|
|
||||||
use App\Models\Employee;
|
use App\Models\Employee;
|
||||||
@@ -12,150 +13,141 @@ use Config\Services;
|
|||||||
*/
|
*/
|
||||||
class OSPOSRules
|
class OSPOSRules
|
||||||
{
|
{
|
||||||
private IncomingRequest $request;
|
private IncomingRequest $request;
|
||||||
private array $config;
|
private array $config;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the username and password sent to the login view. User is logged in on successful validation.
|
* Validates the username and password sent to the login view. User is logged in on successful validation.
|
||||||
*
|
*
|
||||||
* @param string $username Username to check against.
|
* @param string $username Username to check against.
|
||||||
* @param string $fields Comma separated string of the fields for validation.
|
* @param string $fields Comma separated string of the fields for validation.
|
||||||
* @param array $data Data sent to the view.
|
* @param array $data Data sent to the view.
|
||||||
* @param string|null $error The error sent back to the validation handler on failure.
|
* @param string|null $error The error sent back to the validation handler on failure.
|
||||||
* @return bool True if validation passes or false if there are errors.
|
* @return bool True if validation passes or false if there are errors.
|
||||||
* @noinspection PhpUnused
|
* @noinspection PhpUnused
|
||||||
*/
|
*/
|
||||||
public function login_check(string $username, string $fields , array $data, ?string &$error = null): bool
|
public function login_check(string $username, string $fields, array $data, ?string &$error = null): bool
|
||||||
{
|
{
|
||||||
$employee = model(Employee::class);
|
$employee = model(Employee::class);
|
||||||
$this->request = Services::request();
|
$this->request = Services::request();
|
||||||
$this->config = config(OSPOS::class)->settings;
|
$this->config = config(OSPOS::class)->settings;
|
||||||
|
|
||||||
//Installation Check
|
// Installation Check
|
||||||
if(!$this->installation_check())
|
if (!$this->installation_check()) {
|
||||||
{
|
$error = lang('Login.invalid_installation');
|
||||||
$error = lang('Login.invalid_installation');
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$password = $data['password'];
|
$password = $data['password'];
|
||||||
if(!$employee->login($username, $password))
|
if (!$employee->login($username, $password)) {
|
||||||
{
|
$error = lang('Login.invalid_username_and_password');
|
||||||
$error = lang('Login.invalid_username_and_password');
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$gcaptcha_enabled = array_key_exists('gcaptcha_enable', $this->config) && $this->config['gcaptcha_enable'];
|
$gcaptcha_enabled = array_key_exists('gcaptcha_enable', $this->config) && $this->config['gcaptcha_enable'];
|
||||||
if($gcaptcha_enabled)
|
if ($gcaptcha_enabled) {
|
||||||
{
|
$g_recaptcha_response = $this->request->getPost('g-recaptcha-response');
|
||||||
$g_recaptcha_response = $this->request->getPost('g-recaptcha-response');
|
|
||||||
|
|
||||||
if(!$this->gcaptcha_check($g_recaptcha_response))
|
if (!$this->gcaptcha_check($g_recaptcha_response)) {
|
||||||
{
|
$error = lang('Login.invalid_gcaptcha');
|
||||||
$error = lang('Login.invalid_gcaptcha');
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks to see if GCaptcha verification was successful.
|
* Checks to see if GCaptcha verification was successful.
|
||||||
*
|
*
|
||||||
* @param $response
|
* @param $response
|
||||||
* @return bool true on successful GCaptcha verification or false if GCaptcha failed.
|
* @return bool true on successful GCaptcha verification or false if GCaptcha failed.
|
||||||
*/
|
*/
|
||||||
private function gcaptcha_check($response): bool
|
private function gcaptcha_check($response): bool
|
||||||
{
|
{
|
||||||
if(!empty($response))
|
if (!empty($response)) {
|
||||||
{
|
$check = [
|
||||||
$check = [
|
'secret' => $this->config['gcaptcha_secret_key'],
|
||||||
'secret' => $this->config['gcaptcha_secret_key'],
|
'response' => $response,
|
||||||
'response' => $response,
|
'remoteip' => $this->request->getIPAddress()
|
||||||
'remoteip' => $this->request->getIPAddress()
|
];
|
||||||
];
|
|
||||||
|
|
||||||
$ch = curl_init();
|
$ch = curl_init();
|
||||||
|
|
||||||
curl_setopt($ch, CURLOPT_URL, "https://www.google.com/recaptcha/api/siteverify");
|
curl_setopt($ch, CURLOPT_URL, "https://www.google.com/recaptcha/api/siteverify");
|
||||||
curl_setopt($ch, CURLOPT_POST, true);
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($check));
|
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($check));
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
|
||||||
$result = curl_exec($ch);
|
$result = curl_exec($ch);
|
||||||
|
|
||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
|
|
||||||
$status = json_decode($result, true);
|
$status = json_decode($result, true);
|
||||||
|
|
||||||
if(!empty($status['success']))
|
if (!empty($status['success'])) {
|
||||||
{
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks to make sure dependency PHP extensions are installed
|
* Checks to make sure dependency PHP extensions are installed
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
private function installation_check(): bool
|
private function installation_check(): bool
|
||||||
{
|
{
|
||||||
$installed_extensions = implode(', ', get_loaded_extensions());
|
$installed_extensions = implode(', ', get_loaded_extensions());
|
||||||
$required_extensions = ['bcmath', 'intl', 'gd', 'openssl', 'mbstring', 'curl'];
|
$required_extensions = ['bcmath', 'intl', 'gd', 'openssl', 'mbstring', 'curl', 'xml', 'json'];
|
||||||
$pattern = '/';
|
$pattern = '/';
|
||||||
|
|
||||||
foreach($required_extensions as $extension)
|
foreach ($required_extensions as $extension) {
|
||||||
{
|
$pattern .= '(?=.*\b' . preg_quote($extension, '/') . '\b)';
|
||||||
$pattern .= '(?=.*\b' . preg_quote($extension, '/') . '\b)';
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$pattern .= '/i';
|
$pattern .= '/i';
|
||||||
$is_installed = preg_match($pattern, $installed_extensions);
|
$is_installed = preg_match($pattern, $installed_extensions);
|
||||||
|
|
||||||
if(!$is_installed)
|
if (!$is_installed) {
|
||||||
{
|
log_message('error', '[ERROR] Check your php.ini.');
|
||||||
log_message('error', '[ERROR] Check your php.ini.');
|
log_message('error', "PHP installed extensions: $installed_extensions");
|
||||||
log_message('error',"PHP installed extensions: $installed_extensions");
|
log_message('error', 'PHP required extensions: ' . implode(', ', $required_extensions));
|
||||||
log_message('error','PHP required extensions: ' . implode(', ', $required_extensions));
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return $is_installed;
|
return $is_installed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the candidate as a decimal number. Takes the locale into account. Used in validation rule calls.
|
* Validates the candidate as a decimal number. Takes the locale into account. Used in validation rule calls.
|
||||||
*
|
*
|
||||||
* @param string $candidate
|
* @param string $candidate
|
||||||
* @param string|null $error
|
* @param string|null $error
|
||||||
* @return bool
|
* @return bool
|
||||||
* @noinspection PhpUnused
|
* @noinspection PhpUnused
|
||||||
*/
|
*/
|
||||||
public function decimal_locale(string $candidate, ?string &$error = null): bool
|
public function decimal_locale(string $candidate, ?string &$error = null): bool
|
||||||
{
|
{
|
||||||
$validation = Services::validation();
|
return parse_decimals($candidate) !== false;
|
||||||
|
}
|
||||||
|
|
||||||
$validation->setRules([
|
/**
|
||||||
'candidate' => 'decimal'
|
* Validates that a locale-aware decimal value is non-negative (>= 0).
|
||||||
]);
|
*
|
||||||
|
* @param string $candidate
|
||||||
|
* @param string|null $error
|
||||||
|
* @return bool
|
||||||
|
* @noinspection PhpUnused
|
||||||
|
*/
|
||||||
|
public function nonNegativeDecimal(string $candidate, ?string &$error = null): bool
|
||||||
|
{
|
||||||
|
$value = parse_decimals($candidate);
|
||||||
|
|
||||||
$data = [
|
return $value !== false && $value >= 0;
|
||||||
'candidate' => $candidate
|
}
|
||||||
];
|
|
||||||
|
|
||||||
if (!$validation->run($data))
|
|
||||||
{
|
|
||||||
$error = $validation->getErrors();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,4 +59,21 @@ class View extends BaseView
|
|||||||
* @var list<class-string<ViewDecoratorInterface>>
|
* @var list<class-string<ViewDecoratorInterface>>
|
||||||
*/
|
*/
|
||||||
public array $decorators = [];
|
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
62
app/Config/WorkerMode.php
Normal 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;
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Controllers;
|
namespace App\Controllers;
|
||||||
|
|
||||||
use App\Models\Attribute;
|
use App\Models\Attribute;
|
||||||
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
use Config\Services;
|
use Config\Services;
|
||||||
|
|
||||||
require_once('Secure_Controller.php');
|
require_once('Secure_Controller.php');
|
||||||
@@ -12,251 +13,260 @@ require_once('Secure_Controller.php');
|
|||||||
**/
|
**/
|
||||||
class Attributes extends Secure_Controller
|
class Attributes extends Secure_Controller
|
||||||
{
|
{
|
||||||
private Attribute $attribute;
|
private Attribute $attribute;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct('attributes');
|
parent::__construct('attributes');
|
||||||
|
|
||||||
$this->attribute = model(Attribute::class);
|
$this->attribute = model(Attribute::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets and sends the main view for Attributes to the browser.
|
* Gets and sends the main view for Attributes to the browser.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return string
|
||||||
**/
|
**/
|
||||||
public function getIndex(): void
|
public function getIndex(): string
|
||||||
{
|
{
|
||||||
$data['table_headers'] = get_attribute_definition_manage_table_headers();
|
$data['table_headers'] = get_attribute_definition_manage_table_headers();
|
||||||
|
|
||||||
echo view('attributes/manage', $data);
|
return view('attributes/manage', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns attribute table data rows. This will be called with AJAX.
|
* Returns attribute table data rows. This will be called with AJAX.
|
||||||
*/
|
*/
|
||||||
public function getSearch(): void
|
public function getSearch(): ResponseInterface
|
||||||
{
|
{
|
||||||
$search = $this->request->getGet('search');
|
$search = $this->request->getGet('search');
|
||||||
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$sort = $this->sanitizeSortColumn(ATTRIBUTE_DEFINITION_HEADERS, $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'definition_id');
|
$sort = $this->sanitizeSortColumn(attribute_definition_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'definition_id');
|
||||||
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||||
|
|
||||||
$attributes = $this->attribute->search($search, $limit, $offset, $sort, $order);
|
$attributes = $this->attribute->search($search, $limit, $offset, $sort, $order);
|
||||||
$total_rows = $this->attribute->get_found_rows($search);
|
$total_rows = $this->attribute->get_found_rows($search);
|
||||||
|
|
||||||
$data_rows = [];
|
$data_rows = [];
|
||||||
foreach($attributes->getResult() as $attribute_row)
|
foreach ($attributes->getResult() as $attribute_row) {
|
||||||
{
|
$attribute_row->definition_flags = $this->get_attributes($attribute_row->definition_flags);
|
||||||
$attribute_row->definition_flags = $this->get_attributes($attribute_row->definition_flags);
|
$data_rows[] = get_attribute_definition_data_row($attribute_row);
|
||||||
$data_rows[] = get_attribute_definition_data_row($attribute_row);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
|
return $this->response->setJSON(['total' => $total_rows, 'rows' => $data_rows]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AJAX called function which saves the attribute value sent via POST by using the model save function.
|
* AJAX called function which saves the attribute value sent via POST by using the model save function.
|
||||||
* @return void
|
* @return ResponseInterface
|
||||||
* @noinspection PhpUnused
|
* @noinspection PhpUnused
|
||||||
*/
|
*/
|
||||||
public function postSaveAttributeValue(): void
|
public function postSaveAttributeValue(): ResponseInterface
|
||||||
{
|
{
|
||||||
$success = $this->attribute->saveAttributeValue(
|
$success = $this->attribute->saveAttributeValue(
|
||||||
html_entity_decode($this->request->getPost('attribute_value')),
|
html_entity_decode($this->request->getPost('attribute_value')),
|
||||||
$this->request->getPost('definition_id', FILTER_SANITIZE_NUMBER_INT),
|
$this->request->getPost('definition_id', FILTER_SANITIZE_NUMBER_INT),
|
||||||
$this->request->getPost('item_id', FILTER_SANITIZE_NUMBER_INT),
|
$this->request->getPost('item_id', FILTER_SANITIZE_NUMBER_INT) ?? false,
|
||||||
$this->request->getPost('attribute_id', FILTER_SANITIZE_NUMBER_INT)
|
$this->request->getPost('attribute_id', FILTER_SANITIZE_NUMBER_INT) ?? false
|
||||||
);
|
);
|
||||||
|
|
||||||
echo json_encode(['success' => $success != 0]);
|
return $this->response->setJSON(['success' => $success != 0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AJAX called function deleting an attribute value using the model delete function.
|
* AJAX called function deleting an attribute value using the model delete function.
|
||||||
* @return void
|
* @return ResponseInterface
|
||||||
* @noinspection PhpUnused
|
* @noinspection PhpUnused
|
||||||
*/
|
*/
|
||||||
public function postDelete_attribute_value(): void
|
public function postDeleteDropdownAttributeValue(): ResponseInterface
|
||||||
{
|
{
|
||||||
$success = $this->attribute->delete_value(
|
$success = $this->attribute->deleteDropdownAttributeValue(
|
||||||
html_entity_decode($this->request->getPost('attribute_value')),
|
html_entity_decode($this->request->getPost('attribute_value')),
|
||||||
$this->request->getPost('definition_id', FILTER_SANITIZE_NUMBER_INT)
|
$this->request->getPost('definition_id', FILTER_SANITIZE_NUMBER_INT)
|
||||||
);
|
);
|
||||||
|
|
||||||
echo json_encode(['success' => $success]);
|
return $this->response->setJSON(['success' => $success]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AJAX called function which saves the attribute definition.
|
* AJAX called function which saves the attribute definition.
|
||||||
*
|
*
|
||||||
* @param int $definition_id
|
* @param int $definition_id
|
||||||
* @return void
|
* @return ResponseInterface
|
||||||
* @noinspection PhpUnused
|
* @noinspection PhpUnused
|
||||||
*/
|
*/
|
||||||
public function postSaveDefinition(int $definition_id = NO_DEFINITION_ID): void
|
public function postSaveDefinition(int $definition_id = NO_DEFINITION_ID): ResponseInterface
|
||||||
{
|
{
|
||||||
$definition_flags = 0;
|
$definition_flags = 0;
|
||||||
|
|
||||||
$flags = (empty($this->request->getPost('definition_flags'))) ? [] : $this->request->getPost('definition_flags', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
$flags = (empty($this->request->getPost('definition_flags'))) ? [] : $this->request->getPost('definition_flags', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||||
|
|
||||||
foreach($flags as $flag)
|
foreach ($flags as $flag) {
|
||||||
{
|
$definition_flags |= $flag;
|
||||||
$definition_flags |= $flag;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
//Save definition data
|
// Validate definition_group (definition_fk) foreign key
|
||||||
$definition_data = [
|
$definition_group_input = $this->request->getPost('definition_group');
|
||||||
'definition_name' => $this->request->getPost('definition_name'),
|
$definition_fk = $this->validateDefinitionGroup($definition_group_input);
|
||||||
'definition_unit' => $this->request->getPost('definition_unit') != '' ? $this->request->getPost('definition_unit') : null,
|
|
||||||
'definition_flags' => $definition_flags,
|
|
||||||
'definition_fk' => $this->request->getPost('definition_group') != '' ? $this->request->getPost('definition_group') : null
|
|
||||||
];
|
|
||||||
|
|
||||||
if ($this->request->getPost('definition_type') != null)
|
if ($definition_fk === false) {
|
||||||
{
|
return $this->response->setJSON([
|
||||||
$definition_data['definition_type'] = DEFINITION_TYPES[$this->request->getPost('definition_type')];
|
'success' => false,
|
||||||
}
|
'message' => lang('Attributes.definition_invalid_group'),
|
||||||
|
'id' => NEW_ENTRY
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$definition_name = $definition_data['definition_name'];
|
// Save definition data
|
||||||
|
$definition_data = [
|
||||||
|
'definition_name' => $this->request->getPost('definition_name'),
|
||||||
|
'definition_unit' => $this->request->getPost('definition_unit') != '' ? $this->request->getPost('definition_unit') : null,
|
||||||
|
'definition_flags' => $definition_flags,
|
||||||
|
'definition_fk' => $definition_fk
|
||||||
|
];
|
||||||
|
|
||||||
if($this->attribute->save_definition($definition_data, $definition_id))
|
if ($this->request->getPost('definition_type') != null) {
|
||||||
{
|
$definition_data['definition_type'] = DEFINITION_TYPES[$this->request->getPost('definition_type')];
|
||||||
//New definition
|
}
|
||||||
if($definition_id == NO_DEFINITION_ID)
|
|
||||||
{
|
|
||||||
$definition_values = json_decode(html_entity_decode($this->request->getPost('definition_values')));
|
|
||||||
|
|
||||||
foreach($definition_values as $definition_value)
|
$definition_name = $definition_data['definition_name'];
|
||||||
{
|
|
||||||
$this->attribute->saveAttributeValue($definition_value, $definition_data['definition_id']);
|
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode([
|
if ($this->attribute->saveDefinition($definition_data, $definition_id)) {
|
||||||
'success' => true,
|
// New definition
|
||||||
'message' => lang('Attributes.definition_successful_adding') . ' ' . $definition_name,
|
if ($definition_id == NO_DEFINITION_ID) {
|
||||||
'id' => $definition_data['definition_id']
|
$definition_values = json_decode(html_entity_decode($this->request->getPost('definition_values')));
|
||||||
]);
|
|
||||||
}
|
|
||||||
//Existing definition
|
|
||||||
else
|
|
||||||
{
|
|
||||||
echo json_encode([
|
|
||||||
'success' => true,
|
|
||||||
'message' => lang('Attributes.definition_successful_updating') . ' ' . $definition_name,
|
|
||||||
'id' => $definition_id
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Failure
|
|
||||||
else
|
|
||||||
{
|
|
||||||
echo json_encode([
|
|
||||||
'success' => false,
|
|
||||||
'message' => lang('Attributes.definition_error_adding_updating', [$definition_name]),
|
|
||||||
'id' => NEW_ENTRY
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
foreach ($definition_values as $definition_value) {
|
||||||
*
|
$this->attribute->saveAttributeValue($definition_value, $definition_data['definition_id']);
|
||||||
* @param int $definition_id
|
}
|
||||||
* @return void
|
|
||||||
* @noinspection PhpUnused
|
|
||||||
*/
|
|
||||||
public function getSuggestAttribute(int $definition_id): void
|
|
||||||
{
|
|
||||||
$suggestions = $this->attribute->get_suggestions($definition_id, html_entity_decode($this->request->getGet('term')));
|
|
||||||
|
|
||||||
echo json_encode($suggestions);
|
return $this->response->setJSON([
|
||||||
}
|
'success' => true,
|
||||||
|
'message' => lang('Attributes.definition_successful_adding') . ' ' . $definition_name,
|
||||||
|
'id' => $definition_data['definition_id']
|
||||||
|
]);
|
||||||
|
} else { // Existing definition
|
||||||
|
return $this->response->setJSON([
|
||||||
|
'success' => true,
|
||||||
|
'message' => lang('Attributes.definition_successful_updating') . ' ' . $definition_name,
|
||||||
|
'id' => $definition_id
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else { // Failure
|
||||||
|
return $this->response->setJSON([
|
||||||
|
'success' => false,
|
||||||
|
'message' => lang('Attributes.definition_error_adding_updating', [$definition_name]),
|
||||||
|
'id' => NEW_ENTRY
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $row_id
|
* Validates a definition_group foreign key.
|
||||||
* @return void
|
* Returns the validated integer ID, null if empty, or false if invalid.
|
||||||
*/
|
*
|
||||||
public function getRow(int $row_id): void
|
* @param mixed $definition_group_input
|
||||||
{
|
* @return int|null|false
|
||||||
$attribute_definition_info = $this->attribute->getAttributeInfo($row_id);
|
*/
|
||||||
$attribute_definition_info->definition_flags = $this->get_attributes($attribute_definition_info->definition_flags);
|
private function validateDefinitionGroup(mixed $definition_group_input): int|null|false
|
||||||
$data_row = get_attribute_definition_data_row($attribute_definition_info);
|
{
|
||||||
|
if ($definition_group_input === '' || $definition_group_input === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
echo json_encode($data_row);
|
$definition_group_id = (int) $definition_group_input;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// Must be a positive integer, exist in attribute_definitions, and be of type GROUP
|
||||||
* @param int $definition_flags
|
if ($definition_group_id <= 0
|
||||||
* @return array
|
|| !$this->attribute->exists($definition_group_id)
|
||||||
*/
|
|| $this->attribute->getAttributeInfo($definition_group_id)->definition_type !== GROUP
|
||||||
private function get_attributes(int $definition_flags = 0): array
|
) {
|
||||||
{
|
return false;
|
||||||
$definition_flag_names = [];
|
}
|
||||||
foreach (Attribute::get_definition_flags() as $id => $term)
|
|
||||||
{
|
|
||||||
if ($id & $definition_flags)
|
|
||||||
{
|
|
||||||
$definition_flag_names[$id] = lang('Attributes.' . strtolower($term) . '_visibility');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $definition_flag_names;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
return $definition_group_id;
|
||||||
* @param int $definition_id
|
}
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function getView(int $definition_id = NO_DEFINITION_ID): void
|
|
||||||
{
|
|
||||||
$info = $this->attribute->getAttributeInfo($definition_id);
|
|
||||||
foreach(get_object_vars($info) as $property => $value)
|
|
||||||
{
|
|
||||||
$info->$property = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data['definition_id'] = $definition_id;
|
/**
|
||||||
$data['definition_values'] = $this->attribute->get_definition_values($definition_id);
|
*
|
||||||
$data['definition_group'] = $this->attribute->get_definitions_by_type(GROUP, $definition_id);
|
* @param int $definition_id
|
||||||
$data['definition_group'][''] = lang('Common.none_selected_text');
|
* @return ResponseInterface
|
||||||
$data['definition_info'] = $info;
|
* @noinspection PhpUnused
|
||||||
|
*/
|
||||||
|
public function getSuggestAttribute(int $definition_id): ResponseInterface
|
||||||
|
{
|
||||||
|
$suggestions = $this->attribute->get_suggestions($definition_id, html_entity_decode($this->request->getGet('term')));
|
||||||
|
|
||||||
$show_all = Attribute::SHOW_IN_ITEMS | Attribute::SHOW_IN_RECEIVINGS | Attribute::SHOW_IN_SALES;
|
return $this->response->setJSON($suggestions);
|
||||||
$data['definition_flags'] = $this->get_attributes($show_all);
|
}
|
||||||
$selected_flags = $info->definition_flags === '' ? $show_all : $info->definition_flags;
|
|
||||||
$data['selected_definition_flags'] = $this->get_attributes($selected_flags);
|
|
||||||
|
|
||||||
echo view('attributes/form', $data);
|
/**
|
||||||
}
|
* @param int $row_id
|
||||||
|
* @return ResponseInterface
|
||||||
|
*/
|
||||||
|
public function getRow(int $row_id): ResponseInterface
|
||||||
|
{
|
||||||
|
$attribute_definition_info = $this->attribute->getAttributeInfo($row_id);
|
||||||
|
$attribute_definition_info->definition_flags = $this->get_attributes($attribute_definition_info->definition_flags);
|
||||||
|
$data_row = get_attribute_definition_data_row($attribute_definition_info);
|
||||||
|
|
||||||
/**
|
return $this->response->setJSON($data_row);
|
||||||
* AJAX called function to delete an attribute value. This is never called in the code. Perhaps it was boiler plate code that just isn't needed?
|
}
|
||||||
* @param int $attribute_id
|
|
||||||
* @return bool
|
|
||||||
* @noinspection PhpUnused
|
|
||||||
*/
|
|
||||||
public function delete_value(int $attribute_id): bool //TODO: This function appears to never be used in the codebase. Is it needed?
|
|
||||||
{
|
|
||||||
return $this->attribute->delete_value($attribute_id, NO_DEFINITION_ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes an attribute definition
|
* @param int $definition_flags
|
||||||
* @return void
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function postDelete(): void
|
private function get_attributes(int $definition_flags = 0): array
|
||||||
{
|
{
|
||||||
$attributes_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
$definition_flag_names = [];
|
||||||
|
foreach (Attribute::get_definition_flags() as $id => $term) {
|
||||||
|
if ($id & $definition_flags) {
|
||||||
|
$definition_flag_names[$id] = lang('Attributes.' . strtolower($term) . '_visibility');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $definition_flag_names;
|
||||||
|
}
|
||||||
|
|
||||||
if($this->attribute->delete_definition_list($attributes_to_delete))
|
/**
|
||||||
{
|
* @param int $definition_id
|
||||||
$message = lang('Attributes.definition_successful_deleted') . ' ' . count($attributes_to_delete) . ' ' . lang('Attributes.definition_one_or_multiple');
|
* @return string
|
||||||
echo json_encode(['success' => true, 'message' => $message]);
|
*/
|
||||||
}
|
public function getView(int $definition_id = NO_DEFINITION_ID): string
|
||||||
else
|
{
|
||||||
{
|
$info = $this->attribute->getAttributeInfo($definition_id);
|
||||||
echo json_encode(['success' => false, 'message' => lang('Attributes.definition_cannot_be_deleted')]);
|
foreach (get_object_vars($info) as $property => $value) {
|
||||||
}
|
$info->$property = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$data['definition_id'] = $definition_id;
|
||||||
|
$data['definition_values'] = $this->attribute->get_definition_values($definition_id);
|
||||||
|
$data['definition_group'] = $this->attribute->get_definitions_by_type(GROUP, $definition_id);
|
||||||
|
$data['definition_group'][''] = lang('Common.none_selected_text');
|
||||||
|
$data['definition_info'] = $info;
|
||||||
|
|
||||||
|
$show_all = Attribute::SHOW_IN_ITEMS | Attribute::SHOW_IN_RECEIVINGS | Attribute::SHOW_IN_SALES;
|
||||||
|
$data['definition_flags'] = $this->get_attributes($show_all);
|
||||||
|
$selected_flags = $info->definition_flags === '' ? $show_all : $info->definition_flags;
|
||||||
|
$data['selected_definition_flags'] = $this->get_attributes($selected_flags);
|
||||||
|
|
||||||
|
return view('attributes/form', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes an attribute definition
|
||||||
|
* @return ResponseInterface
|
||||||
|
*/
|
||||||
|
public function postDelete(): ResponseInterface
|
||||||
|
{
|
||||||
|
$attributes_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||||
|
|
||||||
|
if($this->attribute->deleteDefinitionList($attributes_to_delete)) {
|
||||||
|
$message = lang('Attributes.definition_successful_deleted') . ' ' . count($attributes_to_delete) . ' ' . lang('Attributes.definition_one_or_multiple');
|
||||||
|
return $this->response->setJSON(['success' => true, 'message' => $message]);
|
||||||
|
} else {
|
||||||
|
return $this->response->setJSON(['success' => false, 'message' => lang('Attributes.definition_cannot_be_deleted')]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,44 +3,28 @@
|
|||||||
namespace App\Controllers;
|
namespace App\Controllers;
|
||||||
|
|
||||||
use CodeIgniter\Controller;
|
use CodeIgniter\Controller;
|
||||||
use CodeIgniter\HTTP\CLIRequest;
|
|
||||||
use CodeIgniter\HTTP\IncomingRequest;
|
|
||||||
use CodeIgniter\HTTP\RequestInterface;
|
use CodeIgniter\HTTP\RequestInterface;
|
||||||
use CodeIgniter\HTTP\ResponseInterface;
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class BaseController
|
|
||||||
*
|
|
||||||
* BaseController provides a convenient place for loading components
|
* BaseController provides a convenient place for loading components
|
||||||
* and performing functions that are needed by all your controllers.
|
* 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
|
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.
|
* Be sure to declare properties for any property fetch you initialized.
|
||||||
* The creation of dynamic property is deprecated in PHP 8.2.
|
* The creation of dynamic property is deprecated in PHP 8.2.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// protected $session;
|
// protected $session;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,11 +32,14 @@ abstract class BaseController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
|
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
|
||||||
{
|
{
|
||||||
// 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);
|
parent::initController($request, $response, $logger);
|
||||||
|
|
||||||
// Preload any models, libraries, etc, here.
|
// Preload any models, libraries, etc, here.
|
||||||
|
// $this->session = service('session');
|
||||||
// E.g.: $this->session = \Config\Services::session();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,306 +5,280 @@ namespace App\Controllers;
|
|||||||
use App\Models\Cashup;
|
use App\Models\Cashup;
|
||||||
use App\Models\Expense;
|
use App\Models\Expense;
|
||||||
use App\Models\Reports\Summary_payments;
|
use App\Models\Reports\Summary_payments;
|
||||||
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
use Config\OSPOS;
|
use Config\OSPOS;
|
||||||
use Config\Services;
|
use Config\Services;
|
||||||
|
|
||||||
class Cashups extends Secure_Controller
|
class Cashups extends Secure_Controller
|
||||||
{
|
{
|
||||||
private Cashup $cashup;
|
private Cashup $cashup;
|
||||||
private Expense $expense;
|
private Expense $expense;
|
||||||
private Summary_payments $summary_payments;
|
private Summary_payments $summary_payments;
|
||||||
private array $config;
|
private array $config;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct('cashups');
|
parent::__construct('cashups');
|
||||||
|
|
||||||
$this->cashup = model(Cashup::class);
|
$this->cashup = model(Cashup::class);
|
||||||
$this->expense = model(Expense::class);
|
$this->expense = model(Expense::class);
|
||||||
$this->summary_payments = model(Summary_payments::class);
|
$this->summary_payments = model(Summary_payments::class);
|
||||||
$this->config = config(OSPOS::class)->settings;
|
$this->config = config(OSPOS::class)->settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getIndex(): void
|
public function getIndex(): string
|
||||||
{
|
{
|
||||||
$data['table_headers'] = get_cashups_manage_table_headers();
|
$data['table_headers'] = get_cashups_manage_table_headers();
|
||||||
|
|
||||||
// filters that will be loaded in the multiselect dropdown
|
// filters that will be loaded in the multiselect dropdown
|
||||||
$data['filters'] = ['is_deleted' => lang('Cashups.is_deleted')];
|
$data['filters'] = ['is_deleted' => lang('Cashups.is_deleted')];
|
||||||
|
|
||||||
echo view('cashups/manage', $data);
|
// Restore filters from URL
|
||||||
}
|
$data = array_merge($data, restoreTableFilters($this->request));
|
||||||
|
|
||||||
/**
|
return view('cashups/manage', $data);
|
||||||
* @return void
|
}
|
||||||
*/
|
|
||||||
public function getSearch(): void
|
|
||||||
{
|
|
||||||
$search = $this->request->getGet('search');
|
|
||||||
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
|
||||||
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
|
||||||
$sort = $this->sanitizeSortColumn(CASHUPS_HEADERS, $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'cashup_id');
|
|
||||||
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
|
||||||
$filters = [
|
|
||||||
'start_date' => $this->request->getGet('start_date', FILTER_SANITIZE_FULL_SPECIAL_CHARS), //TODO: Is this the best way to filter dates
|
|
||||||
'end_date' => $this->request->getGet('end_date', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
|
||||||
'is_deleted' => false
|
|
||||||
];
|
|
||||||
|
|
||||||
// check if any filter is set in the multiselect dropdown
|
/**
|
||||||
$request_filters = array_fill_keys($this->request->getGet('filters', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? [], true);
|
* @return void
|
||||||
$filters = array_merge($filters, $request_filters);
|
*/
|
||||||
$cash_ups = $this->cashup->search($search, $filters, $limit, $offset, $sort, $order);
|
public function getSearch(): ResponseInterface
|
||||||
$total_rows = $this->cashup->get_found_rows($search, $filters);
|
{
|
||||||
$data_rows = [];
|
$search = $this->request->getGet('search');
|
||||||
foreach($cash_ups->getResult() as $cash_up)
|
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
||||||
{
|
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$data_rows[] = get_cash_up_data_row($cash_up);
|
$sort = $this->sanitizeSortColumn(cashup_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'cashup_id');
|
||||||
}
|
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||||
|
$filters = [
|
||||||
|
'start_date' => $this->request->getGet('start_date', FILTER_SANITIZE_FULL_SPECIAL_CHARS), // TODO: Is this the best way to filter dates
|
||||||
|
'end_date' => $this->request->getGet('end_date', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
|
'is_deleted' => false
|
||||||
|
];
|
||||||
|
|
||||||
echo json_encode(['total' => $total_rows, 'rows' => $data_rows]);
|
// Check if any filter is set in the multiselect dropdown
|
||||||
}
|
$request_filters = array_fill_keys($this->request->getGet('filters', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? [], true);
|
||||||
|
$filters = array_merge($filters, $request_filters);
|
||||||
|
$cash_ups = $this->cashup->search($search, $filters, $limit, $offset, $sort, $order);
|
||||||
|
$total_rows = $this->cashup->get_found_rows($search, $filters);
|
||||||
|
$data_rows = [];
|
||||||
|
foreach ($cash_ups->getResult() as $cash_up) {
|
||||||
|
$data_rows[] = get_cash_up_data_row($cash_up);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
return $this->response->setJSON(['total' => $total_rows, 'rows' => $data_rows]);
|
||||||
* @param int $cashup_id
|
}
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function getView(int $cashup_id = NEW_ENTRY): void
|
|
||||||
{
|
|
||||||
$data = [];
|
|
||||||
|
|
||||||
$data['employees'] = [];
|
/**
|
||||||
foreach($this->employee->get_all()->getResult() as $employee)
|
* @param int $cashup_id
|
||||||
{
|
* @return string
|
||||||
foreach(get_object_vars($employee) as $property => $value)
|
*/
|
||||||
{
|
public function getView(int $cashup_id = NEW_ENTRY): string
|
||||||
$employee->$property = $value;
|
{
|
||||||
}
|
$data = [];
|
||||||
|
|
||||||
$data['employees'][$employee->person_id] = $employee->first_name . ' ' . $employee->last_name;
|
$data['employees'] = [];
|
||||||
}
|
foreach ($this->employee->get_all()->getResult() as $employee) {
|
||||||
|
foreach (get_object_vars($employee) as $property => $value) {
|
||||||
|
$employee->$property = $value;
|
||||||
|
}
|
||||||
|
|
||||||
$cash_ups_info = $this->cashup->get_info($cashup_id);
|
$data['employees'][$employee->person_id] = $employee->first_name . ' ' . $employee->last_name;
|
||||||
|
}
|
||||||
|
|
||||||
foreach(get_object_vars($cash_ups_info) as $property => $value)
|
$cash_ups_info = $this->cashup->get_info($cashup_id);
|
||||||
{
|
|
||||||
$cash_ups_info->$property = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// open cashup
|
foreach (get_object_vars($cash_ups_info) as $property => $value) {
|
||||||
if($cash_ups_info->cashup_id == NEW_ENTRY)
|
$cash_ups_info->$property = $value;
|
||||||
{
|
}
|
||||||
$cash_ups_info->open_date = date('Y-m-d H:i:s');
|
|
||||||
$cash_ups_info->close_date = $cash_ups_info->open_date;
|
|
||||||
$cash_ups_info->open_employee_id = $this->employee->get_logged_in_employee_info()->person_id;
|
|
||||||
$cash_ups_info->close_employee_id = $this->employee->get_logged_in_employee_info()->person_id;
|
|
||||||
}
|
|
||||||
// if all the amounts are null or 0 that means it's a close cashup
|
|
||||||
elseif(floatval($cash_ups_info->closed_amount_cash) == 0
|
|
||||||
&& floatval($cash_ups_info->closed_amount_due) == 0
|
|
||||||
&& floatval($cash_ups_info->closed_amount_card) == 0
|
|
||||||
&& floatval($cash_ups_info->closed_amount_check) == 0)
|
|
||||||
{
|
|
||||||
// set the close date and time to the actual as this is a close session
|
|
||||||
$cash_ups_info->close_date = date('Y-m-d H:i:s');
|
|
||||||
|
|
||||||
// the closed amount starts with the open amount -/+ any trasferred amount
|
// Open cashup
|
||||||
$cash_ups_info->closed_amount_cash = $cash_ups_info->open_amount_cash + $cash_ups_info->transfer_amount_cash;
|
if ($cash_ups_info->cashup_id == NEW_ENTRY) {
|
||||||
|
$cash_ups_info->open_date = date('Y-m-d H:i:s');
|
||||||
|
$cash_ups_info->close_date = $cash_ups_info->open_date;
|
||||||
|
$cash_ups_info->open_employee_id = $this->employee->get_logged_in_employee_info()->person_id;
|
||||||
|
$cash_ups_info->close_employee_id = $this->employee->get_logged_in_employee_info()->person_id;
|
||||||
|
}
|
||||||
|
// If all the amounts are null or 0 that means it's a close cashup
|
||||||
|
elseif (
|
||||||
|
floatval($cash_ups_info->closed_amount_cash) == 0
|
||||||
|
&& floatval($cash_ups_info->closed_amount_due) == 0
|
||||||
|
&& floatval($cash_ups_info->closed_amount_card) == 0
|
||||||
|
&& floatval($cash_ups_info->closed_amount_check) == 0
|
||||||
|
) {
|
||||||
|
// Set the close date and time to the actual as this is a close session
|
||||||
|
$cash_ups_info->close_date = date('Y-m-d H:i:s');
|
||||||
|
|
||||||
// if it's date mode only and not date & time truncate the open and end date to date only
|
// The closed amount starts with the open amount -/+ any trasferred amount
|
||||||
if(empty($this->config['date_or_time_format']))
|
$cash_ups_info->closed_amount_cash = $cash_ups_info->open_amount_cash + $cash_ups_info->transfer_amount_cash;
|
||||||
{
|
|
||||||
if($cash_ups_info->open_date != null)
|
|
||||||
{
|
|
||||||
$start_date = substr($cash_ups_info->open_date, 0, 10);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$start_date = null;
|
|
||||||
}
|
|
||||||
if($cash_ups_info->close_date != null)
|
|
||||||
{
|
|
||||||
$end_date = substr($cash_ups_info->close_date, 0, 10);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$end_date = null;
|
|
||||||
}
|
|
||||||
// search for all the payments given the time range
|
|
||||||
$inputs = [
|
|
||||||
'start_date' => $start_date,
|
|
||||||
'end_date' => $end_date,
|
|
||||||
'sale_type' => 'complete',
|
|
||||||
'location_id' => 'all'
|
|
||||||
];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// search for all the payments given the time range
|
|
||||||
$inputs = [
|
|
||||||
'start_date' => $cash_ups_info->open_date,
|
|
||||||
'end_date' => $cash_ups_info->close_date,
|
|
||||||
'sale_type' => 'complete',
|
|
||||||
'location_id' => 'all'
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// get all the transactions payment summaries
|
// If it's date mode only and not date & time truncate the open and end date to date only
|
||||||
$reports_data = $this->summary_payments->getData($inputs);
|
if (empty($this->config['date_or_time_format'])) {
|
||||||
|
if ($cash_ups_info->open_date != null) {
|
||||||
|
$start_date = substr($cash_ups_info->open_date, 0, 10);
|
||||||
|
} else {
|
||||||
|
$start_date = null;
|
||||||
|
}
|
||||||
|
if ($cash_ups_info->close_date != null) {
|
||||||
|
$end_date = substr($cash_ups_info->close_date, 0, 10);
|
||||||
|
} else {
|
||||||
|
$end_date = null;
|
||||||
|
}
|
||||||
|
// Search for all the payments given the time range
|
||||||
|
$inputs = [
|
||||||
|
'start_date' => $start_date,
|
||||||
|
'end_date' => $end_date,
|
||||||
|
'sale_type' => 'complete',
|
||||||
|
'location_id' => 'all'
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
// Search for all the payments given the time range
|
||||||
|
$inputs = [
|
||||||
|
'start_date' => $cash_ups_info->open_date,
|
||||||
|
'end_date' => $cash_ups_info->close_date,
|
||||||
|
'sale_type' => 'complete',
|
||||||
|
'location_id' => 'all'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
foreach($reports_data as $row)
|
// Get all the transactions payment summaries
|
||||||
{
|
$reports_data = $this->summary_payments->getData($inputs);
|
||||||
if($row['trans_group'] == lang('Reports.trans_payments'))
|
|
||||||
{
|
|
||||||
if($row['trans_type'] == lang('Sales.cash'))
|
|
||||||
{
|
|
||||||
$cash_ups_info->closed_amount_cash += $row['trans_amount'];
|
|
||||||
}
|
|
||||||
elseif($row['trans_type'] == lang('Sales.due'))
|
|
||||||
{
|
|
||||||
$cash_ups_info->closed_amount_due += $row['trans_amount'];
|
|
||||||
}
|
|
||||||
elseif($row['trans_type'] == lang('Sales.debit') ||
|
|
||||||
$row['trans_type'] == lang('Sales.credit'))
|
|
||||||
{
|
|
||||||
$cash_ups_info->closed_amount_card += $row['trans_amount'];
|
|
||||||
}
|
|
||||||
elseif($row['trans_type'] == lang('Sales.check'))
|
|
||||||
{
|
|
||||||
$cash_ups_info->closed_amount_check += $row['trans_amount'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// lookup expenses paid in cash
|
foreach ($reports_data as $row) {
|
||||||
$filters = [
|
if ($row['trans_group'] == lang('Reports.trans_payments')) {
|
||||||
'only_cash' => true,
|
if ($row['trans_type'] == lang('Sales.cash')) {
|
||||||
'only_due' => false,
|
$cash_ups_info->closed_amount_cash += $row['trans_amount'];
|
||||||
'only_check' => false,
|
} elseif ($row['trans_type'] == lang('Sales.due')) {
|
||||||
'only_credit' => false,
|
$cash_ups_info->closed_amount_due += $row['trans_amount'];
|
||||||
'only_debit' => false,
|
} elseif (
|
||||||
'is_deleted' => false
|
$row['trans_type'] == lang('Sales.debit') ||
|
||||||
];
|
$row['trans_type'] == lang('Sales.credit')
|
||||||
|
) {
|
||||||
|
$cash_ups_info->closed_amount_card += $row['trans_amount'];
|
||||||
|
} elseif ($row['trans_type'] == lang('Sales.check')) {
|
||||||
|
$cash_ups_info->closed_amount_check += $row['trans_amount'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$payments = $this->expense->get_payments_summary('', array_merge($inputs, $filters));
|
// Lookup expenses paid in cash
|
||||||
|
$filters = [
|
||||||
|
'only_cash' => true,
|
||||||
|
'only_due' => false,
|
||||||
|
'only_check' => false,
|
||||||
|
'only_credit' => false,
|
||||||
|
'only_debit' => false,
|
||||||
|
'is_deleted' => false
|
||||||
|
];
|
||||||
|
|
||||||
foreach($payments as $row)
|
$payments = $this->expense->get_payments_summary('', array_merge($inputs, $filters));
|
||||||
{
|
|
||||||
$cash_ups_info->closed_amount_cash -= $row['amount'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$cash_ups_info->closed_amount_total = $this->_calculate_total($cash_ups_info->open_amount_cash, $cash_ups_info->transfer_amount_cash, $cash_ups_info->closed_amount_cash, $cash_ups_info->closed_amount_due, $cash_ups_info->closed_amount_card, $cash_ups_info->closed_amount_check);
|
foreach ($payments as $row) {
|
||||||
}
|
$cash_ups_info->closed_amount_cash -= $row['amount'];
|
||||||
|
}
|
||||||
|
|
||||||
$data['cash_ups_info'] = $cash_ups_info;
|
$cash_ups_info->closed_amount_total = $this->_calculate_total($cash_ups_info->open_amount_cash, $cash_ups_info->transfer_amount_cash, $cash_ups_info->closed_amount_cash, $cash_ups_info->closed_amount_due, $cash_ups_info->closed_amount_card, $cash_ups_info->closed_amount_check);
|
||||||
|
}
|
||||||
|
|
||||||
echo view("cashups/form", $data);
|
$data['cash_ups_info'] = $cash_ups_info;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
return view("cashups/form", $data);
|
||||||
* @param int $row_id
|
}
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function getRow(int $row_id): void
|
|
||||||
{
|
|
||||||
$cash_ups_info = $this->cashup->get_info($row_id);
|
|
||||||
$data_row = get_cash_up_data_row($cash_ups_info);
|
|
||||||
|
|
||||||
echo json_encode($data_row);
|
/**
|
||||||
}
|
* @param int $row_id
|
||||||
|
* @return ResponseInterface
|
||||||
|
*/
|
||||||
|
public function getRow(int $row_id): ResponseInterface
|
||||||
|
{
|
||||||
|
$cash_ups_info = $this->cashup->get_info($row_id);
|
||||||
|
$data_row = get_cash_up_data_row($cash_ups_info);
|
||||||
|
|
||||||
/**
|
return $this->response->setJSON($data_row);
|
||||||
* @param int $cashup_id
|
}
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function postSave(int $cashup_id = NEW_ENTRY): void
|
|
||||||
{
|
|
||||||
$open_date = $this->request->getPost('open_date');
|
|
||||||
$open_date_formatter = date_create_from_format($this->config['dateformat'] . ' ' . $this->config['timeformat'], $open_date);
|
|
||||||
|
|
||||||
$close_date = $this->request->getPost('close_date');
|
/**
|
||||||
$close_date_formatter = date_create_from_format($this->config['dateformat'] . ' ' . $this->config['timeformat'], $close_date);
|
* @param int $cashup_id
|
||||||
|
* @return ResponseInterface
|
||||||
|
*/
|
||||||
|
public function postSave(int $cashup_id = NEW_ENTRY): ResponseInterface
|
||||||
|
{
|
||||||
|
$open_date = $this->request->getPost('open_date');
|
||||||
|
$open_date_formatter = date_create_from_format($this->config['dateformat'] . ' ' . $this->config['timeformat'], $open_date);
|
||||||
|
|
||||||
$cash_up_data = [
|
$close_date = $this->request->getPost('close_date');
|
||||||
'open_date' => $open_date_formatter->format('Y-m-d H:i:s'),
|
$close_date_formatter = date_create_from_format($this->config['dateformat'] . ' ' . $this->config['timeformat'], $close_date);
|
||||||
'close_date' => $close_date_formatter->format('Y-m-d H:i:s'),
|
|
||||||
'open_amount_cash' => parse_decimals($this->request->getPost('open_amount_cash')),
|
|
||||||
'transfer_amount_cash' => parse_decimals($this->request->getPost('transfer_amount_cash')),
|
|
||||||
'closed_amount_cash' => parse_decimals($this->request->getPost('closed_amount_cash')),
|
|
||||||
'closed_amount_due' => parse_decimals($this->request->getPost('closed_amount_due')),
|
|
||||||
'closed_amount_card' => parse_decimals($this->request->getPost('closed_amount_card')),
|
|
||||||
'closed_amount_check' => parse_decimals($this->request->getPost('closed_amount_check')),
|
|
||||||
'closed_amount_total' => parse_decimals($this->request->getPost('closed_amount_total')),
|
|
||||||
'note' => $this->request->getPost('note') != null,
|
|
||||||
'description' => $this->request->getPost('description', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
|
||||||
'open_employee_id' => $this->request->getPost('open_employee_id', FILTER_SANITIZE_NUMBER_INT),
|
|
||||||
'close_employee_id' => $this->request->getPost('close_employee_id', FILTER_SANITIZE_NUMBER_INT),
|
|
||||||
'deleted' => $this->request->getPost('deleted') != null
|
|
||||||
];
|
|
||||||
|
|
||||||
if($this->cashup->save_value($cash_up_data, $cashup_id))
|
$cash_up_data = [
|
||||||
{
|
'open_date' => $open_date_formatter->format('Y-m-d H:i:s'),
|
||||||
//New cashup_id
|
'close_date' => $close_date_formatter->format('Y-m-d H:i:s'),
|
||||||
if($cashup_id == NEW_ENTRY)
|
'open_amount_cash' => parse_decimals($this->request->getPost('open_amount_cash')),
|
||||||
{
|
'transfer_amount_cash' => parse_decimals($this->request->getPost('transfer_amount_cash')),
|
||||||
echo json_encode(['success' => true, 'message' => lang('Cashups.successful_adding'), 'id' => $cash_up_data['cashup_id']]);
|
'closed_amount_cash' => parse_decimals($this->request->getPost('closed_amount_cash')),
|
||||||
}
|
'closed_amount_due' => parse_decimals($this->request->getPost('closed_amount_due')),
|
||||||
else // Existing Cashup
|
'closed_amount_card' => parse_decimals($this->request->getPost('closed_amount_card')),
|
||||||
{
|
'closed_amount_check' => parse_decimals($this->request->getPost('closed_amount_check')),
|
||||||
echo json_encode(['success' => true, 'message' => lang('Cashups.successful_updating'), 'id' => $cashup_id]);
|
'closed_amount_total' => parse_decimals($this->request->getPost('closed_amount_total')),
|
||||||
}
|
'note' => $this->request->getPost('note') != null,
|
||||||
}
|
'description' => $this->request->getPost('description', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
else//failure
|
'open_employee_id' => $this->request->getPost('open_employee_id', FILTER_SANITIZE_NUMBER_INT),
|
||||||
{
|
'close_employee_id' => $this->request->getPost('close_employee_id', FILTER_SANITIZE_NUMBER_INT),
|
||||||
echo json_encode(['success' => false, 'message' => lang('Cashups.error_adding_updating'), 'id' => NEW_ENTRY]);
|
'deleted' => $this->request->getPost('deleted') != null
|
||||||
}
|
];
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if ($this->cashup->save_value($cash_up_data, $cashup_id)) {
|
||||||
* @return void
|
// New cashup_id
|
||||||
*/
|
if ($cashup_id == NEW_ENTRY) {
|
||||||
public function postDelete(): void
|
return $this->response->setJSON(['success' => true, 'message' => lang('Cashups.successful_adding'), 'id' => $cash_up_data['cashup_id']]);
|
||||||
{
|
} else { // Existing Cashup
|
||||||
$cash_ups_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
return $this->response->setJSON(['success' => true, 'message' => lang('Cashups.successful_updating'), 'id' => $cashup_id]);
|
||||||
|
}
|
||||||
|
} else { // Failure
|
||||||
|
return $this->response->setJSON(['success' => false, 'message' => lang('Cashups.error_adding_updating'), 'id' => NEW_ENTRY]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if($this->cashup->delete_list($cash_ups_to_delete))
|
/**
|
||||||
{
|
* @return ResponseInterface
|
||||||
echo json_encode(['success' => true, 'message' => lang('Cashups.successful_deleted') . ' ' . count($cash_ups_to_delete) . ' ' . lang('Cashups.one_or_multiple'), 'ids' => $cash_ups_to_delete]);
|
*/
|
||||||
}
|
public function postDelete(): ResponseInterface
|
||||||
else
|
{
|
||||||
{
|
$cash_ups_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||||
echo json_encode(['success' => false, 'message' => lang('Cashups.cannot_be_deleted'), 'ids' => $cash_ups_to_delete]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if ($this->cashup->delete_list($cash_ups_to_delete)) {
|
||||||
* Calculate the total for cashups. Used in app\Views\cashups\form.php
|
return $this->response->setJSON(['success' => true, 'message' => lang('Cashups.successful_deleted') . ' ' . count($cash_ups_to_delete) . ' ' . lang('Cashups.one_or_multiple'), 'ids' => $cash_ups_to_delete]);
|
||||||
*
|
} else {
|
||||||
* @return void
|
return $this->response->setJSON(['success' => false, 'message' => lang('Cashups.cannot_be_deleted'), 'ids' => $cash_ups_to_delete]);
|
||||||
* @noinspection PhpUnused
|
}
|
||||||
*/
|
}
|
||||||
public function ajax_cashup_total(): void
|
|
||||||
{
|
|
||||||
$open_amount_cash = parse_decimals($this->request->getPost('open_amount_cash'));
|
|
||||||
$transfer_amount_cash = parse_decimals($this->request->getPost('transfer_amount_cash'));
|
|
||||||
$closed_amount_cash = parse_decimals($this->request->getPost('closed_amount_cash'));
|
|
||||||
$closed_amount_due = parse_decimals($this->request->getPost('closed_amount_due'));
|
|
||||||
$closed_amount_card = parse_decimals($this->request->getPost('closed_amount_card'));
|
|
||||||
$closed_amount_check = parse_decimals($this->request->getPost('closed_amount_check'));
|
|
||||||
|
|
||||||
$total = $this->_calculate_total($open_amount_cash, $transfer_amount_cash, $closed_amount_due, $closed_amount_cash, $closed_amount_card, $closed_amount_check); //TODO: hungarian notation
|
/**
|
||||||
|
* Calculate the total for cashups. Used in app\Views\cashups\form.php
|
||||||
|
*
|
||||||
|
* @return ResponseInterface
|
||||||
|
* @noinspection PhpUnused
|
||||||
|
*/
|
||||||
|
public function postAjax_cashup_total(): ResponseInterface
|
||||||
|
{
|
||||||
|
$open_amount_cash = parse_decimals($this->request->getPost('open_amount_cash'));
|
||||||
|
$transfer_amount_cash = parse_decimals($this->request->getPost('transfer_amount_cash'));
|
||||||
|
$closed_amount_cash = parse_decimals($this->request->getPost('closed_amount_cash'));
|
||||||
|
$closed_amount_due = parse_decimals($this->request->getPost('closed_amount_due'));
|
||||||
|
$closed_amount_card = parse_decimals($this->request->getPost('closed_amount_card'));
|
||||||
|
$closed_amount_check = parse_decimals($this->request->getPost('closed_amount_check'));
|
||||||
|
|
||||||
echo json_encode(['total' => to_currency_no_money($total)]);
|
$total = $this->_calculate_total($open_amount_cash, $transfer_amount_cash, $closed_amount_due, $closed_amount_cash, $closed_amount_card, $closed_amount_check); // TODO: hungarian notation
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
return $this->response->setJSON(['total' => to_currency_no_money($total)]);
|
||||||
* Calculate total
|
}
|
||||||
*/
|
|
||||||
private function _calculate_total(float $open_amount_cash, float $transfer_amount_cash, float $closed_amount_due, float $closed_amount_cash, float $closed_amount_card, $closed_amount_check): float //TODO: need to get rid of hungarian notation here. Also, the signature is pretty long. Perhaps they need to go into an object or array?
|
/**
|
||||||
{
|
* Calculate total
|
||||||
return ($closed_amount_cash - $open_amount_cash - $transfer_amount_cash + $closed_amount_due + $closed_amount_card + $closed_amount_check);
|
*/
|
||||||
}
|
private function _calculate_total(float $open_amount_cash, float $transfer_amount_cash, float $closed_amount_due, float $closed_amount_cash, float $closed_amount_card, $closed_amount_check): float // TODO: need to get rid of hungarian notation here. Also, the signature is pretty long. Perhaps they need to go into an object or array?
|
||||||
|
{
|
||||||
|
return ($closed_amount_cash - $open_amount_cash - $transfer_amount_cash + $closed_amount_due + $closed_amount_card + $closed_amount_check);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,527 +8,478 @@ use App\Models\Customer;
|
|||||||
use App\Models\Customer_rewards;
|
use App\Models\Customer_rewards;
|
||||||
use App\Models\Tax_code;
|
use App\Models\Tax_code;
|
||||||
use CodeIgniter\HTTP\DownloadResponse;
|
use CodeIgniter\HTTP\DownloadResponse;
|
||||||
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
use Config\OSPOS;
|
use Config\OSPOS;
|
||||||
use Config\Services;
|
use Config\Services;
|
||||||
use stdClass;
|
use stdClass;
|
||||||
|
|
||||||
class Customers extends Persons
|
class Customers extends Persons
|
||||||
{
|
{
|
||||||
private string $_list_id;
|
private string $_list_id;
|
||||||
private Mailchimp_lib $mailchimp_lib;
|
private Mailchimp_lib $mailchimp_lib;
|
||||||
private Customer_rewards $customer_rewards;
|
private Customer_rewards $customer_rewards;
|
||||||
private Customer $customer;
|
private Customer $customer;
|
||||||
private Tax_code $tax_code;
|
private Tax_code $tax_code;
|
||||||
private array $config;
|
private array $config;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct('customers');
|
parent::__construct('customers');
|
||||||
$this->mailchimp_lib = new Mailchimp_lib();
|
$this->mailchimp_lib = new Mailchimp_lib();
|
||||||
$this->customer_rewards = model(Customer_rewards::class);
|
$this->customer_rewards = model(Customer_rewards::class);
|
||||||
$this->customer = model(Customer::class);
|
$this->customer = model(Customer::class);
|
||||||
$this->tax_code = model(Tax_code::class);
|
$this->tax_code = model(Tax_code::class);
|
||||||
$this->config = config(OSPOS::class)->settings;
|
$this->config = config(OSPOS::class)->settings;
|
||||||
|
|
||||||
$encrypter = Services::encrypter();
|
$encrypter = Services::encrypter();
|
||||||
|
|
||||||
if(!empty($this->config['mailchimp_list_id']))
|
if (!empty($this->config['mailchimp_list_id'])) {
|
||||||
{
|
$this->_list_id = $encrypter->decrypt($this->config['mailchimp_list_id']);
|
||||||
$this->_list_id = $encrypter->decrypt($this->config['mailchimp_list_id']);
|
} else {
|
||||||
}
|
$this->_list_id = '';
|
||||||
else
|
}
|
||||||
{
|
}
|
||||||
$this->_list_id = '';
|
|
||||||
}
|
/**
|
||||||
}
|
* @return string
|
||||||
|
*/
|
||||||
/**
|
public function getIndex(): string
|
||||||
* @return void
|
{
|
||||||
*/
|
$data['table_headers'] = get_customer_manage_table_headers();
|
||||||
public function getIndex(): void
|
|
||||||
{
|
return view('people/manage', $data);
|
||||||
$data['table_headers'] = get_customer_manage_table_headers();
|
}
|
||||||
|
|
||||||
echo view('people/manage', $data);
|
/**
|
||||||
}
|
* Gets one row for a customer manage table. This is called using AJAX to update one row.
|
||||||
|
* @return ResponseInterface
|
||||||
/**
|
*/
|
||||||
* Gets one row for a customer manage table. This is called using AJAX to update one row.
|
public function getRow(int $row_id): ResponseInterface
|
||||||
*/
|
{
|
||||||
public function getRow(int $row_id): void
|
$person = $this->customer->get_info($row_id);
|
||||||
{
|
|
||||||
$person = $this->customer->get_info($row_id);
|
// Retrieve the total amount the customer spent so far together with min, max and average values
|
||||||
|
$stats = $this->customer->get_stats($person->person_id); // TODO: This and the next 11 lines are duplicated in search(). Extract a method.
|
||||||
// retrieve the total amount the customer spent so far together with min, max and average values
|
|
||||||
$stats = $this->customer->get_stats($person->person_id); //TODO: This and the next 11 lines are duplicated in search(). Extract a method.
|
if (empty($stats)) {
|
||||||
|
// Create object with empty properties.
|
||||||
if(empty($stats))
|
$stats = new stdClass();
|
||||||
{
|
$stats->total = 0;
|
||||||
//create object with empty properties.
|
$stats->min = 0;
|
||||||
$stats = new stdClass();
|
$stats->max = 0;
|
||||||
$stats->total = 0;
|
$stats->average = 0;
|
||||||
$stats->min = 0;
|
$stats->avg_discount = 0;
|
||||||
$stats->max = 0;
|
$stats->quantity = 0;
|
||||||
$stats->average = 0;
|
}
|
||||||
$stats->avg_discount = 0;
|
|
||||||
$stats->quantity = 0;
|
$data_row = get_customer_data_row($person, $stats);
|
||||||
}
|
|
||||||
|
return $this->response->setJSON($data_row);
|
||||||
$data_row = get_customer_data_row($person, $stats);
|
}
|
||||||
|
|
||||||
echo json_encode($data_row);
|
|
||||||
}
|
/**
|
||||||
|
* Returns customer table data rows. This will be called with AJAX.
|
||||||
|
*
|
||||||
/**
|
* @return void
|
||||||
* Returns customer table data rows. This will be called with AJAX.
|
*/
|
||||||
*
|
public function getSearch(): ResponseInterface
|
||||||
* @return void
|
{
|
||||||
*/
|
$search = $this->request->getGet('search');
|
||||||
public function getSearch(): void
|
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
||||||
{
|
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$search = $this->request->getGet('search');
|
$sort = $this->sanitizeSortColumn(customer_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'people.person_id');
|
||||||
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||||
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
|
||||||
$sort = $this->sanitizeSortColumn(CUSTOMER_HEADERS, $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'people.person_id');
|
$customers = $this->customer->search($search, $limit, $offset, $sort, $order);
|
||||||
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
$total_rows = $this->customer->get_found_rows($search);
|
||||||
|
|
||||||
$customers = $this->customer->search($search, $limit, $offset, $sort, $order);
|
$data_rows = [];
|
||||||
$total_rows = $this->customer->get_found_rows($search);
|
|
||||||
|
foreach ($customers->getResult() as $person) {
|
||||||
$data_rows = [];
|
// Retrieve the total amount the customer spent so far together with min, max and average values
|
||||||
|
$stats = $this->customer->get_stats($person->person_id); // TODO: duplicated... see above
|
||||||
foreach($customers->getResult() as $person)
|
if (empty($stats)) {
|
||||||
{
|
// Create object with empty properties.
|
||||||
// retrieve the total amount the customer spent so far together with min, max and average values
|
$stats = new stdClass();
|
||||||
$stats = $this->customer->get_stats($person->person_id); //TODO: duplicated... see above
|
$stats->total = 0;
|
||||||
if(empty($stats))
|
$stats->min = 0;
|
||||||
{
|
$stats->max = 0;
|
||||||
//create object with empty properties.
|
$stats->average = 0;
|
||||||
$stats = new stdClass();
|
$stats->avg_discount = 0;
|
||||||
$stats->total = 0;
|
$stats->quantity = 0;
|
||||||
$stats->min = 0;
|
}
|
||||||
$stats->max = 0;
|
|
||||||
$stats->average = 0;
|
$data_rows[] = get_customer_data_row($person, $stats);
|
||||||
$stats->avg_discount = 0;
|
}
|
||||||
$stats->quantity = 0;
|
|
||||||
}
|
return $this->response->setJSON(['total' => $total_rows, 'rows' => $data_rows]);
|
||||||
|
}
|
||||||
$data_rows[] = get_customer_data_row($person, $stats);
|
|
||||||
}
|
/**
|
||||||
|
* Gives search suggestions based on what is being searched for
|
||||||
echo json_encode (['total' => $total_rows, 'rows' => $data_rows]);
|
* @return ResponseInterface
|
||||||
}
|
*/
|
||||||
|
public function getSuggest(): ResponseInterface
|
||||||
/**
|
{
|
||||||
* Gives search suggestions based on what is being searched for
|
$search = $this->request->getGet('term');
|
||||||
*/
|
$suggestions = $this->customer->get_search_suggestions($search);
|
||||||
public function getSuggest(): void
|
|
||||||
{
|
return $this->response->setJSON($suggestions);
|
||||||
$search = $this->request->getGet('term');
|
}
|
||||||
$suggestions = $this->customer->get_search_suggestions($search);
|
|
||||||
|
/**
|
||||||
echo json_encode($suggestions);
|
* @return ResponseInterface
|
||||||
}
|
*/
|
||||||
|
public function suggest_search(): ResponseInterface
|
||||||
/**
|
{
|
||||||
* @return void
|
$search = $this->request->getGet('term');
|
||||||
*/
|
$suggestions = $this->customer->get_search_suggestions($search, 25, false);
|
||||||
public function suggest_search(): void
|
|
||||||
{
|
return $this->response->setJSON($suggestions);
|
||||||
$search = $this->request->getGet('term');
|
}
|
||||||
$suggestions = $this->customer->get_search_suggestions($search, 25, false);
|
|
||||||
|
/**
|
||||||
echo json_encode($suggestions);
|
* Loads the customer edit form
|
||||||
}
|
* @return string
|
||||||
|
*/
|
||||||
/**
|
public function getView(int $customer_id = NEW_ENTRY): string
|
||||||
* Loads the customer edit form
|
{
|
||||||
*/
|
// Set default values
|
||||||
public function getView(int $customer_id = NEW_ENTRY): void
|
if ($customer_id == null) $customer_id = NEW_ENTRY;
|
||||||
{
|
|
||||||
// Set default values
|
$info = $this->customer->get_info($customer_id);
|
||||||
if($customer_id == null) $customer_id = NEW_ENTRY;
|
foreach (get_object_vars($info) as $property => $value) {
|
||||||
|
$info->$property = $value;
|
||||||
$info = $this->customer->get_info($customer_id);
|
}
|
||||||
foreach(get_object_vars($info) as $property => $value)
|
$data['person_info'] = $info;
|
||||||
{
|
|
||||||
$info->$property = $value;
|
if (empty($info->person_id) || empty($info->date) || empty($info->employee_id)) {
|
||||||
}
|
$data['person_info']->date = date('Y-m-d H:i:s');
|
||||||
$data['person_info'] = $info;
|
$data['person_info']->employee_id = $this->employee->get_logged_in_employee_info()->person_id;
|
||||||
|
}
|
||||||
if(empty($info->person_id) || empty($info->date) || empty($info->employee_id))
|
|
||||||
{
|
$employee_info = $this->employee->get_info($info->employee_id);
|
||||||
$data['person_info']->date = date('Y-m-d H:i:s');
|
$data['employee'] = $employee_info->first_name . ' ' . $employee_info->last_name;
|
||||||
$data['person_info']->employee_id = $this->employee->get_logged_in_employee_info()->person_id;
|
|
||||||
}
|
$tax_code_info = $this->tax_code->get_info($info->sales_tax_code_id);
|
||||||
|
|
||||||
$employee_info = $this->employee->get_info($info->employee_id);
|
if ($tax_code_info->tax_code != null) {
|
||||||
$data['employee'] = $employee_info->first_name . ' ' . $employee_info->last_name;
|
$data['sales_tax_code_label'] = $tax_code_info->tax_code . ' ' . $tax_code_info->tax_code_name;
|
||||||
|
} else {
|
||||||
$tax_code_info = $this->tax_code->get_info($info->sales_tax_code_id);
|
$data['sales_tax_code_label'] = '';
|
||||||
|
}
|
||||||
if($tax_code_info->tax_code != null)
|
|
||||||
{
|
$packages = ['' => lang('Items.none')];
|
||||||
$data['sales_tax_code_label'] = $tax_code_info->tax_code . ' ' . $tax_code_info->tax_code_name;
|
foreach ($this->customer_rewards->get_all()->getResultArray() as $row) {
|
||||||
}
|
$packages[$row['package_id']] = $row['package_name'];
|
||||||
else
|
}
|
||||||
{
|
$data['packages'] = $packages;
|
||||||
$data['sales_tax_code_label'] = '';
|
$data['selected_package'] = $info->package_id;
|
||||||
}
|
|
||||||
|
$data['use_destination_based_tax'] = $this->config['use_destination_based_tax'];
|
||||||
$packages = ['' => lang('Items.none')];
|
|
||||||
foreach($this->customer_rewards->get_all()->getResultArray() as $row)
|
// Retrieve the total amount the customer spent so far together with min, max and average values
|
||||||
{
|
$stats = $this->customer->get_stats($customer_id);
|
||||||
$packages[$row['package_id']] = $row['package_name'];
|
if (!empty($stats)) {
|
||||||
}
|
foreach (get_object_vars($stats) as $property => $value) {
|
||||||
$data['packages'] = $packages;
|
$info->$property = $value;
|
||||||
$data['selected_package'] = $info->package_id;
|
}
|
||||||
|
$data['stats'] = $stats;
|
||||||
$data['use_destination_based_tax'] = $this->config['use_destination_based_tax'];
|
}
|
||||||
|
|
||||||
// retrieve the total amount the customer spent so far together with min, max and average values
|
// Retrieve the info from Mailchimp only if there is an email address assigned
|
||||||
$stats = $this->customer->get_stats($customer_id);
|
if (!empty($info->email)) {
|
||||||
if(!empty($stats))
|
// Collect Mailchimp customer info
|
||||||
{
|
if (($mailchimp_info = $this->mailchimp_lib->getMemberInfo($this->_list_id, $info->email)) !== false) {
|
||||||
foreach(get_object_vars($stats) as $property => $value)
|
$data['mailchimp_info'] = $mailchimp_info;
|
||||||
{
|
|
||||||
$info->$property = $value;
|
// Collect customer Mailchimp emails activities (stats)
|
||||||
}
|
if (($activities = $this->mailchimp_lib->getMemberActivity($this->_list_id, $info->email)) !== false) {
|
||||||
$data['stats'] = $stats;
|
if (array_key_exists('activity', $activities)) {
|
||||||
}
|
$open = 0;
|
||||||
|
$unopen = 0;
|
||||||
// retrieve the info from Mailchimp only if there is an email address assigned
|
$click = 0;
|
||||||
if(!empty($info->email))
|
$total = 0;
|
||||||
{
|
$lastopen = '';
|
||||||
// collect mailchimp customer info
|
|
||||||
if(($mailchimp_info = $this->mailchimp_lib->getMemberInfo($this->_list_id, $info->email)) !== false)
|
foreach ($activities['activity'] as $activity) {
|
||||||
{
|
if ($activity['action'] == 'sent') {
|
||||||
$data['mailchimp_info'] = $mailchimp_info;
|
++$unopen;
|
||||||
|
} elseif ($activity['action'] == 'open') {
|
||||||
// collect customer mailchimp emails activities (stats)
|
if (empty($lastopen)) {
|
||||||
if(($activities = $this->mailchimp_lib->getMemberActivity($this->_list_id, $info->email)) !== false)
|
$lastopen = substr($activity['timestamp'], 0, 10);
|
||||||
{
|
}
|
||||||
if(array_key_exists('activity', $activities))
|
++$open;
|
||||||
{
|
} elseif ($activity['action'] == 'click') {
|
||||||
$open = 0;
|
if (empty($lastopen)) {
|
||||||
$unopen = 0;
|
$lastopen = substr($activity['timestamp'], 0, 10);
|
||||||
$click = 0;
|
}
|
||||||
$total = 0;
|
++$click;
|
||||||
$lastopen = '';
|
}
|
||||||
|
|
||||||
foreach($activities['activity'] as $activity)
|
++$total;
|
||||||
{
|
}
|
||||||
if($activity['action'] == 'sent')
|
|
||||||
{
|
$data['mailchimp_activity']['total'] = $total;
|
||||||
++$unopen;
|
$data['mailchimp_activity']['open'] = $open;
|
||||||
}
|
$data['mailchimp_activity']['unopen'] = $unopen;
|
||||||
elseif($activity['action'] == 'open')
|
$data['mailchimp_activity']['click'] = $click;
|
||||||
{
|
$data['mailchimp_activity']['lastopen'] = $lastopen;
|
||||||
if(empty($lastopen))
|
}
|
||||||
{
|
}
|
||||||
$lastopen = substr($activity['timestamp'], 0, 10);
|
}
|
||||||
}
|
}
|
||||||
++$open;
|
|
||||||
}
|
return view("customers/form", $data);
|
||||||
elseif($activity['action'] == 'click')
|
}
|
||||||
{
|
|
||||||
if(empty($lastopen))
|
/**
|
||||||
{
|
* Inserts/updates a customer
|
||||||
$lastopen = substr($activity['timestamp'], 0, 10);
|
* @return ResponseInterface
|
||||||
}
|
*/
|
||||||
++$click;
|
public function postSave(int $customer_id = NEW_ENTRY): ResponseInterface
|
||||||
}
|
{
|
||||||
|
$first_name = $this->request->getPost('first_name');
|
||||||
++$total;
|
$last_name = $this->request->getPost('last_name');
|
||||||
}
|
$email = strtolower($this->request->getPost('email', FILTER_SANITIZE_EMAIL));
|
||||||
|
|
||||||
$data['mailchimp_activity']['total'] = $total;
|
// Format first and last name properly
|
||||||
$data['mailchimp_activity']['open'] = $open;
|
$first_name = $this->nameize($first_name);
|
||||||
$data['mailchimp_activity']['unopen'] = $unopen;
|
$last_name = $this->nameize($last_name);
|
||||||
$data['mailchimp_activity']['click'] = $click;
|
|
||||||
$data['mailchimp_activity']['lastopen'] = $lastopen;
|
$person_data = [
|
||||||
}
|
'first_name' => $first_name,
|
||||||
}
|
'last_name' => $last_name,
|
||||||
}
|
'gender' => $this->request->getPost('gender', FILTER_SANITIZE_NUMBER_INT),
|
||||||
}
|
'email' => $email,
|
||||||
|
'phone_number' => $this->request->getPost('phone_number'),
|
||||||
echo view("customers/form", $data);
|
'address_1' => $this->request->getPost('address_1'),
|
||||||
}
|
'address_2' => $this->request->getPost('address_2'),
|
||||||
|
'city' => $this->request->getPost('city'),
|
||||||
/**
|
'state' => $this->request->getPost('state'),
|
||||||
* Inserts/updates a customer
|
'zip' => $this->request->getPost('zip'),
|
||||||
*/
|
'country' => $this->request->getPost('country'),
|
||||||
public function postSave(int $customer_id = NEW_ENTRY): void
|
'comments' => $this->request->getPost('comments')
|
||||||
{
|
];
|
||||||
$first_name = $this->request->getPost('first_name');
|
|
||||||
$last_name = $this->request->getPost('last_name');
|
$date_formatter = date_create_from_format($this->config['dateformat'] . ' ' . $this->config['timeformat'], $this->request->getPost('date'));
|
||||||
$email = strtolower($this->request->getPost('email', FILTER_SANITIZE_EMAIL));
|
|
||||||
|
$customer_data = [
|
||||||
// format first and last name properly
|
'consent' => $this->request->getPost('consent') != null,
|
||||||
$first_name = $this->nameize($first_name);
|
'account_number' => $this->request->getPost('account_number') == '' ? null : $this->request->getPost('account_number'),
|
||||||
$last_name = $this->nameize($last_name);
|
'tax_id' => $this->request->getPost('tax_id'),
|
||||||
|
'company_name' => $this->request->getPost('company_name') == '' ? null : $this->request->getPost('company_name'),
|
||||||
$person_data = [
|
'discount' => $this->request->getPost('discount') == '' ? 0.00 : parse_decimals($this->request->getPost('discount')),
|
||||||
'first_name' => $first_name,
|
'discount_type' => $this->request->getPost('discount_type') == null ? PERCENT : $this->request->getPost('discount_type', FILTER_SANITIZE_NUMBER_INT),
|
||||||
'last_name' => $last_name,
|
'package_id' => $this->request->getPost('package_id') == '' ? null : $this->request->getPost('package_id'),
|
||||||
'gender' => $this->request->getPost('gender', FILTER_SANITIZE_NUMBER_INT),
|
'taxable' => $this->request->getPost('taxable') != null,
|
||||||
'email' => $email,
|
'date' => $date_formatter->format('Y-m-d H:i:s'),
|
||||||
'phone_number' => $this->request->getPost('phone_number'),
|
'employee_id' => $this->request->getPost('employee_id', FILTER_SANITIZE_NUMBER_INT),
|
||||||
'address_1' => $this->request->getPost('address_1'),
|
'sales_tax_code_id' => $this->request->getPost('sales_tax_code_id') == '' ? null : $this->request->getPost('sales_tax_code_id', FILTER_SANITIZE_NUMBER_INT)
|
||||||
'address_2' => $this->request->getPost('address_2'),
|
];
|
||||||
'city' => $this->request->getPost('city'),
|
|
||||||
'state' => $this->request->getPost('state'),
|
if ($this->customer->save_customer($person_data, $customer_data, $customer_id)) {
|
||||||
'zip' => $this->request->getPost('zip'),
|
// Save customer to Mailchimp selected list // TODO: addOrUpdateMember should be refactored. Potentially pass an array or object instead of 6 parameters.
|
||||||
'country' => $this->request->getPost('country'),
|
$mailchimp_status = $this->request->getPost('mailchimp_status');
|
||||||
'comments' => $this->request->getPost('comments')
|
$this->mailchimp_lib->addOrUpdateMember(
|
||||||
];
|
$this->_list_id,
|
||||||
|
$email,
|
||||||
$date_formatter = date_create_from_format($this->config['dateformat'] . ' ' . $this->config['timeformat'], $this->request->getPost('date'));
|
$first_name,
|
||||||
|
$last_name,
|
||||||
$customer_data = [
|
$mailchimp_status == null ? "" : $mailchimp_status,
|
||||||
'consent' => $this->request->getPost('consent') != null,
|
['vip' => $this->request->getPost('mailchimp_vip') != null]
|
||||||
'account_number' => $this->request->getPost('account_number') == '' ? null : $this->request->getPost('account_number'),
|
);
|
||||||
'tax_id' => $this->request->getPost('tax_id'),
|
|
||||||
'company_name' => $this->request->getPost('company_name') == '' ? null : $this->request->getPost('company_name'),
|
// New customer
|
||||||
'discount' => $this->request->getPost('discount') == '' ? 0.00 : parse_decimals($this->request->getPost('discount')),
|
if ($customer_id == NEW_ENTRY) {
|
||||||
'discount_type' => $this->request->getPost('discount_type') == null ? PERCENT : $this->request->getPost('discount_type', FILTER_SANITIZE_NUMBER_INT),
|
return $this->response->setJSON([
|
||||||
'package_id' => $this->request->getPost('package_id') == '' ? null : $this->request->getPost('package_id'),
|
'success' => true,
|
||||||
'taxable' => $this->request->getPost('taxable') != null,
|
'message' => lang('Customers.successful_adding') . ' ' . $first_name . ' ' . $last_name,
|
||||||
'date' => $date_formatter->format('Y-m-d H:i:s'),
|
'id' => $customer_data['person_id']
|
||||||
'employee_id' => $this->request->getPost('employee_id', FILTER_SANITIZE_NUMBER_INT),
|
]);
|
||||||
'sales_tax_code_id' => $this->request->getPost('sales_tax_code_id') == '' ? null : $this->request->getPost('sales_tax_code_id', FILTER_SANITIZE_NUMBER_INT)
|
} else { // Existing customer
|
||||||
];
|
return $this->response->setJSON([
|
||||||
|
'success' => true,
|
||||||
if($this->customer->save_customer($person_data, $customer_data, $customer_id))
|
'message' => lang('Customers.successful_updating') . ' ' . $first_name . ' ' . $last_name,
|
||||||
{
|
'id' => $customer_id
|
||||||
// save customer to Mailchimp selected list //TODO: addOrUpdateMember should be refactored... potentially pass an array or object instead of 6 parameters.
|
]);
|
||||||
$mailchimp_status = $this->request->getPost('mailchimp_status');
|
}
|
||||||
$this->mailchimp_lib->addOrUpdateMember(
|
} else { // Failure
|
||||||
$this->_list_id,
|
return $this->response->setJSON([
|
||||||
$email,
|
'success' => false,
|
||||||
$first_name,
|
'message' => lang('Customers.error_adding_updating') . ' ' . $first_name . ' ' . $last_name,
|
||||||
$last_name,
|
'id' => NEW_ENTRY
|
||||||
$mailchimp_status == null ? "" : $mailchimp_status,
|
]);
|
||||||
['vip' => $this->request->getPost('mailchimp_vip') != null]
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
// New customer
|
/**
|
||||||
if($customer_id == NEW_ENTRY)
|
* Verifies if an email address already exists. Used in app/Views/customers/form.php
|
||||||
{
|
*
|
||||||
echo json_encode ([
|
* @return ResponseInterface
|
||||||
'success' => true,
|
* @noinspection PhpUnused
|
||||||
'message' => lang('Customers.successful_adding') . ' ' . $first_name . ' ' . $last_name,
|
*/
|
||||||
'id' => $customer_data['person_id']
|
public function postCheckEmail(): ResponseInterface
|
||||||
]);
|
{
|
||||||
}
|
$email = strtolower($this->request->getPost('email', FILTER_SANITIZE_EMAIL));
|
||||||
else // Existing customer
|
$person_id = $this->request->getPost('person_id', FILTER_SANITIZE_NUMBER_INT);
|
||||||
{
|
|
||||||
echo json_encode ([
|
$exists = $this->customer->check_email_exists($email, $person_id);
|
||||||
'success' => true,
|
|
||||||
'message' => lang('Customers.successful_updating') . ' ' . $first_name . ' ' . $last_name,
|
return $this->response->setJSON(!$exists ? 'true' : 'false');
|
||||||
'id' => $customer_id
|
}
|
||||||
]);
|
|
||||||
}
|
/**
|
||||||
}
|
* Verifies if an account number already exists. Used in app/Views/customers/form.php
|
||||||
else // Failure
|
*
|
||||||
{
|
* @return ResponseInterface
|
||||||
echo json_encode ([
|
* @noinspection PhpUnused
|
||||||
'success' => false,
|
*/
|
||||||
'message' => lang('Customers.error_adding_updating') . ' ' . $first_name . ' ' . $last_name,
|
public function postCheckAccountNumber(): ResponseInterface
|
||||||
'id' => NEW_ENTRY
|
{
|
||||||
]);
|
$exists = $this->customer->check_account_number_exists($this->request->getPost('account_number'), $this->request->getPost('person_id', FILTER_SANITIZE_NUMBER_INT));
|
||||||
}
|
|
||||||
}
|
return $this->response->setJSON(!$exists ? 'true' : 'false');
|
||||||
|
}
|
||||||
/**
|
|
||||||
* Verifies if an email address already exists. Used in app/Views/customers/form.php
|
/**
|
||||||
*
|
* This deletes customers from the customers table
|
||||||
* @return void
|
* @return ResponseInterface
|
||||||
* @noinspection PhpUnused
|
*/
|
||||||
*/
|
public function postDelete(): ResponseInterface
|
||||||
public function postCheckEmail(): void
|
{
|
||||||
{
|
$customers_to_delete = $this->request->getPost('ids');
|
||||||
$email = strtolower($this->request->getPost('email', FILTER_SANITIZE_EMAIL));
|
$customers_info = $this->customer->get_multiple_info($customers_to_delete);
|
||||||
$person_id = $this->request->getPost('person_id', FILTER_SANITIZE_NUMBER_INT);
|
|
||||||
|
$count = 0;
|
||||||
$exists = $this->customer->check_email_exists($email, $person_id);
|
|
||||||
|
foreach ($customers_info->getResult() as $info) {
|
||||||
echo !$exists ? 'true' : 'false';
|
if ($this->customer->delete($info->person_id)) {
|
||||||
}
|
// remove customer from Mailchimp selected list
|
||||||
|
$this->mailchimp_lib->removeMember($this->_list_id, $info->email);
|
||||||
/**
|
|
||||||
* Verifies if an account number already exists. Used in app/Views/customers/form.php
|
$count++;
|
||||||
*
|
}
|
||||||
* @return void
|
}
|
||||||
* @noinspection PhpUnused
|
|
||||||
*/
|
if ($count == count($customers_to_delete)) {
|
||||||
public function postCheckAccountNumber(): void
|
return $this->response->setJSON([
|
||||||
{
|
'success' => true,
|
||||||
$exists = $this->customer->check_account_number_exists($this->request->getPost('account_number'), $this->request->getPost('person_id', FILTER_SANITIZE_NUMBER_INT));
|
'message' => lang('Customers.successful_deleted') . ' ' . $count . ' ' . lang('Customers.one_or_multiple')
|
||||||
|
]);
|
||||||
echo !$exists ? 'true' : 'false';
|
} else {
|
||||||
}
|
return $this->response->setJSON(['success' => false, 'message' => lang('Customers.cannot_be_deleted')]);
|
||||||
|
}
|
||||||
/**
|
}
|
||||||
* This deletes customers from the customers table
|
|
||||||
*/
|
/**
|
||||||
public function postDelete(): void
|
* Customers import from csv spreadsheet
|
||||||
{
|
*
|
||||||
$customers_to_delete = $this->request->getPost('ids');
|
* @return DownloadResponse The template for Customer CSV imports is returned and download forced.
|
||||||
$customers_info = $this->customer->get_multiple_info($customers_to_delete);
|
* @noinspection PhpUnused
|
||||||
|
*/
|
||||||
$count = 0;
|
public function getCsv(): DownloadResponse
|
||||||
|
{
|
||||||
foreach($customers_info->getResult() as $info)
|
$name = 'importCustomers.csv';
|
||||||
{
|
$data = file_get_contents(WRITEPATH . "uploads/$name");
|
||||||
if($this->customer->delete($info->person_id))
|
return $this->response->download($name, $data);
|
||||||
{
|
}
|
||||||
// remove customer from Mailchimp selected list
|
|
||||||
$this->mailchimp_lib->removeMember($this->_list_id, $info->email);
|
/**
|
||||||
|
* Displays the customer CSV import modal. Used in app/Views/people/manage.php
|
||||||
$count++;
|
*
|
||||||
}
|
* @return string
|
||||||
}
|
* @noinspection PhpUnused
|
||||||
|
*/
|
||||||
if($count == count($customers_to_delete))
|
public function getCsvImport(): string
|
||||||
{
|
{
|
||||||
echo json_encode (['success' => true,
|
return view('customers/form_csv_import');
|
||||||
'message' => lang('Customers.successful_deleted') . ' ' . $count . ' ' . lang('Customers.one_or_multiple')]);
|
}
|
||||||
}
|
|
||||||
else
|
/**
|
||||||
{
|
* Imports a CSV file containing customers. Used in app/Views/customers/form_csv_import.php
|
||||||
echo json_encode (['success' => false, 'message' => lang('Customers.cannot_be_deleted')]);
|
*
|
||||||
}
|
* @return ResponseInterface
|
||||||
}
|
* @noinspection PhpUnused
|
||||||
|
*/
|
||||||
/**
|
public function postImportCsvFile(): ResponseInterface
|
||||||
* Customers import from csv spreadsheet
|
{
|
||||||
*
|
if ($_FILES['file_path']['error'] != UPLOAD_ERR_OK) {
|
||||||
* @return DownloadResponse The template for Customer CSV imports is returned and download forced.
|
return $this->response->setJSON(['success' => false, 'message' => lang('Customers.csv_import_failed')]);
|
||||||
* @noinspection PhpUnused
|
} else {
|
||||||
*/
|
if (($handle = fopen($_FILES['file_path']['tmp_name'], 'r')) !== false) {
|
||||||
public function getCsv(): DownloadResponse
|
// Skip the first row as it's the table description
|
||||||
{
|
fgetcsv($handle);
|
||||||
$name = 'importCustomers.csv';
|
$i = 1;
|
||||||
$data = file_get_contents(WRITEPATH . "uploads/$name");
|
|
||||||
return $this->response->download($name, $data);
|
$failCodes = [];
|
||||||
}
|
|
||||||
|
while (($data = fgetcsv($handle)) !== false) {
|
||||||
/**
|
$consent = $data[3] == '' ? 0 : 1;
|
||||||
* Displays the customer CSV import modal. Used in app/Views/people/manage.php
|
|
||||||
*
|
if (sizeof($data) >= 16 && $consent) {
|
||||||
* @return void
|
$email = strtolower($data[4]);
|
||||||
* @noinspection PhpUnused
|
$person_data = [
|
||||||
*/
|
'first_name' => $data[0],
|
||||||
public function getCsvImport(): void
|
'last_name' => $data[1],
|
||||||
{
|
'gender' => $data[2],
|
||||||
echo view('customers/form_csv_import');
|
'email' => $email,
|
||||||
}
|
'phone_number' => $data[5],
|
||||||
|
'address_1' => $data[6],
|
||||||
/**
|
'address_2' => $data[7],
|
||||||
* Imports a CSV file containing customers. Used in app/Views/customers/form_csv_import.php
|
'city' => $data[8],
|
||||||
*
|
'state' => $data[9],
|
||||||
* @return void
|
'zip' => $data[10],
|
||||||
* @noinspection PhpUnused
|
'country' => $data[11],
|
||||||
*/
|
'comments' => $data[12]
|
||||||
public function postImportCsvFile(): void
|
];
|
||||||
{
|
|
||||||
if($_FILES['file_path']['error'] != UPLOAD_ERR_OK)
|
$customer_data = [
|
||||||
{
|
'consent' => $consent,
|
||||||
echo json_encode (['success' => false, 'message' => lang('Customers.csv_import_failed')]);
|
'company_name' => $data[13],
|
||||||
}
|
'discount' => $data[15],
|
||||||
else
|
'discount_type' => $data[16],
|
||||||
{
|
'taxable' => $data[17] == '' ? 0 : 1,
|
||||||
if(($handle = fopen($_FILES['file_path']['tmp_name'], 'r')) !== false)
|
'date' => date('Y-m-d H:i:s'),
|
||||||
{
|
'employee_id' => $this->employee->get_logged_in_employee_info()->person_id
|
||||||
// Skip the first row as it's the table description
|
];
|
||||||
fgetcsv($handle);
|
$account_number = $data[14];
|
||||||
$i = 1;
|
|
||||||
|
// Don't duplicate people with same email
|
||||||
$failCodes = [];
|
$invalidated = $this->customer->check_email_exists($email);
|
||||||
|
|
||||||
while(($data = fgetcsv($handle)) !== false)
|
if ($account_number != '') {
|
||||||
{
|
$customer_data['account_number'] = $account_number;
|
||||||
$consent = $data[3] == '' ? 0 : 1;
|
$invalidated &= $this->customer->check_account_number_exists($account_number);
|
||||||
|
}
|
||||||
if(sizeof($data) >= 16 && $consent)
|
} else {
|
||||||
{
|
$invalidated = true;
|
||||||
$email = strtolower($data[4]);
|
}
|
||||||
$person_data = [
|
|
||||||
'first_name' => $data[0],
|
if ($invalidated) {
|
||||||
'last_name' => $data[1],
|
$failCodes[] = $i;
|
||||||
'gender' => $data[2],
|
log_message('error', "Row $i was not imported: Either email or account number already exist or data was invalid.");
|
||||||
'email' => $email,
|
} elseif ($this->customer->save_customer($person_data, $customer_data)) {
|
||||||
'phone_number' => $data[5],
|
// Save customer to Mailchimp selected list
|
||||||
'address_1' => $data[6],
|
$this->mailchimp_lib->addOrUpdateMember($this->_list_id, $person_data['email'], $person_data['first_name'], '', $person_data['last_name']);
|
||||||
'address_2' => $data[7],
|
} else {
|
||||||
'city' => $data[8],
|
$failCodes[] = $i;
|
||||||
'state' => $data[9],
|
}
|
||||||
'zip' => $data[10],
|
|
||||||
'country' => $data[11],
|
++$i;
|
||||||
'comments' => $data[12]
|
}
|
||||||
];
|
|
||||||
|
if (count($failCodes) > 0) {
|
||||||
$customer_data = [
|
$message = lang('Customers.csv_import_partially_failed', [count($failCodes), implode(', ', $failCodes)]);
|
||||||
'consent' => $consent,
|
|
||||||
'company_name' => $data[13],
|
return $this->response->setJSON(['success' => false, 'message' => $message]);
|
||||||
'discount' => $data[15],
|
} else {
|
||||||
'discount_type' => $data[16],
|
return $this->response->setJSON(['success' => true, 'message' => lang('Customers.csv_import_success')]);
|
||||||
'taxable' => $data[17] == '' ? 0 : 1,
|
}
|
||||||
'date' => date('Y-m-d H:i:s'),
|
} else {
|
||||||
'employee_id' => $this->employee->get_logged_in_employee_info()->person_id
|
return $this->response->setJSON(['success' => false, 'message' => lang('Customers.csv_import_nodata_wrongformat')]);
|
||||||
];
|
}
|
||||||
$account_number = $data[14];
|
}
|
||||||
|
}
|
||||||
// don't duplicate people with same email
|
|
||||||
$invalidated = $this->customer->check_email_exists($email);
|
|
||||||
|
|
||||||
if($account_number != '')
|
|
||||||
{
|
|
||||||
$customer_data['account_number'] = $account_number;
|
|
||||||
$invalidated &= $this->customer->check_account_number_exists($account_number);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$invalidated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if($invalidated)
|
|
||||||
{
|
|
||||||
$failCodes[] = $i;
|
|
||||||
log_message('error',"Row $i was not imported: Either email or account number already exist or data was invalid.");
|
|
||||||
}
|
|
||||||
elseif($this->customer->save_customer($person_data, $customer_data))
|
|
||||||
{
|
|
||||||
// save customer to Mailchimp selected list
|
|
||||||
$this->mailchimp_lib->addOrUpdateMember($this->_list_id, $person_data['email'], $person_data['first_name'], '', $person_data['last_name']);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$failCodes[] = $i;
|
|
||||||
}
|
|
||||||
|
|
||||||
++$i;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(count($failCodes) > 0)
|
|
||||||
{
|
|
||||||
$message = lang('Customers.csv_import_partially_failed', [count($failCodes), implode(', ', $failCodes)]);
|
|
||||||
|
|
||||||
echo json_encode (['success' => false, 'message' => $message]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
echo json_encode (['success' => true, 'message' => lang('Customers.csv_import_success')]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
echo json_encode (['success' => false, 'message' => lang('Customers.csv_import_nodata_wrongformat')]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Controllers;
|
namespace App\Controllers;
|
||||||
|
|
||||||
use App\Models\Module;
|
use App\Models\Module;
|
||||||
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
use Config\Services;
|
use Config\Services;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -13,222 +14,246 @@ use Config\Services;
|
|||||||
*/
|
*/
|
||||||
class Employees extends Persons
|
class Employees extends Persons
|
||||||
{
|
{
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct('employees');
|
parent::__construct('employees');
|
||||||
|
|
||||||
$this->module = model('Module');
|
$this->module = model('Module');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns employee table data rows. This will be called with AJAX.
|
* Returns employee table data rows. This will be called with AJAX.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function getSearch(): void
|
public function getSearch(): ResponseInterface
|
||||||
{
|
{
|
||||||
$search = $this->request->getGet('search');
|
$search = $this->request->getGet('search');
|
||||||
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$sort = $this->sanitizeSortColumn(PERSON_HEADERS, $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'people.person_id');
|
$sort = $this->sanitizeSortColumn(person_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'people.person_id');
|
||||||
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||||
|
|
||||||
$employees = $this->employee->search($search, $limit, $offset, $sort, $order);
|
$employees = $this->employee->search($search, $limit, $offset, $sort, $order);
|
||||||
$total_rows = $this->employee->get_found_rows($search);
|
$total_rows = $this->employee->get_found_rows($search);
|
||||||
|
|
||||||
$data_rows = [];
|
$data_rows = [];
|
||||||
foreach($employees->getResult() as $person)
|
foreach ($employees->getResult() as $person) {
|
||||||
{
|
$data_rows[] = get_person_data_row($person);
|
||||||
$data_rows[] = get_person_data_row($person);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode (['total' => $total_rows, 'rows' => $data_rows]);
|
return $this->response->setJSON(['total' => $total_rows, 'rows' => $data_rows]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AJAX called function gives search suggestions based on what is being searched for.
|
* AJAX called function gives search suggestions based on what is being searched for.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
public function getSuggest(): void
|
public function getSuggest(): ResponseInterface
|
||||||
{
|
{
|
||||||
$search = $this->request->getGet('term');
|
$search = $this->request->getGet('term');
|
||||||
$suggestions = $this->employee->get_search_suggestions($search, 25, true);
|
$suggestions = $this->employee->get_search_suggestions($search, 25, true);
|
||||||
|
|
||||||
echo json_encode($suggestions);
|
return $this->response->setJSON($suggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
public function suggest_search(): void
|
public function suggest_search(): ResponseInterface
|
||||||
{
|
{
|
||||||
$search = $this->request->getPost('term');
|
$search = $this->request->getPost('term');
|
||||||
$suggestions = $this->employee->get_search_suggestions($search);
|
$suggestions = $this->employee->get_search_suggestions($search);
|
||||||
|
|
||||||
echo json_encode($suggestions);
|
return $this->response->setJSON($suggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the employee edit form
|
* Loads the employee edit form
|
||||||
*/
|
* @return string
|
||||||
public function getView(int $employee_id = NEW_ENTRY): void
|
*/
|
||||||
{
|
public function getView(int $employee_id = NEW_ENTRY): string
|
||||||
$person_info = $this->employee->get_info($employee_id);
|
{
|
||||||
foreach(get_object_vars($person_info) as $property => $value)
|
$person_info = $this->employee->get_info($employee_id);
|
||||||
{
|
$current_user = $this->employee->get_logged_in_employee_info();
|
||||||
$person_info->$property = $value;
|
|
||||||
}
|
|
||||||
$data['person_info'] = $person_info;
|
|
||||||
$data['employee_id'] = $employee_id;
|
|
||||||
|
|
||||||
$modules = [];
|
if ($employee_id != NEW_ENTRY && !$this->employee->canModifyEmployee($person_info->person_id, $current_user->person_id)) {
|
||||||
foreach($this->module->get_all_modules()->getResult() as $module)
|
header('Location: ' . base_url('no_access/employees/employees'));
|
||||||
{
|
exit();
|
||||||
$module->grant = $this->employee->has_grant($module->module_id, $person_info->person_id);
|
}
|
||||||
$module->menu_group = $this->employee->get_menu_group($module->module_id, $person_info->person_id);
|
|
||||||
|
|
||||||
$modules[] = $module;
|
foreach (get_object_vars($person_info) as $property => $value) {
|
||||||
}
|
$person_info->$property = $value;
|
||||||
$data['all_modules'] = $modules;
|
}
|
||||||
|
$data['person_info'] = $person_info;
|
||||||
|
$data['employee_id'] = $employee_id;
|
||||||
|
|
||||||
$permissions = [];
|
$modules = [];
|
||||||
foreach($this->module->get_all_subpermissions()->getResult() as $permission) //TODO: subpermissions does not follow naming standards.
|
foreach ($this->module->get_all_modules()->getResult() as $module) {
|
||||||
{
|
$module->grant = $this->employee->has_grant($module->module_id, $person_info->person_id);
|
||||||
$permission->permission_id = str_replace(' ', '_', $permission->permission_id);
|
$module->menu_group = $this->employee->get_menu_group($module->module_id, $person_info->person_id);
|
||||||
$permission->grant = $this->employee->has_grant($permission->permission_id, $person_info->person_id);
|
|
||||||
|
|
||||||
$permissions[] = $permission;
|
$modules[] = $module;
|
||||||
}
|
}
|
||||||
$data['all_subpermissions'] = $permissions;
|
$data['all_modules'] = $modules;
|
||||||
|
|
||||||
echo view('employees/form', $data);
|
$permissions = [];
|
||||||
}
|
foreach ($this->module->get_all_subpermissions()->getResult() as $permission) { // TODO: subpermissions does not follow naming standards.
|
||||||
|
$permission->permission_id = str_replace(' ', '_', $permission->permission_id);
|
||||||
|
$permission->grant = $this->employee->has_grant($permission->permission_id, $person_info->person_id);
|
||||||
|
|
||||||
/**
|
$permissions[] = $permission;
|
||||||
* Inserts/updates an employee
|
}
|
||||||
*/
|
$data['all_subpermissions'] = $permissions;
|
||||||
public function postSave(int $employee_id = NEW_ENTRY): void
|
|
||||||
{
|
|
||||||
$first_name = $this->request->getPost('first_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS); //TODO: duplicated code
|
|
||||||
$last_name = $this->request->getPost('last_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
|
||||||
$email = strtolower($this->request->getPost('email', FILTER_SANITIZE_EMAIL));
|
|
||||||
|
|
||||||
// format first and last name properly
|
return view('employees/form', $data);
|
||||||
$first_name = $this->nameize($first_name);
|
}
|
||||||
$last_name = $this->nameize($last_name);
|
|
||||||
|
|
||||||
$person_data = [
|
/**
|
||||||
'first_name' => $first_name,
|
* Inserts/updates an employee
|
||||||
'last_name' => $last_name,
|
* @return ResponseInterface
|
||||||
'gender' => $this->request->getPost('gender', FILTER_SANITIZE_NUMBER_INT),
|
*/
|
||||||
'email' => $email,
|
public function postSave(int $employee_id = NEW_ENTRY): ResponseInterface
|
||||||
'phone_number' => $this->request->getPost('phone_number', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
{
|
||||||
'address_1' => $this->request->getPost('address_1', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
$current_user = $this->employee->get_logged_in_employee_info();
|
||||||
'address_2' => $this->request->getPost('address_2', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
|
||||||
'city' => $this->request->getPost('city', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
|
||||||
'state' => $this->request->getPost('state', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
|
||||||
'zip' => $this->request->getPost('zip', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
|
||||||
'country' => $this->request->getPost('country', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
|
||||||
'comments' => $this->request->getPost('comments', FILTER_SANITIZE_FULL_SPECIAL_CHARS)
|
|
||||||
];
|
|
||||||
|
|
||||||
$grants_array = [];
|
if ($employee_id != NEW_ENTRY) {
|
||||||
foreach($this->module->get_all_permissions()->getResult() as $permission)
|
$target_employee = $this->employee->get_info($employee_id);
|
||||||
{
|
if (!$this->employee->canModifyEmployee($target_employee->person_id, $current_user->person_id)) {
|
||||||
$grants = [];
|
return $this->response->setJSON([
|
||||||
$grant = $this->request->getPost('grant_'.$permission->permission_id) != null ? $this->request->getPost('grant_' . $permission->permission_id, FILTER_SANITIZE_FULL_SPECIAL_CHARS) : '';
|
'success' => false,
|
||||||
|
'message' => lang('Employees.error_updating_admin'),
|
||||||
|
'id' => NEW_ENTRY
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if($grant == $permission->permission_id)
|
$first_name = $this->request->getPost('first_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS); // TODO: duplicated code
|
||||||
{
|
$last_name = $this->request->getPost('last_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||||
$grants['permission_id'] = $permission->permission_id;
|
$email = strtolower($this->request->getPost('email', FILTER_SANITIZE_EMAIL));
|
||||||
$grants['menu_group'] = $this->request->getPost('menu_group_'.$permission->permission_id) != null ? $this->request->getPost('menu_group_' . $permission->permission_id, FILTER_SANITIZE_FULL_SPECIAL_CHARS) : '--';
|
|
||||||
$grants_array[] = $grants;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Password has been changed OR first time password set
|
// format first and last name properly
|
||||||
if(!empty($this->request->getPost('password')) && ENVIRONMENT != 'testing')
|
$first_name = $this->nameize($first_name);
|
||||||
{
|
$last_name = $this->nameize($last_name);
|
||||||
$exploded = explode(":", $this->request->getPost('language', FILTER_SANITIZE_FULL_SPECIAL_CHARS));
|
|
||||||
$employee_data = [
|
|
||||||
'username' => $this->request->getPost('username', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
|
||||||
'password' => password_hash($this->request->getPost('password'), PASSWORD_DEFAULT),
|
|
||||||
'hash_version' => 2,
|
|
||||||
'language_code' => $exploded[0],
|
|
||||||
'language' => $exploded[1]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
else //Password not changed
|
|
||||||
{
|
|
||||||
$exploded = explode(":", $this->request->getPost('language', FILTER_SANITIZE_FULL_SPECIAL_CHARS));
|
|
||||||
$employee_data = [
|
|
||||||
'username' => $this->request->getPost('username', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
|
||||||
'language_code' => $exploded[0],
|
|
||||||
'language' => $exploded[1]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if($this->employee->save_employee($person_data, $employee_data, $grants_array, $employee_id))
|
$person_data = [
|
||||||
{
|
'first_name' => $first_name,
|
||||||
// New employee
|
'last_name' => $last_name,
|
||||||
if($employee_id == NEW_ENTRY)
|
'gender' => $this->request->getPost('gender', FILTER_SANITIZE_NUMBER_INT),
|
||||||
{
|
'email' => $email,
|
||||||
echo json_encode ([
|
'phone_number' => $this->request->getPost('phone_number', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
'success' => true,
|
'address_1' => $this->request->getPost('address_1', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
'message' => lang('Employees.successful_adding') . ' ' . $first_name . ' ' . $last_name,
|
'address_2' => $this->request->getPost('address_2', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
'id' => $employee_data['person_id']
|
'city' => $this->request->getPost('city', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
]);
|
'state' => $this->request->getPost('state', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
}
|
'zip' => $this->request->getPost('zip', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
else // Existing employee
|
'country' => $this->request->getPost('country', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
{
|
'comments' => $this->request->getPost('comments', FILTER_SANITIZE_FULL_SPECIAL_CHARS)
|
||||||
echo json_encode ([
|
];
|
||||||
'success' => true,
|
|
||||||
'message' => lang('Employees.successful_updating') . ' ' . $first_name . ' ' . $last_name,
|
|
||||||
'id' => $employee_id
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else // Failure
|
|
||||||
{
|
|
||||||
echo json_encode ([
|
|
||||||
'success' => false,
|
|
||||||
'message' => lang('Employees.error_adding_updating') . ' ' . $first_name . ' ' . $last_name,
|
|
||||||
'id' => NEW_ENTRY
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
$grants_array = [];
|
||||||
* This deletes employees from the employees table
|
$isAdmin = $this->employee->isAdmin($current_user->person_id);
|
||||||
*/
|
|
||||||
public function postDelete(): void
|
|
||||||
{
|
|
||||||
$employees_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
|
||||||
|
|
||||||
if($this->employee->delete_list($employees_to_delete)) //TODO: this is passing a string, but delete_list expects an array
|
foreach ($this->module->get_all_permissions()->getResult() as $permission) {
|
||||||
{
|
$grants = [];
|
||||||
echo json_encode ([
|
$grant = $this->request->getPost('grant_' . $permission->permission_id) != null ? $this->request->getPost('grant_' . $permission->permission_id, FILTER_SANITIZE_FULL_SPECIAL_CHARS) : '';
|
||||||
'success' => true,
|
|
||||||
'message' => lang('Employees.successful_deleted') . ' ' . count($employees_to_delete) . ' ' . lang('Employees.one_or_multiple')
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
echo json_encode (['success' => false, 'message' => lang('Employees.cannot_be_deleted')]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if ($grant == $permission->permission_id) {
|
||||||
* Checks an employee username against the database. Used in app\Views\employees\form.php
|
if (!$isAdmin && !$this->employee->has_grant($permission->permission_id, $current_user->person_id)) {
|
||||||
*
|
continue;
|
||||||
* @param $employee_id
|
}
|
||||||
* @return void
|
$grants['permission_id'] = $permission->permission_id;
|
||||||
* @noinspection PhpUnused
|
$grants['menu_group'] = $this->request->getPost('menu_group_' . $permission->permission_id) != null ? $this->request->getPost('menu_group_' . $permission->permission_id, FILTER_SANITIZE_FULL_SPECIAL_CHARS) : '--';
|
||||||
*/
|
$grants_array[] = $grants;
|
||||||
public function getCheckUsername($employee_id): void
|
}
|
||||||
{
|
}
|
||||||
$exists = $this->employee->username_exists($employee_id, $this->request->getGet('username'));
|
|
||||||
echo !$exists ? 'true' : 'false';
|
// Password has been changed OR first time password set
|
||||||
}
|
if (!empty($this->request->getPost('password')) && ENVIRONMENT != 'testing') {
|
||||||
|
$exploded = explode(":", $this->request->getPost('language', FILTER_SANITIZE_FULL_SPECIAL_CHARS));
|
||||||
|
$employee_data = [
|
||||||
|
'username' => $this->request->getPost('username', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
|
'password' => password_hash($this->request->getPost('password'), PASSWORD_DEFAULT),
|
||||||
|
'hash_version' => 2,
|
||||||
|
'language_code' => $exploded[0],
|
||||||
|
'language' => $exploded[1]
|
||||||
|
];
|
||||||
|
} else { // Password not changed
|
||||||
|
$exploded = explode(":", $this->request->getPost('language', FILTER_SANITIZE_FULL_SPECIAL_CHARS));
|
||||||
|
$employee_data = [
|
||||||
|
'username' => $this->request->getPost('username', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
|
'language_code' => $exploded[0],
|
||||||
|
'language' => $exploded[1]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->employee->save_employee($person_data, $employee_data, $grants_array, $employee_id)) {
|
||||||
|
// New employee
|
||||||
|
if ($employee_id == NEW_ENTRY) {
|
||||||
|
return $this->response->setJSON([
|
||||||
|
'success' => true,
|
||||||
|
'message' => lang('Employees.successful_adding') . ' ' . $first_name . ' ' . $last_name,
|
||||||
|
'id' => $employee_data['person_id']
|
||||||
|
]);
|
||||||
|
} else { // Existing employee
|
||||||
|
$logged_in_employee_id = session()->get('person_id');
|
||||||
|
if ($employee_id == $logged_in_employee_id) {
|
||||||
|
session()->set('language_code', $employee_data['language_code']);
|
||||||
|
session()->set('language', $employee_data['language']);
|
||||||
|
}
|
||||||
|
return $this->response->setJSON([
|
||||||
|
'success' => true,
|
||||||
|
'message' => lang('Employees.successful_updating') . ' ' . $first_name . ' ' . $last_name,
|
||||||
|
'id' => $employee_id
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else { // Failure
|
||||||
|
return $this->response->setJSON([
|
||||||
|
'success' => false,
|
||||||
|
'message' => lang('Employees.error_adding_updating') . ' ' . $first_name . ' ' . $last_name,
|
||||||
|
'id' => NEW_ENTRY
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This deletes employees from the employees table
|
||||||
|
* @return ResponseInterface
|
||||||
|
*/
|
||||||
|
public function postDelete(): ResponseInterface
|
||||||
|
{
|
||||||
|
$employees_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||||
|
$current_user = $this->employee->get_logged_in_employee_info();
|
||||||
|
|
||||||
|
if (!$this->employee->isAdmin($current_user->person_id)) {
|
||||||
|
foreach ($employees_to_delete as $emp_id) {
|
||||||
|
if ($this->employee->isAdmin((int)$emp_id)) {
|
||||||
|
return $this->response->setJSON(['success' => false, 'message' => lang('Employees.error_deleting_admin')]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->employee->delete_list($employees_to_delete)) { // TODO: this is passing a string, but delete_list expects an array
|
||||||
|
return $this->response->setJSON([
|
||||||
|
'success' => true,
|
||||||
|
'message' => lang('Employees.successful_deleted') . ' ' . count($employees_to_delete) . ' ' . lang('Employees.one_or_multiple')
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
return $this->response->setJSON(['success' => false, 'message' => lang('Employees.cannot_be_deleted')]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks an employee username against the database. Used in app\Views\employees\form.php
|
||||||
|
*
|
||||||
|
* @param $employee_id
|
||||||
|
* @return ResponseInterface
|
||||||
|
* @noinspection PhpUnused
|
||||||
|
*/
|
||||||
|
public function getCheckUsername($employee_id): ResponseInterface
|
||||||
|
{
|
||||||
|
$exists = $this->employee->username_exists($employee_id, $this->request->getGet('username'));
|
||||||
|
return $this->response->setJSON(!$exists ? 'true' : 'false');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,206 +4,212 @@ namespace App\Controllers;
|
|||||||
|
|
||||||
use App\Models\Expense;
|
use App\Models\Expense;
|
||||||
use App\Models\Expense_category;
|
use App\Models\Expense_category;
|
||||||
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
use Config\OSPOS;
|
use Config\OSPOS;
|
||||||
use Config\Services;
|
use Config\Services;
|
||||||
|
|
||||||
class Expenses extends Secure_Controller
|
class Expenses extends Secure_Controller
|
||||||
{
|
{
|
||||||
private Expense $expense;
|
private Expense $expense;
|
||||||
private Expense_category $expense_category;
|
private Expense_category $expense_category;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct('expenses');
|
parent::__construct('expenses');
|
||||||
|
|
||||||
$this->expense = model(Expense::class);
|
$this->expense = model(Expense::class);
|
||||||
$this->expense_category = model(Expense_category::class);
|
$this->expense_category = model(Expense_category::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function getIndex(): void
|
public function getIndex(): string
|
||||||
{
|
{
|
||||||
$data['table_headers'] = get_expenses_manage_table_headers();
|
$data['table_headers'] = get_expenses_manage_table_headers();
|
||||||
|
|
||||||
// filters that will be loaded in the multiselect dropdown
|
// filters that will be loaded in the multiselect dropdown
|
||||||
$data['filters'] = [
|
$data['filters'] = [
|
||||||
'only_cash' => lang('Expenses.cash_filter'),
|
'only_cash' => lang('Expenses.cash_filter'),
|
||||||
'only_due' => lang('Expenses.due_filter'),
|
'only_due' => lang('Expenses.due_filter'),
|
||||||
'only_check' => lang('Expenses.check_filter'),
|
'only_check' => lang('Expenses.check_filter'),
|
||||||
'only_credit' => lang('Expenses.credit_filter'),
|
'only_credit' => lang('Expenses.credit_filter'),
|
||||||
'only_debit' => lang('Expenses.debit_filter'),
|
'only_debit' => lang('Expenses.debit_filter'),
|
||||||
'is_deleted' => lang('Expenses.is_deleted')
|
'is_deleted' => lang('Expenses.is_deleted')
|
||||||
];
|
];
|
||||||
|
|
||||||
echo view('expenses/manage', $data);
|
// Restore filters from URL
|
||||||
}
|
$data = array_merge($data, restoreTableFilters($this->request));
|
||||||
|
|
||||||
/**
|
return view('expenses/manage', $data);
|
||||||
* @return void
|
}
|
||||||
*/
|
|
||||||
public function getSearch(): void
|
|
||||||
{
|
|
||||||
$search = $this->request->getGet('search');
|
|
||||||
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
|
||||||
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
|
||||||
$sort = $this->sanitizeSortColumn(EXPENSE_HEADERS, $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'expense_id');
|
|
||||||
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
|
||||||
$filters = [
|
|
||||||
'start_date' => $this->request->getGet('start_date', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
|
||||||
'end_date' => $this->request->getGet('end_date', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
|
||||||
'only_cash' => false,
|
|
||||||
'only_due' => false,
|
|
||||||
'only_check' => false,
|
|
||||||
'only_credit' => false,
|
|
||||||
'only_debit' => false,
|
|
||||||
'is_deleted' => false
|
|
||||||
];
|
|
||||||
|
|
||||||
// check if any filter is set in the multiselect dropdown
|
/**
|
||||||
$request_filters = array_fill_keys($this->request->getGet('filters', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? [], true);
|
* @return void
|
||||||
$filters = array_merge($filters, $request_filters);
|
*/
|
||||||
$expenses = $this->expense->search($search, $filters, $limit, $offset, $sort, $order);
|
public function getSearch(): ResponseInterface
|
||||||
$total_rows = $this->expense->get_found_rows($search, $filters);
|
{
|
||||||
$payments = $this->expense->get_payments_summary($search, $filters);
|
$search = $this->request->getGet('search');
|
||||||
$payment_summary = get_expenses_manage_payments_summary($payments, $expenses);
|
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$data_rows = [];
|
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
||||||
|
$sort = $this->sanitizeSortColumn(expense_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'expense_id');
|
||||||
|
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||||
|
$filters = [
|
||||||
|
'start_date' => $this->request->getGet('start_date', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
|
'end_date' => $this->request->getGet('end_date', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
|
'only_cash' => false,
|
||||||
|
'only_due' => false,
|
||||||
|
'only_check' => false,
|
||||||
|
'only_credit' => false,
|
||||||
|
'only_debit' => false,
|
||||||
|
'is_deleted' => false
|
||||||
|
];
|
||||||
|
|
||||||
foreach($expenses->getResult() as $expense)
|
// Check if any filter is set in the multiselect dropdown
|
||||||
{
|
$request_filters = array_fill_keys($this->request->getGet('filters', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? [], true);
|
||||||
$data_rows[] = get_expenses_data_row($expense);
|
$filters = array_merge($filters, $request_filters);
|
||||||
}
|
$expenses = $this->expense->search($search, $filters, $limit, $offset, $sort, $order);
|
||||||
|
$total_rows = $this->expense->get_found_rows($search, $filters);
|
||||||
|
$payments = $this->expense->get_payments_summary($search, $filters);
|
||||||
|
$payment_summary = get_expenses_manage_payments_summary($payments, $expenses);
|
||||||
|
$data_rows = [];
|
||||||
|
|
||||||
if($total_rows > 0)
|
foreach ($expenses->getResult() as $expense) {
|
||||||
{
|
$data_rows[] = get_expenses_data_row($expense);
|
||||||
$data_rows[] = get_expenses_data_last_row($expenses);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode (['total' => $total_rows, 'rows' => $data_rows, 'payment_summary' => $payment_summary]);
|
if ($total_rows > 0) {
|
||||||
}
|
$data_rows[] = get_expenses_data_last_row($expenses);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
return $this->response->setJSON(['total' => $total_rows, 'rows' => $data_rows, 'payment_summary' => $payment_summary]);
|
||||||
* @param int $expense_id
|
}
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function getView(int $expense_id = NEW_ENTRY): void
|
|
||||||
{
|
|
||||||
$data = []; //TODO: Duplicated code
|
|
||||||
|
|
||||||
$data['employees'] = [];
|
/**
|
||||||
foreach($this->employee->get_all()->getResult() as $employee)
|
* @param int $expense_id
|
||||||
{
|
* @return void
|
||||||
foreach(get_object_vars($employee) as $property => $value)
|
*/
|
||||||
{
|
public function getView(int $expense_id = NEW_ENTRY): string
|
||||||
$employee->$property = $value;
|
{
|
||||||
}
|
$data = []; // TODO: Duplicated code
|
||||||
|
|
||||||
$data['employees'][$employee->person_id] = $employee->first_name . ' ' . $employee->last_name;
|
$data['expenses_info'] = $this->expense->get_info($expense_id);
|
||||||
}
|
$expense_id = $data['expenses_info']->expense_id;
|
||||||
|
|
||||||
$data['expenses_info'] = $this->expense->get_info($expense_id);
|
$current_employee_id = $this->employee->get_logged_in_employee_info()->person_id;
|
||||||
|
$can_assign_employee = $this->employee->has_grant('employees', $current_employee_id);
|
||||||
|
|
||||||
$expense_categories = [];
|
$data['employees'] = [];
|
||||||
foreach($this->expense_category->get_all(0, 0, true)->getResultArray() as $row)
|
if ($can_assign_employee) {
|
||||||
{
|
foreach ($this->employee->get_all()->getResult() as $employee) {
|
||||||
$expense_categories[$row['expense_category_id']] = $row['category_name'];
|
$data['employees'][$employee->person_id] = $employee->first_name . ' ' . $employee->last_name;
|
||||||
}
|
}
|
||||||
$data['expense_categories'] = $expense_categories;
|
} else {
|
||||||
|
$stored_employee_id = $expense_id == NEW_ENTRY ? $current_employee_id : $data['expenses_info']->employee_id;
|
||||||
|
$stored_employee = $this->employee->get_info($stored_employee_id);
|
||||||
|
$data['employees'][$stored_employee_id] = $stored_employee->first_name . ' ' . $stored_employee->last_name;
|
||||||
|
}
|
||||||
|
$data['can_assign_employee'] = $can_assign_employee;
|
||||||
|
|
||||||
$expense_id = $data['expenses_info']->expense_id;
|
$expense_categories = [];
|
||||||
|
foreach ($this->expense_category->get_all(0, 0, true)->getResultArray() as $row) {
|
||||||
|
$expense_categories[$row['expense_category_id']] = $row['category_name'];
|
||||||
|
}
|
||||||
|
$data['expense_categories'] = $expense_categories;
|
||||||
|
|
||||||
if($expense_id == NEW_ENTRY)
|
if ($expense_id == NEW_ENTRY) {
|
||||||
{
|
$data['expenses_info']->date = date('Y-m-d H:i:s');
|
||||||
$data['expenses_info']->date = date('Y-m-d H:i:s');
|
$data['expenses_info']->employee_id = $current_employee_id;
|
||||||
$data['expenses_info']->employee_id = $this->employee->get_logged_in_employee_info()->person_id;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$data['payments'] = [];
|
$data['payments'] = [];
|
||||||
foreach($this->expense->get_expense_payment($expense_id)->getResult() as $payment)
|
foreach ($this->expense->get_expense_payment($expense_id)->getResult() as $payment) {
|
||||||
{
|
foreach (get_object_vars($payment) as $property => $value) {
|
||||||
foreach(get_object_vars($payment) as $property => $value)
|
$payment->$property = $value;
|
||||||
{
|
}
|
||||||
$payment->$property = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data['payments'][] = $payment;
|
$data['payments'][] = $payment;
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't allow gift card to be a payment option in a sale transaction edit because it's a complex change
|
// Don't allow gift card to be a payment option in a sale transaction edit because it's a complex change
|
||||||
$data['payment_options'] = $this->expense->get_payment_options();
|
$data['payment_options'] = $this->expense->get_payment_options();
|
||||||
|
|
||||||
echo view("expenses/form", $data);
|
return view("expenses/form", $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $row_id
|
* @param int $row_id
|
||||||
* @return void
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
public function getRow(int $row_id): void
|
public function getRow(int $row_id): ResponseInterface
|
||||||
{
|
{
|
||||||
$expense_info = $this->expense->get_info($row_id);
|
$expense_info = $this->expense->get_info($row_id);
|
||||||
$data_row = get_expenses_data_row($expense_info);
|
$data_row = get_expenses_data_row($expense_info);
|
||||||
|
|
||||||
echo json_encode($data_row);
|
return $this->response->setJSON($data_row);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $expense_id
|
* @param int $expense_id
|
||||||
* @return void
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
public function postSave(int $expense_id = NEW_ENTRY): void
|
public function postSave(int $expense_id = NEW_ENTRY): ResponseInterface
|
||||||
{
|
{
|
||||||
$config = config(OSPOS::class)->settings;
|
$config = config(OSPOS::class)->settings;
|
||||||
$newdate = $this->request->getPost('date', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
$newdate = $this->request->getPost('date', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||||
|
|
||||||
$date_formatter = date_create_from_format($config['dateformat'] . ' ' . $config['timeformat'], $newdate);
|
$date_formatter = date_create_from_format($config['dateformat'] . ' ' . $config['timeformat'], $newdate);
|
||||||
|
|
||||||
$expense_data = [
|
$current_employee_id = $this->employee->get_logged_in_employee_info()->person_id;
|
||||||
'date' => $date_formatter->format('Y-m-d H:i:s'),
|
$submitted_employee_id = $this->request->getPost('employee_id', FILTER_SANITIZE_NUMBER_INT);
|
||||||
'supplier_id' => $this->request->getPost('supplier_id') == '' ? null : $this->request->getPost('supplier_id', FILTER_SANITIZE_NUMBER_INT),
|
|
||||||
'supplier_tax_code' => $this->request->getPost('supplier_tax_code', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
|
||||||
'amount' => parse_decimals($this->request->getPost('amount')),
|
|
||||||
'tax_amount' => parse_decimals($this->request->getPost('tax_amount')),
|
|
||||||
'payment_type' => $this->request->getPost('payment_type', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
|
||||||
'expense_category_id' => $this->request->getPost('expense_category_id', FILTER_SANITIZE_NUMBER_INT),
|
|
||||||
'description' => $this->request->getPost('description', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
|
||||||
'employee_id' => $this->request->getPost('employee_id', FILTER_SANITIZE_NUMBER_INT),
|
|
||||||
'deleted' => $this->request->getPost('deleted') != null
|
|
||||||
];
|
|
||||||
|
|
||||||
if($this->expense->save_value($expense_data, $expense_id))
|
if (!$this->employee->has_grant('employees', $current_employee_id)) {
|
||||||
{
|
if ($expense_id == NEW_ENTRY) {
|
||||||
//New Expense
|
$employee_id = $current_employee_id;
|
||||||
if($expense_id == NEW_ENTRY)
|
} else {
|
||||||
{
|
$existing_expense = $this->expense->get_info($expense_id);
|
||||||
echo json_encode (['success' => true, 'message' => lang('Expenses.successful_adding'), 'id' => $expense_data['expense_id']]);
|
$employee_id = $existing_expense->employee_id;
|
||||||
}
|
}
|
||||||
else // Existing Expense
|
} else {
|
||||||
{
|
$employee_id = $submitted_employee_id;
|
||||||
echo json_encode (['success' => true, 'message' => lang('Expenses.successful_updating'), 'id' => $expense_id]);
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
else//failure
|
|
||||||
{
|
|
||||||
echo json_encode (['success' => false, 'message' => lang('Expenses.error_adding_updating'), 'id' => NEW_ENTRY]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
$expense_data = [
|
||||||
* @return void
|
'date' => $date_formatter->format('Y-m-d H:i:s'),
|
||||||
*/
|
'supplier_id' => $this->request->getPost('supplier_id') == '' ? null : $this->request->getPost('supplier_id', FILTER_SANITIZE_NUMBER_INT),
|
||||||
public function postDelete(): void
|
'supplier_tax_code' => $this->request->getPost('supplier_tax_code', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
{
|
'amount' => parse_decimals($this->request->getPost('amount')),
|
||||||
$expenses_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
'tax_amount' => parse_decimals($this->request->getPost('tax_amount')),
|
||||||
|
'payment_type' => $this->request->getPost('payment_type', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
|
'expense_category_id' => $this->request->getPost('expense_category_id', FILTER_SANITIZE_NUMBER_INT),
|
||||||
|
'description' => $this->request->getPost('description', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
|
'employee_id' => $employee_id,
|
||||||
|
'deleted' => $this->request->getPost('deleted') != null
|
||||||
|
];
|
||||||
|
|
||||||
if($this->expense->delete_list($expenses_to_delete))
|
if ($this->expense->save_value($expense_data, $expense_id)) {
|
||||||
{
|
// New Expense
|
||||||
echo json_encode (['success' => true, 'message' => lang('Expenses.successful_deleted') . ' ' . count($expenses_to_delete) . ' ' . lang('Expenses.one_or_multiple'), 'ids' => $expenses_to_delete]);
|
if ($expense_id == NEW_ENTRY) {
|
||||||
}
|
return $this->response->setJSON(['success' => true, 'message' => lang('Expenses.successful_adding'), 'id' => $expense_data['expense_id']]);
|
||||||
else
|
} else { // Existing Expense
|
||||||
{
|
return $this->response->setJSON(['success' => true, 'message' => lang('Expenses.successful_updating'), 'id' => $expense_id]);
|
||||||
echo json_encode (['success' => false, 'message' => lang('Expenses.cannot_be_deleted'), 'ids' => $expenses_to_delete]);
|
}
|
||||||
}
|
} else { // Failure
|
||||||
}
|
return $this->response->setJSON(['success' => false, 'message' => lang('Expenses.error_adding_updating'), 'id' => NEW_ENTRY]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ResponseInterface
|
||||||
|
*/
|
||||||
|
public function postDelete(): ResponseInterface
|
||||||
|
{
|
||||||
|
$expenses_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||||
|
|
||||||
|
if ($this->expense->delete_list($expenses_to_delete)) {
|
||||||
|
return $this->response->setJSON(['success' => true, 'message' => lang('Expenses.successful_deleted') . ' ' . count($expenses_to_delete) . ' ' . lang('Expenses.one_or_multiple'), 'ids' => $expenses_to_delete]);
|
||||||
|
} else {
|
||||||
|
return $this->response->setJSON(['success' => false, 'message' => lang('Expenses.cannot_be_deleted'), 'ids' => $expenses_to_delete]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,132 +3,123 @@
|
|||||||
namespace App\Controllers;
|
namespace App\Controllers;
|
||||||
|
|
||||||
use App\Models\Expense_category;
|
use App\Models\Expense_category;
|
||||||
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
use Config\Services;
|
use Config\Services;
|
||||||
|
|
||||||
class Expenses_categories extends Secure_Controller //TODO: Is this class ever used?
|
class Expenses_categories extends Secure_Controller // TODO: Is this class ever used?
|
||||||
{
|
{
|
||||||
private Expense_category $expense_category;
|
private Expense_category $expense_category;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct('expenses_categories');
|
parent::__construct('expenses_categories');
|
||||||
|
|
||||||
$this->expense_category = model(Expense_category::class);
|
$this->expense_category = model(Expense_category::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function getIndex(): void
|
public function getIndex(): string
|
||||||
{
|
{
|
||||||
$data['table_headers'] = get_expense_category_manage_table_headers();
|
$data['table_headers'] = get_expense_category_manage_table_headers();
|
||||||
|
|
||||||
echo view('expenses_categories/manage', $data);
|
return view('expenses_categories/manage', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns expense_category_manage table data rows. This will be called with AJAX.
|
* Returns expense_category_manage table data rows. This will be called with AJAX.
|
||||||
**/
|
**/
|
||||||
public function getSearch(): void
|
public function getSearch(): ResponseInterface
|
||||||
{
|
{
|
||||||
$search = $this->request->getGet('search');
|
$search = $this->request->getGet('search');
|
||||||
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$sort = $this->sanitizeSortColumn(EXPENSE_CATEGORY_HEADERS, $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'expense_category_id');
|
$sort = $this->sanitizeSortColumn(expense_category_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'expense_category_id');
|
||||||
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||||
|
|
||||||
$expense_categories = $this->expense_category->search($search, $limit, $offset, $sort, $order);
|
$expense_categories = $this->expense_category->search($search, $limit, $offset, $sort, $order);
|
||||||
$total_rows = $this->expense_category->get_found_rows($search);
|
$total_rows = $this->expense_category->get_found_rows($search);
|
||||||
|
|
||||||
$data_rows = [];
|
$data_rows = [];
|
||||||
foreach($expense_categories->getResult() as $expense_category)
|
foreach ($expense_categories->getResult() as $expense_category) {
|
||||||
{
|
$data_rows[] = get_expense_category_data_row($expense_category);
|
||||||
$data_rows[] = get_expense_category_data_row($expense_category);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode (['total' => $total_rows, 'rows' => $data_rows]);
|
return $this->response->setJSON(['total' => $total_rows, 'rows' => $data_rows]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $row_id
|
* @param int $row_id
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function getRow(int $row_id): void
|
public function getRow(int $row_id): ResponseInterface
|
||||||
{
|
{
|
||||||
$data_row = get_expense_category_data_row($this->expense_category->get_info($row_id));
|
$data_row = get_expense_category_data_row($this->expense_category->get_info($row_id));
|
||||||
|
|
||||||
echo json_encode($data_row);
|
return $this->response->setJSON($data_row);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $expense_category_id
|
* @param int $expense_category_id
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function getView(int $expense_category_id = NEW_ENTRY): void
|
public function getView(int $expense_category_id = NEW_ENTRY): string
|
||||||
{
|
{
|
||||||
$data['category_info'] = $this->expense_category->get_info($expense_category_id);
|
$data['category_info'] = $this->expense_category->get_info($expense_category_id);
|
||||||
|
|
||||||
echo view("expenses_categories/form", $data);
|
return view("expenses_categories/form", $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $expense_category_id
|
* @param int $expense_category_id
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function postSave(int $expense_category_id = NEW_ENTRY): void
|
public function postSave(int $expense_category_id = NEW_ENTRY): ResponseInterface
|
||||||
{
|
{
|
||||||
$expense_category_data = [
|
$expense_category_data = [
|
||||||
'category_name' => $this->request->getPost('category_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
'category_name' => $this->request->getPost('category_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
'category_description' => $this->request->getPost('category_description', FILTER_SANITIZE_FULL_SPECIAL_CHARS)
|
'category_description' => $this->request->getPost('category_description', FILTER_SANITIZE_FULL_SPECIAL_CHARS)
|
||||||
];
|
];
|
||||||
|
|
||||||
if($this->expense_category->save_value($expense_category_data, $expense_category_id))
|
if ($this->expense_category->save_value($expense_category_data, $expense_category_id)) {
|
||||||
{
|
// New expense_category
|
||||||
// New expense_category
|
if ($expense_category_id == NEW_ENTRY) {
|
||||||
if($expense_category_id == NEW_ENTRY)
|
return $this->response->setJSON([
|
||||||
{
|
'success' => true,
|
||||||
echo json_encode ([
|
'message' => lang('Expenses_categories.successful_adding'),
|
||||||
'success' => true,
|
'id' => $expense_category_data['expense_category_id']
|
||||||
'message' => lang('Expenses_categories.successful_adding'),
|
]);
|
||||||
'id' => $expense_category_data['expense_category_id']
|
} else { // Existing Expense Category
|
||||||
]);
|
return $this->response->setJSON([
|
||||||
}
|
'success' => true,
|
||||||
else // Existing Expense Category
|
'message' => lang('Expenses_categories.successful_updating'),
|
||||||
{
|
'id' => $expense_category_id
|
||||||
echo json_encode ([
|
]);
|
||||||
'success' => true,
|
}
|
||||||
'message' => lang('Expenses_categories.successful_updating'),
|
} else { // Failure
|
||||||
'id' => $expense_category_id
|
return $this->response->setJSON([
|
||||||
]);
|
'success' => true,
|
||||||
}
|
'message' => lang('Expenses_categories.error_adding_updating') . ' ' . $expense_category_data['category_name'],
|
||||||
}
|
'id' => NEW_ENTRY
|
||||||
else//failure
|
]);
|
||||||
{
|
}
|
||||||
echo json_encode ([
|
}
|
||||||
'success' => true,
|
|
||||||
'message' => lang('Expenses_categories.error_adding_updating') . ' ' . $expense_category_data['category_name'],
|
|
||||||
'id' => NEW_ENTRY
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function postDelete(): void
|
public function postDelete(): ResponseInterface
|
||||||
{
|
{
|
||||||
$expense_category_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
$expense_category_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||||
|
|
||||||
if($this->expense_category->delete_list($expense_category_to_delete)) //TODO: Convert to ternary notation.
|
if ($this->expense_category->delete_list($expense_category_to_delete)) { // TODO: Convert to ternary notation.
|
||||||
{
|
return $this->response->setJSON([
|
||||||
echo json_encode([
|
'success' => true,
|
||||||
'success' => true,
|
'message' => lang('Expenses_categories.successful_deleted') . ' ' . count($expense_category_to_delete) . ' ' . lang('Expenses_categories.one_or_multiple')
|
||||||
'message' => lang('Expenses_categories.successful_deleted') . ' ' . count($expense_category_to_delete) . ' ' . lang('Expenses_categories.one_or_multiple')
|
]);
|
||||||
]);
|
} else {
|
||||||
}
|
return $this->response->setJSON(['success' => false, 'message' => lang('Expenses_categories.cannot_be_deleted')]);
|
||||||
else
|
}
|
||||||
{
|
}
|
||||||
echo json_encode (['success' => false, 'message' => lang('Expenses_categories.cannot_be_deleted')]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,196 +3,186 @@
|
|||||||
namespace App\Controllers;
|
namespace App\Controllers;
|
||||||
|
|
||||||
use App\Models\Giftcard;
|
use App\Models\Giftcard;
|
||||||
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
use Config\OSPOS;
|
use Config\OSPOS;
|
||||||
use Config\Services;
|
use Config\Services;
|
||||||
|
|
||||||
class Giftcards extends Secure_Controller
|
class Giftcards extends Secure_Controller
|
||||||
{
|
{
|
||||||
private Giftcard $giftcard;
|
private Giftcard $giftcard;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct('giftcards');
|
parent::__construct('giftcards');
|
||||||
|
|
||||||
$this->giftcard = model(Giftcard::class);
|
$this->giftcard = model(Giftcard::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getIndex(): void
|
public function getIndex(): string
|
||||||
{
|
{
|
||||||
$data['table_headers'] = get_giftcards_manage_table_headers();
|
$data['table_headers'] = get_giftcards_manage_table_headers();
|
||||||
|
|
||||||
echo view('giftcards/manage', $data);
|
return view('giftcards/manage', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns Giftcards table data rows. This will be called with AJAX.
|
* Returns Giftcards table data rows. This will be called with AJAX.
|
||||||
*/
|
*/
|
||||||
public function getSearch(): void
|
public function getSearch(): ResponseInterface
|
||||||
{
|
{
|
||||||
$search = $this->request->getGet('search');
|
$search = $this->request->getGet('search');
|
||||||
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$sort = $this->sanitizeSortColumn(GIFTCARD_HEADERS, $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'giftcard_id');
|
$sort = $this->sanitizeSortColumn(giftcard_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'giftcard_id');
|
||||||
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||||
|
|
||||||
$giftcards = $this->giftcard->search($search, $limit, $offset, $sort, $order);
|
$giftcards = $this->giftcard->search($search, $limit, $offset, $sort, $order);
|
||||||
$total_rows = $this->giftcard->get_found_rows($search);
|
$total_rows = $this->giftcard->get_found_rows($search);
|
||||||
|
|
||||||
$data_rows = [];
|
$data_rows = [];
|
||||||
foreach($giftcards->getResult() as $giftcard)
|
foreach ($giftcards->getResult() as $giftcard) {
|
||||||
{
|
$data_rows[] = get_giftcard_data_row($giftcard);
|
||||||
$data_rows[] = get_giftcard_data_row($giftcard);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode (['total' => $total_rows, 'rows' => $data_rows]);
|
return $this->response->setJSON(['total' => $total_rows, 'rows' => $data_rows]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets search suggestions for giftcards. Used in app\Views\sales\register.php
|
* Gets search suggestions for giftcards. Used in app\Views\sales\register.php
|
||||||
*
|
*
|
||||||
* @return void
|
* @return ResponseInterface
|
||||||
* @noinspection PhpUnused
|
* @noinspection PhpUnused
|
||||||
*/
|
*/
|
||||||
public function getSuggest(): void
|
public function getSuggest(): ResponseInterface
|
||||||
{
|
{
|
||||||
$search = $this->request->getGet('term');
|
$search = $this->request->getGet('term');
|
||||||
$suggestions = $this->giftcard->get_search_suggestions($search, true);
|
$suggestions = $this->giftcard->get_search_suggestions($search, true);
|
||||||
|
|
||||||
echo json_encode($suggestions);
|
return $this->response->setJSON($suggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
public function suggest_search(): void
|
public function suggest_search(): ResponseInterface
|
||||||
{
|
{
|
||||||
$search = $this->request->getPost('term');
|
$search = $this->request->getPost('term');
|
||||||
$suggestions = $this->giftcard->get_search_suggestions($search);
|
$suggestions = $this->giftcard->get_search_suggestions($search);
|
||||||
|
|
||||||
echo json_encode($suggestions);
|
return $this->response->setJSON($suggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $row_id
|
* @param int $row_id
|
||||||
* @return void
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
public function getRow(int $row_id): void
|
public function getRow(int $row_id): ResponseInterface
|
||||||
{
|
{
|
||||||
$data_row = get_giftcard_data_row($this->giftcard->get_info($row_id));
|
$data_row = get_giftcard_data_row($this->giftcard->get_info($row_id));
|
||||||
|
|
||||||
echo json_encode($data_row);
|
return $this->response->setJSON($data_row);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $giftcard_id
|
* @param int $giftcard_id
|
||||||
* @return void
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getView(int $giftcard_id = NEW_ENTRY): void
|
public function getView(int $giftcard_id = NEW_ENTRY): string
|
||||||
{
|
{
|
||||||
$config = config(OSPOS::class)->settings;
|
$config = config(OSPOS::class)->settings;
|
||||||
$giftcard_info = $this->giftcard->get_info($giftcard_id);
|
$giftcard_info = $this->giftcard->get_info($giftcard_id);
|
||||||
|
|
||||||
$data['selected_person_name'] = ($giftcard_id > 0 && isset($giftcard_info->person_id)) ? $giftcard_info->first_name . ' ' . $giftcard_info->last_name : '';
|
$data['selected_person_name'] = ($giftcard_id > 0 && isset($giftcard_info->person_id)) ? $giftcard_info->first_name . ' ' . $giftcard_info->last_name : '';
|
||||||
$data['selected_person_id'] = $giftcard_info->person_id;
|
$data['selected_person_id'] = $giftcard_info->person_id;
|
||||||
if($config['giftcard_number'] == 'random')
|
if ($config['giftcard_number'] == 'random') {
|
||||||
{
|
$data['giftcard_number'] = $giftcard_id > 0 ? $giftcard_info->giftcard_number : '';
|
||||||
$data['giftcard_number'] = $giftcard_id > 0 ? $giftcard_info->giftcard_number : '';
|
} else {
|
||||||
}
|
$max_number_obj = $this->giftcard->get_max_number();
|
||||||
else
|
$max_giftnumber = isset($max_number_obj) ? $this->giftcard->get_max_number()->giftcard_number : 0; // TODO: variable does not follow naming standard.
|
||||||
{
|
$data['giftcard_number'] = $giftcard_id > 0 ? $giftcard_info->giftcard_number : $max_giftnumber + 1;
|
||||||
$max_number_obj = $this->giftcard->get_max_number();
|
}
|
||||||
$max_giftnumber = isset($max_number_obj) ? $this->giftcard->get_max_number()->giftcard_number : 0; //TODO: variable does not follow naming standard.
|
$data['giftcard_id'] = $giftcard_id;
|
||||||
$data['giftcard_number'] = $giftcard_id > 0 ? $giftcard_info->giftcard_number : $max_giftnumber + 1;
|
$data['giftcard_value'] = $giftcard_info->value;
|
||||||
}
|
|
||||||
$data['giftcard_id'] = $giftcard_id;
|
|
||||||
$data['giftcard_value'] = $giftcard_info->value;
|
|
||||||
|
|
||||||
echo view("giftcards/form", $data);
|
return view("giftcards/form", $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $giftcard_id
|
* @param int $giftcard_id
|
||||||
* @return void
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
public function postSave(int $giftcard_id = NEW_ENTRY): void
|
public function postSave(int $giftcard_id = NEW_ENTRY): ResponseInterface
|
||||||
{
|
{
|
||||||
$giftcard_number = $this->request->getPost('giftcard_number', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
$giftcard_number = $this->request->getPost('giftcard_number', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||||
|
|
||||||
if($giftcard_id == NEW_ENTRY && trim($giftcard_number) == '')
|
if ($giftcard_id == NEW_ENTRY && trim($giftcard_number) == '') {
|
||||||
{
|
$giftcard_number = $this->giftcard->generate_unique_giftcard_name($giftcard_number);
|
||||||
$giftcard_number = $this->giftcard->generate_unique_giftcard_name($giftcard_number);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$giftcard_data = [
|
$giftcard_data = [
|
||||||
'record_time' => date('Y-m-d H:i:s'),
|
'record_time' => date('Y-m-d H:i:s'),
|
||||||
'giftcard_number' => $giftcard_number,
|
'giftcard_number' => $giftcard_number,
|
||||||
'value' => parse_decimals($this->request->getPost('giftcard_amount')),
|
'value' => parse_decimals($this->request->getPost('giftcard_amount')),
|
||||||
'person_id' => $this->request->getPost('person_id') == '' ? null : $this->request->getPost('person_id', FILTER_SANITIZE_NUMBER_INT)
|
'person_id' => empty($this->request->getPost('person_id')) ? null : $this->request->getPost('person_id', FILTER_SANITIZE_NUMBER_INT)
|
||||||
];
|
];
|
||||||
|
|
||||||
if($this->giftcard->save_value($giftcard_data, $giftcard_id))
|
if ($this->giftcard->save_value($giftcard_data, $giftcard_id)) {
|
||||||
{
|
// New giftcard
|
||||||
//New giftcard
|
if ($giftcard_id == NEW_ENTRY) { // TODO: Constant needed
|
||||||
if($giftcard_id == NEW_ENTRY) //TODO: Constant needed
|
return $this->response->setJSON([
|
||||||
{
|
'success' => true,
|
||||||
echo json_encode ([
|
'message' => lang('Giftcards.successful_adding') . ' ' . $giftcard_data['giftcard_number'],
|
||||||
'success' => true,
|
'id' => $giftcard_data['giftcard_id']
|
||||||
'message' => lang('Giftcards.successful_adding') . ' ' . $giftcard_data['giftcard_number'],
|
]);
|
||||||
'id' => $giftcard_data['giftcard_id']
|
} else { // Existing giftcard
|
||||||
]);
|
return $this->response->setJSON([
|
||||||
}
|
'success' => true,
|
||||||
else //Existing giftcard
|
'message' => lang('Giftcards.successful_updating') . ' ' . $giftcard_data['giftcard_number'],
|
||||||
{
|
'id' => $giftcard_id
|
||||||
echo json_encode ([
|
]);
|
||||||
'success' => true,
|
}
|
||||||
'message' => lang('Giftcards.successful_updating') . ' ' . $giftcard_data['giftcard_number'],
|
} else { // Failure
|
||||||
'id' => $giftcard_id
|
return $this->response->setJSON([
|
||||||
]);
|
'success' => false,
|
||||||
}
|
'message' => lang('Giftcards.error_adding_updating') . ' ' . $giftcard_data['giftcard_number'],
|
||||||
}
|
'id' => NEW_ENTRY
|
||||||
else //failure
|
]);
|
||||||
{
|
}
|
||||||
echo json_encode ([
|
}
|
||||||
'success' => false,
|
|
||||||
'message' => lang('Giftcards.error_adding_updating') . ' ' . $giftcard_data['giftcard_number'],
|
|
||||||
'id' => NEW_ENTRY
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the giftcard number validity. Used in app\Views\giftcards\form.php
|
* Checks the giftcard number validity. Used in app\Views\giftcards\form.php
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
* @noinspection PhpUnused
|
* @noinspection PhpUnused
|
||||||
*/
|
*/
|
||||||
public function postCheckNumberGiftcard(): void
|
public function postCheckNumberGiftcard(): ResponseInterface
|
||||||
{
|
{
|
||||||
$giftcard_amount = parse_decimals($this->request->getPost('giftcard_amount'));
|
$existing_id = $this->request->getPost('giftcard_id', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$parsed_value = filter_var($giftcard_amount, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
|
$giftcard_number = $this->request->getPost('giftcard_number', FILTER_SANITIZE_NUMBER_INT);
|
||||||
echo json_encode (['success' => $parsed_value !== false && $parsed_value > 0 && $giftcard_amount !== false, 'giftcard_amount' => to_currency_no_money($parsed_value)]);
|
$giftcard_id = $this->giftcard->get_giftcard_id($giftcard_number);
|
||||||
}
|
$success = ($giftcard_id == (int) $existing_id || !$giftcard_id );
|
||||||
|
|
||||||
/**
|
return $this->response->setJSON($success ? 'true' : 'false');
|
||||||
* @return void
|
}
|
||||||
*/
|
|
||||||
public function postDelete(): void
|
|
||||||
{
|
|
||||||
$giftcards_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
|
||||||
|
|
||||||
if($this->giftcard->delete_list($giftcards_to_delete))
|
/**
|
||||||
{
|
* @return ResponseInterface
|
||||||
echo json_encode ([
|
*/
|
||||||
'success' => true,
|
public function postDelete(): ResponseInterface
|
||||||
'message' => lang('Giftcards.successful_deleted') . ' ' . count($giftcards_to_delete).' '.lang('Giftcards.one_or_multiple')
|
{
|
||||||
]);
|
$giftcards_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||||
}
|
|
||||||
else
|
if ($this->giftcard->delete_list($giftcards_to_delete)) {
|
||||||
{
|
return $this->response->setJSON([
|
||||||
echo json_encode (['success' => false, 'message' => lang('Giftcards.cannot_be_deleted')]);
|
'success' => true,
|
||||||
}
|
'message' => lang('Giftcards.successful_deleted') . ' ' . count($giftcards_to_delete) . ' ' . lang('Giftcards.one_or_multiple')
|
||||||
}
|
]);
|
||||||
|
} else {
|
||||||
|
return $this->response->setJSON(['success' => false, 'message' => lang('Giftcards.cannot_be_deleted')]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,101 +2,128 @@
|
|||||||
|
|
||||||
namespace App\Controllers;
|
namespace App\Controllers;
|
||||||
|
|
||||||
|
use App\Libraries\MY_Migration;
|
||||||
use CodeIgniter\HTTP\RedirectResponse;
|
use CodeIgniter\HTTP\RedirectResponse;
|
||||||
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
|
|
||||||
class Home extends Secure_Controller
|
class Home extends Secure_Controller
|
||||||
{
|
{
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct('home', null, 'home');
|
parent::__construct('home', null, 'home');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getIndex(): void
|
public function getIndex(): string
|
||||||
{
|
{
|
||||||
$logged_in = $this->employee->is_logged_in();
|
$logged_in = $this->employee->is_logged_in();
|
||||||
echo view('home/home');
|
return view('home/home');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs the currently logged in employee out of the system. Used in app/Views/partial/header.php
|
* Logs the currently logged in employee out of the system. Used in app/Views/partial/header.php
|
||||||
*
|
*
|
||||||
* @return RedirectResponse
|
* @return RedirectResponse
|
||||||
* @noinspection PhpUnused
|
* @noinspection PhpUnused
|
||||||
*/
|
*/
|
||||||
public function getLogout(): RedirectResponse
|
public function getLogout(): RedirectResponse
|
||||||
{
|
{
|
||||||
$this->employee->logout();
|
$this->employee->logout();
|
||||||
return redirect()->to('login');
|
return redirect()->to('login');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load "change employee password" form
|
* Load the "change employee password" form
|
||||||
*
|
*
|
||||||
* @noinspection PhpUnused
|
* @param int $employeeId
|
||||||
*/
|
* @return ResponseInterface|string
|
||||||
public function getChangePassword(int $employee_id = -1): void //TODO: Replace -1 with a constant
|
*/
|
||||||
{
|
public function getChangePassword(int $employeeId = NEW_ENTRY): ResponseInterface|string
|
||||||
$person_info = $this->employee->get_info($employee_id);
|
{
|
||||||
foreach(get_object_vars($person_info) as $property => $value)
|
$loggedInEmployee = $this->employee->get_logged_in_employee_info();
|
||||||
{
|
$currentPersonId = (int) $loggedInEmployee->person_id;
|
||||||
$person_info->$property = $value;
|
|
||||||
}
|
|
||||||
$data['person_info'] = $person_info;
|
|
||||||
|
|
||||||
echo view('home/form_change_password', $data);
|
$employeeId = $employeeId === NEW_ENTRY ? $currentPersonId : $employeeId;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if (!$this->employee->isAdmin($currentPersonId) && $employeeId !== $currentPersonId) {
|
||||||
* Change employee password
|
return $this->response->setStatusCode(403)->setBody(lang('Employees.unauthorized_modify'));
|
||||||
*/
|
}
|
||||||
public function save(int $employee_id = -1): void //TODO: Replace -1 with a constant
|
|
||||||
{
|
|
||||||
if(!empty($this->request->getPost('current_password')) && $employee_id != -1)
|
|
||||||
{
|
|
||||||
if($this->employee->check_password($this->request->getPost('username', FILTER_SANITIZE_FULL_SPECIAL_CHARS), $this->request->getPost('current_password')))
|
|
||||||
{
|
|
||||||
$employee_data = [
|
|
||||||
'username' => $this->request->getPost('username', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
|
||||||
'password' => password_hash($this->request->getPost('password'), PASSWORD_DEFAULT),
|
|
||||||
'hash_version' => 2
|
|
||||||
];
|
|
||||||
|
|
||||||
if($this->employee->change_password($employee_data, $employee_id))
|
$person_info = $this->employee->get_info($employeeId);
|
||||||
{
|
foreach (get_object_vars($person_info) as $property => $value) {
|
||||||
echo json_encode ([
|
$person_info->$property = $value;
|
||||||
'success' => true,
|
}
|
||||||
'message' => lang('Employees.successful_change_password'),
|
$data['person_info'] = $person_info;
|
||||||
'id' => $employee_id
|
|
||||||
]);
|
return view('home/form_change_password', $data);
|
||||||
}
|
}
|
||||||
else//failure
|
|
||||||
{//TODO: Replace -1 with constant
|
/**
|
||||||
echo json_encode ([
|
* Change employee password
|
||||||
'success' => false,
|
*
|
||||||
'message' => lang('Employees.unsuccessful_change_password'),
|
* @return ResponseInterface
|
||||||
'id' => -1
|
*/
|
||||||
]);
|
public function postSave(int $employeeId = NEW_ENTRY): ResponseInterface
|
||||||
}
|
{
|
||||||
}
|
$currentUser = $this->employee->get_logged_in_employee_info();
|
||||||
else
|
$currentPersonId = (int) $currentUser->person_id;
|
||||||
{//TODO: Replace -1 with constant
|
|
||||||
echo json_encode ([
|
$employeeId = $employeeId === NEW_ENTRY ? $currentPersonId : $employeeId;
|
||||||
'success' => false,
|
|
||||||
'message' => lang('Employees.current_password_invalid'),
|
if (!$this->employee->isAdmin($currentPersonId) && $employeeId !== $currentPersonId) {
|
||||||
'id' => -1
|
return $this->response->setStatusCode(403)->setJSON([
|
||||||
]);
|
'success' => false,
|
||||||
}
|
'message' => lang('Employees.unauthorized_modify')
|
||||||
}
|
]);
|
||||||
else
|
}
|
||||||
{//TODO: Replace -1 with constant
|
|
||||||
echo json_encode ([
|
if (!empty($this->request->getPost('current_password')) && $employeeId != NEW_ENTRY) {
|
||||||
'success' => false,
|
if ($this->employee->check_password($this->request->getPost('username', FILTER_SANITIZE_FULL_SPECIAL_CHARS), $this->request->getPost('current_password'))) {
|
||||||
'message' => lang('Employees.current_password_invalid'),
|
// Validate password length BEFORE hashing
|
||||||
'id' => -1
|
$new_password = $this->request->getPost('password');
|
||||||
]);
|
|
||||||
}
|
if (strlen($new_password) < 8) {
|
||||||
}
|
return $this->response->setJSON([
|
||||||
|
'success' => false,
|
||||||
|
'message' => lang('Employees.password_minlength'),
|
||||||
|
'id' => NEW_ENTRY
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$employee_data = [
|
||||||
|
'username' => $this->request->getPost('username', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
|
'password' => password_hash($new_password, PASSWORD_DEFAULT),
|
||||||
|
'hash_version' => 2
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($this->employee->change_password($employee_data, $employeeId)) {
|
||||||
|
return $this->response->setJSON([
|
||||||
|
'success' => true,
|
||||||
|
'message' => lang('Employees.successful_change_password'),
|
||||||
|
'id' => $employeeId
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
return $this->response->setJSON([
|
||||||
|
'success' => false,
|
||||||
|
'message' => lang('Employees.unsuccessful_change_password'),
|
||||||
|
'id' => NEW_ENTRY
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return $this->response->setJSON([
|
||||||
|
'success' => false,
|
||||||
|
'message' => lang('Employees.current_password_invalid'),
|
||||||
|
'id' => NEW_ENTRY
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return $this->response->setJSON([
|
||||||
|
'success' => false,
|
||||||
|
'message' => lang('Employees.current_password_invalid'),
|
||||||
|
'id' => NEW_ENTRY
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,313 +7,289 @@ use App\Libraries\Barcode_lib;
|
|||||||
use App\Models\Item;
|
use App\Models\Item;
|
||||||
use App\Models\Item_kit;
|
use App\Models\Item_kit;
|
||||||
use App\Models\Item_kit_items;
|
use App\Models\Item_kit_items;
|
||||||
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
use Config\Services;
|
use Config\Services;
|
||||||
|
|
||||||
class Item_kits extends Secure_Controller
|
class Item_kits extends Secure_Controller
|
||||||
{
|
{
|
||||||
private Item $item;
|
private Item $item;
|
||||||
private Item_kit $item_kit;
|
private Item_kit $item_kit;
|
||||||
private Item_kit_items $item_kit_items;
|
private Item_kit_items $item_kit_items;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct('item_kits');
|
parent::__construct('item_kits');
|
||||||
|
|
||||||
$this->item = model(Item::class);
|
$this->item = model(Item::class);
|
||||||
$this->item_kit = model(Item_kit::class);
|
$this->item_kit = model(Item_kit::class);
|
||||||
$this->item_kit_items = model(Item_kit_items::class);
|
$this->item_kit_items = model(Item_kit_items::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the total cost and retail price to a passed item_kit retrieving the data from each singular item part of the kit
|
* Add the total cost and retail price to a passed item_kit retrieving the data from each singular item part of the kit
|
||||||
*/
|
*/
|
||||||
private function _add_totals_to_item_kit(object $item_kit): object //TODO: Hungarian notation
|
private function _add_totals_to_item_kit(object $item_kit): object // TODO: Hungarian notation
|
||||||
{
|
{
|
||||||
$kit_item_info = $this->item->get_info($item_kit->kit_item_id ?? $item_kit->item_id);
|
$kit_item_info = $this->item->get_info($item_kit->kit_item_id ?? $item_kit->item_id);
|
||||||
|
|
||||||
$item_kit->total_cost_price = 0;
|
$item_kit->total_cost_price = 0;
|
||||||
$item_kit->total_unit_price = $kit_item_info->unit_price;
|
$item_kit->total_unit_price = $kit_item_info->unit_price;
|
||||||
$total_quantity = 0;
|
$total_quantity = 0;
|
||||||
|
|
||||||
foreach($this->item_kit_items->get_info($item_kit->item_kit_id) as $item_kit_item)
|
foreach ($this->item_kit_items->get_info($item_kit->item_kit_id) as $item_kit_item) {
|
||||||
{
|
$item_info = $this->item->get_info($item_kit_item['item_id']);
|
||||||
$item_info = $this->item->get_info($item_kit_item['item_id']);
|
foreach (get_object_vars($item_info) as $property => $value) {
|
||||||
foreach(get_object_vars($item_info) as $property => $value)
|
$item_info->$property = $value;
|
||||||
{
|
}
|
||||||
$item_info->$property = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
$item_kit->total_cost_price += $item_info->cost_price * $item_kit_item['quantity'];
|
$item_kit->total_cost_price += $item_info->cost_price * $item_kit_item['quantity'];
|
||||||
|
|
||||||
if($item_kit->price_option == PRICE_OPTION_ALL || ($item_kit->price_option == PRICE_OPTION_KIT_STOCK && $item_info->stock_type == HAS_STOCK ))
|
if ($item_kit->price_option == PRICE_OPTION_ALL || ($item_kit->price_option == PRICE_OPTION_KIT_STOCK && $item_info->stock_type == HAS_STOCK)) {
|
||||||
{
|
$item_kit->total_unit_price += $item_info->unit_price * $item_kit_item['quantity'];
|
||||||
$item_kit->total_unit_price += $item_info->unit_price * $item_kit_item['quantity'];
|
$total_quantity += $item_kit_item['quantity'];
|
||||||
$total_quantity += $item_kit_item['quantity'];
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$discount_fraction = bcdiv($item_kit->kit_discount, '100');
|
$discount_fraction = bcdiv($item_kit->kit_discount, '100');
|
||||||
|
|
||||||
$item_kit->total_unit_price = $item_kit->total_unit_price - round(($item_kit->kit_discount_type == PERCENT)
|
$item_kit->total_unit_price = $item_kit->total_unit_price - round(($item_kit->kit_discount_type == PERCENT)
|
||||||
? bcmul($item_kit->total_unit_price, $discount_fraction)
|
? bcmul($item_kit->total_unit_price, $discount_fraction)
|
||||||
: $item_kit->kit_discount, totals_decimals(), PHP_ROUND_HALF_UP);
|
: $item_kit->kit_discount, totals_decimals(), PHP_ROUND_HALF_UP);
|
||||||
|
|
||||||
return $item_kit;
|
return $item_kit;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getIndex(): void
|
public function getIndex(): string
|
||||||
{
|
{
|
||||||
$data['table_headers'] = get_item_kits_manage_table_headers();
|
$data['table_headers'] = get_item_kits_manage_table_headers();
|
||||||
|
|
||||||
echo view('item_kits/manage', $data);
|
return view('item_kits/manage', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns Item_kit table data rows. This will be called with AJAX.
|
* Returns Item_kit table data rows. This will be called with AJAX.
|
||||||
*/
|
*/
|
||||||
public function getSearch(): void
|
public function getSearch(): ResponseInterface
|
||||||
{
|
{
|
||||||
$search = $this->request->getGet('search') ?? '';
|
$search = $this->request->getGet('search') ?? '';
|
||||||
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$sort = $this->sanitizeSortColumn(ITEM_KIT_HEADERS, $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'item_kit_id');
|
$sort = $this->sanitizeSortColumn(item_kit_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'item_kit_id');
|
||||||
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||||
|
|
||||||
$item_kits = $this->item_kit->search($search, $limit, $offset, $sort, $order);
|
$item_kits = $this->item_kit->search($search, $limit, $offset, $sort, $order);
|
||||||
$total_rows = $this->item_kit->get_found_rows($search);
|
$total_rows = $this->item_kit->get_found_rows($search);
|
||||||
|
|
||||||
$data_rows = [];
|
$data_rows = [];
|
||||||
foreach($item_kits->getResult() as $item_kit)
|
foreach ($item_kits->getResult() as $item_kit) {
|
||||||
{
|
// Calculate the total cost and retail price of the Kit, so it can be printed out in the manage table
|
||||||
// calculate the total cost and retail price of the Kit, so it can be printed out in the manage table
|
$item_kit = $this->_add_totals_to_item_kit($item_kit);
|
||||||
$item_kit = $this->_add_totals_to_item_kit($item_kit);
|
$data_rows[] = get_item_kit_data_row($item_kit);
|
||||||
$data_rows[] = get_item_kit_data_row($item_kit);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode (['total' => $total_rows, 'rows' => $data_rows]);
|
return $this->response->setJSON(['total' => $total_rows, 'rows' => $data_rows]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
public function suggest_search(): void
|
public function suggest_search(): ResponseInterface
|
||||||
{
|
{
|
||||||
$search = $this->request->getPost('term');
|
$search = $this->request->getPost('term');
|
||||||
$suggestions = $this->item_kit->get_search_suggestions($search);
|
$suggestions = $this->item_kit->get_search_suggestions($search);
|
||||||
|
|
||||||
echo json_encode($suggestions);
|
return $this->response->setJSON($suggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $row_id
|
* @param int $row_id
|
||||||
* @return void
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
public function getRow(int $row_id): void
|
public function getRow(int $row_id): ResponseInterface
|
||||||
{
|
{
|
||||||
// calculate the total cost and retail price of the Kit, so it can be added to the table refresh
|
// Calculate the total cost and retail price of the Kit, so it can be added to the table refresh
|
||||||
$item_kit = $this->_add_totals_to_item_kit($this->item_kit->get_info($row_id));
|
$item_kit = $this->_add_totals_to_item_kit($this->item_kit->get_info($row_id));
|
||||||
|
|
||||||
echo json_encode(get_item_kit_data_row($item_kit));
|
return $this->response->setJSON(get_item_kit_data_row($item_kit));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $item_kit_id
|
* @param int $item_kit_id
|
||||||
* @return void
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getView(int $item_kit_id = NEW_ENTRY): void
|
public function getView(int $item_kit_id = NEW_ENTRY): string
|
||||||
{
|
{
|
||||||
$info = $this->item_kit->get_info($item_kit_id);
|
$info = $this->item_kit->get_info($item_kit_id);
|
||||||
|
|
||||||
if($item_kit_id == NEW_ENTRY)
|
if ($item_kit_id == NEW_ENTRY) {
|
||||||
{
|
$info->price_option = '0';
|
||||||
$info->price_option = '0';
|
$info->print_option = PRINT_ALL;
|
||||||
$info->print_option = PRINT_ALL;
|
$info->kit_item_id = 0;
|
||||||
$info->kit_item_id = 0;
|
$info->item_number = '';
|
||||||
$info->item_number = '';
|
$info->kit_discount = 0;
|
||||||
$info->kit_discount = 0;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
foreach(get_object_vars($info) as $property => $value)
|
foreach (get_object_vars($info) as $property => $value) {
|
||||||
{
|
$info->$property = $value;
|
||||||
$info->$property = $value;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$data['item_kit_info'] = $info;
|
$data['item_kit_info'] = $info;
|
||||||
|
|
||||||
$items = [];
|
$items = [];
|
||||||
|
|
||||||
foreach($this->item_kit_items->get_info($item_kit_id) as $item_kit_item)
|
foreach ($this->item_kit_items->get_info($item_kit_id) as $item_kit_item) {
|
||||||
{
|
$item['kit_sequence'] = $item_kit_item['kit_sequence'];
|
||||||
$item['kit_sequence'] = $item_kit_item['kit_sequence'];
|
$item['name'] = $this->item->get_info($item_kit_item['item_id'])->name;
|
||||||
$item['name'] = $this->item->get_info($item_kit_item['item_id'])->name;
|
$item['item_id'] = $item_kit_item['item_id'];
|
||||||
$item['item_id'] = $item_kit_item['item_id'];
|
$item['quantity'] = $item_kit_item['quantity'];
|
||||||
$item['quantity'] = $item_kit_item['quantity'];
|
|
||||||
|
|
||||||
$items[] = $item;
|
$items[] = $item;
|
||||||
}
|
}
|
||||||
|
|
||||||
$data['item_kit_items'] = $items;
|
$data['item_kit_items'] = $items;
|
||||||
|
|
||||||
$data['selected_kit_item_id'] = $info->kit_item_id;
|
$data['selected_kit_item_id'] = $info->kit_item_id;
|
||||||
$data['selected_kit_item'] = ($item_kit_id > 0 && isset($info->kit_item_id)) ? $info->item_name : '';
|
$data['selected_kit_item'] = ($item_kit_id > 0 && isset($info->kit_item_id)) ? $info->item_name : '';
|
||||||
|
|
||||||
echo view("item_kits/form", $data);
|
return view("item_kits/form", $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $item_kit_id
|
* @param int $item_kit_id
|
||||||
* @return void
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
public function postSave(int $item_kit_id = NEW_ENTRY): void
|
public function postSave(int $item_kit_id = NEW_ENTRY): ResponseInterface
|
||||||
{
|
{
|
||||||
$item_kit_data = [
|
$item_kit_data = [
|
||||||
'name' => $this->request->getPost('name'),
|
'name' => $this->request->getPost('name'),
|
||||||
'item_kit_number' => $this->request->getPost('item_kit_number'),
|
'item_kit_number' => $this->request->getPost('item_kit_number'),
|
||||||
'item_id' => $this->request->getPost('kit_item_id') ? null : intval($this->request->getPost('kit_item_id')),
|
'item_id' => $this->request->getPost('kit_item_id'),
|
||||||
'kit_discount' => parse_decimals($this->request->getPost('kit_discount')),
|
'kit_discount' => parse_decimals($this->request->getPost('kit_discount')),
|
||||||
'kit_discount_type' => $this->request->getPost('kit_discount_type') === null ? PERCENT : intval($this->request->getPost('kit_discount_type')),
|
'kit_discount_type' => $this->request->getPost('kit_discount_type') === null ? PERCENT : intval($this->request->getPost('kit_discount_type')),
|
||||||
'price_option' => $this->request->getPost('price_option') === null ? PRICE_ALL : intval($this->request->getPost('price_option')),
|
'price_option' => $this->request->getPost('price_option') === null ? PRICE_ALL : intval($this->request->getPost('price_option')),
|
||||||
'print_option' => $this->request->getPost('print_option') === null ? PRINT_ALL : intval($this->request->getPost('print_option')),
|
'print_option' => $this->request->getPost('print_option') === null ? PRINT_ALL : intval($this->request->getPost('print_option')),
|
||||||
'description' => $this->request->getPost('description')
|
'description' => $this->request->getPost('description')
|
||||||
];
|
];
|
||||||
|
|
||||||
if($this->item_kit->save_value($item_kit_data, $item_kit_id))
|
if ($this->item_kit->save_value($item_kit_data, $item_kit_id)) {
|
||||||
{
|
$new_item = false;
|
||||||
$new_item = false;
|
// New item kit
|
||||||
//New item kit
|
if ($item_kit_id == NEW_ENTRY) {
|
||||||
if($item_kit_id == NEW_ENTRY)
|
$item_kit_id = $item_kit_data['item_kit_id'];
|
||||||
{
|
$new_item = true;
|
||||||
$item_kit_id = $item_kit_data['item_kit_id'];
|
}
|
||||||
$new_item = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$item_kit_items_array = $this->request->getPost('item_kit_qty') === null ? null : $this->request->getPost('item_kit_qty');
|
$item_kit_items_array = $this->request->getPost('item_kit_qty') === null ? null : $this->request->getPost('item_kit_qty');
|
||||||
|
|
||||||
if($item_kit_items_array != null)
|
if ($item_kit_items_array != null) {
|
||||||
{
|
$item_kit_items = [];
|
||||||
$item_kit_items = [];
|
foreach ($item_kit_items_array as $item_id => $item_kit_qty) {
|
||||||
foreach($item_kit_items_array as $item_id => $item_kit_qty)
|
$item_kit_items[] = [
|
||||||
{
|
'item_id' => $item_id,
|
||||||
$item_kit_items[] = [
|
'quantity' => $item_kit_qty === null ? 0 : parse_quantity($item_kit_qty),
|
||||||
'item_id' => $item_id,
|
'kit_sequence' => $this->request->getPost("item_kit_seq[$item_id]") === null ? 0 : intval($this->request->getPost("item_kit_seq[$item_id]"))
|
||||||
'quantity' => $item_kit_qty === null ? 0 : parse_quantity($item_kit_qty),
|
];
|
||||||
'kit_sequence' => $this->request->getPost("item_kit_seq[$item_id]") === null ? 0 : intval($this->request->getPost("item_kit_seq[$item_id]"))
|
}
|
||||||
];
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($item_kit_items))
|
if (!empty($item_kit_items)) {
|
||||||
{
|
$success = $this->item_kit_items->save_value($item_kit_items, $item_kit_id);
|
||||||
$success = $this->item_kit_items->save_value($item_kit_items, $item_kit_id);
|
} else {
|
||||||
}
|
$success = true;
|
||||||
else
|
}
|
||||||
{
|
|
||||||
$success = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if($new_item)
|
if ($new_item) {
|
||||||
{
|
return $this->response->setJSON([
|
||||||
echo json_encode ([
|
'success' => $success,
|
||||||
'success' => $success,
|
'message' => lang('Item_kits.successful_adding') . ' ' . $item_kit_data['name'],
|
||||||
'message' => lang('Item_kits.successful_adding').' '.$item_kit_data['name'],
|
'id' => $item_kit_id
|
||||||
'id' => $item_kit_id
|
]);
|
||||||
]);
|
} else {
|
||||||
|
return $this->response->setJSON([
|
||||||
|
'success' => $success,
|
||||||
|
'message' => lang('Item_kits.successful_updating') . ' ' . $item_kit_data['name'],
|
||||||
|
'id' => $item_kit_id
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else { // Failure
|
||||||
|
return $this->response->setJSON([
|
||||||
|
'success' => false,
|
||||||
|
'message' => lang('Item_kits.error_adding_updating') . ' ' . $item_kit_data['name'],
|
||||||
|
'id' => NEW_ENTRY
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
/**
|
||||||
else
|
* @return ResponseInterface
|
||||||
{
|
*/
|
||||||
echo json_encode ([
|
public function postDelete(): ResponseInterface
|
||||||
'success' => $success,
|
{
|
||||||
'message' => lang('Item_kits.successful_updating').' '.$item_kit_data['name'],
|
$item_kits_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||||
'id' => $item_kit_id
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else//failure
|
|
||||||
{
|
|
||||||
echo json_encode ([
|
|
||||||
'success' => false,
|
|
||||||
'message' => lang('Item_kits.error_adding_updating') . ' ' . $item_kit_data['name'],
|
|
||||||
'id' => NEW_ENTRY
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if ($this->item_kit->delete_list($item_kits_to_delete)) {
|
||||||
* @return void
|
return $this->response->setJSON([
|
||||||
*/
|
'success' => true,
|
||||||
public function postDelete(): void
|
'message' => lang('Item_kits.successful_deleted') . ' ' . count($item_kits_to_delete) . ' ' . lang('Item_kits.one_or_multiple')
|
||||||
{
|
]);
|
||||||
$item_kits_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
} else {
|
||||||
|
return $this->response->setJSON(['success' => false, 'message' => lang('Item_kits.cannot_be_deleted')]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if($this->item_kit->delete_list($item_kits_to_delete))
|
/**
|
||||||
{
|
* Checks the validity of the item kit number. Used in app/Views/item_kits/form.php
|
||||||
echo json_encode ([
|
*
|
||||||
'success' => true,
|
* @return ResponseInterface
|
||||||
'message' => lang('Item_kits.successful_deleted') . ' ' . count($item_kits_to_delete) . ' ' . lang('Item_kits.one_or_multiple')
|
* @noinspection PhpUnused
|
||||||
]);
|
*/
|
||||||
}
|
public function postCheckItemNumber(): ResponseInterface
|
||||||
else
|
{
|
||||||
{
|
$exists = $this->item_kit->item_number_exists($this->request->getPost('item_kit_number', FILTER_SANITIZE_FULL_SPECIAL_CHARS), $this->request->getPost('item_kit_id', FILTER_SANITIZE_NUMBER_INT));
|
||||||
echo json_encode (['success' => false, 'message' => lang('Item_kits.cannot_be_deleted')]);
|
return $this->response->setJSON(!$exists ? 'true' : 'false');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the validity of the item kit number. Used in app/Views/item_kits/form.php
|
* AJAX called function that generates barcodes for selected item_kits.
|
||||||
*
|
*
|
||||||
* @return void
|
* @param string $item_kit_ids Colon separated list of item_kit_id values to generate barcodes for.
|
||||||
* @noinspection PhpUnused
|
* @return string
|
||||||
*/
|
* @noinspection PhpUnused
|
||||||
public function postCheckItemNumber(): void
|
*/
|
||||||
{
|
public function getGenerateBarcodes(string $item_kit_ids): string
|
||||||
$exists = $this->item_kit->item_number_exists($this->request->getPost('item_kit_number', FILTER_SANITIZE_FULL_SPECIAL_CHARS), $this->request->getPost('item_kit_id', FILTER_SANITIZE_NUMBER_INT));
|
{
|
||||||
echo !$exists ? 'true' : 'false';
|
$barcode_lib = new Barcode_lib();
|
||||||
}
|
$result = [];
|
||||||
|
|
||||||
/**
|
$item_kit_ids = explode(':', $item_kit_ids);
|
||||||
* AJAX called function that generates barcodes for selected item_kits.
|
foreach ($item_kit_ids as $item_kid_id) {
|
||||||
*
|
// Calculate the total cost and retail price of the Kit, so it can be added to the barcode text at the bottom
|
||||||
* @param string $item_kit_ids Colon separated list of item_kit_id values to generate barcodes for.
|
$item_kit = $this->_add_totals_to_item_kit($this->item_kit->get_info($item_kid_id));
|
||||||
* @return void
|
|
||||||
* @noinspection PhpUnused
|
|
||||||
*/
|
|
||||||
public function getGenerateBarcodes(string $item_kit_ids): void
|
|
||||||
{
|
|
||||||
$barcode_lib = new Barcode_lib();
|
|
||||||
$result = [];
|
|
||||||
|
|
||||||
$item_kit_ids = explode(':', $item_kit_ids);
|
$item_kid_id = 'KIT ' . urldecode($item_kid_id);
|
||||||
foreach($item_kit_ids as $item_kid_id)
|
|
||||||
{
|
|
||||||
// calculate the total cost and retail price of the Kit, so it can be added to the barcode text at the bottom
|
|
||||||
$item_kit = $this->_add_totals_to_item_kit($this->item_kit->get_info($item_kid_id));
|
|
||||||
|
|
||||||
$item_kid_id = 'KIT '. urldecode($item_kid_id);
|
$result[] = [
|
||||||
|
'name' => $item_kit->name,
|
||||||
|
'item_id' => $item_kid_id,
|
||||||
|
'item_number' => $item_kid_id,
|
||||||
|
'cost_price' => $item_kit->total_cost_price,
|
||||||
|
'unit_price' => $item_kit->total_unit_price
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
$result[] = [
|
$data['items'] = $result;
|
||||||
'name' => $item_kit->name,
|
$barcode_config = $barcode_lib->get_barcode_config();
|
||||||
'item_id' => $item_kid_id,
|
// In case the selected barcode type is not Code39 or Code128 we set by default Code128
|
||||||
'item_number' => $item_kid_id,
|
// The rationale for this is that EAN codes cannot have strings as seed, so 'KIT ' is not allowed
|
||||||
'cost_price' => $item_kit->total_cost_price,
|
if ($barcode_config['barcode_type'] != 'C39' && $barcode_config['barcode_type'] != 'C128') {
|
||||||
'unit_price' => $item_kit->total_unit_price
|
$barcode_config['barcode_type'] = 'C128';
|
||||||
];
|
}
|
||||||
}
|
$data['barcode_config'] = $barcode_config;
|
||||||
|
|
||||||
$data['items'] = $result;
|
// Display barcodes
|
||||||
$barcode_config = $barcode_lib->get_barcode_config();
|
return view("barcodes/barcode_sheet", $data);
|
||||||
// in case the selected barcode type is not Code39 or Code128 we set by default Code128
|
}
|
||||||
// the rationale for this is that EAN codes cannot have strings as seed, so 'KIT ' is not allowed
|
|
||||||
if($barcode_config['barcode_type'] != 'C39' && $barcode_config['barcode_type'] != 'C128')
|
|
||||||
{
|
|
||||||
$barcode_config['barcode_type'] = 'C128';
|
|
||||||
}
|
|
||||||
$data['barcode_config'] = $barcode_config;
|
|
||||||
|
|
||||||
// display barcodes
|
|
||||||
echo view("barcodes/barcode_sheet", $data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@ namespace App\Controllers;
|
|||||||
use App\Libraries\MY_Migration;
|
use App\Libraries\MY_Migration;
|
||||||
use App\Models\Employee;
|
use App\Models\Employee;
|
||||||
use CodeIgniter\HTTP\RedirectResponse;
|
use CodeIgniter\HTTP\RedirectResponse;
|
||||||
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
use CodeIgniter\Model;
|
use CodeIgniter\Model;
|
||||||
use Config\OSPOS;
|
use Config\OSPOS;
|
||||||
use Config\Services;
|
use Config\Services;
|
||||||
@@ -14,65 +15,86 @@ use Config\Services;
|
|||||||
*/
|
*/
|
||||||
class Login extends BaseController
|
class Login extends BaseController
|
||||||
{
|
{
|
||||||
public Model $employee;
|
public Model $employee;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return RedirectResponse|string
|
* @return RedirectResponse|string
|
||||||
*/
|
*/
|
||||||
public function index(): string|RedirectResponse
|
public function index(): string|RedirectResponse
|
||||||
{
|
{
|
||||||
$this->employee = model(Employee::class);
|
$this->employee = model(Employee::class);
|
||||||
if(!$this->employee->is_logged_in())
|
if (!$this->employee->is_logged_in()) {
|
||||||
{
|
$migration = new MY_Migration(config('Migrations'));
|
||||||
$migration = new MY_Migration(config('Migrations'));
|
$config = config(OSPOS::class)->settings;
|
||||||
$config = config(OSPOS::class)->settings;
|
|
||||||
|
|
||||||
$gcaptcha_enabled = array_key_exists('gcaptcha_enable', $config)
|
$gcaptcha_enabled = array_key_exists('gcaptcha_enable', $config)
|
||||||
? $config['gcaptcha_enable']
|
? $config['gcaptcha_enable']
|
||||||
: false;
|
: false;
|
||||||
|
|
||||||
$migration->migrate_to_ci4();
|
$migration->migrate_to_ci4();
|
||||||
|
|
||||||
$validation = Services::validation();
|
$validation = Services::validation();
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'has_errors' => false,
|
'has_errors' => false,
|
||||||
'is_latest' => $migration->is_latest(),
|
'is_new_install' => !(MY_Migration::get_current_version()),
|
||||||
'latest_version' => $migration->get_latest_migration(),
|
'is_latest' => $migration->is_latest(),
|
||||||
'gcaptcha_enabled' => $gcaptcha_enabled,
|
'latest_version' => $migration->get_latest_migration(),
|
||||||
'config' => $config,
|
'gcaptcha_enabled' => $gcaptcha_enabled,
|
||||||
'validation' => $validation
|
'config' => $config,
|
||||||
];
|
'validation' => $validation
|
||||||
|
];
|
||||||
|
|
||||||
if($this->request->getMethod() !== 'POST')
|
if ($this->request->getMethod() !== 'POST') {
|
||||||
{
|
return view('login', $data);
|
||||||
return view('login', $data);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$rules = ['username' => 'required|login_check[data]'];
|
if (!$data['is_latest'] || $data['is_new_install']) {
|
||||||
$messages = [
|
set_time_limit(3600);
|
||||||
'username' => [
|
|
||||||
'required' => lang('Login.required_username'),
|
|
||||||
'login_check' => lang('Login.invalid_username_and_password'),
|
|
||||||
]
|
|
||||||
];
|
|
||||||
|
|
||||||
if(!$this->validate($rules, $messages))
|
$migration->setNamespace('App')->latest();
|
||||||
{
|
return redirect()->to('login');
|
||||||
$data['has_errors'] = !empty($validation->getErrors());
|
}
|
||||||
|
|
||||||
return view('login', $data);
|
$rules = ['username' => 'required|login_check[data]'];
|
||||||
}
|
$messages = [
|
||||||
|
'username' => [
|
||||||
|
'required' => lang('Login.required_username'),
|
||||||
|
'login_check' => lang('Login.invalid_username_and_password'),
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
if(!$data['is_latest'])
|
if (!$this->validate($rules, $messages)) {
|
||||||
{
|
$data['has_errors'] = !empty($validation->getErrors());
|
||||||
set_time_limit(3600);
|
|
||||||
|
|
||||||
$migration->setNamespace('App')->latest();
|
return view('login', $data);
|
||||||
return redirect()->to('login');
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return redirect()->to('home');
|
return redirect()->to('home');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function migrate(): ResponseInterface
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$migration = new MY_Migration(config('Migrations'));
|
||||||
|
$migration->migrate_to_ci4();
|
||||||
|
|
||||||
|
set_time_limit(3600);
|
||||||
|
$migration->setNamespace('App')->latest();
|
||||||
|
|
||||||
|
return $this->response->setJSON([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Migration completed successfully'
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
log_message('error', 'Migration failed: ' . $e->getMessage());
|
||||||
|
|
||||||
|
return $this->response->setJSON([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Migration failed: ' . $e->getMessage()
|
||||||
|
])->setStatusCode(500);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,93 +5,87 @@ namespace App\Controllers;
|
|||||||
use App\Libraries\Sms_lib;
|
use App\Libraries\Sms_lib;
|
||||||
|
|
||||||
use App\Models\Person;
|
use App\Models\Person;
|
||||||
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
|
|
||||||
class Messages extends Secure_Controller
|
class Messages extends Secure_Controller
|
||||||
{
|
{
|
||||||
private Sms_lib $sms_lib;
|
private Sms_lib $sms_lib;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct('messages');
|
parent::__construct('messages');
|
||||||
|
|
||||||
$this->sms_lib = new Sms_lib();
|
$this->sms_lib = new Sms_lib();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getIndex(): void
|
public function getIndex(): string
|
||||||
{
|
{
|
||||||
echo view('messages/sms');
|
return view('messages/sms');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $person_id
|
* @param int $person_id
|
||||||
* @return void
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getView(int $person_id = NEW_ENTRY): void
|
public function getView(int $person_id = NEW_ENTRY): string
|
||||||
{
|
{
|
||||||
$person = model(Person::class);
|
$person = model(Person::class);
|
||||||
$info = $person->get_info($person_id);
|
$info = $person->get_info($person_id);
|
||||||
|
|
||||||
foreach(get_object_vars($info) as $property => $value)
|
foreach (get_object_vars($info) as $property => $value) {
|
||||||
{
|
$info->$property = $value;
|
||||||
$info->$property = $value;
|
}
|
||||||
}
|
$data['person_info'] = $info;
|
||||||
$data['person_info'] = $info;
|
|
||||||
|
|
||||||
echo view('messages/form_sms', $data);
|
return view('messages/form_sms', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
public function send(): void
|
public function send(): ResponseInterface
|
||||||
{
|
{
|
||||||
$phone = $this->request->getPost('phone', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
$phone = $this->request->getPost('phone', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||||
$message = $this->request->getPost('message', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
$message = $this->request->getPost('message', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||||
|
|
||||||
$response = $this->sms_lib->sendSMS($phone, $message);
|
$response = $this->sms_lib->sendSMS($phone, $message);
|
||||||
|
|
||||||
if($response)
|
if ($response) {
|
||||||
{
|
return $this->response->setJSON(['success' => true, 'message' => lang('Messages.successfully_sent') . ' ' . esc($phone)]);
|
||||||
echo json_encode (['success' => true, 'message' => lang('Messages.successfully_sent') . ' ' . esc($phone)]);
|
} else {
|
||||||
}
|
return $this->response->setJSON(['success' => false, 'message' => lang('Messages.unsuccessfully_sent') . ' ' . esc($phone)]);
|
||||||
else
|
}
|
||||||
{
|
}
|
||||||
echo json_encode (['success' => false, 'message' => lang('Messages.unsuccessfully_sent') . ' ' . esc($phone)]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends an SMS message to a user. Used in app/Views/messages/form_sms.php.
|
* Sends an SMS message to a user. Used in app/Views/messages/form_sms.php.
|
||||||
*
|
*
|
||||||
* @param int $person_id
|
* @param int $person_id
|
||||||
* @return void
|
* @return ResponseInterface
|
||||||
* @noinspection PhpUnused
|
* @noinspection PhpUnused
|
||||||
*/
|
*/
|
||||||
public function send_form(int $person_id = NEW_ENTRY): void
|
public function send_form(int $person_id = NEW_ENTRY): ResponseInterface
|
||||||
{
|
{
|
||||||
$phone = $this->request->getPost('phone', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
$phone = $this->request->getPost('phone', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||||
$message = $this->request->getPost('message', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
$message = $this->request->getPost('message', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||||
|
|
||||||
$response = $this->sms_lib->sendSMS($phone, $message);
|
$response = $this->sms_lib->sendSMS($phone, $message);
|
||||||
|
|
||||||
if($response)
|
if ($response) {
|
||||||
{
|
return $this->response->setJSON([
|
||||||
echo json_encode ([
|
'success' => true,
|
||||||
'success' => true,
|
'message' => lang('Messages.successfully_sent') . ' ' . esc($phone),
|
||||||
'message' => lang('Messages.successfully_sent') . ' ' . esc($phone),
|
'person_id' => $person_id
|
||||||
'person_id' => $person_id
|
]);
|
||||||
]);
|
} else {
|
||||||
}
|
return $this->response->setJSON([
|
||||||
else
|
'success' => false,
|
||||||
{
|
'message' => lang('Messages.unsuccessfully_sent') . ' ' . esc($phone),
|
||||||
echo json_encode ([
|
'person_id' => NEW_ENTRY
|
||||||
'success' => false,
|
]);
|
||||||
'message' => lang('Messages.unsuccessfully_sent') . ' ' . esc($phone),
|
}
|
||||||
'person_id' => NEW_ENTRY
|
}
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Controllers;
|
namespace App\Controllers;
|
||||||
|
|
||||||
use App\Models\Module;
|
use App\Models\Module;
|
||||||
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Part of the grants mechanism to restrict access to modules that the user doesn't have permission for.
|
* Part of the grants mechanism to restrict access to modules that the user doesn't have permission for.
|
||||||
@@ -12,23 +13,23 @@ use App\Models\Module;
|
|||||||
*/
|
*/
|
||||||
class No_access extends BaseController
|
class No_access extends BaseController
|
||||||
{
|
{
|
||||||
private Module $module;
|
private Module $module;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->module = model(Module::class);
|
$this->module = model(Module::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $module_id
|
* @param string $module_id
|
||||||
* @param string $permission_id
|
* @param string $permission_id
|
||||||
* @return void
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getIndex(string $module_id = '', string $permission_id = ''): void
|
public function getIndex(string $module_id = '', string $permission_id = ''): string
|
||||||
{
|
{
|
||||||
$data['module_name'] = $this->module->get_module_name($module_id);
|
$data['module_name'] = $this->module->get_module_name($module_id);
|
||||||
$data['permission_id'] = $permission_id;
|
$data['permission_id'] = $permission_id;
|
||||||
|
|
||||||
echo view('no_access', $data);
|
return view('no_access', $data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,34 +3,35 @@
|
|||||||
namespace App\Controllers;
|
namespace App\Controllers;
|
||||||
|
|
||||||
use App\Models\Employee;
|
use App\Models\Employee;
|
||||||
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property Employee employee
|
* @property Employee employee
|
||||||
*/
|
*/
|
||||||
class Office extends Secure_Controller
|
class Office extends Secure_Controller
|
||||||
{
|
{
|
||||||
protected Employee $employee;
|
protected Employee $employee;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct('office', null, 'office');
|
parent::__construct('office', null, 'office');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getIndex(): void
|
public function getIndex(): string
|
||||||
{
|
{
|
||||||
echo view('home/office');
|
return view('home/office');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function logout(): void
|
public function logout(): void
|
||||||
{
|
{
|
||||||
$this->employee = model(Employee::class);
|
$this->employee = model(Employee::class);
|
||||||
|
|
||||||
$this->employee->logout();
|
$this->employee->logout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,69 +3,74 @@
|
|||||||
namespace App\Controllers;
|
namespace App\Controllers;
|
||||||
|
|
||||||
use App\Models\Person;
|
use App\Models\Person;
|
||||||
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
use Config\Services;
|
use Config\Services;
|
||||||
use function Tamtamchik\NameCase\str_name_case;
|
use function Tamtamchik\NameCase\str_name_case;
|
||||||
|
|
||||||
abstract class Persons extends Secure_Controller
|
abstract class Persons extends Secure_Controller
|
||||||
{
|
{
|
||||||
protected Person $person;
|
protected Person $person;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string|null $module_id
|
* @param string|null $module_id
|
||||||
*/
|
*/
|
||||||
public function __construct(string $module_id = null)
|
public function __construct(?string $module_id = null)
|
||||||
{
|
{
|
||||||
parent::__construct($module_id);
|
parent::__construct($module_id);
|
||||||
|
|
||||||
$this->person = model(Person::class);
|
$this->person = model(Person::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getIndex(): void
|
public function getIndex(): string
|
||||||
{
|
{
|
||||||
$data['table_headers'] = get_people_manage_table_headers();
|
$data['table_headers'] = get_people_manage_table_headers();
|
||||||
|
|
||||||
echo view('people/manage', $data);
|
return view('people/manage', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gives search suggestions based on what is being searched for
|
* Gives search suggestions based on what is being searched for
|
||||||
*/
|
* @return ResponseInterface
|
||||||
public function getSuggest(): void
|
*/
|
||||||
{
|
public function getSuggest(): ResponseInterface
|
||||||
$search = $this->request->getGet('term');
|
{
|
||||||
$suggestions = $this->person->get_search_suggestions($search);
|
$search = $this->request->getGet('term');
|
||||||
|
$suggestions = $this->person->get_search_suggestions($search);
|
||||||
|
|
||||||
echo json_encode($suggestions);
|
return $this->response->setJSON($suggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets one row for a person manage table. This is called using AJAX to update one row.
|
* Gets one row for a person manage table. This is called using AJAX to update one row.
|
||||||
*/
|
* @return ResponseInterface
|
||||||
public function getRow(int $row_id): void
|
*/
|
||||||
{
|
public function getRow(int $row_id): ResponseInterface
|
||||||
$data_row = get_person_data_row($this->person->get_info($row_id));
|
{
|
||||||
|
$data_row = get_person_data_row($this->person->get_info($row_id));
|
||||||
|
|
||||||
echo json_encode($data_row);
|
return $this->response->setJSON($data_row);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Capitalize segments of a name, and put the rest into lower case.
|
* Capitalize segments of a name, and put the rest into lower case.
|
||||||
* You can pass the characters you want to use as delimiters as exceptions.
|
* You can pass the characters you want to use as delimiters as exceptions.
|
||||||
* The function supports UTF-8 strings
|
* The function supports UTF-8 strings
|
||||||
*
|
*
|
||||||
* Example:
|
* Example:
|
||||||
* i.e. <?php echo nameize("john o'grady-smith"); ?>
|
* i.e. <?php echo nameize("john o'grady-smith"); ?>
|
||||||
*
|
*
|
||||||
* returns John O'Grady-Smith
|
* returns John O'Grady-Smith
|
||||||
*/
|
*/
|
||||||
protected function nameize(string $input): string
|
protected function nameize(string $input): string
|
||||||
{
|
{
|
||||||
$adjusted_name = str_name_case($input);
|
$adjusted_name = str_name_case($input);
|
||||||
|
|
||||||
//TODO:Use preg_replace to match HTML entities and convert them to lowercase. This is a workaround for https://github.com/tamtamchik/namecase/issues/20
|
// TODO: Use preg_replace to match HTML entities and convert them to lowercase. This is a workaround for https://github.com/tamtamchik/namecase/issues/20
|
||||||
return preg_replace_callback('/&[a-zA-Z0-9#]+;/', function($matches) { return strtolower($matches[0]); }, $adjusted_name);
|
return preg_replace_callback('/&[a-zA-Z0-9#]+;/', function ($matches) {
|
||||||
}
|
return strtolower($matches[0]);
|
||||||
|
}, $adjusted_name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ namespace App\Controllers;
|
|||||||
|
|
||||||
use App\Models\Employee;
|
use App\Models\Employee;
|
||||||
use App\Models\Module;
|
use App\Models\Module;
|
||||||
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
use CodeIgniter\Model;
|
use CodeIgniter\Model;
|
||||||
use CodeIgniter\Session\Session;
|
use CodeIgniter\Session\Session;
|
||||||
use Config\OSPOS;
|
use Config\OSPOS;
|
||||||
@@ -22,130 +22,140 @@ use Config\Services;
|
|||||||
*/
|
*/
|
||||||
class Secure_Controller extends BaseController
|
class Secure_Controller extends BaseController
|
||||||
{
|
{
|
||||||
public array $global_view_data;
|
public array $global_view_data;
|
||||||
protected Employee $employee;
|
protected Employee $employee;
|
||||||
protected Module $module;
|
protected Module $module;
|
||||||
protected Session $session;
|
protected Session $session;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $module_id
|
* @param string $module_id
|
||||||
* @param string|null $submodule_id
|
* @param string|null $submodule_id
|
||||||
* @param string|null $menu_group
|
* @param string|null $menu_group
|
||||||
*/
|
*/
|
||||||
public function __construct(string $module_id = '', string $submodule_id = null, string $menu_group = null)
|
public function __construct(string $module_id = '', ?string $submodule_id = null, ?string $menu_group = null)
|
||||||
{
|
{
|
||||||
$this->employee = model(Employee::class);
|
$this->employee = model(Employee::class);
|
||||||
$this->module = model(Module::class);
|
$this->module = model(Module::class);
|
||||||
$config = config(OSPOS::class)->settings;
|
$config = config(OSPOS::class)->settings;
|
||||||
$validation = Services::validation();
|
$validation = Services::validation();
|
||||||
|
|
||||||
if(!$this->employee->is_logged_in())
|
if (!$this->employee->is_logged_in()) {
|
||||||
{
|
header("Location:" . base_url('login'));
|
||||||
header("Location:".base_url('login'));
|
exit();
|
||||||
exit();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$logged_in_employee_info = $this->employee->get_logged_in_employee_info();
|
$logged_in_employee_info = $this->employee->get_logged_in_employee_info();
|
||||||
if(!$this->employee->has_module_grant($module_id, $logged_in_employee_info->person_id)
|
if (
|
||||||
|| (isset($submodule_id) && !$this->employee->has_module_grant($submodule_id, $logged_in_employee_info->person_id)))
|
!$this->employee->has_module_grant($module_id, $logged_in_employee_info->person_id)
|
||||||
{
|
|| (isset($submodule_id) && !$this->employee->has_module_grant($submodule_id, $logged_in_employee_info->person_id))
|
||||||
header("Location:".base_url("no_access/$module_id/$submodule_id"));
|
) {
|
||||||
exit();
|
header("Location:" . base_url("no_access/$module_id/$submodule_id"));
|
||||||
}
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
// load up global global_view_data visible to all the loaded views
|
// Load up global global_view_data visible to all the loaded views
|
||||||
$this->session = session();
|
$this->session = session();
|
||||||
if($menu_group == null)
|
if ($menu_group == null) {
|
||||||
{
|
$menu_group = $this->session->get('menu_group');
|
||||||
$menu_group = $this->session->get('menu_group');
|
} else {
|
||||||
}
|
$this->session->set('menu_group', $menu_group);
|
||||||
else
|
}
|
||||||
{
|
|
||||||
$this->session->set('menu_group', $menu_group);
|
|
||||||
}
|
|
||||||
|
|
||||||
$allowed_modules = $menu_group == 'home'
|
$allowed_modules = $menu_group == 'home'
|
||||||
? $this->module->get_allowed_home_modules($logged_in_employee_info->person_id)
|
? $this->module->get_allowed_home_modules($logged_in_employee_info->person_id)
|
||||||
: $this->module->get_allowed_office_modules($logged_in_employee_info->person_id);
|
: $this->module->get_allowed_office_modules($logged_in_employee_info->person_id);
|
||||||
|
|
||||||
$this->global_view_data = [];
|
$this->global_view_data = [];
|
||||||
foreach($allowed_modules->getResult() as $module)
|
foreach ($allowed_modules->getResult() as $module) {
|
||||||
{
|
$this->global_view_data['allowed_modules'][] = $module;
|
||||||
$this->global_view_data['allowed_modules'][] = $module;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$this->global_view_data += [
|
$this->global_view_data += [
|
||||||
'user_info' => $logged_in_employee_info,
|
'user_info' => $logged_in_employee_info,
|
||||||
'controller_name' => $module_id,
|
'controller_name' => $module_id,
|
||||||
'config' => $config
|
'config' => $config
|
||||||
];
|
];
|
||||||
view('viewData', $this->global_view_data);
|
view('viewData', $this->global_view_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function sanitizeSortColumn($headers, $field, $default): string
|
public function sanitizeSortColumn($headers, $field, $default): string
|
||||||
{
|
{
|
||||||
return $field != null && in_array($field, array_keys(array_merge(...$headers))) ? $field : $default;
|
return $field != null && in_array($field, array_keys(array_merge(...$headers))) ? $field : $default;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AJAX function used to confirm whether values sent in the request are numeric
|
* AJAX function used to confirm whether values sent in the request are numeric
|
||||||
* @return void
|
* @return ResponseInterface
|
||||||
* @noinspection PhpUnused
|
* @noinspection PhpUnused
|
||||||
*/
|
*/
|
||||||
public function getCheckNumeric(): void
|
public function getCheckNumeric(): ResponseInterface
|
||||||
{
|
{
|
||||||
foreach($this->request->getGet() as $value)
|
foreach ($this->request->getGet() as $value) {
|
||||||
{
|
if (parse_decimals($value) === false) {
|
||||||
if (parse_decimals($value) === false)
|
return $this->response->setJSON('false');
|
||||||
{
|
}
|
||||||
echo 'false';
|
}
|
||||||
return;
|
return $this->response->setJSON('true');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
echo 'true';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $key
|
* @param $key
|
||||||
* @return mixed|void
|
* @return mixed|void
|
||||||
*/
|
*/
|
||||||
public function getConfig($key)
|
public function getConfig($key)
|
||||||
{
|
{
|
||||||
if (isset($config[$key]))
|
if (isset($config[$key])) {
|
||||||
{
|
return $config[$key];
|
||||||
return $config[$key];
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return false
|
* @return false
|
||||||
*/
|
*/
|
||||||
public function getIndex() { return false; }
|
public function getIndex()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return false
|
* @return false
|
||||||
*/
|
*/
|
||||||
public function getSearch() { return false; }
|
public function getSearch()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return false
|
* @return false
|
||||||
*/
|
*/
|
||||||
public function suggest_search() { return false; }
|
public function suggest_search()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $data_item_id
|
* @param int $data_item_id
|
||||||
* @return false
|
* @return false
|
||||||
*/
|
*/
|
||||||
public function getView(int $data_item_id = -1) { return false; }
|
public function getView(int $data_item_id = -1)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $data_item_id
|
* @param int $data_item_id
|
||||||
* @return false
|
* @return false
|
||||||
*/
|
*/
|
||||||
public function postSave(int $data_item_id = -1) { return false; }
|
public function postSave(int $data_item_id = -1)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return false
|
* @return false
|
||||||
*/
|
*/
|
||||||
public function postDelete() { return false; }
|
public function postDelete()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,197 +3,190 @@
|
|||||||
namespace App\Controllers;
|
namespace App\Controllers;
|
||||||
|
|
||||||
use App\Models\Supplier;
|
use App\Models\Supplier;
|
||||||
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
use Config\Services;
|
use Config\Services;
|
||||||
|
|
||||||
class Suppliers extends Persons
|
class Suppliers extends Persons
|
||||||
{
|
{
|
||||||
private Supplier $supplier;
|
private Supplier $supplier;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct('suppliers');
|
parent::__construct('suppliers');
|
||||||
|
|
||||||
$this->supplier = model(Supplier::class);
|
$this->supplier = model(Supplier::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getIndex(): void
|
public function getIndex(): string
|
||||||
{
|
{
|
||||||
$data['table_headers'] = get_suppliers_manage_table_headers();
|
$data['table_headers'] = get_suppliers_manage_table_headers();
|
||||||
|
|
||||||
echo view('people/manage', $data);
|
return view('people/manage', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets one row for a supplier manage table. This is called using AJAX to update one row.
|
* Gets one row for a supplier manage table. This is called using AJAX to update one row.
|
||||||
* @param $row_id
|
* @param $row_id
|
||||||
* @return void
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
public function getRow($row_id): void
|
public function getRow($row_id): ResponseInterface
|
||||||
{
|
{
|
||||||
$data_row = get_supplier_data_row($this->supplier->get_info($row_id));
|
$data_row = get_supplier_data_row($this->supplier->get_info($row_id));
|
||||||
$data_row['category'] = $this->supplier->get_category_name($data_row['category']);
|
$data_row['category'] = $this->supplier->get_category_name($data_row['category']);
|
||||||
|
|
||||||
echo json_encode($data_row);
|
return $this->response->setJSON($data_row);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns Supplier table data rows. This will be called with AJAX.
|
* Returns Supplier table data rows. This will be called with AJAX.
|
||||||
* @return void
|
* @return void
|
||||||
**/
|
**/
|
||||||
public function getSearch(): void
|
public function getSearch(): ResponseInterface
|
||||||
{
|
{
|
||||||
$search = $this->request->getGet('search');
|
$search = $this->request->getGet('search');
|
||||||
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$sort = $this->sanitizeSortColumn(SUPPLIER_HEADERS, $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'people.person_id');
|
$sort = $this->sanitizeSortColumn(supplier_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'people.person_id');
|
||||||
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||||
|
|
||||||
$suppliers = $this->supplier->search($search, $limit, $offset, $sort, $order);
|
$suppliers = $this->supplier->search($search, $limit, $offset, $sort, $order);
|
||||||
$total_rows = $this->supplier->get_found_rows($search);
|
$total_rows = $this->supplier->get_found_rows($search);
|
||||||
|
|
||||||
$data_rows = [];
|
$data_rows = [];
|
||||||
|
|
||||||
foreach($suppliers->getResult() as $supplier)
|
foreach ($suppliers->getResult() as $supplier) {
|
||||||
{
|
$row = get_supplier_data_row($supplier);
|
||||||
$row = get_supplier_data_row($supplier);
|
$row['category'] = $this->supplier->get_category_name($row['category']);
|
||||||
$row['category'] = $this->supplier->get_category_name($row['category']);
|
$data_rows[] = $row;
|
||||||
$data_rows[] = $row;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode (['total' => $total_rows, 'rows' => $data_rows]);
|
return $this->response->setJSON(['total' => $total_rows, 'rows' => $data_rows]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gives search suggestions based on what is being searched for
|
* Gives search suggestions based on what is being searched for
|
||||||
**/
|
* @return ResponseInterface
|
||||||
public function getSuggest(): void
|
**/
|
||||||
{
|
public function getSuggest(): ResponseInterface
|
||||||
$search = $this->request->getGet('term');
|
{
|
||||||
$suggestions = $this->supplier->get_search_suggestions($search, true);
|
$search = $this->request->getGet('term');
|
||||||
|
$suggestions = $this->supplier->get_search_suggestions($search, true);
|
||||||
|
|
||||||
echo json_encode($suggestions);
|
return $this->response->setJSON($suggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
public function suggest_search(): void
|
public function suggest_search(): ResponseInterface
|
||||||
{
|
{
|
||||||
$search = $this->request->getPost('term');
|
$search = $this->request->getPost('term');
|
||||||
$suggestions = $this->supplier->get_search_suggestions($search, false);
|
$suggestions = $this->supplier->get_search_suggestions($search, false);
|
||||||
|
|
||||||
echo json_encode($suggestions);
|
return $this->response->setJSON($suggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the supplier edit form
|
* Loads the supplier edit form
|
||||||
*
|
*
|
||||||
* @param int $supplier_id
|
* @param int $supplier_id
|
||||||
* @return void
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getView(int $supplier_id = NEW_ENTRY): void
|
public function getView(int $supplier_id = NEW_ENTRY): string
|
||||||
{
|
{
|
||||||
$info = $this->supplier->get_info($supplier_id);
|
$info = $this->supplier->get_info($supplier_id);
|
||||||
foreach(get_object_vars($info) as $property => $value)
|
foreach (get_object_vars($info) as $property => $value) {
|
||||||
{
|
$info->$property = $value;
|
||||||
$info->$property = $value;
|
}
|
||||||
}
|
$data['person_info'] = $info;
|
||||||
$data['person_info'] = $info;
|
$data['categories'] = $this->supplier->get_categories();
|
||||||
$data['categories'] = $this->supplier->get_categories();
|
|
||||||
|
|
||||||
echo view("suppliers/form", $data);
|
return view("suppliers/form", $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts/updates a supplier
|
* Inserts/updates a supplier
|
||||||
*
|
*
|
||||||
* @param int $supplier_id
|
* @param int $supplier_id
|
||||||
* @return void
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
public function postSave(int $supplier_id = NEW_ENTRY): void
|
public function postSave(int $supplier_id = NEW_ENTRY): ResponseInterface
|
||||||
{
|
{
|
||||||
$first_name = $this->request->getPost('first_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS); //TODO: Duplicate code
|
$first_name = $this->request->getPost('first_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS); // TODO: Duplicate code
|
||||||
$last_name = $this->request->getPost('last_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
$last_name = $this->request->getPost('last_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||||
$email = strtolower($this->request->getPost('email', FILTER_SANITIZE_EMAIL));
|
$email = strtolower($this->request->getPost('email', FILTER_SANITIZE_EMAIL));
|
||||||
|
|
||||||
// format first and last name properly
|
// Format first and last name properly
|
||||||
$first_name = $this->nameize($first_name);
|
$first_name = $this->nameize($first_name);
|
||||||
$last_name = $this->nameize($last_name);
|
$last_name = $this->nameize($last_name);
|
||||||
|
|
||||||
$person_data = [
|
$person_data = [
|
||||||
'first_name' => $first_name,
|
'first_name' => $first_name,
|
||||||
'last_name' => $last_name,
|
'last_name' => $last_name,
|
||||||
'gender' => $this->request->getPost('gender'),
|
'gender' => $this->request->getPost('gender'),
|
||||||
'email' => $email,
|
'email' => $email,
|
||||||
'phone_number' => $this->request->getPost('phone_number', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
'phone_number' => $this->request->getPost('phone_number', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
'address_1' => $this->request->getPost('address_1', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
'address_1' => $this->request->getPost('address_1', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
'address_2' => $this->request->getPost('address_2', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
'address_2' => $this->request->getPost('address_2', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
'city' => $this->request->getPost('city', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
'city' => $this->request->getPost('city', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
'state' => $this->request->getPost('state', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
'state' => $this->request->getPost('state', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
'zip' => $this->request->getPost('zip', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
'zip' => $this->request->getPost('zip', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
'country' => $this->request->getPost('country', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
'country' => $this->request->getPost('country', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
'comments' => $this->request->getPost('comments', FILTER_SANITIZE_FULL_SPECIAL_CHARS)
|
'comments' => $this->request->getPost('comments', FILTER_SANITIZE_FULL_SPECIAL_CHARS)
|
||||||
];
|
];
|
||||||
|
|
||||||
$supplier_data = [
|
$supplier_data = [
|
||||||
'company_name' => $this->request->getPost('company_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
'company_name' => $this->request->getPost('company_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
'agency_name' => $this->request->getPost('agency_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
'agency_name' => $this->request->getPost('agency_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
'category' => $this->request->getPost('category', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
'category' => $this->request->getPost('category', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
'account_number' => $this->request->getPost('account_number') == '' ? null : $this->request->getPost('account_number', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
'account_number' => $this->request->getPost('account_number') == '' ? null : $this->request->getPost('account_number', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
'tax_id' => $this->request->getPost('tax_id', FILTER_SANITIZE_NUMBER_INT)
|
'tax_id' => $this->request->getPost('tax_id', FILTER_SANITIZE_NUMBER_INT)
|
||||||
];
|
];
|
||||||
|
|
||||||
if($this->supplier->save_supplier($person_data, $supplier_data, $supplier_id))
|
if ($this->supplier->save_supplier($person_data, $supplier_data, $supplier_id)) {
|
||||||
{
|
// New supplier
|
||||||
//New supplier
|
if ($supplier_id == NEW_ENTRY) {
|
||||||
if($supplier_id == NEW_ENTRY)
|
return $this->response->setJSON([
|
||||||
{
|
'success' => true,
|
||||||
echo json_encode ([
|
'message' => lang('Suppliers.successful_adding') . ' ' . $supplier_data['company_name'],
|
||||||
'success' => true,
|
'id' => $supplier_data['person_id']
|
||||||
'message' => lang('Suppliers.successful_adding') . ' ' . $supplier_data['company_name'],
|
]);
|
||||||
'id' => $supplier_data['person_id']
|
} else { // Existing supplier
|
||||||
]);
|
|
||||||
}
|
|
||||||
else //Existing supplier
|
|
||||||
{
|
|
||||||
echo json_encode ([
|
|
||||||
'success' => true,
|
|
||||||
'message' => lang('Suppliers.successful_updating') . ' ' . $supplier_data['company_name'],
|
|
||||||
'id' => $supplier_id]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else//failure
|
|
||||||
{
|
|
||||||
echo json_encode ([
|
|
||||||
'success' => false,
|
|
||||||
'message' => lang('Suppliers.error_adding_updating') . ' ' . $supplier_data['company_name'],
|
|
||||||
'id' => NEW_ENTRY
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
return $this->response->setJSON([
|
||||||
* This deletes suppliers from the suppliers table
|
'success' => true,
|
||||||
*
|
'message' => lang('Suppliers.successful_updating') . ' ' . $supplier_data['company_name'],
|
||||||
* @return void
|
'id' => $supplier_id
|
||||||
*/
|
]);
|
||||||
public function postDelete(): void
|
}
|
||||||
{
|
} else { // Failure
|
||||||
$suppliers_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_NUMBER_INT);
|
return $this->response->setJSON([
|
||||||
|
'success' => false,
|
||||||
|
'message' => lang('Suppliers.error_adding_updating') . ' ' . $supplier_data['company_name'],
|
||||||
|
'id' => NEW_ENTRY
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if($this->supplier->delete_list($suppliers_to_delete))
|
/**
|
||||||
{
|
* This deletes suppliers from the suppliers table
|
||||||
echo json_encode ([
|
*
|
||||||
'success' => true,
|
* @return ResponseInterface
|
||||||
'message' => lang('Suppliers.successful_deleted') . ' ' . count($suppliers_to_delete) . ' ' . lang('Suppliers.one_or_multiple')
|
*/
|
||||||
]);
|
public function postDelete(): ResponseInterface
|
||||||
}
|
{
|
||||||
else
|
$suppliers_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_NUMBER_INT);
|
||||||
{
|
|
||||||
echo json_encode (['success' => false, 'message' => lang('Suppliers.cannot_be_deleted')]);
|
if ($this->supplier->delete_list($suppliers_to_delete)) {
|
||||||
}
|
return $this->response->setJSON([
|
||||||
}
|
'success' => true,
|
||||||
|
'message' => lang('Suppliers.successful_deleted') . ' ' . count($suppliers_to_delete) . ' ' . lang('Suppliers.one_or_multiple')
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
return $this->response->setJSON(['success' => false, 'message' => lang('Suppliers.cannot_be_deleted')]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Controllers;
|
namespace App\Controllers;
|
||||||
|
|
||||||
use App\Models\Tax_category;
|
use App\Models\Tax_category;
|
||||||
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
use Config\Services;
|
use Config\Services;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -10,132 +11,122 @@ use Config\Services;
|
|||||||
*/
|
*/
|
||||||
class Tax_categories extends Secure_Controller
|
class Tax_categories extends Secure_Controller
|
||||||
{
|
{
|
||||||
private Tax_category $tax_category;
|
private Tax_category $tax_category;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct('tax_categories');
|
parent::__construct('tax_categories');
|
||||||
|
|
||||||
$this->tax_category = model(Tax_category::class);
|
$this->tax_category = model(Tax_category::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getIndex(): void
|
public function getIndex(): string
|
||||||
{
|
{
|
||||||
$data['tax_categories_table_headers'] = get_tax_categories_table_headers();
|
$data['tax_categories_table_headers'] = get_tax_categories_table_headers();
|
||||||
|
|
||||||
echo view('taxes/tax_categories', $data);
|
return view('taxes/tax_categories', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns tax_category table data rows. This will be called with AJAX.
|
* Returns tax_category table data rows. This will be called with AJAX.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function getSearch(): void
|
public function getSearch(): ResponseInterface
|
||||||
{
|
{
|
||||||
$search = $this->request->getGet('search');
|
$search = $this->request->getGet('search');
|
||||||
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$sort = $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
$sort = $this->sanitizeSortColumn(get_tax_categories_table_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'tax_category_id');
|
||||||
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||||
|
|
||||||
$tax_categories = $this->tax_category->search($search, $limit, $offset, $sort, $order);
|
$tax_categories = $this->tax_category->search($search, $limit, $offset, $sort, $order);
|
||||||
$total_rows = $this->tax_category->get_found_rows($search);
|
$total_rows = $this->tax_category->get_found_rows($search);
|
||||||
|
|
||||||
$data_rows = [];
|
$data_rows = [];
|
||||||
foreach($tax_categories->getResult() as $tax_category)
|
foreach ($tax_categories->getResult() as $tax_category) {
|
||||||
{
|
$data_rows[] = get_tax_categories_data_row($tax_category);
|
||||||
$data_rows[] = get_tax_categories_data_row($tax_category);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode (['total' => $total_rows, 'rows' => $data_rows]);
|
return $this->response->setJSON(['total' => $total_rows, 'rows' => $data_rows]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $row_id
|
* @param $row_id
|
||||||
* @return void
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
public function getRow($row_id): void
|
public function getRow($row_id): ResponseInterface
|
||||||
{
|
{
|
||||||
$data_row = get_tax_categories_data_row($this->tax_category->get_info($row_id));
|
$data_row = get_tax_categories_data_row($this->tax_category->get_info($row_id));
|
||||||
|
|
||||||
echo json_encode($data_row);
|
return $this->response->setJSON($data_row);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $tax_category_id
|
* @param int $tax_category_id
|
||||||
* @return void
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getView(int $tax_category_id = NEW_ENTRY): void
|
public function getView(int $tax_category_id = NEW_ENTRY): string
|
||||||
{
|
{
|
||||||
$data['tax_category_info'] = $this->tax_category->get_info($tax_category_id);
|
$data['tax_category_info'] = $this->tax_category->get_info($tax_category_id);
|
||||||
|
|
||||||
echo view("taxes/tax_category_form", $data);
|
return view("taxes/tax_category_form", $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $tax_category_id
|
* @param int $tax_category_id
|
||||||
* @return void
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
public function postSave(int $tax_category_id = NEW_ENTRY): void
|
public function postSave(int $tax_category_id = NEW_ENTRY): ResponseInterface
|
||||||
{
|
{
|
||||||
$tax_category_data = [
|
$tax_category_data = [
|
||||||
'tax_category' => $this->request->getPost('tax_category', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
'tax_category' => $this->request->getPost('tax_category', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
'tax_category_code' => $this->request->getPost('tax_category_code', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
'tax_category_code' => $this->request->getPost('tax_category_code', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
'tax_group_sequence' => $this->request->getPost('tax_group_sequence', FILTER_SANITIZE_NUMBER_INT)
|
'tax_group_sequence' => $this->request->getPost('tax_group_sequence', FILTER_SANITIZE_NUMBER_INT)
|
||||||
];
|
];
|
||||||
|
|
||||||
if($this->tax_category->save_value($tax_category_data, $tax_category_id))
|
if ($this->tax_category->save_value($tax_category_data, $tax_category_id)) {
|
||||||
{
|
// New tax_category_id
|
||||||
// New tax_category_id
|
if ($tax_category_id == NEW_ENTRY) {
|
||||||
if($tax_category_id == NEW_ENTRY)
|
return $this->response->setJSON([
|
||||||
{
|
'success' => true,
|
||||||
echo json_encode ([
|
'message' => lang('Tax_categories.successful_adding'),
|
||||||
'success' => true,
|
'id' => $tax_category_data['tax_category_id']
|
||||||
'message' => lang('Tax_categories.successful_adding'),
|
]);
|
||||||
'id' => $tax_category_data['tax_category_id']
|
} else {
|
||||||
]);
|
return $this->response->setJSON([
|
||||||
}
|
'success' => true,
|
||||||
else
|
'message' => lang('Tax_categories.successful_updating'),
|
||||||
{
|
'id' => $tax_category_id
|
||||||
echo json_encode ([
|
]);
|
||||||
'success' => true,
|
}
|
||||||
'message' => lang('Tax_categories.successful_updating'),
|
} else {
|
||||||
'id' => $tax_category_id
|
return $this->response->setJSON([
|
||||||
]);
|
'success' => false,
|
||||||
}
|
'message' => lang('Tax_categories.error_adding_updating') . ' ' . $tax_category_data['tax_category'],
|
||||||
}
|
'id' => NEW_ENTRY
|
||||||
else
|
]);
|
||||||
{
|
}
|
||||||
echo json_encode ([
|
}
|
||||||
'success' => false,
|
|
||||||
'message' => lang('Tax_categories.error_adding_updating') . ' ' . $tax_category_data['tax_category'],
|
|
||||||
'id' => NEW_ENTRY
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
public function postDelete(): void
|
public function postDelete(): ResponseInterface
|
||||||
{
|
{
|
||||||
$tax_categories_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_NUMBER_INT);
|
$tax_categories_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_NUMBER_INT);
|
||||||
|
|
||||||
if($this->tax_category->delete_list($tax_categories_to_delete))
|
if ($this->tax_category->delete_list($tax_categories_to_delete)) {
|
||||||
{
|
return $this->response->setJSON([
|
||||||
echo json_encode ([
|
'success' => true,
|
||||||
'success' => true,
|
'message' => lang('Tax_categories.successful_deleted') . ' ' . count($tax_categories_to_delete) . ' ' . lang('Tax_categories.one_or_multiple')
|
||||||
'message' => lang('Tax_categories.successful_deleted') . ' ' . count($tax_categories_to_delete) . ' ' . lang('Tax_categories.one_or_multiple')
|
]);
|
||||||
]);
|
} else {
|
||||||
}
|
return $this->response->setJSON(['success' => false, 'message' => lang('Tax_categories.cannot_be_deleted')]);
|
||||||
else
|
}
|
||||||
{
|
}
|
||||||
echo json_encode (['success' => false, 'message' => lang('Tax_categories.cannot_be_deleted')]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Controllers;
|
namespace App\Controllers;
|
||||||
|
|
||||||
use App\Models\Tax_code;
|
use App\Models\Tax_code;
|
||||||
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
use Config\Services;
|
use Config\Services;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -10,143 +11,133 @@ use Config\Services;
|
|||||||
*/
|
*/
|
||||||
class Tax_codes extends Secure_Controller
|
class Tax_codes extends Secure_Controller
|
||||||
{
|
{
|
||||||
private Tax_code $tax_code;
|
private Tax_code $tax_code;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct('tax_codes');
|
parent::__construct('tax_codes');
|
||||||
|
|
||||||
$this->tax_code = model(Tax_code::class);
|
$this->tax_code = model(Tax_code::class);
|
||||||
helper('tax_helper');
|
helper('tax_helper');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getIndex(): void
|
public function getIndex(): string
|
||||||
{
|
{
|
||||||
echo view('taxes/tax_codes', $this->get_data());
|
return view('taxes/tax_codes', $this->get_data());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function get_data(): array
|
public function get_data(): array
|
||||||
{
|
{
|
||||||
|
|
||||||
$data['table_headers'] = get_tax_code_table_headers();
|
$data['table_headers'] = get_tax_code_table_headers();
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns tax_category table data rows. This will be called with AJAX.
|
* Returns tax_category table data rows. This will be called with AJAX.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function getSearch(): void
|
public function getSearch(): ResponseInterface
|
||||||
{
|
{
|
||||||
$search = $this->request->getGet('search');
|
$search = $this->request->getGet('search');
|
||||||
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$sort = $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
$sort = $this->sanitizeSortColumn(get_tax_code_table_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'tax_code');
|
||||||
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||||
|
|
||||||
$tax_codes = $this->tax_code->search($search, $limit, $offset, $sort, $order);
|
$tax_codes = $this->tax_code->search($search, $limit, $offset, $sort, $order);
|
||||||
$total_rows = $this->tax_code->get_found_rows($search);
|
$total_rows = $this->tax_code->get_found_rows($search);
|
||||||
|
|
||||||
$data_rows = [];
|
$data_rows = [];
|
||||||
|
|
||||||
foreach($tax_codes->getResult() as $tax_code)
|
foreach ($tax_codes->getResult() as $tax_code) {
|
||||||
{
|
$data_rows[] = get_tax_code_data_row($tax_code);
|
||||||
$data_rows[] = get_tax_code_data_row($tax_code);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode (['total' => $total_rows, 'rows' => $data_rows]);
|
return $this->response->setJSON(['total' => $total_rows, 'rows' => $data_rows]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $row_id
|
* @param int $row_id
|
||||||
* @return void
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
public function getRow(int $row_id): void
|
public function getRow(int $row_id): ResponseInterface
|
||||||
{
|
{
|
||||||
$data_row = get_tax_code_data_row($this->tax_code->get_info($row_id));
|
$data_row = get_tax_code_data_row($this->tax_code->get_info($row_id));
|
||||||
|
|
||||||
echo json_encode($data_row);
|
return $this->response->setJSON($data_row);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $tax_code_id
|
* @param int $tax_code_id
|
||||||
* @return void
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getView(int $tax_code_id = NEW_ENTRY): void
|
public function getView(int $tax_code_id = NEW_ENTRY): string
|
||||||
{
|
{
|
||||||
$data['tax_code_info'] = $this->tax_code->get_info($tax_code_id);
|
$data['tax_code_info'] = $this->tax_code->get_info($tax_code_id);
|
||||||
|
|
||||||
echo view("taxes/tax_code_form", $data);
|
return view("taxes/tax_code_form", $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $tax_code_id
|
* @param int $tax_code_id
|
||||||
* @return void
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
public function postSave(int $tax_code_id = NEW_ENTRY): void
|
public function postSave(int $tax_code_id = NEW_ENTRY): ResponseInterface
|
||||||
{
|
{
|
||||||
$tax_code_data = [
|
$tax_code_data = [
|
||||||
'tax_code' => $this->request->getPost('tax_code', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
'tax_code' => $this->request->getPost('tax_code', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
'tax_code_name' => $this->request->getPost('tax_code_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
'tax_code_name' => $this->request->getPost('tax_code_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
'city' => $this->request->getPost('city', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
'city' => $this->request->getPost('city', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
'state' => $this->request->getPost('state', FILTER_SANITIZE_FULL_SPECIAL_CHARS)
|
'state' => $this->request->getPost('state', FILTER_SANITIZE_FULL_SPECIAL_CHARS)
|
||||||
];
|
];
|
||||||
|
|
||||||
if($this->tax_code->save($tax_code_data))
|
if ($this->tax_code->save($tax_code_data)) {
|
||||||
{
|
if ($tax_code_id == NEW_ENTRY) {
|
||||||
if($tax_code_id == NEW_ENTRY)
|
return $this->response->setJSON([
|
||||||
{
|
'success' => true,
|
||||||
echo json_encode ([
|
'message' => lang('Tax_codes.successful_adding'),
|
||||||
'success' => true,
|
'id' => $tax_code_data['tax_code_id']
|
||||||
'message' => lang('Tax_codes.successful_adding'),
|
]);
|
||||||
'id' => $tax_code_data['tax_code_id']
|
} else {
|
||||||
]);
|
return $this->response->setJSON([
|
||||||
}
|
'success' => true,
|
||||||
else
|
'message' => lang('Tax_codes.successful_updating'),
|
||||||
{
|
'id' => $tax_code_id
|
||||||
echo json_encode ([
|
]);
|
||||||
'success' => true,
|
}
|
||||||
'message' => lang('Tax_codes.successful_updating'),
|
} else {
|
||||||
'id' => $tax_code_id
|
return $this->response->setJSON([
|
||||||
]);
|
'success' => false,
|
||||||
}
|
'message' => lang('Tax_codes.error_adding_updating') . ' ' . $tax_code_data['tax_code_id'],
|
||||||
}
|
'id' => NEW_ENTRY
|
||||||
else
|
]);
|
||||||
{
|
}
|
||||||
echo json_encode ([
|
}
|
||||||
'success' => false,
|
|
||||||
'message' => lang('Tax_codes.error_adding_updating') . ' ' . $tax_code_data['tax_code_id'],
|
|
||||||
'id' => NEW_ENTRY
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
public function postDelete(): void
|
public function postDelete(): ResponseInterface
|
||||||
{
|
{
|
||||||
$tax_codes_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_NUMBER_INT);
|
$tax_codes_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_NUMBER_INT);
|
||||||
|
|
||||||
if($this->tax_code->delete_list($tax_codes_to_delete))
|
if ($this->tax_code->delete_list($tax_codes_to_delete)) {
|
||||||
{
|
return $this->response->setJSON([
|
||||||
echo json_encode ([
|
'success' => true,
|
||||||
'success' => true,
|
'message' => lang('Tax_codes.successful_deleted') . ' ' . count($tax_codes_to_delete) . ' ' . lang('Tax_codes.one_or_multiple')
|
||||||
'message' => lang('Tax_codes.successful_deleted') . ' ' . count($tax_codes_to_delete) . ' ' . lang('Tax_codes.one_or_multiple')
|
]);
|
||||||
]);
|
} else {
|
||||||
}
|
return $this->response->setJSON(['success' => false, 'message' => lang('Tax_codes.cannot_be_deleted')]);
|
||||||
else
|
}
|
||||||
{
|
}
|
||||||
echo json_encode (['success' => false, 'message' => lang('Tax_codes.cannot_be_deleted')]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Controllers;
|
namespace App\Controllers;
|
||||||
|
|
||||||
use App\Models\Tax_jurisdiction;
|
use App\Models\Tax_jurisdiction;
|
||||||
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
use Config\Services;
|
use Config\Services;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -10,133 +11,123 @@ use Config\Services;
|
|||||||
*/
|
*/
|
||||||
class Tax_jurisdictions extends Secure_Controller
|
class Tax_jurisdictions extends Secure_Controller
|
||||||
{
|
{
|
||||||
private Tax_jurisdiction $tax_jurisdiction;
|
private Tax_jurisdiction $tax_jurisdiction;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct('tax_jurisdictions');
|
parent::__construct('tax_jurisdictions');
|
||||||
|
|
||||||
$this->tax_jurisdiction = model(Tax_jurisdiction::class);
|
$this->tax_jurisdiction = model(Tax_jurisdiction::class);
|
||||||
|
|
||||||
helper('tax_helper');
|
helper('tax_helper');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getIndex(): void
|
public function getIndex(): string
|
||||||
{
|
{
|
||||||
$data['table_headers'] = get_tax_jurisdictions_table_headers();
|
$data['table_headers'] = get_tax_jurisdictions_table_headers();
|
||||||
|
|
||||||
echo view('taxes/tax_jurisdictions', $data);
|
return view('taxes/tax_jurisdictions', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns tax_category table data rows. This will be called with AJAX.
|
* Returns tax_category table data rows. This will be called with AJAX.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function getSearch(): void
|
public function getSearch(): ResponseInterface
|
||||||
{
|
{
|
||||||
$search = $this->request->getGet('search');
|
$search = $this->request->getGet('search');
|
||||||
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$sort = $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
$sort = $this->sanitizeSortColumn(get_tax_jurisdictions_table_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'jurisdiction_id');
|
||||||
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
||||||
|
|
||||||
$tax_jurisdictions = $this->tax_jurisdiction->search($search, $limit, $offset, $sort, $order);
|
$tax_jurisdictions = $this->tax_jurisdiction->search($search, $limit, $offset, $sort, $order);
|
||||||
$total_rows = $this->tax_jurisdiction->get_found_rows($search);
|
$total_rows = $this->tax_jurisdiction->get_found_rows($search);
|
||||||
|
|
||||||
$data_rows = [];
|
$data_rows = [];
|
||||||
foreach($tax_jurisdictions->getResult() as $tax_jurisdiction)
|
foreach ($tax_jurisdictions->getResult() as $tax_jurisdiction) {
|
||||||
{
|
$data_rows[] = get_tax_jurisdictions_data_row($tax_jurisdiction);
|
||||||
$data_rows[] = get_tax_jurisdictions_data_row($tax_jurisdiction);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode (['total' => $total_rows, 'rows' => $data_rows]);
|
return $this->response->setJSON(['total' => $total_rows, 'rows' => $data_rows]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $row_id
|
* @param int $row_id
|
||||||
* @return void
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
public function getRow(int $row_id): void
|
public function getRow(int $row_id): ResponseInterface
|
||||||
{
|
{
|
||||||
$data_row = get_tax_jurisdictions_data_row($this->tax_jurisdiction->get_info($row_id));
|
$data_row = get_tax_jurisdictions_data_row($this->tax_jurisdiction->get_info($row_id));
|
||||||
|
|
||||||
echo json_encode($data_row);
|
return $this->response->setJSON($data_row);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $tax_jurisdiction_id
|
* @param int $tax_jurisdiction_id
|
||||||
* @return void
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getView(int $tax_jurisdiction_id = NEW_ENTRY): void
|
public function getView(int $tax_jurisdiction_id = NEW_ENTRY): string
|
||||||
{
|
{
|
||||||
$data['tax_jurisdiction_info'] = $this->tax_jurisdiction->get_info($tax_jurisdiction_id);
|
$data['tax_jurisdiction_info'] = $this->tax_jurisdiction->get_info($tax_jurisdiction_id);
|
||||||
|
|
||||||
echo view("taxes/tax_jurisdiction_form", $data);
|
return view("taxes/tax_jurisdiction_form", $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $jurisdiction_id
|
* @param int $jurisdiction_id
|
||||||
* @return void
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
public function postSave(int $jurisdiction_id = NEW_ENTRY): void
|
public function postSave(int $jurisdiction_id = NEW_ENTRY): ResponseInterface
|
||||||
{
|
{
|
||||||
$tax_jurisdiction_data = [
|
$tax_jurisdiction_data = [
|
||||||
'jurisdiction_name' => $this->request->getPost('jurisdiction_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
'jurisdiction_name' => $this->request->getPost('jurisdiction_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
||||||
'reporting_authority' => $this->request->getPost('reporting_authority', FILTER_SANITIZE_FULL_SPECIAL_CHARS)
|
'reporting_authority' => $this->request->getPost('reporting_authority', FILTER_SANITIZE_FULL_SPECIAL_CHARS)
|
||||||
];
|
];
|
||||||
|
|
||||||
if($this->tax_jurisdiction->save_value($tax_jurisdiction_data))
|
if ($this->tax_jurisdiction->save_value($tax_jurisdiction_data)) {
|
||||||
{
|
if ($jurisdiction_id == NEW_ENTRY) {
|
||||||
if($jurisdiction_id == NEW_ENTRY)
|
return $this->response->setJSON([
|
||||||
{
|
'success' => true,
|
||||||
echo json_encode ([
|
'message' => lang('Tax_jurisdictions.successful_adding'),
|
||||||
'success' => true,
|
'id' => $tax_jurisdiction_data['jurisdiction_id']
|
||||||
'message' => lang('Tax_jurisdictions.successful_adding'),
|
]);
|
||||||
'id' => $tax_jurisdiction_data['jurisdiction_id']
|
} else {
|
||||||
]);
|
return $this->response->setJSON([
|
||||||
}
|
'success' => true,
|
||||||
else
|
'message' => lang('Tax_jurisdictions.successful_updating'),
|
||||||
{
|
'id' => $jurisdiction_id
|
||||||
echo json_encode ([
|
]);
|
||||||
'success' => true,
|
}
|
||||||
'message' => lang('Tax_jurisdictions.successful_updating'),
|
} else {
|
||||||
'id' => $jurisdiction_id
|
return $this->response->setJSON([
|
||||||
]);
|
'success' => false,
|
||||||
}
|
'message' => lang('Tax_jurisdictions.error_adding_updating') . ' ' . $tax_jurisdiction_data['jurisdiction_name'],
|
||||||
}
|
'id' => NEW_ENTRY
|
||||||
else
|
]);
|
||||||
{
|
}
|
||||||
echo json_encode ([
|
}
|
||||||
'success' => false,
|
|
||||||
'message' => lang('Tax_jurisdictions.error_adding_updating') . ' ' . $tax_jurisdiction_data['jurisdiction_name'],
|
|
||||||
'id' => NEW_ENTRY
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
public function postDelete(): void
|
public function postDelete(): ResponseInterface
|
||||||
{
|
{
|
||||||
$tax_jurisdictions_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_NUMBER_INT);
|
$tax_jurisdictions_to_delete = $this->request->getPost('ids', FILTER_SANITIZE_NUMBER_INT);
|
||||||
|
|
||||||
if($this->tax_jurisdiction->delete_list($tax_jurisdictions_to_delete))
|
if ($this->tax_jurisdiction->delete_list($tax_jurisdictions_to_delete)) {
|
||||||
{
|
return $this->response->setJSON([
|
||||||
echo json_encode ([
|
'success' => true,
|
||||||
'success' => true,
|
'message' => lang('Tax_jurisdictions.successful_deleted') . ' ' . count($tax_jurisdictions_to_delete) . ' ' . lang('Tax_jurisdictions.one_or_multiple')
|
||||||
'message' => lang('Tax_jurisdictions.successful_deleted') . ' ' . count($tax_jurisdictions_to_delete) . ' ' . lang('Tax_jurisdictions.one_or_multiple')
|
]);
|
||||||
]);
|
} else {
|
||||||
}
|
return $this->response->setJSON(['success' => false, 'message' => lang('Tax_jurisdictions.cannot_be_deleted')]);
|
||||||
else
|
}
|
||||||
{
|
}
|
||||||
echo json_encode (['success' => false, 'message' => lang('Tax_jurisdictions.cannot_be_deleted')]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>403 Forbidden</title>
|
<title>403 Forbidden</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
FROM alpine:3.14
|
FROM alpine:3.14
|
||||||
MAINTAINER jekkos
|
LABEL maintainer="jekkos"
|
||||||
|
|
||||||
ADD database.sql /docker-entrypoint-initdb.d/database.sql
|
ADD database.sql /docker-entrypoint-initdb.d/database.sql
|
||||||
VOLUME /docker-entrypoint-initdb.d
|
VOLUME /docker-entrypoint-initdb.d
|
||||||
|
|||||||
60
app/Database/Migrations/20170501000000_initial_schema.php
Normal file
60
app/Database/Migrations/20170501000000_initial_schema.php
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Database\Migrations;
|
||||||
|
|
||||||
|
use CodeIgniter\Database\Migration;
|
||||||
|
|
||||||
|
class Migration_Initial_Schema extends Migration
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a migration step.
|
||||||
|
* Only runs on fresh installs - skips if database already has tables.
|
||||||
|
*
|
||||||
|
* For testing: CI4's DatabaseTestTrait with $refresh=true handles table
|
||||||
|
* cleanup/creation automatically. This migration only loads initial schema
|
||||||
|
* on fresh databases where no application tables exist.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
// Check if core application tables exist (existing install)
|
||||||
|
// Note: migrations table may exist even on fresh DB due to migration tracking
|
||||||
|
$tables = $this->db->listTables();
|
||||||
|
|
||||||
|
// Check for a core application table, not just migrations table
|
||||||
|
foreach ($tables as $table) {
|
||||||
|
// Strip prefix if present for comparison
|
||||||
|
$tableName = str_replace($this->db->getPrefix(), '', $table);
|
||||||
|
if (in_array($tableName, ['app_config', 'items', 'employees', 'people'])) {
|
||||||
|
// Database already populated - skip initial schema
|
||||||
|
// This is an existing installation upgrading from older version
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fresh install - load initial schema
|
||||||
|
helper('migration');
|
||||||
|
execute_script(APPPATH . 'Database/Migrations/sqlscripts/initial_schema.sql');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Revert a migration step.
|
||||||
|
* Cannot revert initial schema - would lose all data.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
// Cannot safely revert initial schema
|
||||||
|
// Would require dropping all tables which would lose all data
|
||||||
|
$this->db->query('SET FOREIGN_KEY_CHECKS = 0');
|
||||||
|
|
||||||
|
foreach ($this->db->listTables() as $table) {
|
||||||
|
$this->db->query('DROP TABLE IF EXISTS `' . $table . '`');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->db->query('SET FOREIGN_KEY_CHECKS = 1');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,29 +2,57 @@
|
|||||||
|
|
||||||
namespace App\Database\Migrations;
|
namespace App\Database\Migrations;
|
||||||
|
|
||||||
|
use CodeIgniter\Database\Exceptions\DatabaseException;
|
||||||
use CodeIgniter\Database\Migration;
|
use CodeIgniter\Database\Migration;
|
||||||
|
|
||||||
class Migration_Upgrade_To_3_1_1 extends Migration
|
class Migration_Upgrade_To_3_1_1 extends Migration
|
||||||
{
|
{
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform a migration step.
|
* Perform a migration step.
|
||||||
*/
|
*/
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
helper('migration');
|
helper('migration');
|
||||||
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.0.2_to_3.1.1.sql');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// MariaDB blocks CONVERT TO CHARACTER SET on tables with FK constraints.
|
||||||
* Revert a migration step.
|
// Drop all FKs across affected tables before running the SQL script, recreate after.
|
||||||
*/
|
$fkColumns = [
|
||||||
public function down(): void
|
['modules', 'module_id'],
|
||||||
{
|
['stock_locations', 'location_id'],
|
||||||
|
['permissions', 'permission_id'],
|
||||||
|
['people', 'person_id'],
|
||||||
|
['suppliers', 'supplier_id'],
|
||||||
|
['items', 'item_id'],
|
||||||
|
['item_kits', 'item_kit_id'],
|
||||||
|
['sales', 'sale_id'],
|
||||||
|
['receivings', 'receiving_id'],
|
||||||
|
['employees', 'employee_id'],
|
||||||
|
['customers', 'person_id'],
|
||||||
|
];
|
||||||
|
|
||||||
}
|
$constraints = [];
|
||||||
|
foreach ($fkColumns as [$table, $column]) {
|
||||||
|
foreach (dropAllForeignKeyConstraints($table, $column) as $c) {
|
||||||
|
$constraints[$c['constraintName']] = $c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.0.2_to_3.1.1.sql')) {
|
||||||
|
throw new DatabaseException('Migration script 3.0.2_to_3.1.1.sql failed. Check logs for details.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$droppedTables = ['sales_suspended', 'sales_suspended_items', 'sales_suspended_items_taxes', 'sales_suspended_payments'];
|
||||||
|
$toRecreate = array_filter($constraints, fn($c) => !in_array($c['tableName'], $droppedTables, true));
|
||||||
|
recreateForeignKeyConstraints(array_values($toRecreate));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Revert a migration step.
|
||||||
|
*/
|
||||||
|
public function down(): void {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,425 +13,393 @@ use CodeIgniter\Database\ResultInterface;
|
|||||||
*/
|
*/
|
||||||
class Migration_Sales_Tax_Data extends Migration
|
class Migration_Sales_Tax_Data extends Migration
|
||||||
{
|
{
|
||||||
public const ROUND_UP = 5; //TODO: These need to be moved to constants.php
|
public const ROUND_UP = 5; // TODO: These need to be moved to constants.php
|
||||||
public const ROUND_DOWN = 6;
|
public const ROUND_DOWN = 6;
|
||||||
public const HALF_FIVE = 7;
|
public const HALF_FIVE = 7;
|
||||||
public const YES = '1';
|
public const YES = '1';
|
||||||
public const VAT_TAX = '0';
|
public const VAT_TAX = '0';
|
||||||
public const SALES_TAX = '1';
|
public const SALES_TAX = '1';
|
||||||
private Appconfig $appconfig;
|
private Appconfig $appconfig;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
$this->appconfig = model(Appconfig::class);
|
$this->appconfig = model(Appconfig::class);
|
||||||
}
|
}
|
||||||
//TODO: we need to figure out why we get a server error when uncommented portions of this migration run
|
// TODO: we need to figure out why we get a server error when uncommented portions of this migration run
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform a migration step.
|
* Perform a migration step.
|
||||||
*/
|
*/
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
$number_of_unmigrated = $this->get_count_of_unmigrated();
|
$number_of_unmigrated = $this->get_count_of_unmigrated();
|
||||||
error_log("Migrating sales tax history. The number of sales that will be migrated is $number_of_unmigrated");
|
log_message('info', "Migrating sales tax history. The number of sales that will be migrated is $number_of_unmigrated");
|
||||||
|
|
||||||
if($number_of_unmigrated > 0)
|
if ($number_of_unmigrated > 0) {
|
||||||
{
|
$unmigrated_invoices = $this->get_unmigrated($number_of_unmigrated)->getResultArray();
|
||||||
$unmigrated_invoices = $this->get_unmigrated($number_of_unmigrated)->getResultArray();
|
|
||||||
|
|
||||||
foreach($unmigrated_invoices as $key => $unmigrated_invoice)
|
foreach ($unmigrated_invoices as $key => $unmigrated_invoice) {
|
||||||
{
|
$this->upgrade_tax_history_for_sale($unmigrated_invoice['sale_id']);
|
||||||
$this->upgrade_tax_history_for_sale($unmigrated_invoice['sale_id']);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
error_log('Migrating sales tax history. The number of sales that will be migrated is finished.');
|
log_message('info', 'Migrating sales tax history. The number of sales that will be migrated is finished.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Revert a migration step.
|
* Revert a migration step.
|
||||||
*/
|
*/
|
||||||
public function down(): void
|
public function down(): void {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $sale_id
|
* @param int $sale_id
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
private function upgrade_tax_history_for_sale(int $sale_id): void
|
private function upgrade_tax_history_for_sale(int $sale_id): void
|
||||||
{
|
{
|
||||||
$tax_decimals = $this->appconfig->get_value('tax_decimals', 2);
|
$tax_decimals = $this->appconfig->get_value('tax_decimals', 2);
|
||||||
$tax_included = $this->appconfig->get_value('tax_included', Migration_Sales_Tax_Data::YES) == Migration_Sales_Tax_Data::YES;
|
$tax_included = $this->appconfig->get_value('tax_included', Migration_Sales_Tax_Data::YES) == Migration_Sales_Tax_Data::YES;
|
||||||
$customer_sales_tax_support = false;
|
$customer_sales_tax_support = false;
|
||||||
|
|
||||||
if($tax_included) //TODO: Convert to ternary notation.
|
if ($tax_included) { // TODO: Convert to ternary notation.
|
||||||
{
|
$tax_type = Migration_Sales_Tax_Data::VAT_TAX;
|
||||||
$tax_type = Migration_Sales_Tax_Data::VAT_TAX;
|
} else {
|
||||||
}
|
$tax_type = Migration_Sales_Tax_Data::SALES_TAX;
|
||||||
else
|
}
|
||||||
{
|
|
||||||
$tax_type = Migration_Sales_Tax_Data::SALES_TAX;
|
|
||||||
}
|
|
||||||
|
|
||||||
$sales_taxes = [];
|
$sales_taxes = [];
|
||||||
$tax_group_sequence = 0;
|
$tax_group_sequence = 0;
|
||||||
$items = $this->get_sale_items_for_migration($sale_id)->getResultArray();
|
$items = $this->get_sale_items_for_migration($sale_id)->getResultArray();
|
||||||
|
|
||||||
foreach($items as $item)
|
foreach ($items as $item) {
|
||||||
{
|
// This computes tax for each line item and adds it to the tax type total
|
||||||
// This computes tax for each line item and adds it to the tax type total
|
$tax_group = (float)$item['percent'] . '% ' . $item['name'];
|
||||||
$tax_group = (float)$item['percent'] . '% ' . $item['name'];
|
$tax_basis = $this->get_item_total($item['quantity_purchased'], $item['item_unit_price'], $item['discount_percent'], true);
|
||||||
$tax_basis = $this->get_item_total($item['quantity_purchased'], $item['item_unit_price'], $item['discount_percent'], true);
|
|
||||||
|
|
||||||
$item_tax_amount = $tax_included
|
$item_tax_amount = $tax_included
|
||||||
? $this->get_item_tax($item['quantity_purchased'], $item['item_unit_price'], $item['discount_percent'], $item['percent'])
|
? $this->get_item_tax($item['quantity_purchased'], $item['item_unit_price'], $item['discount_percent'], $item['percent'])
|
||||||
: $this->get_sales_tax_for_amount($tax_basis, $item['percent'], PHP_ROUND_HALF_UP, $tax_decimals);
|
: $this->get_sales_tax_for_amount($tax_basis, $item['percent'], PHP_ROUND_HALF_UP, $tax_decimals);
|
||||||
|
|
||||||
$this->update_sales_items_taxes_amount($sale_id, $item['line'], $item['name'], $item['percent'], $tax_type, $item_tax_amount);
|
$this->update_sales_items_taxes_amount($sale_id, $item['line'], $item['name'], $item['percent'], $tax_type, $item_tax_amount);
|
||||||
$this->update_sales_taxes($sales_taxes, $tax_type, $tax_group, $item['percent'], $tax_basis, $item_tax_amount, $tax_group_sequence, PHP_ROUND_HALF_UP, $sale_id, $item['name']);
|
$this->update_sales_taxes($sales_taxes, $tax_type, $tax_group, $item['percent'], $tax_basis, $item_tax_amount, $tax_group_sequence, PHP_ROUND_HALF_UP, $sale_id, $item['name']);
|
||||||
$tax_group_sequence++;
|
$tax_group_sequence++;
|
||||||
}
|
}
|
||||||
//Not sure when this would ever kick in, but this is technically the correct logic.
|
// Not sure when this would ever kick in, but this is technically the correct logic
|
||||||
if($customer_sales_tax_support) //TODO: This will always evaluate to false
|
if ($customer_sales_tax_support) { // TODO: This will always evaluate to false
|
||||||
{
|
$this->apply_invoice_taxing($sales_taxes);
|
||||||
$this->apply_invoice_taxing($sales_taxes);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$this->round_sales_taxes($sales_taxes);
|
$this->round_sales_taxes($sales_taxes);
|
||||||
$this->save_sales_tax($sales_taxes);
|
$this->save_sales_tax($sales_taxes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $block_count
|
* @param int $block_count
|
||||||
* @return ResultInterface
|
* @return ResultInterface
|
||||||
*/
|
*/
|
||||||
private function get_unmigrated(int $block_count): ResultInterface
|
private function get_unmigrated(int $block_count): ResultInterface
|
||||||
{
|
{
|
||||||
$builder = $this->db->table('sales_items_taxes as SIT');
|
$builder = $this->db->table('sales_items_taxes as SIT');
|
||||||
$builder->select('SIT.sale_id');
|
$builder->select('SIT.sale_id');
|
||||||
$builder->select('ST.sale_id as sales_taxes_sale_id');
|
$builder->select('ST.sale_id as sales_taxes_sale_id');
|
||||||
$builder->join('sales_taxes as ST','SIT.sale_id = ST.sale_id', 'left');
|
$builder->join('sales_taxes as ST', 'SIT.sale_id = ST.sale_id', 'left');
|
||||||
$builder->where('ST.sale_id', null);
|
$builder->where('ST.sale_id', null);
|
||||||
$builder->groupBy('SIT.sale_id');
|
$builder->groupBy('SIT.sale_id');
|
||||||
$builder->groupBy('ST.sale_id');
|
$builder->groupBy('ST.sale_id');
|
||||||
$builder->orderBy('SIT.sale_id');
|
$builder->orderBy('SIT.sale_id');
|
||||||
$builder->limit($block_count);
|
$builder->limit($block_count);
|
||||||
|
|
||||||
return $builder->get();
|
return $builder->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $sale_id
|
* @param int $sale_id
|
||||||
* @return ResultInterface
|
* @return ResultInterface
|
||||||
*/
|
*/
|
||||||
private function get_sale_items_for_migration(int $sale_id): ResultInterface
|
private function get_sale_items_for_migration(int $sale_id): ResultInterface
|
||||||
{
|
{
|
||||||
$builder = $this->db->table('sales_items as sales_items');
|
$builder = $this->db->table('sales_items as sales_items');
|
||||||
$builder->select('sales_items.sale_id as sale_id');
|
$builder->select('sales_items.sale_id as sale_id');
|
||||||
$builder->select('sales_items.line as line');
|
$builder->select('sales_items.line as line');
|
||||||
$builder->select('item_unit_price');
|
$builder->select('item_unit_price');
|
||||||
$builder->select('discount_percent');
|
$builder->select('discount_percent');
|
||||||
$builder->select('quantity_purchased');
|
$builder->select('quantity_purchased');
|
||||||
$builder->select('percent');
|
$builder->select('percent');
|
||||||
$builder->select('name');
|
$builder->select('name');
|
||||||
$builder->join('sales_items_taxes as sales_items_taxes', 'sales_items.sale_id = sales_items_taxes.sale_id and sales_items.line = sales_items_taxes.line');
|
$builder->join('sales_items_taxes as sales_items_taxes', 'sales_items.sale_id = sales_items_taxes.sale_id and sales_items.line = sales_items_taxes.line');
|
||||||
$builder->where('sales_items.sale_id', $sale_id);
|
$builder->where('sales_items.sale_id', $sale_id);
|
||||||
|
|
||||||
return $builder->get();
|
return $builder->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
private function get_count_of_unmigrated(): int
|
private function get_count_of_unmigrated(): int
|
||||||
{
|
{
|
||||||
$result = $this->db->query('SELECT COUNT(*) FROM(SELECT SIT.sale_id, ST.sale_id as sales_taxes_sale_id FROM '
|
$result = $this->db->query('SELECT COUNT(*) FROM(SELECT SIT.sale_id, ST.sale_id as sales_taxes_sale_id FROM '
|
||||||
. $this->db->prefixTable('sales_items_taxes')
|
. $this->db->prefixTable('sales_items_taxes')
|
||||||
. ' as SIT LEFT JOIN '
|
. ' as SIT LEFT JOIN '
|
||||||
. $this->db->prefixTable('sales_taxes')
|
. $this->db->prefixTable('sales_taxes')
|
||||||
. ' as ST ON SIT.sale_id = ST.sale_id WHERE ST.sale_id is null GROUP BY SIT.sale_id, ST.sale_id'
|
. ' as ST ON SIT.sale_id = ST.sale_id WHERE ST.sale_id is null GROUP BY SIT.sale_id, ST.sale_id'
|
||||||
. ' ORDER BY SIT.sale_id) as US')->getResultArray();
|
. ' ORDER BY SIT.sale_id) as US')->getResultArray();
|
||||||
|
|
||||||
if(!$result)
|
if (!$result) {
|
||||||
{
|
log_message('info', 'Database error in 20170502221506_sales_tax_data.php related to sales_taxes or sales_items_taxes.');
|
||||||
error_log('Database error in 20170502221506_sales_tax_data.php related to sales_taxes or sales_items_taxes.');
|
return 0;
|
||||||
return 0;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return $result[0]['COUNT(*)'] ?: 0;
|
return $result[0]['COUNT(*)'] ?: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $sale_id
|
* @param int $sale_id
|
||||||
* @param int $line
|
* @param int $line
|
||||||
* @param string $name
|
* @param string $name
|
||||||
* @param float $percent
|
* @param float $percent
|
||||||
* @param int $tax_type
|
* @param int $tax_type
|
||||||
* @param float $item_tax_amount
|
* @param float $item_tax_amount
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
private function update_sales_items_taxes_amount(int $sale_id, int $line, string $name, float $percent, int $tax_type, float $item_tax_amount): void
|
private function update_sales_items_taxes_amount(int $sale_id, int $line, string $name, float $percent, int $tax_type, float $item_tax_amount): void
|
||||||
{
|
{
|
||||||
$builder = $this->db->table('sales_items_taxes');
|
$builder = $this->db->table('sales_items_taxes');
|
||||||
$builder->where('sale_id', $sale_id);
|
$builder->where('sale_id', $sale_id);
|
||||||
$builder->where('line', $line);
|
$builder->where('line', $line);
|
||||||
$builder->where('name', $name);
|
$builder->where('name', $name);
|
||||||
$builder->where('percent', $percent);
|
$builder->where('percent', $percent);
|
||||||
$builder->update(['tax_type' => $tax_type, 'item_tax_amount' => $item_tax_amount]);
|
$builder->update(['tax_type' => $tax_type, 'item_tax_amount' => $item_tax_amount]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array $sales_taxes
|
* @param array $sales_taxes
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
private function save_sales_tax(array &$sales_taxes): void
|
private function save_sales_tax(array &$sales_taxes): void
|
||||||
{
|
{
|
||||||
$builder = $this->db->Table('sales_taxes');
|
$builder = $this->db->Table('sales_taxes');
|
||||||
|
|
||||||
foreach($sales_taxes as $line => $sales_tax)
|
foreach ($sales_taxes as $line => $sales_tax) {
|
||||||
{
|
$builder->insert($sales_tax);
|
||||||
$builder->insert($sales_tax);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $quantity
|
* @param string $quantity
|
||||||
* @param string $price
|
* @param string $price
|
||||||
* @param string $discount_percentage
|
* @param string $discount_percentage
|
||||||
* @param bool $include_discount
|
* @param bool $include_discount
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function get_item_total(string $quantity, string $price, string $discount_percentage, bool $include_discount = false): string
|
public function get_item_total(string $quantity, string $price, string $discount_percentage, bool $include_discount = false): string
|
||||||
{
|
{
|
||||||
$total = bcmul($quantity, $price);
|
$total = bcmul($quantity, $price);
|
||||||
|
|
||||||
if($include_discount)
|
if ($include_discount) {
|
||||||
{
|
$discount_amount = $this->get_item_discount($quantity, $price, $discount_percentage);
|
||||||
$discount_amount = $this->get_item_discount($quantity, $price, $discount_percentage);
|
return bcsub($total, $discount_amount);
|
||||||
return bcsub($total, $discount_amount);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return $total;
|
return $total;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $quantity
|
* @param string $quantity
|
||||||
* @param string $price
|
* @param string $price
|
||||||
* @param string $discount
|
* @param string $discount
|
||||||
* @return float
|
* @return float
|
||||||
*/
|
*/
|
||||||
public function get_item_discount(string $quantity, string $price, string $discount): float
|
public function get_item_discount(string $quantity, string $price, string $discount): float
|
||||||
{
|
{
|
||||||
$total = bcmul($quantity, $price);
|
$total = bcmul($quantity, $price);
|
||||||
$discount_fraction = bcdiv($discount, 100);
|
$discount_fraction = bcdiv($discount, 100);
|
||||||
$discount = bcmul($total, $discount_fraction);
|
$discount = bcmul($total, $discount_fraction);
|
||||||
|
|
||||||
return round($discount, totals_decimals(), PHP_ROUND_HALF_UP); //TODO: I don't think this is currency safe. Round will cast it's first parameter to a float. It also returns a float.
|
return round($discount, totals_decimals(), PHP_ROUND_HALF_UP); // TODO: I don't think this is currency safe. Round will cast it's first parameter to a float. It also returns a float.
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $quantity
|
* @param string $quantity
|
||||||
* @param string $price
|
* @param string $price
|
||||||
* @param string $discount_percentage
|
* @param string $discount_percentage
|
||||||
* @param string $tax_percentage
|
* @param string $tax_percentage
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function get_item_tax(string $quantity, string $price, string $discount_percentage, string $tax_percentage): string
|
public function get_item_tax(string $quantity, string $price, string $discount_percentage, string $tax_percentage): string
|
||||||
{
|
{
|
||||||
$tax_included = $this->appconfig->get_value('tax_included', Migration_Sales_Tax_Data::YES) == Migration_Sales_Tax_Data::YES;
|
$tax_included = $this->appconfig->get_value('tax_included', Migration_Sales_Tax_Data::YES) == Migration_Sales_Tax_Data::YES;
|
||||||
|
|
||||||
$price = $this->get_item_total($quantity, $price, $discount_percentage, true);
|
$price = $this->get_item_total($quantity, $price, $discount_percentage, true);
|
||||||
|
|
||||||
if($tax_included)
|
if ($tax_included) {
|
||||||
{
|
$tax_fraction = bcadd('100', $tax_percentage);
|
||||||
$tax_fraction = bcadd('100', $tax_percentage);
|
$tax_fraction = bcdiv($tax_fraction, '100');
|
||||||
$tax_fraction = bcdiv($tax_fraction, '100');
|
$price_tax_excl = bcdiv($price, $tax_fraction);
|
||||||
$price_tax_excl = bcdiv($price, $tax_fraction);
|
|
||||||
|
|
||||||
return bcsub($price, $price_tax_excl);
|
return bcsub($price, $price_tax_excl);
|
||||||
}
|
}
|
||||||
$tax_fraction = bcdiv($tax_percentage, '100');
|
$tax_fraction = bcdiv($tax_percentage, '100');
|
||||||
|
|
||||||
return bcmul($price, $tax_fraction);
|
return bcmul($price, $tax_fraction);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $tax_basis
|
* @param string $tax_basis
|
||||||
* @param string $tax_percentage
|
* @param string $tax_percentage
|
||||||
* @param int $rounding_mode
|
* @param int $rounding_mode
|
||||||
* @param int $decimals
|
* @param int $decimals
|
||||||
* @return float
|
* @return float
|
||||||
*/
|
*/
|
||||||
public function get_sales_tax_for_amount(string $tax_basis, string $tax_percentage, int $rounding_mode, int $decimals): float
|
public function get_sales_tax_for_amount(string $tax_basis, string $tax_percentage, int $rounding_mode, int $decimals): float
|
||||||
{
|
{
|
||||||
$tax_fraction = bcdiv($tax_percentage, '100');
|
$tax_fraction = bcdiv($tax_percentage, '100');
|
||||||
$tax_amount = bcmul($tax_basis, $tax_fraction);
|
$tax_amount = bcmul($tax_basis, $tax_fraction);
|
||||||
|
|
||||||
return $this->round_number($rounding_mode, $tax_amount, $decimals);
|
return $this->round_number($rounding_mode, $tax_amount, $decimals);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $rounding_mode
|
* @param int $rounding_mode
|
||||||
* @param string $amount
|
* @param string $amount
|
||||||
* @param int $decimals
|
* @param int $decimals
|
||||||
* @return float
|
* @return float
|
||||||
*/
|
*/
|
||||||
public function round_number(int $rounding_mode, string $amount, int $decimals): float
|
public function round_number(int $rounding_mode, string $amount, int $decimals): float
|
||||||
{
|
{
|
||||||
if($rounding_mode == Migration_Sales_Tax_Data::ROUND_UP)
|
$amount = (float)$amount;
|
||||||
{
|
|
||||||
$fig = pow(10,$decimals);
|
|
||||||
$rounded_total = (ceil($fig * $amount) + ceil($fig * $amount - ceil($fig * $amount)))/$fig;
|
|
||||||
}
|
|
||||||
elseif($rounding_mode == Migration_Sales_Tax_Data::ROUND_DOWN)
|
|
||||||
{
|
|
||||||
$fig = pow(10,$decimals);
|
|
||||||
$rounded_total = (floor($fig * $amount) + floor($fig * $amount - floor($fig * $amount)))/$fig;
|
|
||||||
}
|
|
||||||
elseif($rounding_mode == Migration_Sales_Tax_Data::HALF_FIVE)
|
|
||||||
{
|
|
||||||
$rounded_total = round($amount / 5) * 5;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$rounded_total = round($amount, $decimals, $rounding_mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $rounded_total; //TODO: I don't think this is currency safe. I think we need to be using bcmath() functions like we are in the rest of the code.
|
if ($rounding_mode == Migration_Sales_Tax_Data::ROUND_UP) {
|
||||||
}
|
$fig = pow(10, $decimals);
|
||||||
|
$rounded_total = (ceil($fig * $amount) + ceil($fig * $amount - ceil($fig * $amount))) / $fig;
|
||||||
|
} elseif ($rounding_mode == Migration_Sales_Tax_Data::ROUND_DOWN) {
|
||||||
|
$fig = pow(10, $decimals);
|
||||||
|
$rounded_total = (floor($fig * $amount) + floor($fig * $amount - floor($fig * $amount))) / $fig;
|
||||||
|
} elseif ($rounding_mode == Migration_Sales_Tax_Data::HALF_FIVE) {
|
||||||
|
$rounded_total = round($amount / 5) * 5;
|
||||||
|
} else {
|
||||||
|
$rounded_total = round($amount, $decimals, $rounding_mode);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
return $rounded_total; // TODO: I don't think this is currency safe. I think we need to be using bcmath() functions like we are in the rest of the code.
|
||||||
* @param array $sales_taxes
|
}
|
||||||
* @param string $tax_type
|
|
||||||
* @param string $tax_group
|
|
||||||
* @param float $tax_rate
|
|
||||||
* @param string $tax_basis
|
|
||||||
* @param string $item_tax_amount
|
|
||||||
* @param int $tax_group_sequence
|
|
||||||
* @param int $rounding_code
|
|
||||||
* @param int $sale_id
|
|
||||||
* @param string $name
|
|
||||||
* @param string $tax_code
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function update_sales_taxes(array &$sales_taxes, string $tax_type, string $tax_group, float $tax_rate, string $tax_basis, string $item_tax_amount, int $tax_group_sequence, int $rounding_code, int $sale_id, string $name = '', string $tax_code = ''): void
|
|
||||||
{
|
|
||||||
$tax_group_index = $this->clean('X'.$tax_group);
|
|
||||||
if(!array_key_exists($tax_group_index, $sales_taxes))
|
|
||||||
{
|
|
||||||
$insertkey = $tax_group_index; //TODO: $insertkey does not follow naming conventions.
|
|
||||||
$sales_tax = [
|
|
||||||
$insertkey => [
|
|
||||||
'sale_id' => $sale_id,
|
|
||||||
'tax_type' => $tax_type,
|
|
||||||
'tax_group' => $tax_group,
|
|
||||||
'sale_tax_basis' => $tax_basis,
|
|
||||||
'sale_tax_amount' => $item_tax_amount,
|
|
||||||
'print_sequence' => $tax_group_sequence,
|
|
||||||
'name' => $name,
|
|
||||||
'tax_rate' => $tax_rate,
|
|
||||||
'sales_tax_code' => $tax_code,
|
|
||||||
'rounding_code' => $rounding_code
|
|
||||||
]
|
|
||||||
];
|
|
||||||
//add to existing array
|
|
||||||
$sales_taxes += $sales_tax;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Important ... the sales amounts are accumulated for the group at the maximum configurable scale value of 4
|
|
||||||
// but the scale will in reality be the scale specified by the tax_decimal configuration value used for sales_items_taxes
|
|
||||||
$sales_taxes[$tax_group_index]['sale_tax_basis'] = bcadd($sales_taxes[$tax_group_index]['sale_tax_basis'], $tax_basis, 4);
|
|
||||||
$sales_taxes[$tax_group_index]['sale_tax_amount'] = bcadd($sales_taxes[$tax_group_index]['sale_tax_amount'], $item_tax_amount, 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $string
|
* @param array $sales_taxes
|
||||||
* @return string
|
* @param string $tax_type
|
||||||
*/
|
* @param string $tax_group
|
||||||
public function clean(string $string): string //TODO: $string is not a good name for this variable
|
* @param float $tax_rate
|
||||||
{
|
* @param string $tax_basis
|
||||||
$string = str_replace(' ', '-', $string); // Replaces all spaces with hyphens.
|
* @param string $item_tax_amount
|
||||||
|
* @param int $tax_group_sequence
|
||||||
|
* @param int $rounding_code
|
||||||
|
* @param int $sale_id
|
||||||
|
* @param string $name
|
||||||
|
* @param string $tax_code
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function update_sales_taxes(array &$sales_taxes, string $tax_type, string $tax_group, float $tax_rate, string $tax_basis, string $item_tax_amount, int $tax_group_sequence, int $rounding_code, int $sale_id, string $name = '', string $tax_code = ''): void
|
||||||
|
{
|
||||||
|
$tax_group_index = $this->clean('X' . $tax_group);
|
||||||
|
if (!array_key_exists($tax_group_index, $sales_taxes)) {
|
||||||
|
$insertkey = $tax_group_index; // TODO: $insertkey does not follow naming conventions.
|
||||||
|
$sales_tax = [
|
||||||
|
$insertkey => [
|
||||||
|
'sale_id' => $sale_id,
|
||||||
|
'tax_type' => $tax_type,
|
||||||
|
'tax_group' => $tax_group,
|
||||||
|
'sale_tax_basis' => $tax_basis,
|
||||||
|
'sale_tax_amount' => $item_tax_amount,
|
||||||
|
'print_sequence' => $tax_group_sequence,
|
||||||
|
'name' => $name,
|
||||||
|
'tax_rate' => $tax_rate,
|
||||||
|
'sales_tax_code' => $tax_code,
|
||||||
|
'rounding_code' => $rounding_code
|
||||||
|
]
|
||||||
|
];
|
||||||
|
// Add to existing array
|
||||||
|
$sales_taxes += $sales_tax;
|
||||||
|
} else {
|
||||||
|
// Important: the sales amounts are accumulated for the group at the maximum configurable scale value of 4
|
||||||
|
// but the scale will in reality be the scale specified by the tax_decimal configuration value used for sales_items_taxes
|
||||||
|
$sales_taxes[$tax_group_index]['sale_tax_basis'] = bcadd($sales_taxes[$tax_group_index]['sale_tax_basis'], $tax_basis, 4);
|
||||||
|
$sales_taxes[$tax_group_index]['sale_tax_amount'] = bcadd($sales_taxes[$tax_group_index]['sale_tax_amount'], $item_tax_amount, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return preg_replace('/[^A-Za-z0-9\-]/', '', $string); // Removes special chars.
|
/**
|
||||||
}
|
* @param string $string
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function clean(string $string): string // TODO: $string is not a good name for this variable
|
||||||
|
{
|
||||||
|
$string = str_replace(' ', '-', $string); // Replaces all spaces with hyphens.
|
||||||
|
|
||||||
/**
|
return preg_replace('/[^A-Za-z0-9\-]/', '', $string); // Removes special chars.
|
||||||
* @param array $sales_taxes
|
}
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function apply_invoice_taxing(array &$sales_taxes): void
|
|
||||||
{
|
|
||||||
if(!empty($sales_taxes)) //TODO: Duplicated code
|
|
||||||
{
|
|
||||||
$sort = [];
|
|
||||||
|
|
||||||
foreach($sales_taxes as $key => $value)
|
/**
|
||||||
{
|
* @param array $sales_taxes
|
||||||
$sort['print_sequence'][$key] = $value['print_sequence'];
|
* @return void
|
||||||
}
|
*/
|
||||||
|
public function apply_invoice_taxing(array &$sales_taxes): void
|
||||||
|
{
|
||||||
|
if (!empty($sales_taxes)) { // TODO: Duplicated code
|
||||||
|
$sort = [];
|
||||||
|
|
||||||
array_multisort($sort['print_sequence'], SORT_ASC, $sales_taxes);
|
foreach ($sales_taxes as $key => $value) {
|
||||||
}
|
$sort['print_sequence'][$key] = $value['print_sequence'];
|
||||||
|
}
|
||||||
|
|
||||||
$decimals = totals_decimals();
|
array_multisort($sort['print_sequence'], SORT_ASC, $sales_taxes);
|
||||||
|
}
|
||||||
|
|
||||||
foreach($sales_taxes as $row_number => $sales_tax)
|
$decimals = totals_decimals();
|
||||||
{
|
|
||||||
$sales_taxes[$row_number]['sale_tax_amount'] = $this->get_sales_tax_for_amount($sales_tax['sale_tax_basis'], $sales_tax['tax_rate'], $sales_tax['rounding_code'], $decimals);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
foreach ($sales_taxes as $row_number => $sales_tax) {
|
||||||
* @param array $sales_taxes
|
$sales_taxes[$row_number]['sale_tax_amount'] = $this->get_sales_tax_for_amount($sales_tax['sale_tax_basis'], $sales_tax['tax_rate'], $sales_tax['rounding_code'], $decimals);
|
||||||
* @return void
|
}
|
||||||
*/
|
}
|
||||||
public function round_sales_taxes(array &$sales_taxes): void
|
|
||||||
{
|
|
||||||
if(!empty($sales_taxes))
|
|
||||||
{
|
|
||||||
$sort = [];
|
|
||||||
foreach($sales_taxes as $k=>$v)
|
|
||||||
{
|
|
||||||
$sort['print_sequence'][$k] = $v['print_sequence'];
|
|
||||||
}
|
|
||||||
array_multisort($sort['print_sequence'], SORT_ASC, $sales_taxes);
|
|
||||||
}
|
|
||||||
|
|
||||||
$decimals = totals_decimals();
|
/**
|
||||||
|
* @param array $sales_taxes
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function round_sales_taxes(array &$sales_taxes): void
|
||||||
|
{
|
||||||
|
if (!empty($sales_taxes)) {
|
||||||
|
$sort = [];
|
||||||
|
foreach ($sales_taxes as $k => $v) {
|
||||||
|
$sort['print_sequence'][$k] = $v['print_sequence'];
|
||||||
|
}
|
||||||
|
array_multisort($sort['print_sequence'], SORT_ASC, $sales_taxes);
|
||||||
|
}
|
||||||
|
|
||||||
foreach($sales_taxes as $row_number => $sales_tax)
|
$decimals = totals_decimals();
|
||||||
{
|
|
||||||
$sale_tax_amount = $sales_tax['sale_tax_amount'];
|
|
||||||
$rounding_code = $sales_tax['rounding_code'];
|
|
||||||
$rounded_sale_tax_amount = $sale_tax_amount;
|
|
||||||
|
|
||||||
if ($rounding_code == PHP_ROUND_HALF_UP
|
foreach ($sales_taxes as $row_number => $sales_tax) {
|
||||||
|| $rounding_code == PHP_ROUND_HALF_DOWN
|
$sale_tax_amount = (float)$sales_tax['sale_tax_amount'];
|
||||||
|| $rounding_code == PHP_ROUND_HALF_EVEN
|
$rounding_code = $sales_tax['rounding_code'];
|
||||||
|| $rounding_code == PHP_ROUND_HALF_ODD)
|
$rounded_sale_tax_amount = $sale_tax_amount;
|
||||||
{
|
|
||||||
$rounded_sale_tax_amount = round($sale_tax_amount, $decimals, $rounding_code);
|
|
||||||
}
|
|
||||||
elseif($rounding_code == Migration_Sales_Tax_Data::ROUND_UP)
|
|
||||||
{
|
|
||||||
$fig = (int) str_pad('1', $decimals, '0');
|
|
||||||
$rounded_sale_tax_amount = (ceil($sale_tax_amount * $fig) / $fig);
|
|
||||||
}
|
|
||||||
elseif($rounding_code == Migration_Sales_Tax_Data::ROUND_DOWN)
|
|
||||||
{
|
|
||||||
$fig = (int) str_pad('1', $decimals, '0');
|
|
||||||
$rounded_sale_tax_amount = (floor($sale_tax_amount * $fig) / $fig);
|
|
||||||
}
|
|
||||||
elseif($rounding_code == Migration_Sales_Tax_Data::HALF_FIVE)
|
|
||||||
{
|
|
||||||
$rounded_sale_tax_amount = round($sale_tax_amount / 5) * 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
$sales_taxes[$row_number]['sale_tax_amount'] = $rounded_sale_tax_amount;
|
if (
|
||||||
}
|
$rounding_code == PHP_ROUND_HALF_UP
|
||||||
}
|
|| $rounding_code == PHP_ROUND_HALF_DOWN
|
||||||
|
|| $rounding_code == PHP_ROUND_HALF_EVEN
|
||||||
|
|| $rounding_code == PHP_ROUND_HALF_ODD
|
||||||
|
) {
|
||||||
|
$rounded_sale_tax_amount = round($sale_tax_amount, $decimals, $rounding_code);
|
||||||
|
} elseif ($rounding_code == Migration_Sales_Tax_Data::ROUND_UP) {
|
||||||
|
$fig = (int) str_pad('1', $decimals, '0');
|
||||||
|
$rounded_sale_tax_amount = (ceil($sale_tax_amount * $fig) / $fig);
|
||||||
|
} elseif ($rounding_code == Migration_Sales_Tax_Data::ROUND_DOWN) {
|
||||||
|
$fig = (int) str_pad('1', $decimals, '0');
|
||||||
|
$rounded_sale_tax_amount = (floor($sale_tax_amount * $fig) / $fig);
|
||||||
|
} elseif ($rounding_code == Migration_Sales_Tax_Data::HALF_FIVE) {
|
||||||
|
$rounded_sale_tax_amount = round($sale_tax_amount / 5) * 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sales_taxes[$row_number]['sale_tax_amount'] = $rounded_sale_tax_amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,20 +6,17 @@ use CodeIgniter\Database\Migration;
|
|||||||
|
|
||||||
class Migration_Upgrade_To_3_2_0 extends Migration
|
class Migration_Upgrade_To_3_2_0 extends Migration
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Perform a migration step.
|
* Perform a migration step.
|
||||||
*/
|
*/
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
helper('migration');
|
helper('migration');
|
||||||
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.1.1_to_3.2.0.sql');
|
execute_script(APPPATH . 'Database/Migrations/sqlscripts/3.1.1_to_3.2.0.sql');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Revert a migration step.
|
* Revert a migration step.
|
||||||
*/
|
*/
|
||||||
public function down(): void
|
public function down(): void {}
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user