mirror of
https://github.com/espocrm/espocrm.git
synced 2026-03-10 17:47:01 +00:00
Compare commits
535 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
235d8f2264 | ||
|
|
9cfab5a7ea | ||
|
|
86a85ba177 | ||
|
|
36a4cb2451 | ||
|
|
6f60a73c62 | ||
|
|
72fd9184f4 | ||
|
|
0a5ae621b4 | ||
|
|
4e588f6d0e | ||
|
|
f60c5e29de | ||
|
|
1662cfd97d | ||
|
|
0fd8f9d29f | ||
|
|
bf033c31af | ||
|
|
aa788a17d9 | ||
|
|
036ad99ba6 | ||
|
|
7fda7abbe6 | ||
|
|
fa0cf9c9fd | ||
|
|
3c651f8633 | ||
|
|
2b5f1c28e8 | ||
|
|
7fea881d17 | ||
|
|
ada64bba0c | ||
|
|
8fe96b140a | ||
|
|
3b25a1a001 | ||
|
|
f4d98f177c | ||
|
|
9f4f38bb0d | ||
|
|
e76490880a | ||
|
|
e12bb1320c | ||
|
|
c84abfb542 | ||
|
|
656f66f567 | ||
|
|
eca5bdea71 | ||
|
|
2ab4173e85 | ||
|
|
195db973dc | ||
|
|
cd5537cc26 | ||
|
|
94008f5a53 | ||
|
|
bf410b2258 | ||
|
|
0f2a26b744 | ||
|
|
c11fd843d0 | ||
|
|
70a374004d | ||
|
|
0b218833af | ||
|
|
4a1e5c974c | ||
|
|
b08083173a | ||
|
|
c24e7a6939 | ||
|
|
ce99671583 | ||
|
|
d36e5cc0c1 | ||
|
|
13ae2d27c8 | ||
|
|
ea5c76f012 | ||
|
|
acb9b50d14 | ||
|
|
968fc7ad30 | ||
|
|
bb6d7598a1 | ||
|
|
cd9481670a | ||
|
|
505e9e278b | ||
|
|
7e59888fb2 | ||
|
|
74f2d7f1ea | ||
|
|
f319be61d2 | ||
|
|
2cc4c8f974 | ||
|
|
a3aa74013a | ||
|
|
509b3affd1 | ||
|
|
ad9b2b54dd | ||
|
|
247eb00963 | ||
|
|
c0b59a49bf | ||
|
|
b525afe154 | ||
|
|
b449263854 | ||
|
|
12550c3d0a | ||
|
|
803a4ffb7f | ||
|
|
57bfea4d70 | ||
|
|
86f460866a | ||
|
|
8515157916 | ||
|
|
5bd18dee49 | ||
|
|
88ea5e7d6c | ||
|
|
3839335508 | ||
|
|
b8e94b52aa | ||
|
|
eeda450405 | ||
|
|
d051bd7df2 | ||
|
|
690a265450 | ||
|
|
8bec0a3caf | ||
|
|
a89463367e | ||
|
|
05dbcbd917 | ||
|
|
774bde3e20 | ||
|
|
90589e0d26 | ||
|
|
be199235f1 | ||
|
|
1b2d67b027 | ||
|
|
7bc56fc864 | ||
|
|
c706ddc809 | ||
|
|
1b6b2ea140 | ||
|
|
c1db037fc2 | ||
|
|
838e9ea773 | ||
|
|
10efe3513c | ||
|
|
6d301092b2 | ||
|
|
5acb5da8fa | ||
|
|
3ad343b274 | ||
|
|
c5fd749b21 | ||
|
|
1f8e0f16c7 | ||
|
|
92babe7fbc | ||
|
|
fb684837f8 | ||
|
|
669413b184 | ||
|
|
a945a01767 | ||
|
|
b4664eafa5 | ||
|
|
34b06b83aa | ||
|
|
da14681077 | ||
|
|
53e91ad683 | ||
|
|
06434bef99 | ||
|
|
16fab0ced0 | ||
|
|
bc3e531447 | ||
|
|
af038e3306 | ||
|
|
1a06b83d9d | ||
|
|
bcc3cfd143 | ||
|
|
b73123c137 | ||
|
|
8c47f94172 | ||
|
|
b03e17ce22 | ||
|
|
c0ed352293 | ||
|
|
e4eb8794ff | ||
|
|
830ba03448 | ||
|
|
45b2f267de | ||
|
|
891c0d6657 | ||
|
|
8583352033 | ||
|
|
76ea81465e | ||
|
|
9f4b558936 | ||
|
|
773d69c569 | ||
|
|
f03997c927 | ||
|
|
2fc9d405f8 | ||
|
|
4278895995 | ||
|
|
af41c263ee | ||
|
|
0466d6ce83 | ||
|
|
3a4e1d6b11 | ||
|
|
818f697b6b | ||
|
|
83e0d926ac | ||
|
|
782f22325a | ||
|
|
e896a7c960 | ||
|
|
1bb0035b34 | ||
|
|
5cdae539a6 | ||
|
|
bf131b7a0e | ||
|
|
cb08de46b0 | ||
|
|
81f1c3d6b2 | ||
|
|
6b7da3b649 | ||
|
|
05888ae2c3 | ||
|
|
bc97156569 | ||
|
|
e9103eb796 | ||
|
|
62b8b30e3a | ||
|
|
81897253c5 | ||
|
|
a242c9fd12 | ||
|
|
b299220dd0 | ||
|
|
3883881afc | ||
|
|
9061853666 | ||
|
|
20c8da511f | ||
|
|
d7f46e039e | ||
|
|
0532524a50 | ||
|
|
0f0bbff14c | ||
|
|
ce7fbe1471 | ||
|
|
b53d96b9df | ||
|
|
a184f63c00 | ||
|
|
75fb028f1a | ||
|
|
190c0d500a | ||
|
|
663fc784c1 | ||
|
|
38b985defc | ||
|
|
9d5cdea95c | ||
|
|
004c2a4646 | ||
|
|
05d88731e7 | ||
|
|
5b0d244c8a | ||
|
|
60cf844f63 | ||
|
|
02eba06a6d | ||
|
|
c9e148f4d0 | ||
|
|
d7f3e79ce8 | ||
|
|
5549a80e69 | ||
|
|
82f7202ec3 | ||
|
|
77c1db6b25 | ||
|
|
adb7165807 | ||
|
|
d1cfa84f06 | ||
|
|
f9aa080fc8 | ||
|
|
6d47e327cb | ||
|
|
a2f0d32c5f | ||
|
|
4e019a7e84 | ||
|
|
d3c7c6bfe4 | ||
|
|
ddc4cfb197 | ||
|
|
43696d91fc | ||
|
|
69ec7ea23b | ||
|
|
5b0a58372b | ||
|
|
38e2650cde | ||
|
|
307ba20f0a | ||
|
|
1659a731ad | ||
|
|
f28fcde90c | ||
|
|
21cda21b2e | ||
|
|
576d34e9bb | ||
|
|
d6b992ef50 | ||
|
|
f4c8931d7c | ||
|
|
8a17ff2ca3 | ||
|
|
a5681a75bb | ||
|
|
e6b1299293 | ||
|
|
01de38ce97 | ||
|
|
35940f28d2 | ||
|
|
1c1b40ff24 | ||
|
|
31cc9849e4 | ||
|
|
61fb2a0bbe | ||
|
|
0c5af4c6f0 | ||
|
|
bde04d8a4a | ||
|
|
9bbe2ba80d | ||
|
|
1a5e9d287d | ||
|
|
116f04d42a | ||
|
|
92e15f4252 | ||
|
|
192410420f | ||
|
|
5f9db6489a | ||
|
|
dcc97f4f26 | ||
|
|
513398c984 | ||
|
|
10955e3f56 | ||
|
|
261f6daf69 | ||
|
|
b352a194ea | ||
|
|
1c9095a9f4 | ||
|
|
3b69b83b3d | ||
|
|
882e92bbcf | ||
|
|
a5cfaabd13 | ||
|
|
8323d867fb | ||
|
|
d88b99a7f8 | ||
|
|
9bea92f35e | ||
|
|
1fe3aad25f | ||
|
|
10063eaaeb | ||
|
|
7b7c76c8d1 | ||
|
|
ebe0e1ade7 | ||
|
|
b1ac2ebb49 | ||
|
|
348e72ac37 | ||
|
|
fb9df5ff0e | ||
|
|
ad9f31c499 | ||
|
|
4ba0f902ce | ||
|
|
6606a7aef1 | ||
|
|
9b878148cf | ||
|
|
a1de3a1536 | ||
|
|
187e007653 | ||
|
|
6fb4f79f80 | ||
|
|
785eb8ab39 | ||
|
|
f096bf1949 | ||
|
|
a406bfe19f | ||
|
|
664610f283 | ||
|
|
cdc204710f | ||
|
|
b762aafdb2 | ||
|
|
4e1b6a00b0 | ||
|
|
1a7a7dd335 | ||
|
|
d16b967b7b | ||
|
|
9ec45a1730 | ||
|
|
41448ef7eb | ||
|
|
ebbf785701 | ||
|
|
82f9351414 | ||
|
|
b19f05af19 | ||
|
|
8336fe3f66 | ||
|
|
af1fc53e10 | ||
|
|
8607c95c6e | ||
|
|
3e76a51c4b | ||
|
|
8aabf3eb6b | ||
|
|
5c339648ba | ||
|
|
cd453664eb | ||
|
|
a7cb7125b2 | ||
|
|
e8366602f3 | ||
|
|
c25a23ae78 | ||
|
|
6fbafcfc36 | ||
|
|
05940b916f | ||
|
|
dbfcf35084 | ||
|
|
2dfe48d4b2 | ||
|
|
90c2e21788 | ||
|
|
9eb5cd69c5 | ||
|
|
72fee60900 | ||
|
|
d4fb9fc79a | ||
|
|
a4b3be8323 | ||
|
|
30a39f57ed | ||
|
|
3660aa3fc8 | ||
|
|
f970ac02f2 | ||
|
|
4d114bc2e8 | ||
|
|
ea0d53d0e6 | ||
|
|
8a2c0853c2 | ||
|
|
28c77b2890 | ||
|
|
4c3b4017eb | ||
|
|
a4240a7d89 | ||
|
|
c3e4f343a4 | ||
|
|
168077ad1b | ||
|
|
7679f019f9 | ||
|
|
f80ed523bb | ||
|
|
e58132b6b3 | ||
|
|
f98810d87f | ||
|
|
e3e5bf1651 | ||
|
|
99af4a6dc7 | ||
|
|
14510f9130 | ||
|
|
f9ae0d266e | ||
|
|
2ecc9ac10f | ||
|
|
2a0b8957fc | ||
|
|
4dc289f2bc | ||
|
|
107856f57d | ||
|
|
5eb4d27a83 | ||
|
|
8a4302a8d9 | ||
|
|
db5b37b221 | ||
|
|
34a191446f | ||
|
|
2a3e9c143f | ||
|
|
85b38f8119 | ||
|
|
9f70e7dce2 | ||
|
|
d73222875a | ||
|
|
a90098666d | ||
|
|
6f06c75fdd | ||
|
|
6977f1adaa | ||
|
|
67beeca698 | ||
|
|
acc6684e2d | ||
|
|
7eb4818739 | ||
|
|
a1caf22474 | ||
|
|
588887b9d8 | ||
|
|
2c480cc45b | ||
|
|
068eff9c94 | ||
|
|
ff627dd7dc | ||
|
|
bcf6a24b9f | ||
|
|
714907d498 | ||
|
|
14660ed0ae | ||
|
|
3632e53f12 | ||
|
|
f0f6455969 | ||
|
|
24546658b8 | ||
|
|
2fc728cce9 | ||
|
|
0ec1de2e8e | ||
|
|
8ec6a350fd | ||
|
|
2b824537eb | ||
|
|
c9caebca28 | ||
|
|
5fdae984db | ||
|
|
f6500d6c08 | ||
|
|
b3b04a5346 | ||
|
|
7aee43c2ff | ||
|
|
376ddbb5ff | ||
|
|
dc00c53731 | ||
|
|
eadfa783f7 | ||
|
|
5e0e644b5e | ||
|
|
e4a75a75d2 | ||
|
|
1d16a3cbfd | ||
|
|
8b27bb6fb4 | ||
|
|
3cf1db1e6f | ||
|
|
c2d0b2fb86 | ||
|
|
99dde4d4e1 | ||
|
|
0845baa72e | ||
|
|
1567fd4feb | ||
|
|
edf1bac058 | ||
|
|
f02a193bda | ||
|
|
a9dff223ed | ||
|
|
2bfb414495 | ||
|
|
d55b82a6ea | ||
|
|
c58ffc6cac | ||
|
|
6dfd2d49bb | ||
|
|
dc0a866277 | ||
|
|
32f07d81d4 | ||
|
|
44460362c5 | ||
|
|
1c5537bb55 | ||
|
|
2636ac0b31 | ||
|
|
e5cfe759b1 | ||
|
|
a6f7df72b5 | ||
|
|
5b4788825d | ||
|
|
c0355cb2b2 | ||
|
|
2eac1b3721 | ||
|
|
78aa4ce6c5 | ||
|
|
dd7bd118af | ||
|
|
225c6efe3e | ||
|
|
fa3a2b729b | ||
|
|
2a3e3dde0a | ||
|
|
878ce83448 | ||
|
|
db5d1850a9 | ||
|
|
9b6b4e4199 | ||
|
|
1b7a06b909 | ||
|
|
0925c6706e | ||
|
|
f5829310b6 | ||
|
|
43ab056313 | ||
|
|
57ef6f1b7e | ||
|
|
0525a0b3cd | ||
|
|
7a4cbec022 | ||
|
|
705f791e12 | ||
|
|
69f836575c | ||
|
|
f82b8ad49f | ||
|
|
2e388b32f3 | ||
|
|
de1e4abf0f | ||
|
|
c37c3d32aa | ||
|
|
01e252085c | ||
|
|
45b8764670 | ||
|
|
a21c6dda36 | ||
|
|
82be8d7fbd | ||
|
|
97ae13a27f | ||
|
|
edd2d6a630 | ||
|
|
8475e0a99c | ||
|
|
02e3d1823f | ||
|
|
6b6f522f1c | ||
|
|
80d695239c | ||
|
|
d3684df6e3 | ||
|
|
d2bf82d232 | ||
|
|
e6f4b337ce | ||
|
|
f4e58d9bee | ||
|
|
af9e77a5bb | ||
|
|
1d9af232b9 | ||
|
|
60b6c85864 | ||
|
|
709c301dda | ||
|
|
37dc6a1217 | ||
|
|
ba82989e97 | ||
|
|
731273c296 | ||
|
|
2a11d9e7ed | ||
|
|
67dea8fd5e | ||
|
|
8aff7316e5 | ||
|
|
6331300806 | ||
|
|
64d04e1d78 | ||
|
|
6258893aa8 | ||
|
|
3e27868760 | ||
|
|
cbf8301a89 | ||
|
|
1601a12e20 | ||
|
|
4420511e02 | ||
|
|
bbfe3c7366 | ||
|
|
64d31c8324 | ||
|
|
e93860101c | ||
|
|
04444aa03a | ||
|
|
6eba798cd8 | ||
|
|
c5555c2909 | ||
|
|
56164b9446 | ||
|
|
9f40bb4b1e | ||
|
|
d67323d35f | ||
|
|
3f26095281 | ||
|
|
d63871957d | ||
|
|
553c692b9a | ||
|
|
4a71ab5f5d | ||
|
|
3fbdff674e | ||
|
|
e6aad6eabc | ||
|
|
3b1d307fad | ||
|
|
66fc4c2294 | ||
|
|
a21e4b836d | ||
|
|
3159181c5c | ||
|
|
770cac9b87 | ||
|
|
ec4afe0e11 | ||
|
|
163d51770d | ||
|
|
364a5c50f0 | ||
|
|
581f09eb9b | ||
|
|
d75bc354cf | ||
|
|
5e3f00f11d | ||
|
|
ffe83d24ce | ||
|
|
ae874c44f5 | ||
|
|
e80269b61c | ||
|
|
2c4d5fb0f8 | ||
|
|
1fe222852a | ||
|
|
60ae87a3cb | ||
|
|
7f092ad31d | ||
|
|
fd8090e13d | ||
|
|
4aba2aab4c | ||
|
|
8c03b0a6cd | ||
|
|
e87fda3f3a | ||
|
|
4cabd6c2f1 | ||
|
|
f38361c7c7 | ||
|
|
ca3d10e7a2 | ||
|
|
e4cb9071d0 | ||
|
|
2b2777b3da | ||
|
|
83fd96a876 | ||
|
|
c1b1eaf847 | ||
|
|
f63cc22f44 | ||
|
|
3da1632e4b | ||
|
|
0bf4624839 | ||
|
|
0d38dfb385 | ||
|
|
70fff1167b | ||
|
|
196c300be5 | ||
|
|
a894f9a70b | ||
|
|
9e3c258c90 | ||
|
|
0893082326 | ||
|
|
18d12c1faf | ||
|
|
46d9dd7bea | ||
|
|
4fa562a283 | ||
|
|
1f02c8320b | ||
|
|
774aff1d17 | ||
|
|
74c142a1ac | ||
|
|
06d604796f | ||
|
|
f1db095fdc | ||
|
|
bac5bf46b5 | ||
|
|
a0f085e415 | ||
|
|
c69f13dda6 | ||
|
|
f6362b3aa5 | ||
|
|
4433325abb | ||
|
|
f2e5197568 | ||
|
|
bffcd3adb7 | ||
|
|
20e22c43f0 | ||
|
|
99f0194b80 | ||
|
|
a5422fd789 | ||
|
|
fc5203c3ba | ||
|
|
5deefc5f35 | ||
|
|
40531f4d4e | ||
|
|
78358057e6 | ||
|
|
a6b7b5ca61 | ||
|
|
0b71b4fc7d | ||
|
|
4b6c177af6 | ||
|
|
48c665b641 | ||
|
|
506f7cf410 | ||
|
|
a5cec0370e | ||
|
|
2e92adff81 | ||
|
|
1e3a3b909b | ||
|
|
1dbc41619f | ||
|
|
e4237abdbf | ||
|
|
b2e1ff0d5a | ||
|
|
790c34cd8a | ||
|
|
1e175429c8 | ||
|
|
575cdf8f5b | ||
|
|
02948e09c5 | ||
|
|
20b6dbe5ac | ||
|
|
328b6fcaa0 | ||
|
|
42355f3bec | ||
|
|
37a84f8cea | ||
|
|
f1ad8a734a | ||
|
|
870cdb19e3 | ||
|
|
56e5ce0bba | ||
|
|
576015fffe | ||
|
|
5f3d34b027 | ||
|
|
01bb105808 | ||
|
|
d9125a5204 | ||
|
|
93a26dca36 | ||
|
|
5c539b99d8 | ||
|
|
9e622bbfbd | ||
|
|
283a25162e | ||
|
|
0c44eec377 | ||
|
|
0cbdf88b21 | ||
|
|
2f06f0b7d2 | ||
|
|
cee9d21b7c | ||
|
|
c03afcce33 | ||
|
|
5e9ee92011 | ||
|
|
ad212d694f | ||
|
|
9530b2802b | ||
|
|
b4e79d57ea | ||
|
|
7efd36ebe8 | ||
|
|
9f90802fb2 | ||
|
|
18f4cd4587 | ||
|
|
fc01905134 | ||
|
|
91523553fb | ||
|
|
ca449b9952 | ||
|
|
3e0eade2f3 | ||
|
|
68cca25635 | ||
|
|
050fb4330e | ||
|
|
14d43bda1c | ||
|
|
34a8158284 | ||
|
|
8871842863 | ||
|
|
9392a26f81 | ||
|
|
8eac39bd50 | ||
|
|
2c8407581c | ||
|
|
b89349d0ee | ||
|
|
f554a6f649 | ||
|
|
e4a8383758 | ||
|
|
db94026d52 | ||
|
|
18e4aeb05d | ||
|
|
835ca877c7 | ||
|
|
89c1cf1f25 | ||
|
|
7cf512f75b | ||
|
|
d625647b29 | ||
|
|
21fbef7fb4 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -4,6 +4,7 @@
|
||||
/data/preferences/*
|
||||
/data/.backup/*
|
||||
/data/config.php
|
||||
/data/tmp/*
|
||||
/build
|
||||
/node_modules
|
||||
npm-debug.log
|
||||
@@ -13,6 +14,7 @@ npm-debug.log
|
||||
/tests/unit/testData/cache/*
|
||||
!/tests/unit/testData/cache/.data
|
||||
/tests/integration/config.php
|
||||
.phpunit.result.cache
|
||||
composer.phar
|
||||
vendor/
|
||||
/custom/Espo/Custom/*
|
||||
|
||||
@@ -20,5 +20,5 @@ DirectoryIndex index.php index.html
|
||||
|
||||
RewriteRule .* - [E=HTTP_ESPO_CGI_AUTH:%{HTTP:Authorization}]
|
||||
|
||||
RewriteRule reset/?$ reset.html [QSA,L]
|
||||
RewriteRule /?web\.config - [F]
|
||||
</IfModule>
|
||||
@@ -4,6 +4,12 @@ Before we can merge your pull request you need to accept our CLA [here](https://
|
||||
|
||||
[Code Style Guidelines](https://github.com/espocrm/espocrm/wiki/Code-Style-Guidelines).
|
||||
|
||||
Branches:
|
||||
|
||||
* *hotfix/** – upcoming maintenance release; fixes should be pushed to this branch;
|
||||
* *master* – develop branch; new features should be pushed to this branch;
|
||||
* *stable* – last stable release.
|
||||
|
||||
## Issues
|
||||
|
||||
We don't provide developer help or any kind of support on github. Please use our [forum](https://forum.espocrm.com) for this.
|
||||
|
||||
46
Gruntfile.js
46
Gruntfile.js
@@ -21,10 +21,11 @@
|
||||
|
||||
/**
|
||||
* * `grunt` - full build
|
||||
* * `grunt dev` - build only items needed for development
|
||||
* * `grunt offline` - skips *composer install*
|
||||
* * `grunt dev` - build only items needed for development (takes less time)
|
||||
* * `grunt offline` - build but skip *composer install*
|
||||
* * `grant release` - full build plus upgrade packages`
|
||||
* * `grant tests` - build and run tests
|
||||
* * `grant test` - build for test running
|
||||
* * `grant run-tests` - build and run unit and integratino tests
|
||||
*/
|
||||
|
||||
module.exports = function (grunt) {
|
||||
@@ -300,19 +301,23 @@ module.exports = function (grunt) {
|
||||
});
|
||||
|
||||
grunt.registerTask("composer", function() {
|
||||
cp.execSync("composer install", {stdio: 'ignore'});
|
||||
cp.execSync("composer install --ignore-platform-reqs --no-dev", {stdio: 'ignore'});
|
||||
});
|
||||
|
||||
grunt.registerTask("composer-dev", function() {
|
||||
cp.execSync("composer install --ignore-platform-reqs", {stdio: 'ignore'});
|
||||
});
|
||||
|
||||
grunt.registerTask("upgrade", function() {
|
||||
cp.execSync("node diff --all --vendor", {stdio: 'inherit'});
|
||||
});
|
||||
|
||||
grunt.registerTask("unit-tests", function() {
|
||||
cp.execSync("phpunit --bootstrap=vendor/autoload.php tests/unit", {stdio: 'inherit'});
|
||||
grunt.registerTask("unit-tests-run", function() {
|
||||
cp.execSync("vendor/bin/phpunit --bootstrap=./vendor/autoload.php tests/unit", {stdio: 'inherit'});
|
||||
});
|
||||
|
||||
grunt.registerTask("integration-tests", function() {
|
||||
cp.execSync("phpunit --bootstrap=vendor/autoload.php tests/integration", {stdio: 'inherit'});
|
||||
grunt.registerTask("integration-tests-run", function() {
|
||||
cp.execSync("vendor/bin/phpunit --bootstrap=./vendor/autoload.php tests/integration", {stdio: 'inherit'});
|
||||
});
|
||||
|
||||
grunt.registerTask("zip", function() {
|
||||
@@ -382,14 +387,29 @@ module.exports = function (grunt) {
|
||||
'clean:release',
|
||||
]);
|
||||
|
||||
grunt.registerTask('tests', [
|
||||
'default',
|
||||
'unit-tests',
|
||||
'integration-tests',
|
||||
grunt.registerTask('run-tests', [
|
||||
'test',
|
||||
'unit-tests-run',
|
||||
'integration-tests-run',
|
||||
]);
|
||||
|
||||
grunt.registerTask('run-unit-tests', [
|
||||
'composer-dev',
|
||||
'unit-tests-run',
|
||||
]);
|
||||
|
||||
grunt.registerTask('run-integration-tests', [
|
||||
'test',
|
||||
'integration-tests-run',
|
||||
]);
|
||||
|
||||
grunt.registerTask('dev', [
|
||||
'composer',
|
||||
'composer-dev',
|
||||
'less',
|
||||
]);
|
||||
|
||||
grunt.registerTask('test', [
|
||||
'composer-dev',
|
||||
'offline',
|
||||
]);
|
||||
};
|
||||
|
||||
72
README.md
72
README.md
@@ -1,21 +1,21 @@
|
||||
## EspoCRM
|
||||
|
||||
<a href='https://www.espocrm.com'>EspoCRM is an Open Source CRM</a> (Customer Relationship Management) software that allows you to see, enter and evaluate all your company relationships regardless of the type. People, companies or opportunities - all in an easy and intuitive interface.
|
||||
[EspoCRM is an Open Source CRM](https://www.espocrm.com) (Customer Relationship Management) software that allows you to see, enter and evaluate all your company relationships regardless of the type. People, companies or opportunities - all in an easy and intuitive interface.
|
||||
|
||||
It's a web application with a frontend designed as a single page application based on backbone.js and a REST API backend written in PHP.
|
||||
It's a web application with a frontend designed as a single page application and REST API backend written in PHP.
|
||||
|
||||
Download the latest release from our [website](https://www.espocrm.com).
|
||||
[Download](https://www.espocrm.com/download/) the latest release from our website.
|
||||
|
||||
### Requirements
|
||||
|
||||
* PHP 7.2 and later (with pdo, json, gd, openssl, zip, imap, mbstring, curl extensions);
|
||||
* MySQL 5.7 (and later), or MariaDB 10.1 (and later).
|
||||
|
||||
For more information about server configuration see [this article](https://www.espocrm.com/documentation/administration/server-configuration/).
|
||||
For more information about server configuration see [this article](https://docs.espocrm.com/administration/server-configuration/).
|
||||
|
||||
### Documentation
|
||||
|
||||
Documentation for administrators, users and developers is available [here](https://www.espocrm.com/documentation/).
|
||||
Documentation for administrators, users and developers is available [here](https://docs.espocrm.com).
|
||||
|
||||
### How to report a bug
|
||||
|
||||
@@ -23,34 +23,15 @@ Create an issue [here](https://github.com/espocrm/espocrm/issues) or post on our
|
||||
|
||||
### How to install a stable version
|
||||
|
||||
[Download](https://www.espocrm.com/download/) the latest version. See the [instructions](https://www.espocrm.com/documentation/administration/installation/) about installation.
|
||||
[Download](https://www.espocrm.com/download/) the latest version. See the [instructions](https://docs.espocrm.com/administration/installation/) about installation.
|
||||
|
||||
### Getting started (for developers)
|
||||
### Development
|
||||
|
||||
1. Clone repository to your local computer.
|
||||
2. Change to the project's root directory.
|
||||
3. Install [composer](https://getcomposer.org/doc/00-intro.md).
|
||||
4. Run `composer install` if composer is installed globally or `php composer.phar install` if locally.
|
||||
* [Getting started](https://docs.espocrm.com/development/how-to-start)
|
||||
* [Running tests](https://docs.espocrm.com/development/tests)
|
||||
* [Making translation](https://docs.espocrm.com/development/translation)
|
||||
|
||||
Never update composer dependencies if you are going to contribute code back.
|
||||
|
||||
Now you can build. Build will create compiled css files.
|
||||
|
||||
To compose a proper config.php and populate database you can run install by opening `http(s)://{YOUR_CRM_URL}/install` location in a browser. Then open `data/config.php` file and add `isDeveloperMode => true`.
|
||||
|
||||
### How to build (for developers)
|
||||
|
||||
You need to have nodejs and Grunt CLI installed.
|
||||
|
||||
1. Change to the project's root directory.
|
||||
2. Install project dependencies with `npm install`.
|
||||
3. Run Grunt with `grunt`.
|
||||
|
||||
The build will be created in the `build` directory.
|
||||
|
||||
Upgrade packages can be built with `grunt upgrade`.
|
||||
|
||||
### How to contribute (for developers)
|
||||
### How to contribute
|
||||
|
||||
Before we can merge your pull request you need to accept our CLA [here](https://github.com/espocrm/cla). It's very simple to do.
|
||||
|
||||
@@ -60,37 +41,6 @@ Branches:
|
||||
* *master* – develop branch; new features should be pushed to this branch;
|
||||
* *stable* – last stable release.
|
||||
|
||||
### Running tests (for developers)
|
||||
|
||||
You need to have *phpunit* installed.
|
||||
|
||||
Unit tests:
|
||||
|
||||
```
|
||||
phpunit --bootstrap=vendor/autoload.php tests/unit
|
||||
```
|
||||
|
||||
Integration tests:
|
||||
|
||||
```
|
||||
phpunit --bootstrap=vendor/autoload.php tests/integration
|
||||
```
|
||||
|
||||
### How to make a translation
|
||||
|
||||
Build po file with command:
|
||||
`node po.js en_EN`
|
||||
(specify needed language instead of en_EN)
|
||||
|
||||
After that translate the generated po file.
|
||||
|
||||
Build json files from the translated po file:
|
||||
|
||||
1. Put your po file espocrm-en_EN.po into `build` directory
|
||||
2. Run `node lang.js en_EN`
|
||||
|
||||
Json files will be created in build directory grouped by folders.
|
||||
|
||||
### License
|
||||
|
||||
EspoCRM is published under the GNU GPLv3 [license](https://raw.githubusercontent.com/espocrm/espocrm/master/LICENSE.txt).
|
||||
|
||||
9
SECURITY.md
Normal file
9
SECURITY.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
If you believe you have discovered a vulnerability in EspoCRM please contacts us via [this](https://www.espocrm.com/contacts/) or [this](https://www.espocrm.com/support/) forms.
|
||||
|
||||
## Supported versions
|
||||
|
||||
For severe vulnerabilities we provide fixes for 2 minor versions (the second number in the version string) back from the current stable version. Separate patches or manual fix guidelines will be provided for more old versions.
|
||||
@@ -9,4 +9,6 @@ RewriteEngine On
|
||||
RewriteRule .* - [E=HTTP_ESPO_CGI_AUTH:%{HTTP:Authorization}]
|
||||
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^ index.php [QSA,L]
|
||||
RewriteRule ^ index.php [QSA,L]
|
||||
|
||||
RewriteRule /?web\.config - [F]
|
||||
@@ -9,4 +9,6 @@ RewriteEngine On
|
||||
RewriteRule .* - [E=HTTP_ESPO_CGI_AUTH:%{HTTP:Authorization}]
|
||||
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^ index.php [QSA,L]
|
||||
RewriteRule ^ index.php [QSA,L]
|
||||
|
||||
RewriteRule /?web\.config - [F]
|
||||
@@ -63,11 +63,6 @@ class ActionHistoryRecord extends \Espo\Core\Controllers\Record
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
public function beforeMassDelete()
|
||||
{
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
public function beforeMassConvertCurrency()
|
||||
{
|
||||
throw new Forbidden();
|
||||
|
||||
@@ -57,4 +57,21 @@ class Attachment extends \Espo\Core\Controllers\Record
|
||||
|
||||
return $this->getRecordService()->getCopiedAttachment($data)->getValueMap();
|
||||
}
|
||||
|
||||
public function getActionFile($params, $data, $request, $response)
|
||||
{
|
||||
$id = $params['id'] ?? null;
|
||||
|
||||
if (!$id) throw new BadRequest();
|
||||
|
||||
$fileData = $this->getRecordService()->getFileData($id);
|
||||
|
||||
$response->headers->set('Content-Type', $fileData->type);
|
||||
$response->headers->set('Content-Disposition', 'Content-Disposition: attachment; filename="'.$fileData->name.'"');
|
||||
if ($fileData->size) {
|
||||
$response->headers->set('Content-Length', $fileData->size);
|
||||
}
|
||||
|
||||
return $fileData->contents;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace Espo\Controllers;
|
||||
|
||||
use \Espo\Core\Exceptions\Forbidden;
|
||||
use \Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
class EmailAccount extends \Espo\Core\Controllers\Record
|
||||
{
|
||||
|
||||
@@ -93,12 +93,7 @@ class ExternalAccount extends \Espo\Core\Controllers\Record
|
||||
{
|
||||
list($integration, $userId) = explode('__', $params['id']);
|
||||
|
||||
if ($this->getUser()->id != $userId && !$this->getUser()->isAdmin()) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
$entity = $this->getEntityManager()->getEntity('ExternalAccount', $params['id']);
|
||||
return $entity->toArray();
|
||||
return $this->getRecordService()->read($params['id'])->getValueMap();
|
||||
}
|
||||
|
||||
public function actionUpdate($params, $data, $request)
|
||||
|
||||
@@ -30,62 +30,49 @@
|
||||
namespace Espo\Controllers;
|
||||
|
||||
use Espo\Core\Utils as Utils;
|
||||
use \Espo\Core\Exceptions\NotFound;
|
||||
use \Espo\Core\Exceptions\Error;
|
||||
use \Espo\Core\Exceptions\Forbidden;
|
||||
use \Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
|
||||
class Layout extends \Espo\Core\Controllers\Base
|
||||
{
|
||||
public function actionRead($params, $data)
|
||||
public function getActionRead($params, $data)
|
||||
{
|
||||
return $this->getServiceFactory()->create('Layout')->getForFrontend($params['scope'], $params['name']);
|
||||
$scope = $params['scope'] ?? null;
|
||||
$name = $params['name'] ?? null;
|
||||
|
||||
return $this->getServiceFactory()->create('Layout')->getForFrontend($scope, $name);
|
||||
}
|
||||
|
||||
public function actionUpdate($params, $data, $request)
|
||||
public function putActionUpdate($params, $data, $request)
|
||||
{
|
||||
if (is_object($data)) {
|
||||
$data = get_object_vars($data);
|
||||
}
|
||||
if (is_object($data)) $data = get_object_vars($data);
|
||||
|
||||
if (!$this->getUser()->isAdmin()) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
if (!$this->getUser()->isAdmin()) throw new Forbidden();
|
||||
|
||||
if (!$request->isPut() && !$request->isPatch()) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
$scope = $params['scope'] ?? null;
|
||||
$name = $params['name'] ?? null;
|
||||
$setId = $params['setId'] ?? null;
|
||||
|
||||
$layoutManager = $this->getContainer()->get('layout');
|
||||
$layoutManager->set($data, $params['scope'], $params['name']);
|
||||
$result = $layoutManager->save();
|
||||
|
||||
if ($result === false) {
|
||||
throw new Error("Error while saving layout.");
|
||||
}
|
||||
|
||||
$this->getContainer()->get('dataManager')->updateCacheTimestamp();
|
||||
|
||||
return $layoutManager->get($params['scope'], $params['name']);
|
||||
}
|
||||
|
||||
public function actionPatch($params, $data, $request)
|
||||
{
|
||||
return $this->actionUpdate($params, $data, $request);
|
||||
return $this->getServiceFactory()->create('Layout')->update($scope, $name, $setId, $data);
|
||||
}
|
||||
|
||||
public function postActionResetToDefault($params, $data, $request)
|
||||
{
|
||||
if (!$this->getUser()->isAdmin()) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
if (!$this->getUser()->isAdmin()) throw new Forbidden();
|
||||
|
||||
if (empty($data->scope) || empty($data->name)) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
if (empty($data->scope) || empty($data->name)) throw new BadRequest();
|
||||
|
||||
$this->getContainer()->get('dataManager')->updateCacheTimestamp();
|
||||
return $this->getServiceFactory()->create('Layout')->resetToDefault($data->scope, $data->name, $data->setId ?? null);
|
||||
}
|
||||
|
||||
return $this->getContainer()->get('layout')->resetToDefault($data->scope, $data->name);
|
||||
public function getActionGetOriginal($params, $data, $request)
|
||||
{
|
||||
if (!$this->getUser()->isAdmin()) throw new Forbidden();
|
||||
|
||||
return $this->getServiceFactory()->create('Layout')->getOriginal(
|
||||
$request->get('scope'), $request->get('name'), $request->get('setId')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
42
application/Espo/Controllers/LayoutSet.php
Normal file
42
application/Espo/Controllers/LayoutSet.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Controllers;
|
||||
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
|
||||
class LayoutSet extends \Espo\Core\Controllers\Record
|
||||
{
|
||||
protected function checkControllerAccess()
|
||||
{
|
||||
if (!$this->getUser()->isAdmin()) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,29 +69,7 @@ class User extends \Espo\Core\Controllers\Record
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
if ($this->getConfig()->get('passwordRecoveryDisabled')) {
|
||||
throw new Forbidden("Password recovery disabled");
|
||||
}
|
||||
|
||||
$request = $this->getEntityManager()->getRepository('PasswordChangeRequest')->where([
|
||||
'requestId' => $data->requestId
|
||||
])->findOne();
|
||||
|
||||
if (!$request) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
$userId = $request->get('userId');
|
||||
if (!$userId) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
if ($this->getService('User')->changePassword($userId, $data->password)) {
|
||||
$this->getEntityManager()->removeEntity($request);
|
||||
return [
|
||||
'url' => $request->get('url')
|
||||
];
|
||||
}
|
||||
return $this->getService('User')->changePasswordByRequest($data->requestId, $data->password);
|
||||
}
|
||||
|
||||
public function postActionPasswordChangeRequest($params, $data, $request)
|
||||
|
||||
@@ -260,6 +260,9 @@ class Application
|
||||
$route = $slim->router()->getCurrentRoute();
|
||||
$conditions = $route->getConditions();
|
||||
|
||||
$response = $slim->response();
|
||||
$response->headers->set('Content-Type', 'application/json');
|
||||
|
||||
if (isset($conditions['useController']) && $conditions['useController'] == false) {
|
||||
return;
|
||||
}
|
||||
@@ -304,13 +307,16 @@ class Application
|
||||
});
|
||||
|
||||
$this->getSlim()->hook('slim.after.router', function () use (&$slim) {
|
||||
$slim->contentType('application/json');
|
||||
$response = $slim->response();
|
||||
|
||||
$res = $slim->response();
|
||||
$res->header('Expires', '0');
|
||||
$res->header('Last-Modified', gmdate("D, d M Y H:i:s") . " GMT");
|
||||
$res->header('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
|
||||
$res->header('Pragma', 'no-cache');
|
||||
if (!$response->headers->has('Content-Type')) {
|
||||
$response->headers->set('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
$response->headers->set('Expires', '0');
|
||||
$response->headers->set('Last-Modified', gmdate("D, d M Y H:i:s") . " GMT");
|
||||
$response->headers->set('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
|
||||
$response->headers->set('Pragma', 'no-cache');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
63
application/Espo/Core/Cleanup/Base.php
Normal file
63
application/Espo/Core/Cleanup/Base.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Cleanup;
|
||||
|
||||
abstract class Base extends \Espo\Core\Injectable
|
||||
{
|
||||
protected function init()
|
||||
{
|
||||
$this->addDependency('config');
|
||||
$this->addDependency('metadata');
|
||||
$this->addDependency('entityManager');
|
||||
$this->addDependency('fileManager');
|
||||
}
|
||||
|
||||
protected function getConfig()
|
||||
{
|
||||
return $this->getInjection('config');
|
||||
}
|
||||
|
||||
protected function getMetadata()
|
||||
{
|
||||
return $this->getInjection('metadata');
|
||||
}
|
||||
|
||||
protected function getEntityManager()
|
||||
{
|
||||
return $this->getInjection('entityManager');
|
||||
}
|
||||
|
||||
protected function getFileManager()
|
||||
{
|
||||
return $this->getInjection('fileManager');
|
||||
}
|
||||
|
||||
abstract public function process();
|
||||
}
|
||||
50
application/Espo/Core/Cleanup/Reminders.php
Normal file
50
application/Espo/Core/Cleanup/Reminders.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Cleanup;
|
||||
|
||||
class Reminders extends Base
|
||||
{
|
||||
protected $cleanupRemindersPeriod = '15 days';
|
||||
|
||||
public function process()
|
||||
{
|
||||
$period = '-' . $this->getConfig()->get('cleanupRemindersPeriod', $this->cleanupRemindersPeriod);
|
||||
|
||||
$datetime = new \DateTime();
|
||||
$datetime->modify($period);
|
||||
|
||||
$pdo = $this->getEntityManager()->getPDO();
|
||||
|
||||
$query = "DELETE FROM `reminder` WHERE DATE(remind_at) < " . $pdo->quote($datetime->format('Y-m-d'));
|
||||
|
||||
$sth = $pdo->prepare($query);
|
||||
$sth->execute();
|
||||
}
|
||||
}
|
||||
59
application/Espo/Core/Cleanup/WebhookQueue.php
Normal file
59
application/Espo/Core/Cleanup/WebhookQueue.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Cleanup;
|
||||
|
||||
class WebhookQueue extends Base
|
||||
{
|
||||
protected $cleanupWebhookQueuePeriod = '10 days';
|
||||
|
||||
public function process()
|
||||
{
|
||||
$pdo = $this->getEntityManager()->getPDO();
|
||||
|
||||
$period = '-' . $this->getConfig()->get('cleanupWebhookQueuePeriod', $this->cleanupWebhookQueuePeriod);
|
||||
$datetime = new \DateTime();
|
||||
$datetime->modify($period);
|
||||
$from = $datetime->format('Y-m-d H:i:s');
|
||||
|
||||
$query = "
|
||||
DELETE FROM `webhook_queue_item`
|
||||
WHERE
|
||||
DATE(created_at) < ".$pdo->quote($from)." AND
|
||||
(status <> 'Pending' OR deleted = 1)
|
||||
";
|
||||
$pdo->query($query);
|
||||
|
||||
$query = "
|
||||
DELETE FROM `webhook_event_queue_item`
|
||||
WHERE DATE(created_at) < ".$pdo->quote($from)." AND (is_processed = 1 OR deleted = 1)
|
||||
";
|
||||
$pdo->query($query);
|
||||
}
|
||||
}
|
||||
195
application/Espo/Core/Console/Commands/Extension.php
Normal file
195
application/Espo/Core/Console/Commands/Extension.php
Normal file
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Console\Commands;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
class Extension extends Base
|
||||
{
|
||||
protected $extensionManager = null;
|
||||
|
||||
public function run($options, $flagList, $argumentList)
|
||||
{
|
||||
if (in_array('u', $flagList)) {
|
||||
// uninstall
|
||||
|
||||
$name = $options['name'] ?? null;
|
||||
$id = $options['id'] ?? null;
|
||||
if (!$name && !$id) {
|
||||
$this->out("Can't uninstall. Specify --name=\"Extension Name\".\n");
|
||||
return;
|
||||
}
|
||||
$params = [];
|
||||
if ($id) {
|
||||
$params['id'] = $id;
|
||||
} else {
|
||||
$params['name'] = $name;
|
||||
}
|
||||
$params['delete'] = !in_array('k', $flagList);
|
||||
|
||||
$this->runUninstall($params);
|
||||
return;
|
||||
} else {
|
||||
// install
|
||||
|
||||
$file = $options['file'] ?? null;
|
||||
if (!$file) {
|
||||
$this->out("Can't install. Specify --file=\"path/to/package.zip\".\n");
|
||||
return;
|
||||
}
|
||||
|
||||
$this->runInstall($file);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected function runInstall(string $file)
|
||||
{
|
||||
$manager = $this->createExtensionManager();
|
||||
|
||||
if (!file_exists($file)) {
|
||||
$this->out("File does not exist.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
$fileData = file_get_contents($file);
|
||||
$fileData = 'data:application/zip;base64,' . base64_encode($fileData);
|
||||
|
||||
try {
|
||||
$id = $manager->upload($fileData);
|
||||
} catch (\Throwable $e) {
|
||||
$this->out($e->getMessage() . "\n");
|
||||
return;
|
||||
}
|
||||
|
||||
$manifest = $manager->getManifestById($id);
|
||||
|
||||
$name = $manifest['name'] ?? null;
|
||||
$version = $manifest['version'] ?? null;
|
||||
|
||||
if (!$name) {
|
||||
$this->out("Can't install. Bad manifest.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
$this->out("Installing... Do not close the terminal. This may take a while...");
|
||||
|
||||
try {
|
||||
$manager->install(['id' => $id]);
|
||||
} catch (\Throwable $e) {
|
||||
$this->out("\n");
|
||||
$this->out($e->getMessage() . "\n");
|
||||
return;
|
||||
}
|
||||
|
||||
$this->out("\n");
|
||||
$this->out("Extension '{$name}' version {$version} is installed.\nExtension ID: '{$id}'.\n");
|
||||
}
|
||||
|
||||
protected function runUninstall(array $params)
|
||||
{
|
||||
$id = $params['id'] ?? null;
|
||||
|
||||
if ($id) {
|
||||
$record = $this->getEntityManager()->getRepository('Extension')->where([
|
||||
'id' => $id,
|
||||
'isInstalled' => true,
|
||||
])->findOne();
|
||||
|
||||
if (!$record) {
|
||||
$this->out("Extension with ID '{$id}' is not installed.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
$name = $record->get('name');
|
||||
} else {
|
||||
$name = $params['name'] ?? null;
|
||||
if (!$name) {
|
||||
$this->out("Can't uninstall. No --name or --id specified.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
$record = $this->getEntityManager()->getRepository('Extension')->where([
|
||||
'name' => $name,
|
||||
'isInstalled' => true,
|
||||
])->findOne();
|
||||
|
||||
if (!$record) {
|
||||
$this->out("Extension '{$name}' is not installed.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
$id = $record->id;
|
||||
}
|
||||
|
||||
$manager = $this->createExtensionManager();
|
||||
|
||||
$this->out("Uninstalling... Do not close the terminal. This may take a while...");
|
||||
|
||||
try {
|
||||
$manager->uninstall(['id' => $id]);
|
||||
} catch (\Throwable $e) {
|
||||
$this->out("\n");
|
||||
$this->out($e->getMessage() . "\n");
|
||||
return;
|
||||
}
|
||||
|
||||
$this->out("\n");
|
||||
|
||||
if ($params['delete'] ?? false) {
|
||||
try {
|
||||
$manager->delete(['id' => $id]);
|
||||
} catch (\Throwable $e) {
|
||||
$this->out($e->getMessage() . "\n");
|
||||
$this->out("Extension '{$name}' is uninstalled but could not be deleted.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
$this->out("Extension '{$name}' is uninstalled and deleted.\n");
|
||||
} else {
|
||||
$this->out("Extension '{$name}' is uninstalled.\n");
|
||||
}
|
||||
}
|
||||
|
||||
protected function createExtensionManager()
|
||||
{
|
||||
return new \Espo\Core\ExtensionManager($this->getContainer());
|
||||
}
|
||||
|
||||
protected function getEntityManager()
|
||||
{
|
||||
return $this->getContainer()->get('entityManager');
|
||||
}
|
||||
|
||||
protected function out(string $string)
|
||||
{
|
||||
fwrite(\STDOUT, $string);
|
||||
}
|
||||
}
|
||||
88
application/Espo/Core/Console/Commands/SetPassword.php
Normal file
88
application/Espo/Core/Console/Commands/SetPassword.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Console\Commands;
|
||||
|
||||
class SetPassword extends Base
|
||||
{
|
||||
public function run($options, $flagList, $argumentList)
|
||||
{
|
||||
$userName = $argumentList[0] ?? null;
|
||||
|
||||
if (!$userName) {
|
||||
$this->out("User name must be specified.\n");
|
||||
die;
|
||||
}
|
||||
|
||||
$em = $this->getContainer()->get('entityManager');
|
||||
|
||||
$user = $em->getRepository('User')->where(['userName' => $userName])->findOne();
|
||||
|
||||
if (!$user) {
|
||||
$this->out("User '{$userName}' not found.\n");
|
||||
die;
|
||||
}
|
||||
|
||||
if (!in_array($user->get('type'), ['admin', 'super-admin', 'portal', 'regular'])) {
|
||||
$this->out("Can't set password for user of type '".$user->get('type')."'.\n");
|
||||
die;
|
||||
}
|
||||
|
||||
$this->out("Enter a new password:\n");
|
||||
|
||||
$password = $this->ask();
|
||||
|
||||
$password = trim($password);
|
||||
|
||||
if (!$password) {
|
||||
$this->out("Password can not be empty.\n");
|
||||
die;
|
||||
}
|
||||
|
||||
$hash = $this->getContainer()->get('passwordHash');
|
||||
|
||||
$user->set('password', $hash->hash($password));
|
||||
|
||||
$em->saveEntity($user);
|
||||
|
||||
$this->out("Password for user '{$userName}' is changed.\n");
|
||||
}
|
||||
|
||||
protected function ask()
|
||||
{
|
||||
$input = fgets(\STDIN);
|
||||
|
||||
return rtrim($input, "\n");
|
||||
}
|
||||
|
||||
protected function out($string)
|
||||
{
|
||||
fwrite(\STDOUT, $string);
|
||||
}
|
||||
}
|
||||
@@ -140,8 +140,7 @@ class Upgrade extends Base
|
||||
|
||||
fwrite(\STDOUT, "\n");
|
||||
|
||||
$app = new \Espo\Core\Application();
|
||||
$currentVerison = $app->getContainer()->get('config')->get('version');
|
||||
$currentVerison = $this->getCurrentVersion();
|
||||
|
||||
fwrite(\STDOUT, "Upgrade is complete. Current version is {$currentVerison}.\n");
|
||||
|
||||
@@ -211,8 +210,7 @@ class Upgrade extends Base
|
||||
return;
|
||||
}
|
||||
|
||||
$app = new \Espo\Core\Application();
|
||||
$currentVerison = $app->getContainer()->get('config')->get('version');
|
||||
$currentVerison = $this->getCurrentVersion();
|
||||
|
||||
fwrite(\STDOUT, "Upgrade is complete. Current version is {$currentVerison}.\n");
|
||||
|
||||
@@ -261,6 +259,8 @@ class Upgrade extends Base
|
||||
|
||||
try {
|
||||
foreach ($stepList as $stepName) {
|
||||
fwrite(\STDOUT, ".");
|
||||
|
||||
$upgradeManager = $this->getUpgradeManager(true);
|
||||
$upgradeManager->runInstallStep($stepName, ['id' => $upgradeId]);
|
||||
}
|
||||
@@ -277,8 +277,9 @@ class Upgrade extends Base
|
||||
$phpExecutablePath = $this->getPhpExecutablePath();
|
||||
|
||||
foreach ($stepList as $stepName) {
|
||||
$command = $phpExecutablePath . " command.php upgrade-step --step=". ucfirst($stepName) ." --id=". $upgradeId;
|
||||
fwrite(\STDOUT, ".");
|
||||
|
||||
$command = $phpExecutablePath . " command.php upgrade-step --step=". ucfirst($stepName) ." --id=". $upgradeId;
|
||||
$shellResult = shell_exec($command);
|
||||
if ($shellResult !== 'true') {
|
||||
$GLOBALS['log']->error('Upgrade Error: ' . $shellResult);
|
||||
@@ -403,4 +404,15 @@ class Upgrade extends Base
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getCurrentVersion()
|
||||
{
|
||||
$configData = include "data/config.php";
|
||||
|
||||
if (!$configData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $configData['version'] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
44
application/Espo/Core/Console/Commands/Version.php
Normal file
44
application/Espo/Core/Console/Commands/Version.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Console\Commands;
|
||||
|
||||
class Version extends Base
|
||||
{
|
||||
public function run($options, $flagList, $argumentList)
|
||||
{
|
||||
$version = $this->getContainer()->get('config')->get('version');
|
||||
|
||||
if (is_null($version)) {
|
||||
return;
|
||||
}
|
||||
|
||||
echo $version . "\n";
|
||||
}
|
||||
}
|
||||
@@ -29,11 +29,12 @@
|
||||
|
||||
namespace Espo\Core\Controllers;
|
||||
|
||||
use \Espo\Core\Exceptions\Error;
|
||||
use \Espo\Core\Exceptions\Forbidden;
|
||||
use \Espo\Core\Exceptions\NotFound;
|
||||
use \Espo\Core\Exceptions\BadRequest;
|
||||
use \Espo\Core\Utils\Util;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Utils\Util;
|
||||
use Espo\Core\Exceptions\ForbiddenSilent;
|
||||
|
||||
class Record extends Base
|
||||
{
|
||||
@@ -88,7 +89,7 @@ class Record extends Base
|
||||
}
|
||||
|
||||
if (!$this->getAcl()->check($this->name, 'create')) {
|
||||
throw new Forbidden();
|
||||
throw new Forbidden("No create access for {$this->name}.");
|
||||
}
|
||||
|
||||
$service = $this->getRecordService();
|
||||
@@ -109,7 +110,7 @@ class Record extends Base
|
||||
}
|
||||
|
||||
if (!$this->getAcl()->check($this->name, 'edit')) {
|
||||
throw new Forbidden();
|
||||
throw new Forbidden("No edit access for {$this->name}.");
|
||||
}
|
||||
|
||||
$id = $params['id'];
|
||||
@@ -124,7 +125,7 @@ class Record extends Base
|
||||
public function actionList($params, $data, $request)
|
||||
{
|
||||
if (!$this->getAcl()->check($this->name, 'read')) {
|
||||
throw new Forbidden();
|
||||
throw new Forbidden("No read access for {$this->name}.");
|
||||
}
|
||||
|
||||
$params = [];
|
||||
@@ -156,7 +157,7 @@ class Record extends Base
|
||||
public function getActionListKanban($params, $data, $request)
|
||||
{
|
||||
if (!$this->getAcl()->check($this->name, 'read')) {
|
||||
throw new Forbidden();
|
||||
throw new Forbidden("No read access for {$this->name}.");
|
||||
}
|
||||
|
||||
$params = [];
|
||||
@@ -286,14 +287,14 @@ class Record extends Base
|
||||
}
|
||||
|
||||
if (!$this->getAcl()->check($this->name, 'edit')) {
|
||||
throw new Forbidden();
|
||||
throw new Forbidden("No edit access for {$this->name}.");
|
||||
}
|
||||
if (empty($data->attributes)) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
if ($this->getAcl()->get('massUpdatePermission') !== 'yes') {
|
||||
throw new Forbidden();
|
||||
throw new Forbidden("No massUpdatePermission.");
|
||||
}
|
||||
|
||||
$actionParams = $this->getMassActionParamsFromData($data);
|
||||
@@ -313,7 +314,7 @@ class Record extends Base
|
||||
|
||||
if (array_key_exists('where', $actionParams)) {
|
||||
if ($this->getAcl()->get('massUpdatePermission') !== 'yes') {
|
||||
throw new Forbidden();
|
||||
throw new Forbidden("No massUpdatePermission.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,7 +413,7 @@ class Record extends Base
|
||||
throw new BadRequest();
|
||||
}
|
||||
if (!$this->getAcl()->check($this->name, 'stream')) {
|
||||
throw new Forbidden();
|
||||
throw new Forbidden("No stream access for {$this->name}.");
|
||||
}
|
||||
$id = $params['id'];
|
||||
return $this->getRecordService()->follow($id);
|
||||
@@ -424,7 +425,7 @@ class Record extends Base
|
||||
throw new BadRequest();
|
||||
}
|
||||
if (!$this->getAcl()->check($this->name, 'read')) {
|
||||
throw new Forbidden();
|
||||
throw new Forbidden("No read access for {$this->name}.");
|
||||
}
|
||||
$id = $params['id'];
|
||||
return $this->getRecordService()->unfollow($id);
|
||||
@@ -444,7 +445,7 @@ class Record extends Base
|
||||
$attributes = $data->attributes;
|
||||
|
||||
if (!$this->getAcl()->check($this->name, 'edit')) {
|
||||
throw new Forbidden();
|
||||
throw new Forbidden("No edit access for {$this->name}.");
|
||||
}
|
||||
|
||||
return $this->getRecordService()->merge($targetId, $sourceIds, $attributes);
|
||||
@@ -480,7 +481,7 @@ class Record extends Base
|
||||
public function postActionMassUnfollow($params, $data, $request)
|
||||
{
|
||||
if (!$this->getAcl()->check($this->name, 'stream')) {
|
||||
throw new Forbidden();
|
||||
throw new Forbidden("No stream access for {$this->name}.");
|
||||
}
|
||||
|
||||
$actionParams = $this->getMassActionParamsFromData($data);
|
||||
|
||||
@@ -316,7 +316,7 @@ class CronManager
|
||||
$job->set('attempts', 0);
|
||||
$skipLog = true;
|
||||
} else {
|
||||
$GLOBALS['log']->error('CronManager: Failed job running, job ['.$job->id.']. Error Details: '.$e->getMessage());
|
||||
$GLOBALS['log']->error('CronManager: Failed job running, job ['. $job->id .']. Error Details: '. $e->getMessage() .' at '. $e->getFile() . ':' . $e->getLine());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -219,7 +219,7 @@ class DataManager
|
||||
|
||||
$cryptKey = $config->get('cryptKey');
|
||||
if (!$cryptKey) {
|
||||
$cryptKey = \Espo\Core\Utils\Util::generateKey();
|
||||
$cryptKey = \Espo\Core\Utils\Util::generateSecretKey();
|
||||
$config->set('cryptKey', $cryptKey);
|
||||
}
|
||||
|
||||
|
||||
@@ -62,9 +62,11 @@ class Csv extends \Espo\Core\Injectable
|
||||
|
||||
$delimiter = $this->getInjection('preferences')->get('exportDelimiter');
|
||||
if (empty($delimiter)) {
|
||||
$delimiter = $this->getInjection('config')->get('exportDelimiter', ';');
|
||||
$delimiter = $this->getInjection('config')->get('exportDelimiter', ',');
|
||||
}
|
||||
|
||||
$delimiter = str_replace('\t', "\t", $delimiter);
|
||||
|
||||
$fp = fopen('php://temp', 'w');
|
||||
fputcsv($fp, $attributeList, $delimiter);
|
||||
|
||||
|
||||
@@ -29,9 +29,11 @@
|
||||
|
||||
namespace Espo\Core\ExternalAccount;
|
||||
|
||||
use \Espo\Core\Exceptions\Error;
|
||||
use \Espo\Core\Exceptions\Forbidden;
|
||||
use \Espo\Core\Exceptions\NotFound;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
|
||||
use Espo\Core\InjectableFactory;
|
||||
|
||||
class ClientManager
|
||||
{
|
||||
@@ -39,13 +41,17 @@ class ClientManager
|
||||
|
||||
protected $metadata;
|
||||
|
||||
protected $clientMap = array();
|
||||
protected $clientMap = [];
|
||||
|
||||
public function __construct($entityManager, $metadata, $config)
|
||||
protected $injectableFactory = null;
|
||||
|
||||
public function __construct(
|
||||
$entityManager, $metadata, $config, ?InjectableFactory $injectableFactory = null)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
$this->metadata = $metadata;
|
||||
$this->config = $config;
|
||||
$this->injectableFactory = $injectableFactory;
|
||||
}
|
||||
|
||||
protected function getMetadata()
|
||||
@@ -69,24 +75,68 @@ class ClientManager
|
||||
$externalAccountEntity = $this->clientMap[$hash]['externalAccountEntity'];
|
||||
$externalAccountEntity->set('accessToken', $data['accessToken']);
|
||||
$externalAccountEntity->set('tokenType', $data['tokenType']);
|
||||
$externalAccountEntity->set('expiresAt', $data['expiresAt'] ?? null);
|
||||
|
||||
if ($data['refreshToken'] ?? null) {
|
||||
$externalAccountEntity->set('refreshToken', $data['refreshToken']);
|
||||
}
|
||||
|
||||
$copy = $this->getEntityManager()->getEntity('ExternalAccount', $externalAccountEntity->id);
|
||||
if ($copy) {
|
||||
if (!$copy->get('enabled')) {
|
||||
throw new Error("External Account Client Manager: Account got disabled.");
|
||||
}
|
||||
|
||||
$copy->set('accessToken', $data['accessToken']);
|
||||
$copy->set('tokenType', $data['tokenType']);
|
||||
$this->getEntityManager()->saveEntity($copy, ['isTokenRenewal' => true]);
|
||||
$copy->set('expiresAt', $data['expiresAt'] ?? null);
|
||||
if ($data['refreshToken'] ?? null) {
|
||||
$copy->set('refreshToken', $data['refreshToken'] ?? null);
|
||||
}
|
||||
$this->getEntityManager()->saveEntity($copy, ['isTokenRenewal' => true, 'skipHooks' => true]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function create($integration, $userId)
|
||||
public function create(string $integration, string $userId)
|
||||
{
|
||||
$authMethod = $this->getMetadata()->get("integrations.{$integration}.authMethod");
|
||||
$methodName = 'create' . ucfirst($authMethod);
|
||||
return $this->$methodName($integration, $userId);
|
||||
|
||||
if (method_exists($this, $methodName)) {
|
||||
return $this->$methodName($integration, $userId);
|
||||
}
|
||||
|
||||
if (!$this->injectableFactory) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
$integrationEntity = $this->getEntityManager()->getEntity('Integration', $integration);
|
||||
$externalAccountEntity = $this->getEntityManager()->getEntity('ExternalAccount', $integration . '__' . $userId);
|
||||
|
||||
if (!$externalAccountEntity) {
|
||||
throw new Error("External Account {$integration} not found for {$userId}");
|
||||
}
|
||||
|
||||
if (!$integrationEntity->get('enabled')) return null;
|
||||
if (!$externalAccountEntity->get('enabled')) return null;
|
||||
|
||||
$className = $this->getMetadata()->get("integrations.{$integration}.clientClassName");
|
||||
$client = $this->injectableFactory->createByClassName($className);
|
||||
|
||||
$client->setup(
|
||||
$userId,
|
||||
$integrationEntity,
|
||||
$externalAccountEntity,
|
||||
$this
|
||||
);
|
||||
|
||||
$this->addToClientMap($client, $integrationEntity, $externalAccountEntity, $userId);
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
protected function createOAuth2($integration, $userId)
|
||||
protected function createOAuth2(string $integration, string $userId)
|
||||
{
|
||||
$integrationEntity = $this->getEntityManager()->getEntity('Integration', $integration);
|
||||
$externalAccountEntity = $this->getEntityManager()->getEntity('ExternalAccount', $integration . '__' . $userId);
|
||||
@@ -113,7 +163,7 @@ class ClientManager
|
||||
|
||||
$oauth2Client = new \Espo\Core\ExternalAccount\OAuth2\Client();
|
||||
|
||||
$client = new $className($oauth2Client, array(
|
||||
$params = [
|
||||
'endpoint' => $this->getMetadata()->get("integrations.{$integration}.params.endpoint"),
|
||||
'tokenEndpoint' => $this->getMetadata()->get("integrations.{$integration}.params.tokenEndpoint"),
|
||||
'clientId' => $integrationEntity->get('clientId'),
|
||||
@@ -122,7 +172,16 @@ class ClientManager
|
||||
'accessToken' => $externalAccountEntity->get('accessToken'),
|
||||
'refreshToken' => $externalAccountEntity->get('refreshToken'),
|
||||
'tokenType' => $externalAccountEntity->get('tokenType'),
|
||||
), $this);
|
||||
'expiresAt' => $externalAccountEntity->get('expiresAt'),
|
||||
];
|
||||
|
||||
foreach (get_object_vars($integrationEntity->getValueMap()) as $k => $v) {
|
||||
if (array_key_exists($k, $params)) continue;
|
||||
if ($integrationEntity->hasAttribute($k)) continue;
|
||||
$params[$k] = $v;
|
||||
}
|
||||
|
||||
$client = new $className($oauth2Client, $params, $this);
|
||||
|
||||
$this->addToClientMap($client, $integrationEntity, $externalAccountEntity, $userId);
|
||||
|
||||
@@ -139,5 +198,82 @@ class ClientManager
|
||||
'externalAccountEntity' => $externalAccountEntity,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getClientRecord($client) : \Espo\ORM\Entity
|
||||
{
|
||||
$data = $this->clientMap[spl_object_hash($client)];
|
||||
|
||||
if (!$data) {
|
||||
throw new Error("External Account Client Manager: Client not found in hash.");
|
||||
}
|
||||
|
||||
return $data['externalAccountEntity'];
|
||||
}
|
||||
|
||||
public function isClientLocked($client) : bool
|
||||
{
|
||||
$externalAccountEntity = $this->getClientRecord($client);
|
||||
$id = $externalAccountEntity->id;
|
||||
|
||||
$e = $this->getEntityManager()->getRepository('ExternalAccount')
|
||||
->select(['id', 'isLocked'])->where(['id' => $id])->findOne();
|
||||
|
||||
if (!$e) {
|
||||
throw new Error("External Account Client Manager: Client {$id} not found in DB.");
|
||||
}
|
||||
|
||||
return $e->get('isLocked');
|
||||
}
|
||||
|
||||
public function lockClient($client)
|
||||
{
|
||||
$externalAccountEntity = $this->getClientRecord($client);
|
||||
$id = $externalAccountEntity->id;
|
||||
|
||||
$e = $this->getEntityManager()->getRepository('ExternalAccount')
|
||||
->select(['id', 'isLocked'])->where(['id' => $id])->findOne();
|
||||
|
||||
if (!$e) {
|
||||
throw new Error("External Account Client Manager: Client {$id} not found in DB.");
|
||||
}
|
||||
|
||||
$e->set('isLocked', true);
|
||||
|
||||
$this->getEntityManager()->saveEntity($e, ['skipHooks' => true, 'silent' => true]);
|
||||
}
|
||||
|
||||
public function unlockClient($client)
|
||||
{
|
||||
$externalAccountEntity = $this->getClientRecord($client);
|
||||
$id = $externalAccountEntity->id;
|
||||
|
||||
$e = $this->getEntityManager()->getRepository('ExternalAccount')
|
||||
->select(['id', 'isLocked'])->where(['id' => $id])->findOne();
|
||||
|
||||
if (!$e) {
|
||||
throw new Error("External Account Client Manager: Client {$id} not found in DB.");
|
||||
}
|
||||
|
||||
$e->set('isLocked', false);
|
||||
|
||||
$this->getEntityManager()->saveEntity($e, ['skipHooks' => true, 'silent' => true]);
|
||||
}
|
||||
|
||||
public function reFetchClient($client)
|
||||
{
|
||||
$externalAccountEntity = $this->getClientRecord($client);
|
||||
$id = $externalAccountEntity->id;
|
||||
|
||||
$e = $this->getEntityManager()->getEntity('ExternalAccount', $id);
|
||||
|
||||
if (!$e) {
|
||||
throw new Error("External Account Client Manager: Client {$id} not found in DB.");
|
||||
}
|
||||
|
||||
$data = $e->getValueMap();
|
||||
|
||||
$externalAccountEntity->set($data);
|
||||
|
||||
$client->setParams(get_object_vars($data));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,9 +29,9 @@
|
||||
|
||||
namespace Espo\Core\ExternalAccount\Clients;
|
||||
|
||||
use \Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
use \Espo\Core\ExternalAccount\OAuth2\Client;
|
||||
use Espo\Core\ExternalAccount\OAuth2\Client;
|
||||
|
||||
abstract class OAuth2Abstract implements IClient
|
||||
{
|
||||
@@ -39,7 +39,7 @@ abstract class OAuth2Abstract implements IClient
|
||||
|
||||
protected $manager = null;
|
||||
|
||||
protected $paramList = array(
|
||||
protected $paramList = [
|
||||
'endpoint',
|
||||
'tokenEndpoint',
|
||||
'clientId',
|
||||
@@ -48,7 +48,8 @@ abstract class OAuth2Abstract implements IClient
|
||||
'accessToken',
|
||||
'refreshToken',
|
||||
'redirectUri',
|
||||
);
|
||||
'expiresAt',
|
||||
];
|
||||
|
||||
protected $clientId = null;
|
||||
|
||||
@@ -60,7 +61,15 @@ abstract class OAuth2Abstract implements IClient
|
||||
|
||||
protected $redirectUri = null;
|
||||
|
||||
public function __construct($client, array $params = array(), $manager = null)
|
||||
protected $expiresAt = null;
|
||||
|
||||
const ACCESS_TOKEN_EXPIRATION_MARGIN = '20 seconds';
|
||||
|
||||
const LOCK_TIMEOUT = 5;
|
||||
|
||||
const LOCK_CHECK_STEP = 0.5;
|
||||
|
||||
public function __construct($client, array $params = [], $manager = null)
|
||||
{
|
||||
$this->client = $client;
|
||||
|
||||
@@ -90,7 +99,7 @@ abstract class OAuth2Abstract implements IClient
|
||||
public function setParams(array $params)
|
||||
{
|
||||
foreach ($this->paramList as $name) {
|
||||
if (!empty($params[$name])) {
|
||||
if (array_key_exists($name, $params)) {
|
||||
$this->setParam($name, $params[$name]);
|
||||
}
|
||||
}
|
||||
@@ -103,6 +112,28 @@ abstract class OAuth2Abstract implements IClient
|
||||
}
|
||||
}
|
||||
|
||||
protected function getAccessTokenDataFromResponseResult($result)
|
||||
{
|
||||
$data = [];
|
||||
|
||||
$data['accessToken'] = $result['access_token'];
|
||||
$data['tokenType'] = $result['token_type'];
|
||||
|
||||
$data['expiresAt'] = null;
|
||||
|
||||
if (isset($result['refresh_token']) && $result['refresh_token'] !== $this->refreshToken) {
|
||||
$data['refreshToken'] = $result['refresh_token'];
|
||||
}
|
||||
|
||||
if (isset($result['expires_in']) && is_numeric($result['expires_in'])) {
|
||||
$data['expiresAt'] = (new \DateTime())
|
||||
->modify('+' . $result['expires_in'] . ' seconds')
|
||||
->format('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getAccessTokenFromAuthorizationCode($code)
|
||||
{
|
||||
$r = $this->client->getAccessToken($this->getParam('tokenEndpoint'), Client::GRANT_TYPE_AUTHORIZATION_CODE, [
|
||||
@@ -111,15 +142,16 @@ abstract class OAuth2Abstract implements IClient
|
||||
]);
|
||||
|
||||
if ($r['code'] == 200) {
|
||||
$data = [];
|
||||
if (!empty($r['result'])) {
|
||||
$data['accessToken'] = $r['result']['access_token'];
|
||||
$data['tokenType'] = $r['result']['token_type'];
|
||||
$data = $this->getAccessTokenDataFromResponseResult($r['result']);
|
||||
|
||||
$data['refreshToken'] = $r['result']['refresh_token'];
|
||||
|
||||
return $data;
|
||||
} else {
|
||||
$GLOBALS['log']->debug("OAuth getAccessTokenFromAuthorizationCode; Response: " . json_encode($r));
|
||||
return null;
|
||||
}
|
||||
return $data;
|
||||
} else {
|
||||
$GLOBALS['log']->debug("OAuth getAccessTokenFromAuthorizationCode; Response: " . json_encode($r));
|
||||
}
|
||||
@@ -144,9 +176,69 @@ abstract class OAuth2Abstract implements IClient
|
||||
}
|
||||
}
|
||||
|
||||
public function handleAccessTokenActuality()
|
||||
{
|
||||
if ($this->getParam('expiresAt')) {
|
||||
try {
|
||||
$dt = new \DateTime($this->getParam('expiresAt'));
|
||||
$dt->modify('-' . $this::ACCESS_TOKEN_EXPIRATION_MARGIN);
|
||||
} catch (\Exception $e) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($dt->format('U') <= (new \DateTime())->format('U')) {
|
||||
$GLOBALS['log']->debug("Oauth: Refreshing expired token for client {$this->clientId}.");
|
||||
|
||||
$until = microtime(true) + $this::LOCK_TIMEOUT;
|
||||
|
||||
if ($this->isLocked()) {
|
||||
while (true) {
|
||||
usleep($this::LOCK_CHECK_STEP * 1000000);
|
||||
|
||||
if (!$this->isLocked()) {
|
||||
$GLOBALS['log']->debug("Oauth: Waited until unlocked for client {$this->clientId}.");
|
||||
$this->reFetch();
|
||||
return;
|
||||
}
|
||||
|
||||
if (microtime(true) > $until) {
|
||||
$GLOBALS['log']->debug("Oauth: Waited until unlocked but timed out for client {$this->clientId}.");
|
||||
$this->unlock();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->refreshToken();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function isLocked()
|
||||
{
|
||||
return $this->manager->isClientLocked($this);
|
||||
}
|
||||
|
||||
protected function lock()
|
||||
{
|
||||
$this->manager->lockClient($this);
|
||||
}
|
||||
|
||||
protected function unlock()
|
||||
{
|
||||
$this->manager->unlockClient($this);
|
||||
}
|
||||
|
||||
protected function reFetch()
|
||||
{
|
||||
$this->manager->reFetchClient($this);
|
||||
}
|
||||
|
||||
public function request($url, $params = null, $httpMethod = Client::HTTP_METHOD_GET, $contentType = null, $allowRenew = true)
|
||||
{
|
||||
$httpHeaders = array();
|
||||
$this->handleAccessTokenActuality();
|
||||
|
||||
$httpHeaders = [];
|
||||
if (!empty($contentType)) {
|
||||
$httpHeaders['Content-Type'] = $contentType;
|
||||
switch ($contentType) {
|
||||
@@ -192,24 +284,43 @@ abstract class OAuth2Abstract implements IClient
|
||||
|
||||
protected function refreshToken()
|
||||
{
|
||||
if (!empty($this->refreshToken)) {
|
||||
$r = $this->client->getAccessToken($this->getParam('tokenEndpoint'), Client::GRANT_TYPE_REFRESH_TOKEN, array(
|
||||
'refresh_token' => $this->refreshToken,
|
||||
));
|
||||
if ($r['code'] == 200) {
|
||||
if (is_array($r['result'])) {
|
||||
if (!empty($r['result']['access_token'])) {
|
||||
$data = array();
|
||||
$data['accessToken'] = $r['result']['access_token'];
|
||||
$data['tokenType'] = $r['result']['token_type'];
|
||||
if (empty($this->refreshToken)) {
|
||||
throw new Error(
|
||||
"Oauth: Could not refresh token for client {$this->clientId}, because refreshToken is empty."
|
||||
);
|
||||
}
|
||||
|
||||
$this->setParams($data);
|
||||
$this->afterTokenRefreshed($data);
|
||||
return true;
|
||||
}
|
||||
$this->lock();
|
||||
|
||||
try {
|
||||
$r = $this->client->getAccessToken($this->getParam('tokenEndpoint'), Client::GRANT_TYPE_REFRESH_TOKEN, [
|
||||
'refresh_token' => $this->refreshToken,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
$this->unlock();
|
||||
throw new Error("Oauth: Error while refreshing token: " . $e->getMessage());
|
||||
}
|
||||
|
||||
if ($r['code'] == 200) {
|
||||
if (is_array($r['result'])) {
|
||||
if (!empty($r['result']['access_token'])) {
|
||||
$data = $this->getAccessTokenDataFromResponseResult($r['result']);
|
||||
|
||||
$this->setParams($data);
|
||||
$this->afterTokenRefreshed($data);
|
||||
|
||||
$this->unlock();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->unlock();
|
||||
|
||||
$GLOBALS['log']->error("Oauth: Refreshing token failed for client {$this->clientId}: " . json_encode($r));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function handleErrorResponse($r)
|
||||
@@ -217,21 +328,20 @@ abstract class OAuth2Abstract implements IClient
|
||||
if ($r['code'] == 401 && !empty($r['result'])) {
|
||||
$result = $r['result'];
|
||||
if (strpos($r['header'], 'error=invalid_token') !== false) {
|
||||
return array(
|
||||
return [
|
||||
'action' => 'refreshToken'
|
||||
);
|
||||
];
|
||||
} else {
|
||||
return array(
|
||||
return [
|
||||
'action' => 'renew'
|
||||
);
|
||||
];
|
||||
}
|
||||
} else if ($r['code'] == 400 && !empty($r['result'])) {
|
||||
if ($r['result']['error'] == 'invalid_token') {
|
||||
return array(
|
||||
return [
|
||||
'action' => 'refreshToken'
|
||||
);
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,8 @@ class Client
|
||||
|
||||
protected $accessToken = null;
|
||||
|
||||
protected $expiresAt = null;
|
||||
|
||||
protected $authType = self::AUTH_TYPE_URI;
|
||||
|
||||
protected $tokenType = self::TOKEN_TYPE_URI;
|
||||
@@ -72,9 +74,9 @@ class Client
|
||||
|
||||
protected $certificateFile = null;
|
||||
|
||||
protected $curlOptions = array();
|
||||
protected $curlOptions = [];
|
||||
|
||||
public function __construct(array $params = array())
|
||||
public function __construct(array $params = [])
|
||||
{
|
||||
if (!extension_loaded('curl')) {
|
||||
throw new \Exception('CURL extension not found.');
|
||||
@@ -121,12 +123,17 @@ class Client
|
||||
$this->tokenType = $tokenType;
|
||||
}
|
||||
|
||||
public function setExpiresAt($value)
|
||||
{
|
||||
$this->expiresAt = $value;
|
||||
}
|
||||
|
||||
public function setAccessTokenSecret($accessTokenSecret)
|
||||
{
|
||||
$this->accessTokenSecret = $accessTokenSecret;
|
||||
}
|
||||
|
||||
public function request($url, $params = null, $httpMethod = self::HTTP_METHOD_GET, array $httpHeaders = array())
|
||||
public function request($url, $params = null, $httpMethod = self::HTTP_METHOD_GET, array $httpHeaders = [])
|
||||
{
|
||||
if ($this->accessToken) {
|
||||
switch ($this->tokenType) {
|
||||
@@ -148,7 +155,7 @@ class Client
|
||||
return $this->execute($url, $params, $httpMethod, $httpHeaders);
|
||||
}
|
||||
|
||||
private function execute($url, $params = null, $httpMethod, array $httpHeaders = array())
|
||||
private function execute($url, $params = null, $httpMethod, array $httpHeaders = [])
|
||||
{
|
||||
$curlOptions = array(
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
@@ -200,13 +207,11 @@ class Client
|
||||
|
||||
curl_setopt($ch, CURLOPT_HEADER, 1);
|
||||
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
|
||||
|
||||
if (!empty($this->certificateFile)) {
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
|
||||
curl_setopt($ch, CURLOPT_CAINFO, $this->certificateFile);
|
||||
} else {
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
}
|
||||
|
||||
if (!empty($this->curlOptions)) {
|
||||
@@ -243,7 +248,7 @@ class Client
|
||||
{
|
||||
$params['grant_type'] = $grantType;
|
||||
|
||||
$httpHeaders = array();
|
||||
$httpHeaders = [];
|
||||
switch ($this->tokenType) {
|
||||
case self::AUTH_TYPE_URI:
|
||||
case self::AUTH_TYPE_FORM:
|
||||
@@ -261,4 +266,3 @@ class Client
|
||||
return $this->execute($url, $params, self::HTTP_METHOD_POST, $httpHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,9 +46,11 @@ class ArrayType extends BaseType
|
||||
|
||||
public function checkArray(\Espo\ORM\Entity $entity, string $field, $validationValue, $data) : bool
|
||||
{
|
||||
if (!$entity->has($field) || $entity->get($field) === null) return true;
|
||||
if (isset($data->$field) && $data->$field !== null && !is_array($data->$field)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return is_array($entity->get($field));
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function isNotEmpty(\Espo\ORM\Entity $entity, $field)
|
||||
|
||||
49
application/Espo/Core/FieldValidators/JsonArrayType.php
Normal file
49
application/Espo/Core/FieldValidators/JsonArrayType.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\FieldValidators;
|
||||
|
||||
class JsonArrayType extends BaseType
|
||||
{
|
||||
public function checkArray(\Espo\ORM\Entity $entity, string $field, $validationValue, $data) : bool
|
||||
{
|
||||
if (!$entity->has($field) || $entity->get($field) === null) return true;
|
||||
|
||||
return is_array($entity->get($field));
|
||||
}
|
||||
|
||||
protected function isNotEmpty(\Espo\ORM\Entity $entity, $field)
|
||||
{
|
||||
if (!$entity->has($field) || $entity->get($field) === null) return false;
|
||||
$list = $entity->get($field);
|
||||
if (!is_array($list)) return false;
|
||||
if (count($list)) return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Formula\Functions\ArrayGroup;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
class AtType extends \Espo\Core\Formula\Functions\Base
|
||||
{
|
||||
public function process(\StdClass $item)
|
||||
{
|
||||
$args = $this->fetchArguments($item);
|
||||
if (count($args) < 2) throw new Error("Formula: array\\at: Not enough arguments.");
|
||||
|
||||
$array = $args[0];
|
||||
$index = $args[1];
|
||||
|
||||
if (!is_array($array)) throw new Error("Formula: array\\at: First argument must be array.");
|
||||
if (!is_int($index)) throw new Error("Formula: array\\at: Second argument must be integer.");
|
||||
|
||||
if (!array_key_exists($index, $array)) {
|
||||
$GLOBALS['log']->notice("Formula: array\\at: Index doesn't exist.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return $array[$index];
|
||||
}
|
||||
}
|
||||
@@ -122,4 +122,9 @@ abstract class Base implements Injectable
|
||||
|
||||
return $eArgs;
|
||||
}
|
||||
}
|
||||
|
||||
protected function fetchRawArguments(\StdClass $item)
|
||||
{
|
||||
return $item->value ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,9 +62,9 @@ class DayOfWeekType extends \Espo\Core\Formula\Functions\Base
|
||||
if (empty($value)) return -1;
|
||||
|
||||
if (strlen($value) > 11) {
|
||||
$resultString = $this->getInjection('dateTime')->convertSystemDateTime($value, $timezone, 'e');
|
||||
$resultString = $this->getInjection('dateTime')->convertSystemDateTime($value, $timezone, 'd');
|
||||
} else {
|
||||
$resultString = $this->getInjection('dateTime')->convertSystemDate($value, 'e');
|
||||
$resultString = $this->getInjection('dateTime')->convertSystemDate($value, 'd');
|
||||
}
|
||||
|
||||
$result = intval($resultString);
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Formula\Functions\ExtGroup\EmailGroup;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
class ApplyTemplateType extends \Espo\Core\Formula\Functions\Base
|
||||
{
|
||||
protected function init()
|
||||
{
|
||||
$this->addDependency('entityManager');
|
||||
$this->addDependency('serviceFactory');
|
||||
}
|
||||
|
||||
public function process(\StdClass $item)
|
||||
{
|
||||
$args = $this->fetchArguments($item);
|
||||
|
||||
if (count($args) < 2) throw new Error("Formula ext\email\applyTemplate: Too few arguments.");
|
||||
|
||||
$id = $args[0];
|
||||
$templateId = $args[1];
|
||||
$parentType = $args[2] ?? null;
|
||||
$parentId = $args[3] ?? null;
|
||||
|
||||
if (!$id || !is_string($id))
|
||||
throw new Error("Formula ext\\email\applyTemplate: 1st argument should be string and not be empty.");
|
||||
|
||||
if (!$templateId || !is_string($templateId))
|
||||
throw new Error("Formula ext\\email\applyTemplate: 2nd argument should be string and not be empty.");
|
||||
|
||||
if ($parentType && !is_string($parentType))
|
||||
throw new Error("Formula ext\\email\applyTemplate: 3st argument should be string.");
|
||||
|
||||
if ($parentId && !is_string($parentId))
|
||||
throw new Error("Formula ext\\email\applyTemplate: 4th argument should be string.");
|
||||
|
||||
$em = $this->getInjection('entityManager');
|
||||
|
||||
$email = $em->getEntity('Email', $id);
|
||||
$emailTemplate = $em->getEntity('EmailTemplate', $templateId);
|
||||
|
||||
if (!$email) {
|
||||
$GLOBALS['log']->warning("Formula ext\\email\applyTemplate: Email {$id} does not exist.");
|
||||
return false;
|
||||
}
|
||||
if (!$emailTemplate) {
|
||||
$GLOBALS['log']->warning("Formula ext\\email\applyTemplate: EmailTemplate {$templateId} does not exist.");
|
||||
return false;
|
||||
}
|
||||
|
||||
$status = $email->get('status');
|
||||
|
||||
if ($status && in_array($status, ['Sent'])) {
|
||||
$GLOBALS['log']->warning("Formula ext\\email\applyTemplate: Can't apply template to email with 'Sent' status.");
|
||||
return false;
|
||||
}
|
||||
|
||||
$emailTemplateService = $this->getInjection('serviceFactory')->create('EmailTemplate');
|
||||
|
||||
$params = [];
|
||||
|
||||
if (!$parentType || !$parentId) {
|
||||
$parentType = $email->get('parentType');
|
||||
$parentId = $email->get('parentId');
|
||||
}
|
||||
|
||||
if ($parentType && $parentId) {
|
||||
$params['parentType'] = $parentType;
|
||||
$params['parentId'] = $parentId;
|
||||
}
|
||||
|
||||
$emailAddressList = $email->get('toEmailAddresses');
|
||||
if (count($emailAddressList)) {
|
||||
$params['emailAddress'] = $emailAddressList[0]->get('name');
|
||||
}
|
||||
|
||||
$data = $emailTemplateService->parseTemplate($emailTemplate, $params, true, true);
|
||||
|
||||
$attachmentsIds = $email->getLinkMultipleIdList('attachments');
|
||||
$attachmentsIds = array_merge($attachmentsIds, $data['attachmentsIds']);
|
||||
|
||||
$email->set([
|
||||
'name' => $data['subject'],
|
||||
'body' => $data['body'],
|
||||
'isHtml' => $data['isHtml'],
|
||||
'attachmentsIds' => $attachmentsIds,
|
||||
]);
|
||||
|
||||
$em->saveEntity($email, [
|
||||
'modifiedById' => 'system',
|
||||
]);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Formula\Functions\ExtGroup\EmailGroup;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
class SendType extends \Espo\Core\Formula\Functions\Base
|
||||
{
|
||||
protected function init()
|
||||
{
|
||||
$this->addDependency('entityManager');
|
||||
$this->addDependency('serviceFactory');
|
||||
$this->addDependency('config');
|
||||
}
|
||||
|
||||
public function process(\StdClass $item)
|
||||
{
|
||||
$args = $this->fetchArguments($item);
|
||||
|
||||
if (count($args) < 1) throw new Error("Formula ext\email\send: Too few arguments.");
|
||||
$id = $args[0];
|
||||
|
||||
if (!$id) throw new Error("Formula ext\\email\send: First argument should not be empty.");
|
||||
if (!is_string($id)) throw new Error("Formula ext\\email\send: First argument should be a string.");
|
||||
|
||||
$em = $this->getInjection('entityManager');
|
||||
|
||||
$email = $em->getEntity('Email', $id);
|
||||
|
||||
if (!$email) {
|
||||
$GLOBALS['log']->warning("Formula ext\\email\send: Email {$id} does not exist.");
|
||||
return false;
|
||||
}
|
||||
|
||||
$status = $email->get('status');
|
||||
|
||||
if ($status && in_array($status, ['Sent'])) {
|
||||
$GLOBALS['log']->warning("Formula ext\\email\send: Can't send email that has 'Sent' status.");
|
||||
return false;
|
||||
}
|
||||
|
||||
$service = $this->getInjection('serviceFactory')->create('Email');
|
||||
$service->loadAdditionalFields($email);
|
||||
|
||||
$toSave = false;
|
||||
|
||||
if ($status !== 'Sending') {
|
||||
$email->set('status', 'Sending');
|
||||
$toSave = true;
|
||||
}
|
||||
|
||||
if (!$email->get('from')) {
|
||||
$from = $this->getInjection('config')->get('outboundEmailFromAddress');
|
||||
if ($from) {
|
||||
$email->set('from', $from);
|
||||
$toSave = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($toSave) {
|
||||
$em->saveEntity($email, [
|
||||
'modifiedById' => 'system',
|
||||
'silent' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
$service->sendEntity($email);
|
||||
} catch (\Exception $e) {
|
||||
$GLOBALS['log']->error("Formula ext\\email\send: Error while sending. Message: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Formula\Functions\ExtGroup\PdfGroup;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
class GenerateType extends \Espo\Core\Formula\Functions\Base
|
||||
{
|
||||
protected function init()
|
||||
{
|
||||
$this->addDependency('entityManager');
|
||||
$this->addDependency('serviceFactory');
|
||||
}
|
||||
|
||||
public function process(\StdClass $item)
|
||||
{
|
||||
$args = $this->fetchArguments($item);
|
||||
|
||||
if (count($args) < 3) throw new Error("Formula ext\\pdf\\generate: Too few arguments.");
|
||||
|
||||
$entityType = $args[0];
|
||||
$id = $args[1];
|
||||
$templateId = $args[2];
|
||||
$fileName = $args[3];
|
||||
|
||||
if (!$entityType || !is_string($entityType))
|
||||
throw new Error("Formula ext\\pdf\\generate: 1st argument should be string and not be empty.");
|
||||
if (!$id || !is_string($id))
|
||||
throw new Error("Formula ext\\pdf\\generate: 2nd argument should be string and not be empty.");
|
||||
if (!$templateId || !is_string($templateId))
|
||||
throw new Error("Formula ext\\pdf\\generate: 3rd argument should be string and not be empty.");
|
||||
if ($fileName && !is_string($fileName))
|
||||
throw new Error("Formula ext\\pdf\\generate: 4rd argument should be string.");
|
||||
|
||||
$em = $this->getInjection('entityManager');
|
||||
|
||||
try {
|
||||
$entity = $em->getEntity($entityType, $id);
|
||||
} catch (\Exception $e) {
|
||||
$GLOBALS['log']->error("Formula ext\\pdf\\generate: Message: " . $e->getMessage());
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$entity) {
|
||||
$GLOBALS['log']->warning("Formula ext\\pdf\\generate: Record {$entityType} {$id} does not exist.");
|
||||
return null;
|
||||
}
|
||||
|
||||
$template = $em->getEntity('Template', $templateId);
|
||||
|
||||
if (!$template) {
|
||||
$GLOBALS['log']->warning("Formula ext\\pdf\\generate: Template {$templateId} does not exist.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($fileName) {
|
||||
if (substr($fileName, -4) !== '.pdf') {
|
||||
$fileName .= '.pdf';
|
||||
}
|
||||
} else {
|
||||
$fileName = \Espo\Core\Utils\Util::sanitizeFileName($entity->get('name')) . '.pdf';
|
||||
}
|
||||
|
||||
try {
|
||||
$contents = $this->getInjection('serviceFactory')->create('Pdf')->buildFromTemplate($entity, $template);
|
||||
} catch (\Exception $e) {
|
||||
$GLOBALS['log']->error("Formula ext\\pdf\\generate: Error while generating. Message: " . $e->getMessage());
|
||||
return null;
|
||||
}
|
||||
|
||||
$attachment = $em->createEntity('Attachment', [
|
||||
'name' => $fileName,
|
||||
'type' => 'application/pdf',
|
||||
'contents' => $contents,
|
||||
'relatedId' => $id,
|
||||
'relatedType' => $entityType,
|
||||
'role' => 'Attachment',
|
||||
]);
|
||||
|
||||
return $attachment->id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Formula\Functions\NumberGroup;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
class RandomIntType extends \Espo\Core\Formula\Functions\Base
|
||||
{
|
||||
public function process(\StdClass $item)
|
||||
{
|
||||
$args = $this->fetchArguments($item);
|
||||
|
||||
$min = $args[0] ?? 0;
|
||||
$max = $args[1] ?? PHP_INT_MAX;
|
||||
|
||||
if (!is_int($min) || !is_int($max) ) throw new Error("Non-integer arguments passed to function number\\randomInt.");
|
||||
|
||||
return random_int($min, $max);
|
||||
}
|
||||
}
|
||||
@@ -82,7 +82,7 @@ class CountType extends \Espo\Core\Formula\Functions\Base
|
||||
while ($i < count($item->value) - 1) {
|
||||
$key = $this->evaluate($item->value[$i]);
|
||||
$value = $this->evaluate($item->value[$i + 1]);
|
||||
$whereClause[$key] = $value;
|
||||
$whereClause[] = [$key => $value];
|
||||
$i = $i + 2;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Formula\Functions\RecordGroup;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
class CreateType extends \Espo\Core\Formula\Functions\Base
|
||||
{
|
||||
protected function init()
|
||||
{
|
||||
$this->addDependency('entityManager');
|
||||
}
|
||||
|
||||
public function process(\StdClass $item)
|
||||
{
|
||||
$args = $this->fetchArguments($item);
|
||||
|
||||
if (count($args) < 1) throw new Error("Formula record\create: Too few arguments.");
|
||||
$entityType = $args[0];
|
||||
|
||||
if (!is_string($entityType)) throw new Error("Formula record\create: First argument should be a string.");
|
||||
|
||||
$data = [];
|
||||
|
||||
$i = 1;
|
||||
while ($i < count($args) - 1) {
|
||||
$attribute = $args[$i];
|
||||
if (!is_string($entityType)) throw new Error("Formula record\create: Attribute should be a string.");
|
||||
$value = $args[$i + 1];
|
||||
$data[$attribute] = $value;
|
||||
$i = $i + 2;
|
||||
}
|
||||
|
||||
$entity = $this->getInjection('entityManager')->createEntity($entityType, $data);
|
||||
|
||||
if ($entity) {
|
||||
return $entity->id;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,7 @@ class ExistsType extends \Espo\Core\Formula\Functions\Base
|
||||
while ($i < count($item->value) - 1) {
|
||||
$key = $this->evaluate($item->value[$i]);
|
||||
$value = $this->evaluate($item->value[$i + 1]);
|
||||
$whereClause[$key] = $value;
|
||||
$whereClause[] = [$key => $value];
|
||||
$i = $i + 2;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Formula\Functions\RecordGroup;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
class FindRelatedManyType extends \Espo\Core\Formula\Functions\Base
|
||||
{
|
||||
protected function init()
|
||||
{
|
||||
$this->addDependency('entityManager');
|
||||
$this->addDependency('selectManagerFactory');
|
||||
$this->addDependency('metadata');
|
||||
}
|
||||
|
||||
public function process(\StdClass $item)
|
||||
{
|
||||
$args = $this->fetchArguments($item);
|
||||
|
||||
if (count($args) < 4) {
|
||||
throw new Error("Formula record\\findRelatedMany: Too few arguments.");
|
||||
}
|
||||
|
||||
$entityManager = $this->getInjection('entityManager');
|
||||
|
||||
$entityType = $args[0];
|
||||
$id = $args[1];
|
||||
$link = $args[2];
|
||||
$limit = $args[3];
|
||||
|
||||
$orderBy = null;
|
||||
$order = null;
|
||||
|
||||
if (count($args) > 4) {
|
||||
$orderBy = $args[4];
|
||||
}
|
||||
if (count($args) > 5) {
|
||||
$order = $args[5];
|
||||
}
|
||||
|
||||
if (!$entityType) throw new Error("Formula record\\findRelatedMany: Empty entityType.");
|
||||
if (!is_string($entityType)) throw new Error("Formula record\\findRelatedMany: entityType should be string.");
|
||||
|
||||
if (!$id) {
|
||||
$GLOBALS['log']->warning("Formula record\\findRelatedMany: Empty id.");
|
||||
return [];
|
||||
}
|
||||
if (!is_string($id)) throw new Error("Formula record\\findRelatedMany: id should be string.");
|
||||
|
||||
if (!$link) throw new Error("Formula record\\findRelatedMany: Empty link.");
|
||||
if (!is_string($link)) throw new Error("Formula record\\findRelatedMany: link should be string.");
|
||||
|
||||
if (!is_int($limit)) throw new Error("Formula record\\findRelatedMany: limit should be int.");
|
||||
|
||||
$entity = $entityManager->getEntity($entityType, $id);
|
||||
|
||||
if (!$entity) {
|
||||
$GLOBALS['log']->notice("Formula record\\findRelatedMany: Entity {$entity} {$id} not found.");
|
||||
return [];
|
||||
}
|
||||
|
||||
$metadata = $this->getInjection('metadata');
|
||||
|
||||
if (!$orderBy) {
|
||||
$orderBy = $metadata->get(['entityDefs', $entityType, 'collection', 'orderBy']);
|
||||
if (is_null($order)) {
|
||||
$order = $metadata->get(['entityDefs', $entityType, 'collection', 'order']) ?? 'asc';
|
||||
}
|
||||
} else {
|
||||
$order = $order ?? 'asc';
|
||||
}
|
||||
|
||||
$relationType = $entity->getRelationParam($link, 'type');
|
||||
|
||||
if (in_array($relationType, ['belongsTo', 'hasOne', 'belongsToParent'])) {
|
||||
throw new Error("Formula record\\findRelatedMany: Not supported link type '{$relationType}'.");
|
||||
}
|
||||
|
||||
$foreignEntityType = $entity->getRelationParam($link, 'entity');
|
||||
if (!$foreignEntityType) throw new Error("Formula record\\findRelatedMany: Bad or not supported link '{$link}'.");
|
||||
|
||||
$foreignLink = $entity->getRelationParam($link, 'foreign');
|
||||
if (!$foreignLink) throw new Error("Formula record\\findRelatedMany: Not supported link '{$link}'.");
|
||||
|
||||
$selectManager = $this->getInjection('selectManagerFactory')->create($foreignEntityType);
|
||||
$selectParams = $selectManager->getEmptySelectParams();
|
||||
|
||||
if ($relationType === 'hasChildren') {
|
||||
$selectParams['whereClause'][] = [$foreignLink . 'Id' => $entity->id];
|
||||
$selectParams['whereClause'][] = [$foreignLink . 'Type' => $entity->getEntityType()];
|
||||
} else {
|
||||
$selectManager->addJoin($foreignLink, $selectParams);
|
||||
$selectParams['whereClause'][] = [$foreignLink . '.id' => $entity->id];
|
||||
}
|
||||
|
||||
if (count($args) <= 7) {
|
||||
$filter = null;
|
||||
if (count($args) == 7) {
|
||||
$filter = $args[6];
|
||||
}
|
||||
if ($filter) {
|
||||
if (!is_string($filter)) throw new Error("Formula record\\findRelatedMany: Bad filter.");
|
||||
$selectManager->applyFilter($filter, $selectParams);
|
||||
}
|
||||
} else {
|
||||
$i = 6;
|
||||
while ($i < count($args) - 1) {
|
||||
$key = $args[$i];
|
||||
$value = $args[$i + 1];
|
||||
$selectParams['whereClause'][] = [$key => $value];
|
||||
$i = $i + 2;
|
||||
}
|
||||
}
|
||||
|
||||
$selectParams['limit'] = $limit;
|
||||
|
||||
if ($orderBy) {
|
||||
$selectManager->applyOrder($orderBy, $order, $selectParams);
|
||||
}
|
||||
|
||||
$collection = $entityManager->getRepository($foreignEntityType)->select(['id'])->find($selectParams);
|
||||
|
||||
$idList = [];
|
||||
|
||||
foreach ($collection as $e) {
|
||||
$idList[] = $e->id;
|
||||
}
|
||||
|
||||
return $idList;
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ class FindRelatedOneType extends \Espo\Core\Formula\Functions\Base
|
||||
{
|
||||
$this->addDependency('entityManager');
|
||||
$this->addDependency('selectManagerFactory');
|
||||
$this->addDependency('metadata');
|
||||
}
|
||||
|
||||
public function process(\StdClass $item)
|
||||
@@ -49,7 +50,7 @@ class FindRelatedOneType extends \Espo\Core\Formula\Functions\Base
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
if (count($item->value) < 5) {
|
||||
if (count($item->value) < 3) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
@@ -58,8 +59,16 @@ class FindRelatedOneType extends \Espo\Core\Formula\Functions\Base
|
||||
$entityType = $this->evaluate($item->value[0]);
|
||||
$id = $this->evaluate($item->value[1]);
|
||||
$link = $this->evaluate($item->value[2]);
|
||||
$orderBy = $this->evaluate($item->value[3]);
|
||||
$order = $this->evaluate($item->value[4]) ?? 'asc';
|
||||
|
||||
$orderBy = null;
|
||||
$order = null;
|
||||
|
||||
if (count($item->value) > 3) {
|
||||
$orderBy = $this->evaluate($item->value[3]);
|
||||
}
|
||||
if (count($item->value) > 4) {
|
||||
$order = $this->evaluate($item->value[4]) ?? null;
|
||||
}
|
||||
|
||||
if (!$entityType) throw new Error("Formula record\\findRelatedOne: Empty entityType.");
|
||||
if (!$id) return null;
|
||||
@@ -69,8 +78,29 @@ class FindRelatedOneType extends \Espo\Core\Formula\Functions\Base
|
||||
|
||||
if (!$entity) return null;
|
||||
|
||||
$metadata = $this->getInjection('metadata');
|
||||
|
||||
$relationType = $entity->getRelationParam($link, 'type');
|
||||
|
||||
if (in_array($relationType, ['belongsTo', 'hasOne', 'belongsToParent'])) {
|
||||
$relatedEntity = $entityManager->getRepository($entityType)->findRelated($entity, $link, [
|
||||
'select' => ['id'],
|
||||
]);
|
||||
if (!$relatedEntity) {
|
||||
return null;
|
||||
}
|
||||
return $relatedEntity->id;
|
||||
}
|
||||
|
||||
if (!$orderBy) {
|
||||
$orderBy = $metadata->get(['entityDefs', $entityType, 'collection', 'orderBy']);
|
||||
if (is_null($order)) {
|
||||
$order = $metadata->get(['entityDefs', $entityType, 'collection', 'order']) ?? 'asc';
|
||||
}
|
||||
} else {
|
||||
$order = $order ?? 'asc';
|
||||
}
|
||||
|
||||
$foreignEntityType = $entity->getRelationParam($link, 'entity');
|
||||
if (!$foreignEntityType) throw new Error("Formula record\\findRelatedOne: Bad or not supported link '{$link}'.");
|
||||
|
||||
@@ -80,7 +110,6 @@ class FindRelatedOneType extends \Espo\Core\Formula\Functions\Base
|
||||
$selectManager = $this->getInjection('selectManagerFactory')->create($foreignEntityType);
|
||||
$selectParams = $selectManager->getEmptySelectParams();
|
||||
|
||||
|
||||
if ($relationType === 'hasChildren') {
|
||||
$selectParams['whereClause'][] = [$foreignLink . 'Id' => $entity->id];
|
||||
$selectParams['whereClause'][] = [$foreignLink . 'Type' => $entity->getEntityType()];
|
||||
@@ -92,7 +121,7 @@ class FindRelatedOneType extends \Espo\Core\Formula\Functions\Base
|
||||
if (count($item->value) <= 6) {
|
||||
$filter = null;
|
||||
if (count($item->value) == 6) {
|
||||
$filter = $this->evaluate($item->value[3]);
|
||||
$filter = $this->evaluate($item->value[5]);
|
||||
}
|
||||
if ($filter) {
|
||||
if (!is_string($filter)) throw new Error("Formula record\\findRelatedOne: Bad filter.");
|
||||
|
||||
@@ -61,9 +61,19 @@ class RelateType extends \Espo\Core\Formula\Functions\Base
|
||||
$entity = $em->getEntity($entityType, $id);
|
||||
if (!$entity) return null;
|
||||
|
||||
if ($em->getRepository($entityType)->isRelated($entity, $link, $foreignId))
|
||||
if (is_array($foreignId)) {
|
||||
foreach ($foreignId as $itemId) {
|
||||
$em->getRepository($entityType)->relate($entity, $link, $itemId);
|
||||
}
|
||||
return true;
|
||||
|
||||
return $em->getRepository($entityType)->relate($entity, $link, $foreignId);
|
||||
} else if (is_string($foreignId)) {
|
||||
if ($em->getRepository($entityType)->isRelated($entity, $link, $foreignId)) {
|
||||
return true;
|
||||
}
|
||||
return $em->getRepository($entityType)->relate($entity, $link, $foreignId);
|
||||
} else {
|
||||
throw new Error("Formula record\\relate: foreignId type is wrong.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Formula\Functions\RecordGroup;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
class UpdateType extends \Espo\Core\Formula\Functions\Base
|
||||
{
|
||||
protected function init()
|
||||
{
|
||||
$this->addDependency('entityManager');
|
||||
}
|
||||
|
||||
public function process(\StdClass $item)
|
||||
{
|
||||
$args = $this->fetchArguments($item);
|
||||
|
||||
if (count($args) < 2) throw new Error("Formula record\update: Too few arguments.");
|
||||
$entityType = $args[0];
|
||||
$id = $args[1];
|
||||
|
||||
if (!is_string($entityType)) throw new Error("Formula record\update: First argument should be a string.");
|
||||
if (!is_string($id)) throw new Error("Formula record\update: Second argument should be a string.");
|
||||
|
||||
$data = [];
|
||||
|
||||
$i = 2;
|
||||
while ($i < count($args) - 1) {
|
||||
$attribute = $args[$i];
|
||||
if (!is_string($entityType)) throw new Error("Formula record\update: Attribute should be a string.");
|
||||
$value = $args[$i + 1];
|
||||
$data[$attribute] = $value;
|
||||
$i = $i + 2;
|
||||
}
|
||||
|
||||
$em = $this->getInjection('entityManager');
|
||||
|
||||
$entity = $em->getEntity($entityType, $id);
|
||||
|
||||
if ($entity) {
|
||||
$entity->set($data);
|
||||
if ($em->saveEntity($entity)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Formula\Functions\StringGroup;
|
||||
|
||||
use \Espo\Core\Exceptions\Error;
|
||||
|
||||
class MatchAllType extends \Espo\Core\Formula\Functions\Base
|
||||
{
|
||||
public function process(\StdClass $item)
|
||||
{
|
||||
$args = $this->fetchArguments($item);
|
||||
|
||||
if (count($args) < 2) throw new Error("Formula: string\\matchAll: Too few arguments.");
|
||||
|
||||
$string = $args[0];
|
||||
$regexp = $args[1];
|
||||
|
||||
if (!is_string($string)) return null;
|
||||
if (!is_string($regexp)) return null;
|
||||
|
||||
$offset = $args[2] ?? 0;
|
||||
|
||||
$matches = null;
|
||||
$result = preg_match_all($regexp, $string, $matches, \PREG_PATTERN_ORDER, $offset);
|
||||
|
||||
if (!$result) return null;
|
||||
|
||||
$matchesResult = [];
|
||||
|
||||
if (!count($matches)) return null;
|
||||
|
||||
return $matches[0];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Formula\Functions\StringGroup;
|
||||
|
||||
use \Espo\Core\Exceptions\Error;
|
||||
|
||||
class MatchType extends \Espo\Core\Formula\Functions\Base
|
||||
{
|
||||
public function process(\StdClass $item)
|
||||
{
|
||||
$args = $this->fetchArguments($item);
|
||||
|
||||
if (count($args) < 2) throw new Error("Formula: string\\match: Too few arguments.");
|
||||
|
||||
$string = $args[0];
|
||||
$regexp = $args[1];
|
||||
|
||||
if (!is_string($string)) return null;
|
||||
if (!is_string($regexp)) return null;
|
||||
|
||||
$offset = $args[2] ?? 0;
|
||||
|
||||
$matches = null;
|
||||
$result = preg_match($regexp, $string, $matches, 0, $offset);
|
||||
|
||||
if (!$result) return null;
|
||||
|
||||
$matchesResult = [];
|
||||
|
||||
if (!count($matches)) return null;
|
||||
|
||||
return $matches[0];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Formula\Functions\StringGroup;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
class ReplaceType extends \Espo\Core\Formula\Functions\Base
|
||||
{
|
||||
public function process(\StdClass $item)
|
||||
{
|
||||
$args = $this->fetchArguments($item);
|
||||
|
||||
if (count($args) < 3) throw new Error("Formula: string\\replace: Too few arguments.");
|
||||
|
||||
$string = $args[0];
|
||||
$search = $args[1];
|
||||
$replace = $args[2];
|
||||
|
||||
if (!is_string($string)) {
|
||||
$GLOBALS['log']->warning("Formula: string\\replace: 1st argument should be string.");
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!is_string($search)) {
|
||||
$GLOBALS['log']->warning("Formula: string\\replace: 2nd argument should be string.");
|
||||
return $string;
|
||||
}
|
||||
|
||||
if (!is_string($replace)) {
|
||||
$GLOBALS['log']->warning("Formula: string\\replace: 3rd argument should be string.");
|
||||
return $string;
|
||||
}
|
||||
|
||||
return str_replace($search, $replace, $string);
|
||||
}
|
||||
}
|
||||
48
application/Espo/Core/Formula/Functions/WhileType.php
Normal file
48
application/Espo/Core/Formula/Functions/WhileType.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Formula\Functions;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
class WhileType extends Base
|
||||
{
|
||||
public function process(\StdClass $item)
|
||||
{
|
||||
$args = $this->fetchRawArguments($item);
|
||||
|
||||
if (count($args) < 2) {
|
||||
throw new Error("Function \'while\' should receieve 2 arguments.");
|
||||
}
|
||||
|
||||
while ($this->evaluate($args[0])) {
|
||||
$this->evaluate($args[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,6 +109,8 @@ class Parser
|
||||
{
|
||||
$isString = false;
|
||||
$isSingleQuote = false;
|
||||
$isComment = false;
|
||||
$isLineComment = false;
|
||||
|
||||
$modifiedString = $string;
|
||||
|
||||
@@ -116,27 +118,31 @@ class Parser
|
||||
|
||||
for ($i = 0; $i < strlen($string); $i++) {
|
||||
$isStringStart = false;
|
||||
if ($string[$i] === "'" && ($i === 0 || $string[$i - 1] !== "\\")) {
|
||||
if (!$isString) {
|
||||
$isString = true;
|
||||
$isSingleQuote = true;
|
||||
$isStringStart = true;
|
||||
} else {
|
||||
if ($isSingleQuote) {
|
||||
$isString = false;
|
||||
|
||||
if (!$isLineComment && !$isComment) {
|
||||
if ($string[$i] === "'" && ($i === 0 || $string[$i - 1] !== "\\")) {
|
||||
if (!$isString) {
|
||||
$isString = true;
|
||||
$isSingleQuote = true;
|
||||
$isStringStart = true;
|
||||
} else {
|
||||
if ($isSingleQuote) {
|
||||
$isString = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ($string[$i] === "\"" && ($i === 0 || $string[$i - 1] !== "\\")) {
|
||||
if (!$isString) {
|
||||
$isString = true;
|
||||
$isStringStart = true;
|
||||
$isSingleQuote = false;
|
||||
} else {
|
||||
if (!$isSingleQuote) {
|
||||
$isString = false;
|
||||
} else if ($string[$i] === "\"" && ($i === 0 || $string[$i - 1] !== "\\")) {
|
||||
if (!$isString) {
|
||||
$isString = true;
|
||||
$isStringStart = true;
|
||||
$isSingleQuote = false;
|
||||
} else {
|
||||
if (!$isSingleQuote) {
|
||||
$isString = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($isString) {
|
||||
if ($string[$i] === '(' || $string[$i] === ')') {
|
||||
$modifiedString[$i] = '_';
|
||||
@@ -144,24 +150,51 @@ class Parser
|
||||
$modifiedString[$i] = ' ';
|
||||
}
|
||||
} else {
|
||||
if ($string[$i] === '(') {
|
||||
$braceCounter++;
|
||||
}
|
||||
if ($string[$i] === ')') {
|
||||
$braceCounter--;
|
||||
}
|
||||
if (!$isLineComment && !$isComment) {
|
||||
|
||||
if ($braceCounter === 0) {
|
||||
if (!is_null($splitterIndexList)) {
|
||||
if ($string[$i] === ';') {
|
||||
$splitterIndexList[] = $i;
|
||||
if (!$isComment) {
|
||||
if ($i && $string[$i] === '/' && $string[$i - 1] === '/') {
|
||||
$isLineComment = true;
|
||||
}
|
||||
}
|
||||
if ($intoOneLine) {
|
||||
if ($string[$i] === "\r" || $string[$i] === "\n" || $string[$i] === "\t") {
|
||||
$string[$i] = ' ';
|
||||
|
||||
if (!$isLineComment) {
|
||||
if ($i && $string[$i] === '*' && $string[$i - 1] === '/') {
|
||||
$isComment = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($string[$i] === '(') {
|
||||
$braceCounter++;
|
||||
}
|
||||
if ($string[$i] === ')') {
|
||||
$braceCounter--;
|
||||
}
|
||||
|
||||
if ($braceCounter === 0) {
|
||||
if (!is_null($splitterIndexList)) {
|
||||
if ($string[$i] === ';') {
|
||||
$splitterIndexList[] = $i;
|
||||
}
|
||||
}
|
||||
if ($intoOneLine) {
|
||||
if ($string[$i] === "\r" || $string[$i] === "\n" || $string[$i] === "\t") {
|
||||
$string[$i] = ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($isLineComment) {
|
||||
if ($string[$i] === "\n") {
|
||||
$isLineComment = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($isComment) {
|
||||
if ($string[$i - 1] === "*" && $string[$i] === "/") {
|
||||
$isComment = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -367,9 +400,13 @@ class Parser
|
||||
}
|
||||
|
||||
if (is_numeric($expression)) {
|
||||
$value = filter_var($expression, FILTER_VALIDATE_INT) !== false ?
|
||||
(int) $expression :
|
||||
(float) $expression;
|
||||
|
||||
return (object) [
|
||||
'type' => 'value',
|
||||
'value' => ($expression == (int) $expression) ? (int) $expression : (float) $expression
|
||||
'value' => $value,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
57
application/Espo/Core/Htmlizer/Factory.php
Normal file
57
application/Espo/Core/Htmlizer/Factory.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Htmlizer;
|
||||
|
||||
use Espo\Core\Container;
|
||||
|
||||
class Factory
|
||||
{
|
||||
protected $container;
|
||||
|
||||
public function __construct(Container $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
public function create(bool $skipAcl = false)
|
||||
{
|
||||
return new Htmlizer(
|
||||
$this->container->get('fileManager'),
|
||||
$this->container->get('dateTime'),
|
||||
$this->container->get('number'),
|
||||
!$skipAcl ? $this->container->get('acl') : null,
|
||||
$this->container->get('entityManager'),
|
||||
$this->container->get('metadata'),
|
||||
$this->container->get('defaultLanguage'),
|
||||
$this->container->get('config'),
|
||||
$this->container->get('serviceFactory')
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Language;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Core\serviceFactory;
|
||||
use Espo\Core\ServiceFactory;
|
||||
|
||||
use LightnCandy\LightnCandy as LightnCandy;
|
||||
|
||||
@@ -228,7 +228,8 @@ class Htmlizer
|
||||
|
||||
if ($fieldType === 'currency' && $this->metadata) {
|
||||
if ($entity->getAttributeParam($attribute, 'attributeRole') === 'currency') {
|
||||
if ($currencyValue = $data[$attribute]) {
|
||||
$currencyValue = $data[$attribute] ?? null;
|
||||
if ($currencyValue) {
|
||||
$data[$attribute . 'Symbol'] = $this->metadata->get(['app', 'currency', 'symbolMap', $currencyValue]);
|
||||
}
|
||||
}
|
||||
@@ -450,6 +451,16 @@ class Htmlizer
|
||||
return $context['inverse'] ? $context['inverse']() : '';
|
||||
}
|
||||
},
|
||||
'ifMultipleOf' => function () {
|
||||
$args = func_get_args();
|
||||
$context = $args[count($args) - 1];
|
||||
|
||||
if ($args[0] % $args[1] === 0) {
|
||||
return $context['fn']();
|
||||
} else {
|
||||
return $context['inverse'] ? $context['inverse']() : '';
|
||||
}
|
||||
},
|
||||
'tableTag' => function () {
|
||||
$args = func_get_args();
|
||||
$context = $args[count($args) - 1];
|
||||
@@ -566,11 +577,13 @@ class Htmlizer
|
||||
$data[$k] = $value;
|
||||
}
|
||||
|
||||
$data['__config'] = $this->config;
|
||||
$data['__dateTime'] = $this->dateTime;
|
||||
$data['__metadata'] = $this->metadata;
|
||||
$data['__entityManager'] = $this->entityManager;
|
||||
$data['__language'] = $this->language;
|
||||
$data['__serviceFactory'] = $this->serviceFactory;
|
||||
$data['__entityType'] = $entity->getEntityType();
|
||||
|
||||
$html = $renderer($data);
|
||||
|
||||
|
||||
40
application/Espo/Core/Loaders/HtmlizerFactory.php
Normal file
40
application/Espo/Core/Loaders/HtmlizerFactory.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Loaders;
|
||||
|
||||
class HtmlizerFactory extends Base
|
||||
{
|
||||
public function load()
|
||||
{
|
||||
return new \Espo\Core\Htmlizer\Factory(
|
||||
$this->getContainer()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,8 @@ class MailSender extends Base
|
||||
{
|
||||
return new \Espo\Core\Mail\Sender(
|
||||
$this->getContainer()->get('config'),
|
||||
$this->getContainer()->get('entityManager')
|
||||
$this->getContainer()->get('entityManager'),
|
||||
$this->getContainer()->get('serviceFactory')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
40
application/Espo/Core/Loaders/PasswordHash.php
Normal file
40
application/Espo/Core/Loaders/PasswordHash.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Loaders;
|
||||
|
||||
class PasswordHash extends Base
|
||||
{
|
||||
public function load()
|
||||
{
|
||||
return new \Espo\Core\Utils\PasswordHash(
|
||||
$this->getContainer()->get('config')
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
namespace Espo\Core\Mail;
|
||||
|
||||
use \Zend\Mime\Mime as Mime;
|
||||
use Laminas\Mime\Mime as Mime;
|
||||
|
||||
use \Espo\ORM\Entity;
|
||||
use \Espo\ORM\Email;
|
||||
@@ -72,7 +72,15 @@ class Importer
|
||||
return $this->notificator;
|
||||
}
|
||||
|
||||
public function importMessage($parserType = 'ZendMail', $message, $assignedUserId = null, $teamsIdList = [], $userIdList = [], $filterList = [], $fetchOnlyHeader = false, $folderData = null)
|
||||
public function importMessage(
|
||||
string $parserType,
|
||||
$message,
|
||||
$assignedUserId = null,
|
||||
$teamsIdList = [],
|
||||
$userIdList = [],
|
||||
$filterList = [],
|
||||
$fetchOnlyHeader = false,
|
||||
$folderData = null)
|
||||
{
|
||||
$parser = $message->getParser();
|
||||
$parserClassName = '\\Espo\\Core\\Mail\\Parsers\\' . $parserType;
|
||||
|
||||
@@ -32,18 +32,11 @@
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
/**
|
||||
* Zend Framework (http://framework.zend.com/)
|
||||
*
|
||||
* @link http://github.com/zendframework/zf2 for the canonical source repository
|
||||
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
|
||||
* @license http://framework.zend.com/license/new-bsd New BSD License
|
||||
*/
|
||||
|
||||
namespace Espo\Core\Mail\Mail\Header;
|
||||
|
||||
use \Zend\Mail\Header;
|
||||
use Zend\Mime\Mime;
|
||||
use Laminas\Mail\Header;
|
||||
use Laminas\Mime\Mime;
|
||||
|
||||
class XQueueItemId implements Header\HeaderInterface
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
@@ -40,9 +40,9 @@ use ArrayIterator;
|
||||
use Countable;
|
||||
use Iterator;
|
||||
use Traversable;
|
||||
use Zend\Loader\PluginClassLocator;
|
||||
use Laminas\Loader\PluginClassLocator;
|
||||
|
||||
class Headers extends \Zend\Mail\Headers
|
||||
class Headers extends \Laminas\Mail\Headers
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
namespace Espo\Core\Mail\Mail\Storage;
|
||||
|
||||
class Imap extends \Zend\Mail\Storage\Imap
|
||||
class Imap extends \Laminas\Mail\Storage\Imap
|
||||
{
|
||||
public function getIdsFromUID($uid)
|
||||
{
|
||||
|
||||
@@ -30,13 +30,13 @@
|
||||
namespace Espo\Core\Mail\Mail\Storage;
|
||||
|
||||
use Espo\Core\Mail\Mail\Headers;
|
||||
use Zend\Mail\Header\HeaderInterface;
|
||||
use Zend\Mime;
|
||||
use Zend\Mail\Storage\Exception;
|
||||
use Zend\Mail\Storage\AbstractStorage;
|
||||
use Zend\Stdlib\ErrorHandler;
|
||||
use Laminas\Mail\Header\HeaderInterface;
|
||||
use Laminas\Mime;
|
||||
use Laminas\Mail\Storage\Exception;
|
||||
use Laminas\Mail\Storage\AbstractStorage;
|
||||
use Laminas\Stdlib\ErrorHandler;
|
||||
|
||||
class Message extends \Zend\Mail\Storage\Message
|
||||
class Message extends \Laminas\Mail\Storage\Message
|
||||
{
|
||||
public function __construct(array $params)
|
||||
{
|
||||
@@ -79,7 +79,7 @@ class Message extends \Zend\Mail\Storage\Message
|
||||
$this->headers = new Headers();
|
||||
$this->headers->addHeaders($params['headers']);
|
||||
} else {
|
||||
if ($params['headers'] instanceof \Zend\Mail\Headers) {
|
||||
if ($params['headers'] instanceof \Laminas\Mail\Headers) {
|
||||
$this->headers = $params['headers'];
|
||||
} else {
|
||||
if (empty($params['noToplines'])) {
|
||||
@@ -160,4 +160,3 @@ class Message extends \Zend\Mail\Storage\Message
|
||||
$headers = Headers::fromString($headers, $EOL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ class MessageWrapper
|
||||
|
||||
private $zendMessage = null;
|
||||
|
||||
protected $zendMessageClass = '\Zend\Mail\Storage\Message';
|
||||
protected $zendMessageClass = '\Laminas\Mail\Storage\Message';
|
||||
|
||||
protected $fullRawContent = null;
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ class MailMimeParser
|
||||
|
||||
foreach (['from', 'to', 'cc', 'reply-To'] as $type) {
|
||||
$header = $this->getMessage($message)->getHeader($type);
|
||||
if ($header) {
|
||||
if ($header && method_exists($header, 'getAddresses')) {
|
||||
$list = $header->getAddresses();
|
||||
foreach ($list as $item) {
|
||||
$address = $item->getEmail();
|
||||
@@ -125,7 +125,7 @@ class MailMimeParser
|
||||
{
|
||||
$addressList = [];
|
||||
$header = $this->getMessage($message)->getHeader($type);
|
||||
if ($header) {
|
||||
if ($header && method_exists($header, 'getAddresses')) {
|
||||
foreach ($header->getAddresses() as $item) {
|
||||
return [
|
||||
'address' => $item->getEmail(),
|
||||
@@ -140,7 +140,7 @@ class MailMimeParser
|
||||
{
|
||||
$addressList = [];
|
||||
$header = $this->getMessage($message)->getHeader($type);
|
||||
if ($header) {
|
||||
if ($header && method_exists($header, 'getAddresses')) {
|
||||
$list = $header->getAddresses();
|
||||
foreach ($list as $address) {
|
||||
$addressList[] = $address->getEmail();
|
||||
@@ -181,7 +181,9 @@ class MailMimeParser
|
||||
if ($bodyHtml) {
|
||||
$email->set('isHtml', true);
|
||||
$email->set('body', $bodyHtml);
|
||||
$email->set('bodyPlain', $bodyPlain);
|
||||
if ($bodyPlain) {
|
||||
$email->set('bodyPlain', $bodyPlain);
|
||||
}
|
||||
} else {
|
||||
$email->set('isHtml', false);
|
||||
$email->set('body', $bodyPlain);
|
||||
@@ -198,18 +200,24 @@ class MailMimeParser
|
||||
foreach ($attachmentObjList as $attachmentObj) {
|
||||
$attachment = $this->getEntityManager()->getEntity('Attachment');
|
||||
|
||||
$content = $attachmentObj->getContent();
|
||||
|
||||
$disposition = $attachmentObj->getHeaderValue('Content-Disposition');
|
||||
|
||||
$attachment = $this->getEntityManager()->getEntity('Attachment');
|
||||
|
||||
$contentType = $attachmentObj->getHeaderValue('Content-Type');
|
||||
|
||||
$filename = $attachmentObj->getHeaderParameter('Content-Disposition', 'filename', null);
|
||||
if ($filename === null) {
|
||||
$filename = $attachmentObj->getHeaderParameter('Content-Type', 'name', 'unnamed');
|
||||
}
|
||||
$attachment->set('name', $filename);
|
||||
$attachment->set('type', $attachmentObj->getHeaderValue('Content-Type'));
|
||||
$attachment->set('type', $contentType);
|
||||
|
||||
$content = '';
|
||||
$binaryContentStream = $attachmentObj->getBinaryContentStream();
|
||||
if ($binaryContentStream) {
|
||||
$content = $binaryContentStream->getContents();
|
||||
}
|
||||
|
||||
$contentId = $attachmentObj->getHeaderValue('Content-ID');
|
||||
|
||||
@@ -257,4 +265,3 @@ class MailMimeParser
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -283,7 +283,7 @@ class ZendMail
|
||||
|
||||
protected function getContentFromPart($part)
|
||||
{
|
||||
if ($part instanceof \Zend\Mime\Part) {
|
||||
if ($part instanceof \Laminas\Mime\Part) {
|
||||
$content = $part->getRawContent();
|
||||
if (strtolower($part->charset) != 'utf-8') {
|
||||
$content = mb_convert_encoding($content, 'UTF-8', $part->charset);
|
||||
|
||||
@@ -31,14 +31,14 @@ namespace Espo\Core\Mail;
|
||||
|
||||
use Espo\Entities\Email;
|
||||
|
||||
use Zend\Mime\Message as MimeMessage;
|
||||
use Zend\Mime\Part as MimePart;
|
||||
use Zend\Mime\Mime as Mime;
|
||||
use Laminas\Mime\Message as MimeMessage;
|
||||
use Laminas\Mime\Part as MimePart;
|
||||
use Laminas\Mime\Mime as Mime;
|
||||
|
||||
use Zend\Mail\Message;
|
||||
use Zend\Mail\Transport\Smtp as SmtpTransport;
|
||||
use Zend\Mail\Transport\SmtpOptions;
|
||||
use Zend\Mail\Transport\Envelope;
|
||||
use Laminas\Mail\Message;
|
||||
use Laminas\Mail\Transport\Smtp as SmtpTransport;
|
||||
use Laminas\Mail\Transport\SmtpOptions;
|
||||
use Laminas\Mail\Transport\Envelope;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
@@ -48,16 +48,28 @@ class Sender
|
||||
|
||||
protected $entityManager;
|
||||
|
||||
protected $serviceFactory;
|
||||
|
||||
protected $transport;
|
||||
|
||||
protected $isGlobal = false;
|
||||
|
||||
protected $params = [];
|
||||
|
||||
public function __construct($config, $entityManager)
|
||||
private $systemInboundEmail = null;
|
||||
|
||||
private $inboundEmailService = null;
|
||||
|
||||
private $systemInboundEmailIsCached = false;
|
||||
|
||||
private $envelope = null;
|
||||
|
||||
public function __construct($config, $entityManager, $serviceFactory = null)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->serviceFactory = $serviceFactory;
|
||||
|
||||
$this->useGlobal();
|
||||
}
|
||||
|
||||
@@ -74,6 +86,7 @@ class Sender
|
||||
public function resetParams() : self
|
||||
{
|
||||
$this->params = [];
|
||||
$this->envelope = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -86,6 +99,19 @@ class Sender
|
||||
public function useSmtp(array $params = []) : self
|
||||
{
|
||||
$this->isGlobal = false;
|
||||
$this->applySmtp($params);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function useGlobal()
|
||||
{
|
||||
$this->params = [];
|
||||
$this->isGlobal = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function applySmtp(array $params = [])
|
||||
{
|
||||
$this->params = $params;
|
||||
|
||||
$this->transport = new SmtpTransport();
|
||||
@@ -106,11 +132,12 @@ class Sender
|
||||
$options['connectionConfig'][$key] = $value;
|
||||
}
|
||||
|
||||
if ($params['auth']) {
|
||||
if (!empty($params['smtpAuthMechanism'])) {
|
||||
$smtpAuthMechanism = preg_replace("([\.]{2,})", '', $params['smtpAuthMechanism']);
|
||||
if (in_array($smtpAuthMechanism, ['login', 'crammd5', 'plain'])) {
|
||||
$options['connectionClass'] = $smtpAuthMechanism;
|
||||
if ($params['auth'] ?? false) {
|
||||
$authMechanism = $params['authMechanism'] ?? $params['smtpAuthMechanism'] ?? null;
|
||||
if ($authMechanism) {
|
||||
$authMechanism = preg_replace("([\.]{2,})", '', $authMechanism);
|
||||
if (in_array($authMechanism, ['login', 'crammd5', 'plain'])) {
|
||||
$options['connectionClass'] = $authMechanism;
|
||||
} else {
|
||||
$options['connectionClass'] = 'login';
|
||||
}
|
||||
@@ -121,11 +148,12 @@ class Sender
|
||||
$options['connectionConfig']['password'] = $params['password'];
|
||||
}
|
||||
|
||||
if (!empty($params['smtpAuthClassName'])) {
|
||||
$options['connectionClass'] = $params['smtpAuthClassName'];
|
||||
$authClassName = $params['authClassName'] ?? $params['smtpAuthClassName'] ?? null;
|
||||
if ($authClassName) {
|
||||
$options['connectionClass'] = $authClassName;
|
||||
}
|
||||
|
||||
if ($params['security']) {
|
||||
if ($params['security'] ?? null) {
|
||||
$options['connectionConfig']['ssl'] = strtolower($params['security']);
|
||||
}
|
||||
|
||||
@@ -139,47 +167,79 @@ class Sender
|
||||
$smtpOptions = new SmtpOptions($options);
|
||||
$this->transport->setOptions($smtpOptions);
|
||||
|
||||
return $this;
|
||||
if ($this->envelope) {
|
||||
$this->transport->setEnvelope($this->envelope);
|
||||
}
|
||||
}
|
||||
|
||||
public function useGlobal()
|
||||
protected function applyGlobal()
|
||||
{
|
||||
$this->params = [];
|
||||
if ($this->isGlobal) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->transport = new SmtpTransport();
|
||||
|
||||
$config = $this->config;
|
||||
|
||||
$localHostName = $config->get('smtpLocalHostName', gethostname());
|
||||
if (!$config->get('smtpServer') && $config->get('outboundEmailFromAddress')) {
|
||||
$inboundEmail = $this->getSystemInboundEmail();
|
||||
if ($inboundEmail) {
|
||||
$service = $this->getInboundEmailService();
|
||||
if ($service) {
|
||||
$params = $service->getSmtpParamsFromAccount($inboundEmail);
|
||||
$this->applySmtp($params);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$options = [
|
||||
'name' => $localHostName,
|
||||
'host' => $config->get('smtpServer'),
|
||||
$this->applySmtp([
|
||||
'server' => $config->get('smtpServer'),
|
||||
'port' => $config->get('smtpPort'),
|
||||
'connection_config' => [],
|
||||
];
|
||||
if ($config->get('smtpAuth')) {
|
||||
$options['connection_class'] = $config->get('smtpAuthMechanism', 'login');
|
||||
$options['connection_config']['username'] = $config->get('smtpUsername');
|
||||
$options['connection_config']['password'] = $config->get('smtpPassword');
|
||||
}
|
||||
if ($config->get('smtpSecurity')) {
|
||||
$options['connection_config']['ssl'] = strtolower($config->get('smtpSecurity'));
|
||||
'auth' => $config->get('smtpAuth'),
|
||||
'authMechanism' => $config->get('smtpAuthMechanism', 'login'),
|
||||
'username' => $config->get('smtpUsername'),
|
||||
'password' => $config->get('smtpPassword'),
|
||||
'security' => $config->get('smtpSecurity'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function hasSystemSmtp()
|
||||
{
|
||||
if ($this->config->get('smtpServer')) return true;
|
||||
if ($this->getSystemInboundEmail()) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getSystemInboundEmail()
|
||||
{
|
||||
$address = $this->config->get('outboundEmailFromAddress');
|
||||
|
||||
if (!$this->systemInboundEmailIsCached && $address) {
|
||||
$this->systemInboundEmail = $this->getEntityManager()->getRepository('InboundEmail')->where([
|
||||
'status' => 'Active',
|
||||
'useSmtp' => true,
|
||||
'emailAddress' => $address,
|
||||
])->findOne();
|
||||
$this->systemInboundEmailIsCached = true;
|
||||
}
|
||||
|
||||
$smtpOptions = new SmtpOptions($options);
|
||||
$this->transport->setOptions($smtpOptions);
|
||||
return $this->systemInboundEmail;
|
||||
}
|
||||
|
||||
$this->isGlobal = true;
|
||||
protected function getInboundEmailService()
|
||||
{
|
||||
if (!$this->serviceFactory) return null;
|
||||
|
||||
return $this;
|
||||
if (!$this->inboundEmailService) {
|
||||
$this->inboundEmailService = $this->serviceFactory->create('InboundEmail');
|
||||
}
|
||||
|
||||
return $this->inboundEmailService;
|
||||
}
|
||||
|
||||
public function send(Email $email, ?array $params = [], $message = null, $attachmentList = [])
|
||||
{
|
||||
if ($this->isGlobal) {
|
||||
$this->applyGlobal();
|
||||
}
|
||||
|
||||
if (!$message) {
|
||||
$message = new Message();
|
||||
}
|
||||
@@ -226,7 +286,7 @@ class Sender
|
||||
}
|
||||
$email->set('fromString', $fromString);
|
||||
|
||||
$sender = new \Zend\Mail\Header\Sender();
|
||||
$sender = new \Laminas\Mail\Header\Sender();
|
||||
$sender->setAddress($email->get('from'));
|
||||
$message->getHeaders()->addHeader($sender);
|
||||
|
||||
@@ -383,7 +443,7 @@ class Sender
|
||||
$message->getHeaders()->addHeaderLine('Content-Type', 'text/plain; charset=UTF-8');
|
||||
} else {
|
||||
if (!$message->getHeaders()->has('content-type')) {
|
||||
$contentTypeHeader = new \Zend\Mail\Header\ContentType();
|
||||
$contentTypeHeader = new \Laminas\Mail\Header\ContentType();
|
||||
$message->getHeaders()->addHeader($contentTypeHeader);
|
||||
}
|
||||
$message->getHeaders()->get('content-type')->setType($messageType);
|
||||
@@ -403,7 +463,7 @@ class Sender
|
||||
$messageId = substr($messageId, 1, strlen($messageId) - 2);
|
||||
}
|
||||
|
||||
$messageIdHeader = new \Zend\Mail\Header\MessageId();
|
||||
$messageIdHeader = new \Laminas\Mail\Header\MessageId();
|
||||
$messageIdHeader->setId($messageId);
|
||||
$message->getHeaders()->addHeader($messageIdHeader);
|
||||
|
||||
@@ -412,10 +472,12 @@ class Sender
|
||||
$email->set('status', 'Sent');
|
||||
$email->set('dateSent', date("Y-m-d H:i:s"));
|
||||
} catch (\Exception $e) {
|
||||
$this->resetParams();
|
||||
$this->useGlobal();
|
||||
throw new Error($e->getMessage(), 500);
|
||||
}
|
||||
|
||||
$this->resetParams();
|
||||
$this->useGlobal();
|
||||
}
|
||||
|
||||
@@ -437,7 +499,8 @@ class Sender
|
||||
|
||||
public function setEnvelopeOptions(array $options) : self
|
||||
{
|
||||
$this->transport->setEnvelope(new Envelope($options));
|
||||
$this->envelope = new Envelope($options);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
301
application/Espo/Core/Password/Recovery.php
Normal file
301
application/Espo/Core/Password/Recovery.php
Normal file
@@ -0,0 +1,301 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Password;
|
||||
|
||||
use Espo\Core\Utils\Util;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Entities\PasswordChangeRequest;
|
||||
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
class Recovery implements \Espo\Core\Interfaces\Injectable
|
||||
{
|
||||
use \Espo\Core\Traits\Injectable;
|
||||
|
||||
const REQUEST_DELAY = 3000; //ms
|
||||
|
||||
const REQUEST_LIFETIME = '3 hours';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->addDependencyList([
|
||||
'entityManager',
|
||||
'config',
|
||||
'mailSender',
|
||||
'htmlizerFactory',
|
||||
'templateFileManager',
|
||||
]);
|
||||
}
|
||||
|
||||
public function getRequest(string $id) : PasswordChangeRequest
|
||||
{
|
||||
$config = $this->getInjection('config');
|
||||
$em = $this->getInjection('entityManager');
|
||||
|
||||
if ($config->get('passwordRecoveryDisabled')) {
|
||||
throw new Forbidden("Password recovery: Disabled.");
|
||||
}
|
||||
|
||||
$request = $em->getRepository('PasswordChangeRequest')->where([
|
||||
'requestId' => $id,
|
||||
])->findOne();
|
||||
|
||||
if (!$request) {
|
||||
throw new NotFound("Password recovery: Request not found by id.");
|
||||
}
|
||||
|
||||
$userId = $request->get('userId');
|
||||
if (!$userId) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
public function removeRequest(string $id)
|
||||
{
|
||||
$em = $this->getInjection('entityManager');
|
||||
$request = $em->getRepository('PasswordChangeRequest')->where([
|
||||
'requestId' => $id,
|
||||
])->findOne();
|
||||
|
||||
if ($request) {
|
||||
$em->removeEntity($request);
|
||||
}
|
||||
}
|
||||
|
||||
public function request(string $emailAddress, ?string $userName = null, ?string $url) : bool
|
||||
{
|
||||
$config = $this->getInjection('config');
|
||||
$em = $this->getInjection('entityManager');
|
||||
|
||||
$noExposure = $config->get('passwordRecoveryNoExposure') ?? false;
|
||||
|
||||
if ($config->get('passwordRecoveryDisabled')) {
|
||||
throw new Forbidden("Password recovery: Disabled.");
|
||||
}
|
||||
|
||||
$user = $em->getRepository('User')->where([
|
||||
'userName' => $userName,
|
||||
'emailAddress' => $emailAddress,
|
||||
])->findOne();
|
||||
|
||||
if (!$user) {
|
||||
$this->fail("Password recovery: User {$emailAddress} not found.", 404);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$user->isActive()) {
|
||||
$this->fail("Password recovery: User {$user->id} is not active.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($user->isApi() || $user->isSystem() || $user->isSuperAdmin()) {
|
||||
$this->fail("Password recovery: User {$user->id} is not allowed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($config->get('passwordRecoveryForInternalUsersDisabled')) {
|
||||
if ($user->isRegular() || $user->isAdmin()) {
|
||||
$this->fail("Password recovery: User {$user->id} is not allowed, disabled for internal users.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($config->get('passwordRecoveryForAdminDisabled')) {
|
||||
if ($user->isAdmin()) {
|
||||
$this->fail("Password recovery: User {$user->id} is not allowed, disabled for admin users.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$user->isAdmin() && $config->get('authenticationMethod', 'Espo') !== 'Espo') {
|
||||
$this->fail("Password recovery: User {$user->id} is not allowed, authentication method is not 'Espo'.");
|
||||
return false;
|
||||
}
|
||||
|
||||
$passwordChangeRequest = $em->getRepository('PasswordChangeRequest')->where([
|
||||
'userId' => $user->id,
|
||||
])->findOne();
|
||||
|
||||
if ($passwordChangeRequest) {
|
||||
if (!$noExposure) {
|
||||
throw new Forbidden(json_encode(['reason' => 'Already-Sent']));
|
||||
}
|
||||
|
||||
$this->fail("Password recovery: Denied for {$user->id}, already sent.");
|
||||
return false;
|
||||
}
|
||||
|
||||
$requestId = Util::generateCryptId();
|
||||
|
||||
$passwordChangeRequest = $em->getEntity('PasswordChangeRequest');
|
||||
$passwordChangeRequest->set([
|
||||
'userId' => $user->id,
|
||||
'requestId' => $requestId,
|
||||
'url' => $url,
|
||||
]);
|
||||
|
||||
|
||||
$microtime = microtime(true);
|
||||
|
||||
$this->send($requestId, $emailAddress, $user);
|
||||
|
||||
$em->saveEntity($passwordChangeRequest);
|
||||
|
||||
if (!$passwordChangeRequest->id) throw new Error();
|
||||
|
||||
$lifetime = $config->get('passwordRecoveryRequestLifetime') ?? self::REQUEST_LIFETIME;
|
||||
|
||||
$dt = new \DateTime();
|
||||
$dt->modify('+' . $lifetime);
|
||||
|
||||
$em->createEntity('Job', [
|
||||
'serviceName' => 'User',
|
||||
'methodName' => 'removeChangePasswordRequestJob',
|
||||
'data' => ['id' => $passwordChangeRequest->id],
|
||||
'executeTime' => $dt->format('Y-m-d H:i:s'),
|
||||
'queue' => 'q1',
|
||||
]);
|
||||
|
||||
$timeDiff = $this->getDelay() - floor((microtime(true) - $microtime) / 1000);
|
||||
|
||||
if ($noExposure && $timeDiff > 0) {
|
||||
$this->delay($timeDiff);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function getDelay()
|
||||
{
|
||||
return $this->getInjection('config')->get('passwordRecoveryRequestDelay') ?? self::REQUEST_DELAY;
|
||||
}
|
||||
|
||||
protected function delay(?int $delay = null)
|
||||
{
|
||||
$delay = $delay ?? $this->getDelay();
|
||||
|
||||
usleep($delay * 1000);
|
||||
}
|
||||
|
||||
protected function send(string $requestId, string $emailAddress, User $user)
|
||||
{
|
||||
$config = $this->getInjection('config');
|
||||
$em = $this->getInjection('entityManager');
|
||||
$mailSender = $this->getInjection('mailSender');
|
||||
$htmlizerFactory = $this->getInjection('htmlizerFactory');
|
||||
|
||||
$templateFileManager = $this->getInjection('templateFileManager');
|
||||
|
||||
if (!$emailAddress) return;
|
||||
|
||||
$email = $em->getEntity('Email');
|
||||
|
||||
if (!$mailSender->hasSystemSmtp() && !$config->get('internalSmtpServer')) {
|
||||
throw new Error("Password recovery: SMTP credentials are not defined.");
|
||||
}
|
||||
|
||||
$subjectTpl = $templateFileManager->getTemplate('passwordChangeLink', 'subject', 'User');
|
||||
$bodyTpl = $templateFileManager->getTemplate('passwordChangeLink', 'body', 'User');
|
||||
|
||||
$siteUrl = $config->getSiteUrl();
|
||||
|
||||
if ($user->isPortal()) {
|
||||
$portal = $em->getRepository('Portal')->distinct()->join('users')->where([
|
||||
'isActive' => true,
|
||||
'users.id' => $user->id,
|
||||
])->findOne();
|
||||
if ($portal) {
|
||||
if ($portal->get('customUrl')) {
|
||||
$siteUrl = $portal->get('customUrl');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$data = [];
|
||||
$link = $siteUrl . '?entryPoint=changePassword&id=' . $requestId;
|
||||
$data['link'] = $link;
|
||||
|
||||
$htmlizer = $htmlizerFactory->create(true);
|
||||
|
||||
$subject = $htmlizer->render($user, $subjectTpl, null, $data, true);
|
||||
$body = $htmlizer->render($user, $bodyTpl, null, $data, true);
|
||||
|
||||
$email->set([
|
||||
'subject' => $subject,
|
||||
'body' => $body,
|
||||
'to' => $emailAddress,
|
||||
'isSystem' => true,
|
||||
]);
|
||||
|
||||
if ($mailSender->hasSystemSmtp()) {
|
||||
$mailSender->useGlobal();
|
||||
} else {
|
||||
$mailSender->useSmtp([
|
||||
'server' => $config->get('internalSmtpServer'),
|
||||
'port' => $config->get('internalSmtpPort'),
|
||||
'auth' => $config->get('internalSmtpAuth'),
|
||||
'username' => $config->get('internalSmtpUsername'),
|
||||
'password' => $config->get('internalSmtpPassword'),
|
||||
'security' => $config->get('internalSmtpSecurity'),
|
||||
'fromAddress' => $config->get('internalOutboundEmailFromAddress', $config->get('outboundEmailFromAddress')),
|
||||
]);
|
||||
}
|
||||
|
||||
$mailSender->send($email);
|
||||
}
|
||||
|
||||
private function fail(?string $msg = null, int $errorCode = 403)
|
||||
{
|
||||
$config = $this->getInjection('config');
|
||||
|
||||
$noExposure = $config->get('passwordRecoveryNoExposure') ?? false;
|
||||
|
||||
if ($msg) {
|
||||
$GLOBALS['log']->warning($msg);
|
||||
}
|
||||
|
||||
if (!$noExposure) {
|
||||
if ($errorCode === 403) {
|
||||
throw new Forbidden();
|
||||
} else if ($errorCode === 404) {
|
||||
throw new NotFound();
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$this->delay();
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,11 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
/************************************************************************
|
||||
* This file contains code parts copied from TCPDF software library
|
||||
* that is published under GNU Lesser General Public License.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Pdf;
|
||||
|
||||
define('K_TCPDF_EXTERNAL_CONFIG', true);
|
||||
@@ -68,8 +73,12 @@ class Tcpdf extends \TCPDF
|
||||
{
|
||||
protected $footerHtml = '';
|
||||
|
||||
protected $headerHtml = '';
|
||||
|
||||
protected $footerPosition = 15;
|
||||
|
||||
protected $headerPosition = 10;
|
||||
|
||||
protected $useGroupNumbers = false;
|
||||
|
||||
public function serializeTCPDFtagParameters($data)
|
||||
@@ -87,6 +96,11 @@ class Tcpdf extends \TCPDF
|
||||
$this->useGroupNumbers = $value;
|
||||
}
|
||||
|
||||
public function setHeaderHtml($html)
|
||||
{
|
||||
$this->headerHtml = $html;
|
||||
}
|
||||
|
||||
public function setFooterHtml($html)
|
||||
{
|
||||
$this->footerHtml = $html;
|
||||
@@ -97,6 +111,34 @@ class Tcpdf extends \TCPDF
|
||||
$this->footerPosition = $position;
|
||||
}
|
||||
|
||||
public function setHeaderPosition($position)
|
||||
{
|
||||
$this->headerPosition = $position;
|
||||
}
|
||||
|
||||
public function Header()
|
||||
{
|
||||
$this->SetY($this->headerPosition);
|
||||
|
||||
$html = $this->headerHtml;
|
||||
|
||||
if ($this->useGroupNumbers) {
|
||||
$html = str_replace('{pageNumber}', '{:png:}', $html);
|
||||
$html = str_replace('{pageAbsoluteNumber}', '{:pnp:}', $html);
|
||||
} else {
|
||||
$html = str_replace('{pageNumber}', '{:pnp:}', $html);
|
||||
$html = str_replace('{pageAbsoluteNumber}', '{:pnp:}', $html);
|
||||
}
|
||||
|
||||
if ($this->isUnicodeFont()) {
|
||||
$html = str_replace('{totalPageNumber}', '{{:ptp:}}', $html);
|
||||
} else {
|
||||
$html = str_replace('{totalPageNumber}', '{:ptp:}', $html);
|
||||
}
|
||||
|
||||
$this->writeHTMLCell(0, 0, '', '', $html, 0, 1, 0, '', 0, false, 'T');
|
||||
}
|
||||
|
||||
public function Footer()
|
||||
{
|
||||
$breakMargin = $this->getBreakMargin();
|
||||
|
||||
@@ -38,6 +38,8 @@ class Event extends \Espo\Core\ORM\Repositories\RDB
|
||||
|
||||
protected $reminderSkippingStatusList = ['Held', 'Not Held'];
|
||||
|
||||
protected $preserveDuration = true;
|
||||
|
||||
protected function init()
|
||||
{
|
||||
parent::init();
|
||||
@@ -86,7 +88,13 @@ class Event extends \Espo\Core\ORM\Repositories\RDB
|
||||
}
|
||||
|
||||
if (!$entity->isNew()) {
|
||||
if ($entity->isAttributeChanged('dateStart') && $entity->isAttributeChanged('dateStart') && !$entity->isAttributeChanged('dateEnd')) {
|
||||
if (
|
||||
$this->preserveDuration
|
||||
&&
|
||||
$entity->isAttributeChanged('dateStart') && $entity->get('dateStart')
|
||||
&&
|
||||
$entity->isAttributeChanged('dateStart') && !$entity->isAttributeChanged('dateEnd')
|
||||
) {
|
||||
$dateEndPrevious = $entity->getFetched('dateEnd');
|
||||
$dateStartPrevious = $entity->getFetched('dateStart');
|
||||
if ($dateStartPrevious && $dateEndPrevious) {
|
||||
@@ -251,15 +259,17 @@ class Event extends \Espo\Core\ORM\Repositories\RDB
|
||||
|
||||
protected function convertDateTimeToDefaultTimezone($string)
|
||||
{
|
||||
$dateTime = \DateTime::createFromFormat($this->getDateTime()->getInternalDateTimeFormat(), $string);
|
||||
$timeZone = $this->getConfig()->get('timeZone');
|
||||
if (empty($timeZone)) {
|
||||
$timeZone = 'UTC';
|
||||
}
|
||||
$tz = $timezone = new \DateTimeZone($timeZone);
|
||||
$timeZone = $this->getConfig()->get('timeZone') ?? 'UTC';
|
||||
|
||||
if ($dateTime) {
|
||||
return $dateTime->setTimezone($tz)->format($this->getDateTime()->getInternalDateTimeFormat());
|
||||
$tz = new \DateTimeZone($timeZone);
|
||||
|
||||
try {
|
||||
$dt = \DateTime::createFromFormat($this->getDateTime()->getInternalDateTimeFormat(), $string, $tz);
|
||||
} catch (\Exception $e) {}
|
||||
|
||||
if ($dt) {
|
||||
$utcTz = new \DateTimeZone('UTC');
|
||||
return $dt->setTimezone($utcTz)->format($this->getDateTime()->getInternalDateTimeFormat());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ class Base
|
||||
$desc = $desc === strtolower('desc');
|
||||
}
|
||||
|
||||
if (!empty($sortBy)) {
|
||||
if ($sortBy) {
|
||||
$result['orderBy'] = $sortBy;
|
||||
$type = $this->getMetadata()->get(['entityDefs', $this->getEntityType(), 'fields', $sortBy, 'type']);
|
||||
if (in_array($type, ['link', 'file', 'image', 'linkOne'])) {
|
||||
@@ -171,13 +171,11 @@ class Base
|
||||
} else if ($type === 'linkParent') {
|
||||
$result['orderBy'] .= 'Type';
|
||||
} else if ($type === 'address') {
|
||||
if (!$desc) {
|
||||
$orderPart = 'ASC';
|
||||
} else {
|
||||
$orderPart = 'DESC';
|
||||
}
|
||||
$result['orderBy'] = [[$sortBy . 'Country', $orderPart], [$sortBy . 'City', $orderPart], [$sortBy . 'Street', $orderPart]];
|
||||
return;
|
||||
$result['orderBy'] = [
|
||||
[$sortBy . 'Country', $desc],
|
||||
[$sortBy . 'City', $desc],
|
||||
[$sortBy . 'Street', $desc],
|
||||
];
|
||||
} else if ($type === 'enum') {
|
||||
$list = $this->getMetadata()->get(['entityDefs', $this->getEntityType(), 'fields', $sortBy, 'options']);
|
||||
if ($list && is_array($list) && count($list)) {
|
||||
@@ -190,8 +188,7 @@ class Base
|
||||
foreach ($list as $i => $listItem) {
|
||||
$list[$i] = str_replace(',', '_COMMA_', $listItem);
|
||||
}
|
||||
$result['orderBy'] = 'LIST:' . $sortBy . ':' . implode(',', $list);
|
||||
return;
|
||||
$result['orderBy'] = [['LIST:' . $sortBy . ':' . implode(',', $list)]];
|
||||
}
|
||||
} else {
|
||||
if (strpos($sortBy, '.') === false && strpos($sortBy, ':') === false) {
|
||||
@@ -200,7 +197,27 @@ class Base
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$orderByAttribute = null;
|
||||
|
||||
if (!is_array($result['orderBy'])) {
|
||||
$orderByAttribute = $result['orderBy'];
|
||||
$result['orderBy'] = [[$result['orderBy'], $desc]];
|
||||
}
|
||||
|
||||
if (
|
||||
$sortBy != 'id'
|
||||
&&
|
||||
(!$orderByAttribute || !$this->getSeed()->getAttributeParam($orderByAttribute, 'unique'))
|
||||
&&
|
||||
$this->getSeed()->hasAttribute('id')
|
||||
) {
|
||||
$result['orderBy'][] = ['id', $desc];
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$desc) {
|
||||
$result['order'] = 'ASC';
|
||||
} else {
|
||||
@@ -1863,6 +1880,7 @@ class Base
|
||||
$method = 'filter' . ucfirst($filter);
|
||||
if (method_exists($this, $method)) {
|
||||
$this->$method($result);
|
||||
return;
|
||||
} else {
|
||||
$className = $this->getMetadata()->get(['entityDefs', $this->entityType, 'collection', 'filters', $filter, 'className']);
|
||||
if ($className) {
|
||||
@@ -1877,7 +1895,10 @@ class Base
|
||||
}
|
||||
$impl->applyFilter($this->entityType, $filter, $result, $this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$result['whereClause'][] = ['id' => null];
|
||||
}
|
||||
|
||||
public function applyFilter(string $filter, array &$result)
|
||||
@@ -2272,12 +2293,23 @@ class Base
|
||||
|
||||
$relevanceExpression = $fullTextSearchData['where'];
|
||||
|
||||
if (!isset($result['orderBy']) || $this->fullTextOrderType === self::FT_ORDER_RELEVANCE) {
|
||||
$fullTextOrderType = $this->fullTextOrderType;
|
||||
|
||||
$orderTypeMap = [
|
||||
'combined' => self::FT_ORDER_COMBINTED,
|
||||
'relavance' => self::FT_ORDER_RELEVANCE,
|
||||
'original' => self::FT_ORDER_ORIGINAL,
|
||||
];
|
||||
|
||||
$mOrderType = $this->getMetadata()->get(['entityDefs', $this->entityType, 'collection', 'fullTextSearchOrderType']);
|
||||
if ($mOrderType) $fullTextOrderType = $orderTypeMap[$mOrderType];
|
||||
|
||||
if (!isset($result['orderBy']) || $fullTextOrderType === self::FT_ORDER_RELEVANCE) {
|
||||
$result['orderBy'] = [[$relevanceExpression, 'desc']];
|
||||
$result['order'] = null;
|
||||
} else {
|
||||
if ($this->fullTextOrderType === self::FT_ORDER_COMBINTED) {
|
||||
$relevanceExpression =
|
||||
if ($fullTextOrderType === self::FT_ORDER_COMBINTED) {
|
||||
$relevanceExpression =
|
||||
'ROUND:(DIV:(' . $fullTextSearchData['where'] . ','.$this->fullTextOrderRelevanceDivider.'))';
|
||||
|
||||
if (is_string($result['orderBy'])) {
|
||||
@@ -2285,6 +2317,11 @@ class Base
|
||||
[$relevanceExpression, 'desc'],
|
||||
[$result['orderBy'], $result['order'] ?? 'asc'],
|
||||
];
|
||||
} else if (is_array($result['orderBy'])) {
|
||||
$result['orderBy'] = array_merge(
|
||||
[[$relevanceExpression, 'desc']],
|
||||
$result['orderBy']
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2657,9 +2694,9 @@ class Base
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($params['orderBy'])) {
|
||||
$sortByField = $params['orderBy'];
|
||||
$sortByField = $params['orderBy'] ?? $this->getMetadata()->get(['entityDefs', $this->entityType, 'collection', 'orderBy']);
|
||||
|
||||
if ($sortByField) {
|
||||
$sortByAttributeList = $this->getFieldManagerUtil()->getAttributeList($this->getEntityType(), $sortByField);
|
||||
foreach ($sortByAttributeList as $attribute) {
|
||||
if (!in_array($attribute, $attributeList) && $seed->hasAttribute($attribute)) {
|
||||
@@ -2680,4 +2717,27 @@ class Base
|
||||
|
||||
return $attributeList;
|
||||
}
|
||||
|
||||
protected function hasInOrderBy(string $attribute, array &$result)
|
||||
{
|
||||
$orderBy = $result['orderBy'] ?? null;
|
||||
|
||||
if (!$orderBy) return false;
|
||||
|
||||
if (is_string($orderBy)) {
|
||||
return $attribute === $orderBy;
|
||||
}
|
||||
|
||||
if (is_array($orderBy)) {
|
||||
foreach ($orderBy as $item) {
|
||||
if (is_array($item) && count($item)) {
|
||||
if ($item[0] === $attribute) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
194
application/Espo/Core/TemplateHelpers/GoogleMaps.php
Normal file
194
application/Espo/Core/TemplateHelpers/GoogleMaps.php
Normal file
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\TemplateHelpers;
|
||||
|
||||
class GoogleMaps
|
||||
{
|
||||
public static function image()
|
||||
{
|
||||
$args = func_get_args();
|
||||
$context = $args[count($args) - 1];
|
||||
$hash = $context['hash'];
|
||||
$data = $context['data']['root'];
|
||||
|
||||
$em = $data['__entityManager'];
|
||||
$metadata = $data['__metadata'];
|
||||
$config = $data['__config'];
|
||||
|
||||
$entityType = $data['__entityType'];
|
||||
|
||||
$field = $hash['field'] ?? null;
|
||||
|
||||
$size = $hash['size'] ?? '400x400';
|
||||
$zoom = $hash['zoom'] ?? null;
|
||||
$language = $hash['language'] ?? $config->get('language');
|
||||
|
||||
if (strpos($size, 'x') === false) {
|
||||
$size = $size .'x' . $size;
|
||||
}
|
||||
|
||||
if ($field && $metadata->get(['entityDefs', $entityType, 'fields', $field, 'type']) !== 'address') {
|
||||
$GLOBALS['log']->warning("Template helper _googleMapsImage: Specified field is not of address type.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
!$field &&
|
||||
!array_key_exists('street', $hash) &&
|
||||
!array_key_exists('city', $hash) &&
|
||||
!array_key_exists('country', $hash) &&
|
||||
!array_key_exists('state', $hash) &&
|
||||
!array_key_exists('postalCode', $hash)
|
||||
) {
|
||||
$field = ($entityType === 'Account') ? 'billingAddress' : 'address';
|
||||
}
|
||||
|
||||
if ($field) {
|
||||
$street = $data[$field . 'Street'] ?? null;
|
||||
$city = $data[$field . 'City'] ?? null;
|
||||
$country = $data[$field . 'Country'] ?? null;
|
||||
$state = $data[$field . 'State'] ?? null;
|
||||
$postalCode = $data[$field . 'postalCode'] ?? null;
|
||||
} else {
|
||||
$street = $hash['street'] ?? null;
|
||||
$city = $hash['city'] ?? null;
|
||||
$country = $hash['country'] ?? null;
|
||||
$state = $hash['state'] ?? null;
|
||||
$postalCode = $hash['postalCode'] ?? null;
|
||||
}
|
||||
|
||||
$address = '';
|
||||
if ($street) {
|
||||
$address .= $street;
|
||||
}
|
||||
if ($city) {
|
||||
if ($address != '') {
|
||||
$address .= ', ';
|
||||
}
|
||||
$address .= $city;
|
||||
}
|
||||
if ($state) {
|
||||
if ($address != '') {
|
||||
$address .= ', ';
|
||||
}
|
||||
$address .= $state;
|
||||
}
|
||||
if ($postalCode) {
|
||||
if ($state || $city) {
|
||||
$address .= ' ';
|
||||
} else {
|
||||
if ($address) {
|
||||
$address .= ', ';
|
||||
}
|
||||
}
|
||||
$address .= $postalCode;
|
||||
}
|
||||
if ($country) {
|
||||
if ($address != '') {
|
||||
$address .= ', ';
|
||||
}
|
||||
$address .= $country;
|
||||
}
|
||||
|
||||
$address = urlencode($address);
|
||||
|
||||
$apiKey = $config->get('googleMapsApiKey');
|
||||
|
||||
if (!$apiKey) {
|
||||
$GLOBALS['log']->error("Template helper _googleMapsImage: No Google Maps API key.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$address) {
|
||||
$GLOBALS['log']->debug("Template helper _googleMapsImage: No address to display.");
|
||||
return null;
|
||||
}
|
||||
|
||||
$format = 'jpg;';
|
||||
|
||||
$url = "https://maps.googleapis.com/maps/api/staticmap?" .
|
||||
'center=' . $address .
|
||||
'format=' . $format .
|
||||
'&size=' . $size .
|
||||
'&key=' . $apiKey;
|
||||
|
||||
if ($zoom) {
|
||||
$url .= '&zoom=' . $zoom;
|
||||
}
|
||||
if ($language) {
|
||||
$url .= '&language=' . $language;
|
||||
}
|
||||
|
||||
$GLOBALS['log']->debug("Template helper _googleMapsImage: URL: {$url}.");
|
||||
|
||||
$image = \Espo\Core\TemplateHelpers\GoogleMaps::getImage($url);
|
||||
|
||||
if (!$image) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
$filePath = tempnam(sys_get_temp_dir(), 'google_maps_image');
|
||||
file_put_contents($filePath, $image);
|
||||
|
||||
list($width, $height) = explode('x', $size);
|
||||
|
||||
$tag = "<img src=\"{$filePath}\" width=\"{$width}\" height=\"{$height}\">";
|
||||
|
||||
return new LightnCandy\SafeString($tag);
|
||||
}
|
||||
|
||||
public static function getImage(string $url)
|
||||
{
|
||||
$headers = [];
|
||||
$headers[] = 'Accept: image/jpeg, image/pjpeg';
|
||||
$headers[] = 'Connection: Keep-Alive';
|
||||
|
||||
$agent = 'Mozilla/5.0';
|
||||
|
||||
$c = curl_init();
|
||||
|
||||
curl_setopt($c, \CURLOPT_URL, $url);
|
||||
curl_setopt($c, \CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($c, \CURLOPT_HEADER, 0);
|
||||
curl_setopt($c, \CURLOPT_USERAGENT, $agent);
|
||||
curl_setopt($c, \CURLOPT_TIMEOUT, 10);
|
||||
curl_setopt($c, \CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($c, \CURLOPT_FOLLOWLOCATION, 1);
|
||||
curl_setopt($c, \CURLOPT_BINARYTRANSFER, 1);
|
||||
|
||||
|
||||
$raw = curl_exec($c);
|
||||
curl_close($c);
|
||||
|
||||
return $raw;
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,26 @@
|
||||
"activitiesCreate": true,
|
||||
"historyCreate": true
|
||||
},
|
||||
"bottomPanels": {
|
||||
"detail": [
|
||||
{
|
||||
"name": "scheduler",
|
||||
"label": "Scheduler",
|
||||
"view": "crm:views/meeting/record/panels/scheduler",
|
||||
"disabled": true,
|
||||
"order": 3
|
||||
}
|
||||
],
|
||||
"edit": [
|
||||
{
|
||||
"name": "scheduler",
|
||||
"label": "Scheduler",
|
||||
"view": "crm:views/meeting/record/panels/scheduler",
|
||||
"disabled": true,
|
||||
"order": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"filterList": [
|
||||
{
|
||||
"name":"planned"
|
||||
|
||||
@@ -87,7 +87,8 @@
|
||||
},
|
||||
"links": {
|
||||
"parent": {
|
||||
"type": "belongsToParent"
|
||||
"type": "belongsToParent",
|
||||
"foreign": "{entityTypeLowerFirst}Children"
|
||||
},
|
||||
"createdBy": {
|
||||
"type": "belongsTo",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"labels": {
|
||||
"Create {entityType}": "Vytvořit {entityTypeTranslated}"
|
||||
"Create {entityType}": "Crear {entityTypeTranslated}"
|
||||
}
|
||||
}
|
||||
@@ -398,11 +398,19 @@ class Auth
|
||||
|
||||
$requestTimeFrom = (new \DateTime('@' . intval($_SERVER['REQUEST_TIME_FLOAT'])))->modify('-' . $failedAttemptsPeriod);
|
||||
|
||||
$failAttemptCount = $this->getEntityManager()->getRepository('AuthLogRecord')->where([
|
||||
$failAttemptCount = 0;
|
||||
|
||||
$where = [
|
||||
'requestTime>' => $requestTimeFrom->format('U'),
|
||||
'ipAddress' => $_SERVER['REMOTE_ADDR'],
|
||||
'isDenied' => true
|
||||
])->count();
|
||||
'isDenied' => true,
|
||||
];
|
||||
|
||||
$wasFailed = !!$this->getEntityManager()->getRepository('AuthLogRecord')->select(['id'])->where($where)->findOne();
|
||||
|
||||
if ($wasFailed) {
|
||||
$failAttemptCount = $this->getEntityManager()->getRepository('AuthLogRecord')->where($where)->count();
|
||||
}
|
||||
|
||||
if ($failAttemptCount > $maxFailedAttempts) {
|
||||
$GLOBALS['log']->warning("AUTH: Max failed login attempts exceeded for IP '".$_SERVER['REMOTE_ADDR']."'.");
|
||||
@@ -434,7 +442,7 @@ class Auth
|
||||
if ($authToken->get('secret')) {
|
||||
$sentSecret = $_COOKIE['auth-token-secret'] ?? null;
|
||||
if ($sentSecret === $authToken->get('secret')) {
|
||||
setcookie('auth-token-secret', null, -1, '/');
|
||||
$this->setSecretInCookie(null);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -479,15 +487,21 @@ class Auth
|
||||
$this->getEntityManager()->saveEntity($authLogRecord);
|
||||
}
|
||||
|
||||
protected function setSecretInCookie(string $secret)
|
||||
protected function setSecretInCookie(?string $secret)
|
||||
{
|
||||
if (!$secret) {
|
||||
$time = -1;
|
||||
} else {
|
||||
$time = strtotime('+1000 days');
|
||||
}
|
||||
|
||||
if (version_compare(\PHP_VERSION, '7.3.0') < 0) {
|
||||
setcookie('auth-token-secret', $secret, strtotime('+1000 days'), '/', '', false, true);
|
||||
setcookie('auth-token-secret', $secret, $time, '/', '', false, true);
|
||||
return;
|
||||
}
|
||||
|
||||
setcookie('auth-token-secret', $secret, [
|
||||
'expires' => strtotime('+1000 days'),
|
||||
'expires' => $time,
|
||||
'path' => '/',
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
|
||||
@@ -53,6 +53,12 @@ class Espo extends Base
|
||||
]
|
||||
]);
|
||||
|
||||
if ($user && $authToken) {
|
||||
if ($user->id !== $authToken->get('userId')) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
namespace Espo\Core\Utils\Authentication\LDAP;
|
||||
|
||||
class Client extends \Zend\Ldap\Ldap
|
||||
class Client extends \Laminas\Ldap\Ldap
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Authentication\LDAP;
|
||||
|
||||
use \Espo\Core\Utils\Config;
|
||||
|
||||
class Utils
|
||||
@@ -95,7 +96,7 @@ class Utils
|
||||
);
|
||||
|
||||
/**
|
||||
* accountCanonicalForm Map between Espo and Zend value
|
||||
* accountCanonicalForm Map between Espo and Laminas value
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
@@ -181,7 +182,7 @@ class Utils
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Zend options for using Zend\Ldap
|
||||
* Get Laminas options for using Laminas\Ldap
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
|
||||
@@ -153,6 +153,9 @@ class ClientManager
|
||||
'scriptsHtml' => $scriptsHtml,
|
||||
'additionalStyleSheetsHtml' => $additionalStyleSheetsHtml,
|
||||
'linksHtml' => $linksHtml,
|
||||
'favicon196Path' => $this->getMetadata()->get(['app', 'client', 'favicon196']) ?? 'client/img/favicon196x196.png',
|
||||
'faviconPath' => $this->getMetadata()->get(['app', 'client', 'favicon']) ?? 'client/img/favicon.ico',
|
||||
'ajaxTimeout' => $this->getConfig()->get('ajaxTimeout') ?? 60000,
|
||||
];
|
||||
|
||||
$html = file_get_contents($htmlFilePath);
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
|
||||
namespace Espo\Core\Utils;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
class Config
|
||||
{
|
||||
private $defaultConfigPath = 'application/Espo/Core/defaults/config.php';
|
||||
@@ -181,7 +183,16 @@ class Config
|
||||
|
||||
$removeData = empty($this->removeData) ? null : $this->removeData;
|
||||
|
||||
$data = include($this->configPath);
|
||||
$configPath = $this->getConfigPath();
|
||||
|
||||
if (!file_exists($configPath)) {
|
||||
throw new Error('Config file ['. $configPath .'] is not found.');
|
||||
}
|
||||
|
||||
$data = include($configPath);
|
||||
if (!is_array($data)) {
|
||||
$data = include($configPath);
|
||||
}
|
||||
|
||||
if (is_array($values)) {
|
||||
foreach ($values as $key => $value) {
|
||||
@@ -195,7 +206,21 @@ class Config
|
||||
}
|
||||
}
|
||||
|
||||
$result = $this->getFileManager()->putPhpContents($this->configPath, $data, true);
|
||||
if (!is_array($data)) {
|
||||
$GLOBALS['log']->error('Invalid config data ['. var_export($data, true) .'] while saving to ['. $configPath .'].');
|
||||
throw new Error('Invalid config data while saving.');
|
||||
}
|
||||
|
||||
$data['microtime'] = $microtime = microtime(true);
|
||||
|
||||
$result = $this->getFileManager()->putPhpContents($configPath, $data, true, true);
|
||||
|
||||
if ($result) {
|
||||
$reloadedData = include($configPath);
|
||||
if (!is_array($reloadedData) || $microtime !== ($reloadedData['microtime'] ?? null)) {
|
||||
$result = $this->getFileManager()->putPhpContents($configPath, $data, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
if ($result) {
|
||||
$this->changedData = array();
|
||||
|
||||
@@ -97,6 +97,6 @@ class Crypt
|
||||
|
||||
public function generateKey()
|
||||
{
|
||||
return \Espo\Core\Utils\Util::generateKey();
|
||||
return \Espo\Core\Utils\Util::generateSecretKey();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\FieldTypes;
|
||||
|
||||
class JsonArrayType extends \Doctrine\DBAL\Types\TextType
|
||||
class JsonArrayType extends TextType
|
||||
{
|
||||
const JSON_ARRAY = 'jsonArray';
|
||||
|
||||
@@ -37,10 +37,4 @@ class JsonArrayType extends \Doctrine\DBAL\Types\TextType
|
||||
{
|
||||
return self::JSON_ARRAY;
|
||||
}
|
||||
|
||||
public static function getDbTypeName()
|
||||
{
|
||||
return 'TEXT';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\FieldTypes;
|
||||
|
||||
class JsonObjectType extends \Doctrine\DBAL\Types\TextType
|
||||
class JsonObjectType extends TextType
|
||||
{
|
||||
const JSON_OBJECT = 'jsonObject';
|
||||
|
||||
@@ -37,10 +37,4 @@ class JsonObjectType extends \Doctrine\DBAL\Types\TextType
|
||||
{
|
||||
return self::JSON_OBJECT;
|
||||
}
|
||||
|
||||
public static function getDbTypeName()
|
||||
{
|
||||
return 'TEXT';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\FieldTypes;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
|
||||
class LongtextType extends TextType
|
||||
{
|
||||
const LONGTEXT = 'longtext';
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return self::LONGTEXT;
|
||||
}
|
||||
|
||||
public static function getDbTypeName()
|
||||
{
|
||||
return 'longtext';
|
||||
}
|
||||
|
||||
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
|
||||
{
|
||||
return 'LONGTEXT';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\FieldTypes;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
|
||||
class TextType extends \Doctrine\DBAL\Types\TextType
|
||||
{
|
||||
public static function getDbTypeName()
|
||||
{
|
||||
return 'mediumtext';
|
||||
}
|
||||
|
||||
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
|
||||
{
|
||||
return 'MEDIUMTEXT';
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,7 @@ class Comparator extends \Doctrine\DBAL\Schema\Comparator
|
||||
public function diffColumn(Column $column1, Column $column2)
|
||||
{
|
||||
$changedProperties = array();
|
||||
|
||||
if ( $column1->getType() != $column2->getType() ) {
|
||||
|
||||
//espo: fix problem with executing query for custom types
|
||||
@@ -82,15 +83,6 @@ class Comparator extends \Doctrine\DBAL\Schema\Comparator
|
||||
}
|
||||
}
|
||||
|
||||
if ($column1->getType() instanceof \Doctrine\DBAL\Types\TextType) {
|
||||
$length1 = $column1->getLength() ?: 16777215/* mediumtext length*/;
|
||||
$length2 = $column2->getLength() ?: 16777215;
|
||||
|
||||
if ($length2 > $length1) {
|
||||
$changedProperties[] = 'length';
|
||||
}
|
||||
}
|
||||
|
||||
if ($column1->getType() instanceof \Doctrine\DBAL\Types\DecimalType) {
|
||||
if (($column1->getPrecision()?:10) != ($column2->getPrecision()?:10)) {
|
||||
$changedProperties[] = 'precision';
|
||||
@@ -124,7 +116,7 @@ class Comparator extends \Doctrine\DBAL\Schema\Comparator
|
||||
|
||||
$changedProperties = array_merge($changedProperties, $diffKeys);
|
||||
|
||||
/** Espo: do not change a field length while changing other parameters */
|
||||
/** Espo: do not change a field length downwards */
|
||||
if (!empty($changedProperties) && !in_array('length', $changedProperties) && $column1->getType() instanceof \Doctrine\DBAL\Types\StringType) {
|
||||
$length1 = $column1->getLength() ?: 255;
|
||||
$length2 = $column2->getLength() ?: 255;
|
||||
@@ -134,6 +126,15 @@ class Comparator extends \Doctrine\DBAL\Schema\Comparator
|
||||
$column2->setLength($length1);
|
||||
}
|
||||
}
|
||||
|
||||
if (in_array('type', $changedProperties) && $column1->getType() instanceof \Doctrine\DBAL\Types\TextType && isset($column1DbTypeName) && isset($column2DbTypeName)) {
|
||||
$constName1 = '\Espo\Core\Utils\Database\DBAL\Platforms\MySqlPlatform::LENGTH_LIMIT_' . strtoupper($column1DbTypeName);
|
||||
$constName2 = '\Espo\Core\Utils\Database\DBAL\Platforms\MySqlPlatform::LENGTH_LIMIT_' . strtoupper($column2DbTypeName);
|
||||
|
||||
if (defined($constName1) && defined($constName2) && constant($constName1) >= constant($constName2)) {
|
||||
$changedProperties = array_diff($changedProperties, ['type']);
|
||||
}
|
||||
}
|
||||
/** Espo: end */
|
||||
|
||||
return $changedProperties;
|
||||
|
||||
@@ -209,7 +209,7 @@ class MySqlSchemaManager extends \Doctrine\DBAL\Schema\MySqlSchemaManager
|
||||
case 'text':
|
||||
case 'mediumtext':
|
||||
case 'longtext':
|
||||
$length = $this->_platform->getClobTypeLength($dbType);
|
||||
$length = null;
|
||||
break;
|
||||
/* Espo: end */
|
||||
}
|
||||
|
||||
@@ -145,14 +145,21 @@ class Helper
|
||||
|
||||
switch ($tableEngine) {
|
||||
case 'InnoDB':
|
||||
$version = $this->getFullDatabaseVersion();
|
||||
$databaseType = $this->getDatabaseType();
|
||||
$version = $this->getDatabaseVersion();
|
||||
|
||||
if (version_compare($version, '10.0.0') >= 0) {
|
||||
return 767; //InnoDB, MariaDB
|
||||
}
|
||||
switch ($databaseType) {
|
||||
case 'MariaDB':
|
||||
if (version_compare($version, '10.2.2') >= 0) {
|
||||
return 3072; //InnoDB, MariaDB 10.2.2+
|
||||
}
|
||||
break;
|
||||
|
||||
if (version_compare($version, '5.7.0') >= 0) {
|
||||
return 3072; //InnoDB, MySQL 5.7+
|
||||
case 'MySQL':
|
||||
if (version_compare($version, '5.7.0') >= 0) {
|
||||
return 3072; //InnoDB, MySQL 5.7+
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return 767; //InnoDB
|
||||
|
||||
@@ -224,7 +224,7 @@ class Base
|
||||
{
|
||||
$foreignField = $this->getMetadata()->get(['entityDefs', $entityType, 'fields', $name]);
|
||||
|
||||
if ($foreignField['type'] == 'personName') {
|
||||
if (isset($foreignField['type']) && $foreignField['type'] == 'personName') {
|
||||
$personNameFormat = $this->config->get('personNameFormat');
|
||||
|
||||
switch ($personNameFormat) {
|
||||
|
||||
@@ -81,13 +81,7 @@ class Converter
|
||||
'index' => 'index',
|
||||
/*'conditions' => 'conditions',
|
||||
'additionalColumns' => 'additionalColumns', */
|
||||
'default' => array(
|
||||
'condition' => '^javascript:',
|
||||
'conditionEquals' => false,
|
||||
'value' => array(
|
||||
'default' => '{0}',
|
||||
),
|
||||
),
|
||||
'default' => 'default',
|
||||
'select' => 'select',
|
||||
'orderBy' => 'orderBy',
|
||||
'where' => 'where',
|
||||
@@ -178,6 +172,8 @@ class Converter
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($entityMetadata['skipRebuild'] ?? false) $ormMetadata[$entityName]['skipRebuild'] = true;
|
||||
|
||||
$ormMetadata = Util::merge($ormMetadata, $this->convertEntity($entityName, $entityMetadata));
|
||||
}
|
||||
|
||||
@@ -480,26 +476,24 @@ class Converter
|
||||
|
||||
protected function getInitValues(array $fieldParams)
|
||||
{
|
||||
$values = array();
|
||||
$values = [];
|
||||
|
||||
foreach($this->fieldAccordances as $espoType => $ormType) {
|
||||
|
||||
if (isset($fieldParams[$espoType])) {
|
||||
if (!array_key_exists($espoType, $fieldParams)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_array($ormType)) {
|
||||
|
||||
$conditionRes = false;
|
||||
if (!is_array($fieldParams[$espoType])) {
|
||||
$conditionRes = preg_match('/'.$ormType['condition'].'/i', $fieldParams[$espoType]);
|
||||
switch ($espoType) {
|
||||
case 'default':
|
||||
if (is_array($fieldParams[$espoType]) || !preg_match('/^javascript:/i', $fieldParams[$espoType])) {
|
||||
$values[$ormType] = $fieldParams[$espoType];
|
||||
}
|
||||
break;
|
||||
|
||||
if (!$conditionRes || ($conditionRes && $conditionRes === $ormType['conditionEquals']) ) {
|
||||
$value = is_array($fieldParams[$espoType]) ? json_encode($fieldParams[$espoType]) : $fieldParams[$espoType];
|
||||
$values = Util::merge( $values, Util::replaceInArray('{0}', $value, $ormType['value']) );
|
||||
}
|
||||
} else {
|
||||
default:
|
||||
$values[$ormType] = $fieldParams[$espoType];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ class Currency extends Base
|
||||
]
|
||||
];
|
||||
|
||||
$foreignAlias = "{$alias}{$entityType}Foreign";
|
||||
$foreignAlias = "{$alias}{$entityType}{alias}Foreign";
|
||||
|
||||
$params = $this->getFieldParams($fieldName);
|
||||
if (!empty($params['notStorable'])) {
|
||||
@@ -99,6 +99,7 @@ class Currency extends Base
|
||||
'orderBy' => [
|
||||
'sql' => $converedFieldName . " {direction}",
|
||||
'leftJoins' => $leftJoins,
|
||||
'additionalSelect' => ["{$alias}.rate"],
|
||||
],
|
||||
'attributeRole' => 'valueConverted',
|
||||
'fieldType' => 'currency',
|
||||
@@ -107,6 +108,7 @@ class Currency extends Base
|
||||
$defs[$entityType]['fields'][$fieldName]['orderBy'] = [
|
||||
'sql' => $part . " * {$alias}.rate {direction}",
|
||||
'leftJoins' => $leftJoins,
|
||||
'additionalSelect' => ["{$alias}.rate"],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -33,8 +33,8 @@ class Email extends Base
|
||||
{
|
||||
protected function load($fieldName, $entityType)
|
||||
{
|
||||
$foreignJoinAlias = "{$fieldName}{$entityType}Foreign";
|
||||
$foreignJoinMiddleAlias = "{$fieldName}{$entityType}ForeignMiddle";
|
||||
$foreignJoinAlias = "{$fieldName}{$entityType}{alias}Foreign";
|
||||
$foreignJoinMiddleAlias = "{$fieldName}{$entityType}{alias}ForeignMiddle";
|
||||
|
||||
return [
|
||||
$entityType => [
|
||||
@@ -53,6 +53,7 @@ class Email extends Base
|
||||
[
|
||||
"{$foreignJoinMiddleAlias}.entityId:" => "{alias}.id",
|
||||
"{$foreignJoinMiddleAlias}.primary" => 1,
|
||||
"{$foreignJoinMiddleAlias}.deleted" => 0,
|
||||
]
|
||||
],
|
||||
[
|
||||
@@ -60,6 +61,7 @@ class Email extends Base
|
||||
$foreignJoinAlias,
|
||||
[
|
||||
"{$foreignJoinAlias}.id:" => "{$foreignJoinMiddleAlias}.emailAddressId",
|
||||
"{$foreignJoinAlias}.deleted" => 0,
|
||||
]
|
||||
]
|
||||
],
|
||||
@@ -108,6 +110,7 @@ class Email extends Base
|
||||
'orderBy' => [
|
||||
'sql' => 'emailAddresses.lower {direction}',
|
||||
'leftJoins' => [['emailAddresses', 'emailAddresses', ['primary' => 1]]],
|
||||
'additionalSelect' => ['emailAddresses.lower'],
|
||||
],
|
||||
],
|
||||
$fieldName .'Data' => [
|
||||
@@ -119,6 +122,28 @@ class Email extends Base
|
||||
'type' => 'bool',
|
||||
'notStorable' => true,
|
||||
'select' => 'emailAddresses.opt_out',
|
||||
'selectForeign' => [
|
||||
'sql' => "{$foreignJoinAlias}.opt_out",
|
||||
'leftJoins' => [
|
||||
[
|
||||
'EntityEmailAddress',
|
||||
$foreignJoinMiddleAlias,
|
||||
[
|
||||
"{$foreignJoinMiddleAlias}.entityId:" => "{alias}.id",
|
||||
"{$foreignJoinMiddleAlias}.primary" => 1,
|
||||
"{$foreignJoinMiddleAlias}.deleted" => 0,
|
||||
]
|
||||
],
|
||||
[
|
||||
'EmailAddress',
|
||||
$foreignJoinAlias,
|
||||
[
|
||||
"{$foreignJoinAlias}.id:" => "{$foreignJoinMiddleAlias}.emailAddressId",
|
||||
"{$foreignJoinAlias}.deleted" => 0,
|
||||
]
|
||||
]
|
||||
],
|
||||
],
|
||||
'where' => [
|
||||
'= TRUE' => [
|
||||
'sql' => 'emailAddresses.opt_out = true AND emailAddresses.opt_out IS NOT NULL',
|
||||
@@ -129,7 +154,11 @@ class Email extends Base
|
||||
'leftJoins' => [['emailAddresses', 'emailAddresses', ['primary' => 1]]],
|
||||
]
|
||||
],
|
||||
'orderBy' => 'emailAddresses.opt_out {direction}'
|
||||
'orderBy' => [
|
||||
'sql' => 'emailAddresses.opt_out {direction}',
|
||||
'leftJoins' => [['emailAddresses', 'emailAddresses', ['primary' => 1]]],
|
||||
'additionalSelect' => ['emailAddresses.opt_out'],
|
||||
],
|
||||
]
|
||||
],
|
||||
'relations' => [
|
||||
|
||||
@@ -33,8 +33,8 @@ class Phone extends Base
|
||||
{
|
||||
protected function load($fieldName, $entityType)
|
||||
{
|
||||
$foreignJoinAlias = "{$fieldName}{$entityType}Foreign";
|
||||
$foreignJoinMiddleAlias = "{$fieldName}{$entityType}ForeignMiddle";
|
||||
$foreignJoinAlias = "{$fieldName}{$entityType}{alias}Foreign";
|
||||
$foreignJoinMiddleAlias = "{$fieldName}{$entityType}{alias}ForeignMiddle";
|
||||
|
||||
return [
|
||||
$entityType => [
|
||||
@@ -53,6 +53,7 @@ class Phone extends Base
|
||||
[
|
||||
"{$foreignJoinMiddleAlias}.entityId:" => "{alias}.id",
|
||||
"{$foreignJoinMiddleAlias}.primary" => 1,
|
||||
"{$foreignJoinMiddleAlias}.deleted" => 0,
|
||||
]
|
||||
],
|
||||
[
|
||||
@@ -60,6 +61,7 @@ class Phone extends Base
|
||||
$foreignJoinAlias,
|
||||
[
|
||||
"{$foreignJoinAlias}.id:" => "{$foreignJoinMiddleAlias}.phoneNumberId",
|
||||
"{$foreignJoinAlias}.deleted" => 0,
|
||||
]
|
||||
]
|
||||
],
|
||||
@@ -109,6 +111,7 @@ class Phone extends Base
|
||||
'orderBy' => [
|
||||
'sql' => 'phoneNumbers.name {direction}',
|
||||
'leftJoins' => [['phoneNumbers', 'phoneNumbers', ['primary' => 1]]],
|
||||
'additionalSelect' => ['phoneNumbers.name'],
|
||||
],
|
||||
),
|
||||
$fieldName .'Data' => array(
|
||||
@@ -120,6 +123,28 @@ class Phone extends Base
|
||||
'type' => 'bool',
|
||||
'notStorable' => true,
|
||||
'select' => 'phoneNumbers.opt_out',
|
||||
'selectForeign' => [
|
||||
'sql' => "{$foreignJoinAlias}.opt_out",
|
||||
'leftJoins' => [
|
||||
[
|
||||
'EntityPhoneNumber',
|
||||
$foreignJoinMiddleAlias,
|
||||
[
|
||||
"{$foreignJoinMiddleAlias}.entityId:" => "{alias}.id",
|
||||
"{$foreignJoinMiddleAlias}.primary" => 1,
|
||||
"{$foreignJoinMiddleAlias}.deleted" => 0,
|
||||
]
|
||||
],
|
||||
[
|
||||
'PhoneNumber',
|
||||
$foreignJoinAlias,
|
||||
[
|
||||
"{$foreignJoinAlias}.id:" => "{$foreignJoinMiddleAlias}.phoneNumberId",
|
||||
"{$foreignJoinAlias}.deleted" => 0,
|
||||
]
|
||||
]
|
||||
],
|
||||
],
|
||||
'where' => [
|
||||
'= TRUE' => [
|
||||
'sql' => 'phoneNumbers.opt_out = true AND phoneNumbers.opt_out IS NOT NULL',
|
||||
@@ -130,7 +155,11 @@ class Phone extends Base
|
||||
'leftJoins' => [['phoneNumbers', 'phoneNumbers', ['primary' => 1]]],
|
||||
]
|
||||
],
|
||||
'orderBy' => 'phoneNumbers.opt_out {direction}'
|
||||
'orderBy' => [
|
||||
'sql' => 'phoneNumbers.opt_out {direction}',
|
||||
'leftJoins' => [['phoneNumbers', 'phoneNumbers', ['primary' => 1]]],
|
||||
'additionalSelect' => ['phoneNumbers.opt_out'],
|
||||
],
|
||||
],
|
||||
$fieldName . 'Numeric' => [
|
||||
'type' => 'varchar',
|
||||
|
||||
@@ -135,12 +135,14 @@ class RelationManager
|
||||
}
|
||||
|
||||
if (isset($className) && $className !== false) {
|
||||
$foreignLinkName = (is_array($foreignLink) && array_key_exists('name', $foreignLink)) ? $foreignLink['name'] : null;
|
||||
|
||||
$helperClass = new $className($this->metadata, $ormMetadata, $entityDefs, $this->config);
|
||||
return $helperClass->process($linkName, $entityName, $foreignLink['name'], $foreignEntityName);
|
||||
return $helperClass->process($linkName, $entityName, $foreignLinkName, $foreignEntityName);
|
||||
}
|
||||
//END: relationDefs defined in separate file
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,11 +181,12 @@ class Converter
|
||||
$schema = $this->getSchema(true);
|
||||
|
||||
$indexList = SchemaUtils::getIndexList($ormMeta);
|
||||
$fieldListExceededIndexMaxLength = SchemaUtils::getFieldListExceededIndexMaxLength($ormMeta, $this->getMaxIndexLength());
|
||||
|
||||
$tables = array();
|
||||
foreach ($ormMeta as $entityName => $entityParams) {
|
||||
|
||||
if ($entityParams['skipRebuild'] ?? false) continue;
|
||||
|
||||
$tableName = Util::toUnderScore($entityName);
|
||||
|
||||
if ($schema->hasTable($tableName)) {
|
||||
@@ -225,10 +226,6 @@ class Converter
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($fieldListExceededIndexMaxLength[$entityName]) && in_array($fieldName, $fieldListExceededIndexMaxLength[$entityName])) {
|
||||
$fieldParams['utf8mb3'] = true;
|
||||
}
|
||||
|
||||
$columnName = Util::toUnderScore($fieldName);
|
||||
if (!$tables[$entityName]->hasColumn($columnName)) {
|
||||
$tables[$entityName]->addColumn($columnName, $fieldType, $this->getDbFieldParams($fieldParams));
|
||||
@@ -315,7 +312,7 @@ class Converter
|
||||
$table->addIndex(array($columnName), SchemaUtils::generateIndexName($columnName));
|
||||
$uniqueIndex[] = $columnName;
|
||||
}
|
||||
}
|
||||
}
|
||||
//END: add midKeys to a schema
|
||||
|
||||
//add additionalColumns
|
||||
|
||||
@@ -238,7 +238,7 @@ class Utils
|
||||
'varchar' => 255,
|
||||
);
|
||||
|
||||
$type = static::getFieldType($ormFieldDefs);
|
||||
$type = static::getDbFieldType($ormFieldDefs);
|
||||
|
||||
$length = isset($defaultLength[$type]) ? $defaultLength[$type] : $length;
|
||||
$length = isset($ormFieldDefs['len']) ? $ormFieldDefs['len'] : $length;
|
||||
@@ -252,8 +252,13 @@ class Utils
|
||||
return $length;
|
||||
}
|
||||
|
||||
protected static function getFieldType(array $ormFieldDefs)
|
||||
protected static function getDbFieldType(array $ormFieldDefs)
|
||||
{
|
||||
return isset($ormFieldDefs['dbType']) ? $ormFieldDefs['dbType'] : $ormFieldDefs['type'];
|
||||
}
|
||||
|
||||
protected static function getFieldType(array $ormFieldDefs)
|
||||
{
|
||||
return isset($ormFieldDefs['type']) ? $ormFieldDefs['type'] : static::getDbFieldType($ormFieldDefs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,8 +46,7 @@ return [
|
||||
'type' => 'id',
|
||||
],
|
||||
'data' => [
|
||||
'type' => 'text',
|
||||
'len' => 16777216,
|
||||
'type' => 'text'
|
||||
]
|
||||
]
|
||||
],
|
||||
|
||||
@@ -176,7 +176,7 @@ class DateTime
|
||||
$dateTime = new \DateTime();
|
||||
$dateTime->setTimezone($tz);
|
||||
|
||||
$format = $format ?? $this->getDateTimeFormat();
|
||||
$format = $format ?? $this->getDateFormat();
|
||||
|
||||
$carbon = Carbon::instance($dateTime);
|
||||
$carbon->locale($this->language);
|
||||
|
||||
@@ -341,6 +341,7 @@ class EntityManager
|
||||
$filePath = $templatePath . "/Metadata/{$type}/entityDefs.json";
|
||||
$entityDefsDataContents = $this->getFileManager()->getContents($filePath);
|
||||
$entityDefsDataContents = str_replace('{entityType}', $name, $entityDefsDataContents);
|
||||
$entityDefsDataContents = str_replace('{entityTypeLowerFirst}', lcfirst($name), $entityDefsDataContents);
|
||||
$entityDefsDataContents = str_replace('{tableName}', $this->getEntityManager()->getQuery()->toDb($name), $entityDefsDataContents);
|
||||
foreach ($replaceData as $key => $value) {
|
||||
$entityDefsDataContents = str_replace('{'.$key.'}', $value, $entityDefsDataContents);
|
||||
@@ -937,7 +938,7 @@ class EntityManager
|
||||
'fields' => [
|
||||
$link => [
|
||||
'type' => 'linkParent',
|
||||
'entityList' => $params['parentEntityTypeList'] ?? [],
|
||||
'entityList' => $params['parentEntityTypeList'] ?? null,
|
||||
],
|
||||
],
|
||||
'links' => [
|
||||
@@ -980,7 +981,7 @@ class EntityManager
|
||||
|
||||
if ($linkType === 'childrenToParent') {
|
||||
$foreignLinkEntityTypeList = $params['foreignLinkEntityTypeList'] ?? null;
|
||||
if ($foreignLinkEntityTypeList && is_array($foreignLinkEntityTypeList)) {
|
||||
if ($linkForeign && is_array($foreignLinkEntityTypeList)) {
|
||||
$this->updateParentForeignLinks($entity, $link, $linkForeign, $foreignLinkEntityTypeList);
|
||||
}
|
||||
}
|
||||
@@ -1093,7 +1094,8 @@ class EntityManager
|
||||
|
||||
if ($linkType === 'belongsToParent') {
|
||||
$parentEntityTypeList = $params['parentEntityTypeList'] ?? null;
|
||||
if ($parentEntityTypeList && is_array($parentEntityTypeList)) {
|
||||
|
||||
if (is_array($parentEntityTypeList)) {
|
||||
$data = [
|
||||
'fields' => [
|
||||
$link => [
|
||||
@@ -1106,7 +1108,7 @@ class EntityManager
|
||||
}
|
||||
|
||||
$foreignLinkEntityTypeList = $params['foreignLinkEntityTypeList'] ?? null;
|
||||
if ($foreignLinkEntityTypeList && is_array($foreignLinkEntityTypeList)) {
|
||||
if ($linkForeign && is_array($foreignLinkEntityTypeList)) {
|
||||
$this->updateParentForeignLinks($entity, $link, $linkForeign, $foreignLinkEntityTypeList);
|
||||
}
|
||||
}
|
||||
@@ -1185,7 +1187,9 @@ class EntityManager
|
||||
'links.' . $link,
|
||||
]);
|
||||
$this->getMetadata()->save();
|
||||
$this->updateParentForeignLinks($entity, $link, $linkForeign, []);
|
||||
if ($linkForeign) {
|
||||
$this->updateParentForeignLinks($entity, $link, $linkForeign, []);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,8 @@ class Manager
|
||||
|
||||
private $permissionDeniedList = array();
|
||||
|
||||
protected $tmpDir = 'data/tmp';
|
||||
|
||||
public function __construct(\Espo\Core\Utils\Config $config = null)
|
||||
{
|
||||
$params = null;
|
||||
@@ -195,7 +197,7 @@ class Manager
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function putContents($path, $data, $flags = 0)
|
||||
public function putContents($path, $data, $flags = 0, bool $useRenaming = false)
|
||||
{
|
||||
$fullPath = $this->concatPaths($path); //todo remove after changing the params
|
||||
|
||||
@@ -203,12 +205,49 @@ class Manager
|
||||
throw new Error('Permission denied for '. $fullPath);
|
||||
}
|
||||
|
||||
$res = (file_put_contents($fullPath, $data, $flags) !== FALSE);
|
||||
if ($res && function_exists('opcache_invalidate')) {
|
||||
$result = false;
|
||||
|
||||
if ($useRenaming) {
|
||||
$result = $this->putContentsUseRenaming($fullPath, $data);
|
||||
}
|
||||
|
||||
if (!$result) {
|
||||
$result = (file_put_contents($fullPath, $data, $flags) !== FALSE);
|
||||
}
|
||||
|
||||
if ($result && function_exists('opcache_invalidate')) {
|
||||
@opcache_invalidate($fullPath);
|
||||
}
|
||||
|
||||
return $res;
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function putContentsUseRenaming($path, $data)
|
||||
{
|
||||
$tmpDir = $this->tmpDir;
|
||||
if (!$this->isDir($tmpDir)) $this->mkdir($tmpDir);
|
||||
if (!$this->isDir($tmpDir)) return false;
|
||||
|
||||
$tmpPath = tempnam($tmpDir, 'tmp');
|
||||
$tmpPath = $this->getRelativePath($tmpPath);
|
||||
|
||||
if (!$tmpPath) return false;
|
||||
if (!$this->isFile($tmpPath)) return false;
|
||||
if (!$this->isWritable($tmpPath)) return false;
|
||||
|
||||
$h = fopen($tmpPath, 'w');
|
||||
fwrite($h, $data);
|
||||
fclose($h);
|
||||
|
||||
$this->getPermissionUtils()->setDefaultPermissions($tmpPath);
|
||||
|
||||
if (!$this->isReadable($tmpPath)) return false;
|
||||
|
||||
$result = rename($tmpPath, $path);
|
||||
|
||||
if ($this->isFile($tmpPath)) $this->removeFile($tmpPath);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -219,9 +258,9 @@ class Manager
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function putPhpContents($path, $data, $withObjects = false)
|
||||
public function putPhpContents($path, $data, $withObjects = false, bool $useRenaming = false)
|
||||
{
|
||||
return $this->putContents($path, $this->wrapForDataExport($data, $withObjects), LOCK_EX);
|
||||
return $this->putContents($path, $this->wrapForDataExport($data, $withObjects), LOCK_EX, $useRenaming);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -356,7 +395,7 @@ class Manager
|
||||
protected function concatPaths($paths)
|
||||
{
|
||||
if (is_string($paths)) {
|
||||
return $paths;
|
||||
return Utils\Util::fixPath($paths);
|
||||
}
|
||||
|
||||
$fullPath = '';
|
||||
@@ -940,4 +979,24 @@ class Manager
|
||||
|
||||
return $fullPath;
|
||||
}
|
||||
|
||||
public function getRelativePath($path, $basePath = null, $dirSeparator = null)
|
||||
{
|
||||
if (!$basePath) {
|
||||
$basePath = getcwd();
|
||||
}
|
||||
|
||||
$path = Utils\Util::fixPath($path);
|
||||
$basePath = Utils\Util::fixPath($basePath);
|
||||
|
||||
if (!$dirSeparator) {
|
||||
$dirSeparator = Utils\Util::getSeparator();
|
||||
}
|
||||
|
||||
if (substr($basePath, -1) != $dirSeparator) {
|
||||
$basePath .= $dirSeparator;
|
||||
}
|
||||
|
||||
return preg_replace('/^'. preg_quote($basePath, $dirSeparator) . '/', '', $path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,9 @@ class Permission
|
||||
'application/Espo/Modules' => [
|
||||
'recursive' => false,
|
||||
],
|
||||
'client/custom' => [
|
||||
'recursive' => true,
|
||||
],
|
||||
'client/modules' => [
|
||||
'recursive' => false,
|
||||
],
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user