Compare commits

...

958 Commits

Author SHA1 Message Date
Yuri Kuznetsov 2a64537da1 8.4.2 2024-10-10 18:09:49 +03:00
Yuri Kuznetsov 6d3c765ef6 kanban group change save handler 2024-10-10 17:58:41 +03:00
Yuri Kuznetsov 8f96c8c3e1 build cleanup modules 2024-10-07 19:26:29 +03:00
Yuri Kuznetsov 43749cbabb task email link no foreign name 2024-10-04 11:43:45 +03:00
Yuri Kuznetsov 803185b747 fix datepicker error 2024-09-30 09:56:06 +03:00
Yuri Kuznetsov 4f5886fbd7 remove from modal fixes 2024-09-28 16:45:45 +03:00
Yuri Kuznetsov c28ba84f03 email: fix collection trash/archive sync 2024-09-27 19:20:38 +03:00
Yuri Kuznetsov 7f08df77f6 email afterSave not called fix 2024-09-26 10:01:43 +03:00
Yuri Kuznetsov 48116f8dcf 8.4.1 2024-09-17 11:09:08 +03:00
Yuri Kuznetsov 0784fd589a update dompurify 2024-09-17 09:59:54 +03:00
Yuri Kuznetsov 8b8d3ee158 auth token lifetime int conversion fix 2024-09-14 10:47:13 +03:00
Yuri Kuznetsov 628e7e8131 hmac api key check with hex 2024-09-12 13:00:56 +03:00
Eymen Elkum 408a161595 fix row action return undefined (#3140) 2024-09-11 15:23:53 +03:00
Yuri Kuznetsov b46c6d76a5 case portal user follow acl check 2024-09-09 13:30:51 +03:00
Yuri Kuznetsov d78f7d499a restore deleteId for UI upgrade only 2024-09-05 13:45:18 +03:00
Yuri Kuznetsov 5662fed5eb color changes 2024-09-05 09:56:08 +03:00
Yuri Kuznetsov 3f4cdf629f mass email hour limit required 2024-09-04 12:30:30 +03:00
Yuri Kuznetsov bedab9c780 fix diff 2024-09-03 18:17:01 +03:00
Yuri Kuznetsov 291bcf1684 note replace app link fix 2024-09-03 11:37:41 +03:00
Yuri Kuznetsov bd9e1bf353 fix tag 2024-09-03 09:08:56 +03:00
Yuri Kuznetsov 7d507460be fix header markup 2024-09-02 18:09:29 +03:00
Yuri Kuznetsov 99b4205181 lang 2024-09-02 09:08:40 +03:00
Yuri Kuznetsov 5ae56454fc color change 2024-09-01 10:50:37 +03:00
Yuri Kuznetsov 1e2369bba1 jsdoc 2024-09-01 10:35:18 +03:00
Yuri Kuznetsov c42bfa3aab kanban reuse subCollections after rebuild 2024-09-01 10:30:21 +03:00
Yuri Kuznetsov 2b01cefb66 cleanup 2024-08-31 16:50:57 +03:00
Yuri Kuznetsov 0b72848e33 hide show detail modes 2024-08-31 16:41:24 +03:00
Yuri Kuznetsov ecc7f80c80 schema 2024-08-31 14:32:28 +03:00
Yurii be5fff011b change email folder order 2024-08-30 17:16:52 +03:00
Yuri Kuznetsov d7a29fdc71 int field: support big int in frontend 2024-08-30 15:40:35 +03:00
Yurii e1c2203750 int range validation 2024-08-30 10:42:51 +03:00
Yuri Kuznetsov 66cde8c86c email read notification websocket 2024-08-30 08:39:25 +03:00
Yuri Kuznetsov 10fb006fd8 comment 2024-08-30 08:39:09 +03:00
Osama Bashir b94c5ff869 Get only active users in email address lookup (#3132)
* Get only active users in email address lookup

* fix isActive field name
2024-08-29 09:37:35 +03:00
Yuri Kuznetsov 39898bbbe8 preferences page title 2024-08-29 09:35:09 +03:00
Yuri Kuznetsov 62a6c2761f fix dashlet sort translation 2024-08-28 11:09:02 +03:00
Yuri Kuznetsov 6fe6a52f47 collapsed modal overlap fix 2 2024-08-28 10:48:46 +03:00
Yuri Kuznetsov c5d7fa5b0b style fix 2024-08-28 10:37:54 +03:00
Yuri Kuznetsov ceaac6c3dc collapsed modal z-index 2024-08-28 10:37:05 +03:00
Yuri Kuznetsov 490843e371 fix email body null 2024-08-28 10:27:14 +03:00
Yuri Kuznetsov b290fbfeb2 fix email plain 2024-08-27 17:33:39 +03:00
Yuri Kuznetsov ca1e649b11 emailReplyForceHtml true by default 2024-08-27 16:57:43 +03:00
Yuri Kuznetsov 5e74cd8d06 font change 2024-08-27 16:15:15 +03:00
Yuri Kuznetsov 63508423cc email archive row action 2024-08-27 11:03:48 +03:00
Yurii Kuznietsov a5961811b4 Update feature_request.md 2024-08-27 00:05:30 +03:00
Yuri Kuznetsov fdb1595cd5 sticky bar fix 2024-08-26 23:55:03 +03:00
Yuri Kuznetsov 4bfedf8db3 fix test 2024-08-26 15:02:24 +03:00
Yuri Kuznetsov 325429eb52 comment out email index 2024-08-26 13:34:31 +03:00
Yuri Kuznetsov e558a3139c fix move to folder 2024-08-26 10:33:13 +03:00
Yuri Kuznetsov e424c26963 fix select/create related from modal 2024-08-26 10:31:06 +03:00
dependabot[bot] b2cc1f97c4 Bump axios from 1.7.2 to 1.7.5 (#3128)
Bumps [axios](https://github.com/axios/axios) from 1.7.2 to 1.7.5.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.7.2...v1.7.5)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-26 10:11:12 +03:00
dependabot[bot] d48ce0fc58 Bump micromatch from 4.0.5 to 4.0.8 (#3127)
Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.5 to 4.0.8.
- [Release notes](https://github.com/micromatch/micromatch/releases)
- [Changelog](https://github.com/micromatch/micromatch/blob/4.0.8/CHANGELOG.md)
- [Commits](https://github.com/micromatch/micromatch/compare/4.0.5...4.0.8)

---
updated-dependencies:
- dependency-name: micromatch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-26 10:11:02 +03:00
Yuri Kuznetsov b61462a1cd version 2024-08-26 09:15:55 +03:00
Andrew Fontana 1bb82151da Fix Typo (#3125)
Fix typo enabled to enable.
2024-08-25 11:05:14 +03:00
Anthony Andriano 9852e25c6e replaced literal with enum (#3124) 2024-08-25 09:26:52 +03:00
Yuri Kuznetsov 90fe358753 ics attendees 2024-08-23 19:18:16 +03:00
Yuri Kuznetsov bd65e62fce ref, test 2024-08-23 18:37:21 +03:00
Yuri Kuznetsov fff9d7a781 group email account is system read only field 2024-08-23 11:47:38 +03:00
Yuri Kuznetsov 8441fa05d2 calendar non working all-day timzone correction 2024-08-23 11:20:10 +03:00
Yuri Kuznetsov 9e86649b1c ref cs 2024-08-23 10:33:36 +03:00
Yuri Kuznetsov 3c50a0e058 backdrop click 2024-08-22 17:28:23 +03:00
Yuri Kuznetsov e571a63a30 change user position action 2024-08-22 17:25:38 +03:00
Yuri Kuznetsov 14d405542b ref 2024-08-21 09:24:29 +03:00
Yuri Kuznetsov 429d03504c ref 2024-08-20 22:17:13 +03:00
Yuri Kuznetsov fc24840a7b ref 2024-08-20 15:43:54 +03:00
Yuri Kuznetsov 603ca985df fix mass select 2024-08-20 12:05:44 +03:00
Yuri Kuznetsov a12940e36c ref 2024-08-20 11:09:16 +03:00
Yuri Kuznetsov dcab0ad7d0 ref 2024-08-20 09:42:42 +03:00
Yuri Kuznetsov 89124b354b idea code style change 2024-08-20 09:42:29 +03:00
Yuri Kuznetsov 03c275f495 pdf template: x-if attribute 2024-08-19 14:21:43 +03:00
Yuri Kuznetsov da89e0a321 pdf templates: compare and logic helpers 2024-08-19 12:45:32 +03:00
Yuri Kuznetsov 90af51b8b9 ref 2024-08-19 10:03:49 +03:00
Yuri Kuznetsov 92b82b9d4c cs fix 2024-08-19 09:36:27 +03:00
Yuri Kuznetsov c0aabe65d7 cleanup 2024-08-18 12:51:48 +03:00
Yuri Kuznetsov 6e6478bb6a change test 2024-08-18 11:19:14 +03:00
Yuri Kuznetsov d457b461e7 config default databaseHandler 2024-08-17 10:16:34 +03:00
Yuri Kuznetsov 2f6673f8d9 fix test 2024-08-16 19:54:09 +03:00
Yuri Kuznetsov ee94408394 fix redundant escaping in regexp 2024-08-16 19:33:19 +03:00
Yuri Kuznetsov 6d343cb564 ref 2024-08-16 19:31:47 +03:00
Yuri Kuznetsov f5c07c9d6b htmllizer: apply access control for iterated related records 2024-08-16 19:21:38 +03:00
Yuri Kuznetsov f9f2eaeb1f ref 2024-08-16 19:06:25 +03:00
Yuri Kuznetsov 16d8015d92 ref 2024-08-16 19:04:01 +03:00
Yuri Kuznetsov 2fa330d6f3 cs 2024-08-16 18:57:04 +03:00
Yuri Kuznetsov 3bd2c8a813 calendar 0 duration fix 2024-08-16 10:23:38 +03:00
Yuri Kuznetsov 12ba23e8ab jsdoc 2024-08-16 10:09:51 +03:00
Yuri Kuznetsov e7c3314285 afterOrEqual datetime 2024-08-15 20:32:24 +03:00
Yuri Kuznetsov 13ec8dd49f validate call after 2024-08-15 20:17:39 +03:00
Yuri Kuznetsov 80146e8bca fix massActionDefs undefined 2024-08-15 16:13:18 +03:00
Yuri Kuznetsov b974c673d2 orm event dispatched unsubscribe method 2024-08-15 10:05:28 +03:00
Yuri Kuznetsov c7ae23fd01 type change 2024-08-15 09:46:13 +03:00
Yuri Kuznetsov ccd21ebf76 comment 2024-08-14 19:08:50 +03:00
Yuri Kuznetsov f5c1fdebf3 ref 2024-08-14 19:07:19 +03:00
Yuri Kuznetsov 863cd03eb3 fix call on undefined 2024-08-14 18:31:12 +03:00
Yuri Kuznetsov d3eb08b171 clear runtime cache in entity factory 2024-08-14 16:42:29 +03:00
Yuri Kuznetsov 5a39450e6d ref 2024-08-14 09:55:13 +03:00
Yuri Kuznetsov dd40786b6f ref 2024-08-14 09:41:06 +03:00
Yuri Kuznetsov 35e894a391 ref 2024-08-14 09:18:12 +03:00
Yuri Kuznetsov 8bd2b32908 prevent removal link fields 2024-08-13 16:31:50 +03:00
Yuri Kuznetsov ce58cc262f disable kanban on status field unset 2024-08-13 12:32:47 +03:00
Yuri Kuznetsov 2dad119c3c ref 2024-08-13 09:47:46 +03:00
Yuri Kuznetsov 7e872e845c filters layout 2024-08-12 10:21:53 +03:00
Yuri Kuznetsov 678e39b0e5 jsdoc fix 2024-08-11 18:34:29 +03:00
Yuri Kuznetsov ce10665bc3 ref, cs 2024-08-11 16:05:05 +03:00
Yuri Kuznetsov d091ebbd23 fix regexp 2024-08-11 15:47:36 +03:00
Yuri Kuznetsov 68e495566d ref 2024-08-10 11:50:29 +03:00
Yuri Kuznetsov 9943e413c1 adjust header font size on button show hide 2024-08-10 11:38:40 +03:00
Yuri Kuznetsov 8f4f4b4ac1 suppress inspection 2024-08-10 11:14:33 +03:00
Yuri Kuznetsov 4a7f967d4f ref 2024-08-09 14:01:53 +03:00
Yuri Kuznetsov abf5690239 ref 2024-08-09 13:41:07 +03:00
Yuri Kuznetsov bc6946e6be cleanup 2024-08-09 13:18:46 +03:00
Yuri Kuznetsov 06001a4b4c fix mention regular expression 2024-08-09 11:46:42 +03:00
Yuri Kuznetsov d5d913cf12 ref 2024-08-09 11:36:15 +03:00
Yuri Kuznetsov 37f7fa8464 layout set layout label change 2024-08-09 11:35:30 +03:00
Yuri Kuznetsov 27c98bdf0f ref 2024-08-09 11:35:01 +03:00
Yuri Kuznetsov 2c223a0739 supress inspection 2024-08-09 11:31:04 +03:00
Yuri Kuznetsov 3c756e6252 ref 2024-08-09 10:59:57 +03:00
Yuri Kuznetsov e7331efcbe ref 2024-08-08 19:59:49 +03:00
Yuri Kuznetsov 62ae30aa8f ref 2024-08-08 18:45:05 +03:00
Yuri Kuznetsov c49f6c8045 ref 2024-08-08 18:39:35 +03:00
Yuri Kuznetsov 16ce6eb0b9 ref 2024-08-08 17:06:22 +03:00
Yuri Kuznetsov 701422892a merge 2024-08-08 14:25:54 +03:00
Yuri Kuznetsov abd2016444 8.3.6 2024-08-08 14:07:26 +03:00
Eymen Elkum cc68bd640e improve admin index quick search 2024-08-08 13:54:07 +03:00
Yuri Kuznetsov c247a24db4 fix email edit access error 2024-08-08 13:41:03 +03:00
Yuri Kuznetsov b8a484cb7e ref 2024-08-08 11:22:57 +03:00
Yuri Kuznetsov 61d546a649 ref 2024-08-08 11:06:24 +03:00
Yuri Kuznetsov 23b9c08752 ref 2024-08-08 11:04:42 +03:00
Yuri Kuznetsov 191f834e0f ref 2024-08-08 10:39:06 +03:00
Yuri Kuznetsov 9fa9198963 delete has-children foreign links with entity deletion 2024-08-07 14:47:35 +03:00
Yuri Kuznetsov f89e8acb79 tasks dashlet order 2024-08-07 13:58:10 +03:00
Yuri Kuznetsov 33fbe125fa ref 2024-08-07 10:35:55 +03:00
Yuri Kuznetsov d93dd9f6df export all, select all 2024-08-06 15:09:18 +03:00
Yuri Kuznetsov 08841f7da5 avatar cache timestamp in no cache 2024-08-06 12:41:24 +03:00
Yuri Kuznetsov 725222e4c2 ref 2024-08-06 12:33:48 +03:00
Yuri Kuznetsov 003f7864b3 ref 2024-08-06 12:05:00 +03:00
Yuri Kuznetsov 48efcdb7a8 ref 2024-08-06 11:57:28 +03:00
Yuri Kuznetsov 7bc8c2f161 ref 2024-08-05 10:12:55 +03:00
Yuri Kuznetsov 4e3cb38477 ref 2024-08-05 10:00:04 +03:00
Yuri Kuznetsov 20ac2dd2d7 fix 2024-08-05 09:49:02 +03:00
Yuri Kuznetsov 0cabbaab01 ref 2024-08-05 09:48:08 +03:00
Yuri Kuznetsov 8cc7adee3f ref 2024-08-04 17:13:31 +03:00
Yuri Kuznetsov 9d297d5587 ref 2024-08-04 17:13:22 +03:00
Yuri Kuznetsov 6e2409d969 fix typo 2024-08-02 15:18:29 +03:00
Yuri Kuznetsov b8cdbfb585 cs 2024-08-02 15:15:06 +03:00
Yuri Kuznetsov 5c06345424 range int ref, disable formatting 2024-08-02 15:14:32 +03:00
Yuri Kuznetsov 142cbfa4b8 replace bootstrap 2024-08-02 13:13:52 +03:00
Yuri Kuznetsov 04018bba93 afterOrEqual 2024-08-02 10:21:13 +03:00
Yuri Kuznetsov e77143640b working time range dynamic logic 2024-08-02 10:07:22 +03:00
Yuri Kuznetsov 2964fff2cd working time range tooltips 2024-08-02 09:58:35 +03:00
Yuri Kuznetsov 0c8cdb61dc stream create shortcut 2024-08-01 19:48:28 +03:00
Yuri Kuznetsov a9e0dad0dd log: load formatter for handlers loaded with loader 2024-08-01 18:47:57 +03:00
Yuri Kuznetsov b34d8ebca8 mass email: allow create if only edit access to campaign 2024-08-01 15:02:02 +03:00
Yuri Kuznetsov ed795d0d49 docs, ref 2024-08-01 14:37:54 +03:00
Yuri Kuznetsov 2673f60831 jsdocs, ref 2024-08-01 14:14:02 +03:00
Yuri Kuznetsov 09b56fd8c0 jsdocs 2024-08-01 14:11:31 +03:00
Yuri Kuznetsov 44175614d4 frontend tests update 2024-08-01 12:11:52 +03:00
Yuri Kuznetsov 1b7657bf0c readme fix 2024-08-01 11:35:55 +03:00
Yuri Kuznetsov 469fcdb8bc invitation, set parent 2024-08-01 10:59:11 +03:00
Yuri Kuznetsov fde53deae6 ref 2024-08-01 10:50:04 +03:00
Yuri Kuznetsov e470bf4eb1 strip prefixes from relationship name 2024-08-01 10:24:46 +03:00
Yuri Kuznetsov 5011f1e197 link manager strip c 2024-08-01 10:16:31 +03:00
Yuri Kuznetsov bc37a83982 datepicker restrict after 2024-07-31 15:57:54 +03:00
Yuri Kuznetsov d97b9be4c9 update mailmimeparser with dependencies 2024-07-31 15:03:58 +03:00
Yuri Kuznetsov e51a9621f3 test connection fix 2024-07-31 15:01:08 +03:00
Yuri Kuznetsov 6dde915b7e comment 2024-07-31 14:14:42 +03:00
Yuri Kuznetsov a109e1353d authentication: bypass second step 2024-07-31 14:12:09 +03:00
Yuri Kuznetsov 8018478b13 cleanup 2024-07-31 11:38:18 +03:00
Yuri Kuznetsov 9edb8bd59a cs 2024-07-31 11:32:42 +03:00
Yuri Kuznetsov 3b3b05286a address search for specific entity type 2024-07-31 11:26:29 +03:00
Yuri Kuznetsov bb534e6c46 record list dashlet additional row actions 2024-07-30 15:04:04 +03:00
Yuri Kuznetsov fce05fd5d2 user list view avatar 2024-07-29 17:05:45 +03:00
Yuri Kuznetsov 875be06d28 opp kanban layout change 2024-07-29 17:04:29 +03:00
Yuri Kuznetsov 0125ad0db9 fix link multiple fetched columns 2024-07-29 14:09:50 +03:00
Yuri Kuznetsov 4634c5d30f move selectRelated and createRelated to helpers 2024-07-29 11:35:12 +03:00
Yuri Kuznetsov 6bd717fe5d bottom view for small record views 2024-07-29 10:12:52 +03:00
Yuri Kuznetsov be56156516 ref 2024-07-29 09:58:36 +03:00
Yuri Kuznetsov 3da90da35b fix panel label 2024-07-29 09:47:42 +03:00
Yuri Kuznetsov 6b9ebdc731 field change reRended fromField check 2024-07-27 09:57:39 +03:00
Yuri Kuznetsov 1d0d12649f modal edit headerText option 2024-07-27 09:20:29 +03:00
Yuri Kuznetsov e2aa06f755 link multiple saver: skip not existing columns 2024-07-26 16:50:28 +03:00
Yuri Kuznetsov 19e4943410 reset to default color 2024-07-26 12:00:46 +03:00
Yuri Kuznetsov 668233a978 exclude personal addresses from cc when replying 2024-07-25 18:50:25 +03:00
Yuri Kuznetsov 5a3bfe5f68 create portal user lower case name 2024-07-25 18:35:13 +03:00
Yuri Kuznetsov 90ff998e5c ref 2024-07-25 18:34:00 +03:00
Yuri Kuznetsov 71b7c1af2b working time specific calendar 2024-07-25 09:26:50 +03:00
Yuri Kuznetsov 997e5cc44c ref 2024-07-25 08:59:28 +03:00
Yuri Kuznetsov 3f8e6b2854 fix 2024-07-23 21:24:13 +03:00
Yuri Kuznetsov 3bf7ec7e9d fix mid keys only for custom 2024-07-23 20:35:28 +03:00
Yuri Kuznetsov 1d7c47f005 do not load link multiple is loaded 2024-07-23 12:20:56 +03:00
Yuri Kuznetsov b1d77bdca0 midKeys order fix 2024-07-23 11:21:17 +03:00
Yuri Kuznetsov 36a9563e69 cleanup 2024-07-23 08:36:51 +03:00
Yuri Kuznetsov 67e1917426 mid keys converter fix 2024-07-23 08:36:19 +03:00
Yuri Kuznetsov 344714fda0 ref 2024-07-21 15:05:03 +03:00
Yuri Kuznetsov 836de2d624 phpdocs 2024-07-21 09:52:36 +03:00
Yuri Kuznetsov 2c5e548c07 orm getRelation 2024-07-21 09:49:19 +03:00
Yuri Kuznetsov f0ac0dc03e related list parent model 2024-07-19 15:20:50 +03:00
Yuri Kuznetsov 2bb53c5495 highlighted classes 2024-07-19 10:21:34 +03:00
Yuri Kuznetsov e0256018de noDefaultFilters 2024-07-18 09:05:03 +03:00
Yuri Kuznetsov 019cea2641 kanban update total event 2024-07-18 07:41:09 +03:00
Yuri Kuznetsov d8286a2de6 kanban topBarDisabled 2024-07-18 07:15:15 +03:00
Yuri Kuznetsov 685f034e15 enum set translated options method 2024-07-17 13:19:21 +03:00
Yuri Kuznetsov 3210733bc7 kanban methods 2024-07-17 12:56:45 +03:00
Yuri Kuznetsov 38ad6def8c fix selectable click 2024-07-17 12:22:06 +03:00
Yuri Kuznetsov b2d31d0230 fix kanban show more 2024-07-17 00:07:57 +03:00
Yuri Kuznetsov ddd9a1dc88 kanban createActionHandler 2024-07-16 23:51:19 +03:00
Yuri Kuznetsov 3355ee7192 fix 2024-07-16 19:44:54 +03:00
Yuri Kuznetsov a4e694412c kanban group item more data 2024-07-16 19:40:36 +03:00
Yuri Kuznetsov 7222a5a546 kanban empty render 2024-07-16 19:38:38 +03:00
Yuri Kuznetsov 423f26cb29 jsdocs 2024-07-16 18:04:52 +03:00
Yuri Kuznetsov fe4679cbc5 cleanup 2024-07-16 17:55:08 +03:00
Yuri Kuznetsov df0c56bd9b kanban ref 2024-07-16 17:53:19 +03:00
Yuri Kuznetsov 269f38e07c deprecation 2024-07-16 17:36:02 +03:00
Yuri Kuznetsov 48c89aec97 kanban ref 2024-07-16 17:35:01 +03:00
Yuri Kuznetsov 2cca4fe429 docs 2024-07-16 12:45:40 +03:00
Yuri Kuznetsov 8672fd1dff ref 2024-07-16 12:31:38 +03:00
Yuri Kuznetsov 755d3b863a dev 2024-07-16 12:15:29 +03:00
Yuri Kuznetsov 453e2bd1a0 ref 2024-07-16 12:10:15 +03:00
Yuri Kuznetsov 9ab35f457c ref 2024-07-16 11:51:07 +03:00
Yuri Kuznetsov 0a60ffefa5 ref 2024-07-16 11:30:17 +03:00
Yuri Kuznetsov 47fad3eb07 docs 2024-07-16 11:25:33 +03:00
Yuri Kuznetsov 7cca2353d8 system time zone 2024-07-16 11:07:45 +03:00
Yuri Kuznetsov 089c8d56f8 Merge branch 'fix' 2024-07-15 23:39:19 +03:00
Yuri Kuznetsov 17c9379c15 fix dynamic logic date time zone 2024-07-15 23:39:00 +03:00
Yuri Kuznetsov d6d83a209f label fixes 2024-07-15 23:12:43 +03:00
Yuri Kuznetsov 86b77266bb schema 2024-07-15 12:28:27 +03:00
Yuri Kuznetsov 9339082f9b link: set attributes at once 2024-07-15 11:48:05 +03:00
Yuri Kuznetsov bac385545c fix defaults promise wait 2024-07-15 11:25:25 +03:00
Yuri Kuznetsov 3e4b4f2df8 detail mode buttons to the left 2024-07-14 14:50:14 +03:00
Yuri Kuznetsov 990406889b app param error log 2024-07-12 10:33:48 +03:00
Yuri Kuznetsov 487d8dc909 custom layout in another module 2024-07-12 10:17:21 +03:00
Yuri Kuznetsov 4945e19fdf docs 2024-07-11 18:55:32 +03:00
Yuri Kuznetsov 0eecaf3d5a select related entityType param 2024-07-11 18:21:02 +03:00
Yuri Kuznetsov 337dd67c36 fix 2024-07-11 18:04:04 +03:00
Yuri Kuznetsov e8f0d38554 panel actions acl race condition fix 2024-07-11 18:00:21 +03:00
Yuri Kuznetsov e7904b976b schema 2024-07-11 13:27:50 +03:00
Yuri Kuznetsov 2360b75f97 panel actions 2024-07-11 13:22:30 +03:00
Yuri Kuznetsov 785c3a8545 link multiple translate empty role 2024-07-10 19:08:09 +03:00
Yuri Kuznetsov 1d1fccaed9 user only me filter 2024-07-10 12:59:13 +03:00
Yuri Kuznetsov fb921bb023 Merge branch 'fix' 2024-07-10 11:43:34 +03:00
Yuri Kuznetsov 0ffe39cec8 8.3.5 2024-07-10 11:04:51 +03:00
Yuri Kuznetsov 21034bfeb2 acl scope data resolve boolean to true if not false 2024-07-09 16:25:44 +03:00
Yuri Kuznetsov 94188f4256 ref 2024-07-09 15:15:08 +03:00
Yuri Kuznetsov 20f6d67f40 Merge branch 'master' of https://github.com/espocrm/espocrm 2024-07-09 15:07:34 +03:00
Yuri Kuznetsov a3c289aee2 model defaults preparator 2024-07-09 15:04:06 +03:00
Yuri Kuznetsov e56121bc18 ref 2024-07-09 14:25:56 +03:00
Arkadiy Asuratov 5b90d4c3f3 fix typos in databasePlatforms.json schema
* Updated descriptions for pre- and post-rebuild actions
2024-07-09 13:25:41 +03:00
Yuri Kuznetsov f69eed63d7 acl resolve foreign to boolean 2024-07-09 13:11:56 +03:00
Yuri Kuznetsov 06c173486e collection parentModel 2024-07-09 07:56:27 +03:00
Yuri Kuznetsov 32f1bfb1c6 model sync event option 2024-07-09 07:01:28 +03:00
Yuri Kuznetsov 68fee2ec9f fix msg 2024-07-08 22:34:08 +03:00
Yuri Kuznetsov d15ab2df31 jsdoc 2024-07-08 22:29:31 +03:00
Yuri Kuznetsov cb7f87a3f1 archive email require import access 2024-07-08 22:18:15 +03:00
Yuri Kuznetsov 6783331aab email: user massUpdateDisabled 2024-07-08 22:11:58 +03:00
Yuri Kuznetsov 88d4b6f27c Merge branch 'fix' 2024-07-08 22:03:12 +03:00
Yuri Kuznetsov 3347b7fba8 email from address check 2024-07-08 18:43:32 +03:00
Yuri Kuznetsov cff703db05 list rebuild 2024-07-08 16:46:26 +03:00
Yuri Kuznetsov 16ba6ce7cf settings helper injected 2024-07-08 16:34:55 +03:00
Yuri Kuznetsov d4bda1fa9d panel heading text 2024-07-08 14:25:30 +03:00
Yuri Kuznetsov 06b5100e87 list update-total event 2024-07-08 14:11:19 +03:00
Yuri Kuznetsov f1a3cd397a checkbox disable 2024-07-08 13:25:38 +03:00
Yuri Kuznetsov 2481e1a652 cf 2024-07-08 13:01:32 +03:00
Yuri Kuznetsov e32ad76590 cleanup 2024-07-08 12:44:43 +03:00
Yuri Kuznetsov 81f1374f55 force sticky bar param 2024-07-08 11:58:20 +03:00
Yuri Kuznetsov d3fd314e35 list sticky bar edge 2024-07-08 08:56:46 +03:00
Yuri Kuznetsov 20b1b06d5f mass action no aclScope fix 2024-07-07 19:45:29 +03:00
Yuri Kuznetsov a364ae1923 massUpdateDisabled in entityDefs fields 2024-07-07 19:00:47 +03:00
Yuri Kuznetsov bb2ce37a38 ref 2024-07-07 18:47:55 +03:00
Yuri Kuznetsov fd092a3eb1 group index -100 2024-07-07 15:26:08 +03:00
Yuri Kuznetsov fb3bdde2c3 link create with name 2024-07-07 13:21:59 +03:00
Yuri Kuznetsov 66623c02e2 rerender row actions on model change 2024-07-07 13:16:20 +03:00
Yuri Kuznetsov 22fded93fb docs 2024-07-07 11:54:57 +03:00
Yuri Kuznetsov 6da20c6f86 modal view related entityType option 2024-07-07 09:50:23 +03:00
Yuri Kuznetsov 34834b2d5c css class panel-actions-container-left 2024-07-06 19:21:50 +03:00
Yuri Kuznetsov c971304b03 array keepItems param 2024-07-06 16:13:11 +03:00
Yuri Kuznetsov 6c7d424349 removeMassAction public 2024-07-06 10:51:36 +03:00
Yuri Kuznetsov f8e43a3694 fix accounts field 2024-07-06 10:07:09 +03:00
Yuri Kuznetsov 17dddbc248 ref 2024-07-06 09:57:43 +03:00
Yuri Kuznetsov 2246ab0cfd select primary id 2024-07-06 09:57:34 +03:00
Yuri Kuznetsov a42f2cbe3a dynamic logic schema fix 2024-07-06 09:45:00 +03:00
Yuri Kuznetsov ad4c039b4a link multiple html fix 2024-07-06 09:38:45 +03:00
Yuri Kuznetsov 2638fdb884 link-multiple-with-columns list text color 2024-07-06 09:23:46 +03:00
Yuri Kuznetsov c0cfe8a36f cs 2024-07-06 09:21:12 +03:00
Yuri Kuznetsov b9ab872ccd autocomplete user-select none 2024-07-06 09:16:58 +03:00
Yuri Kuznetsov 018b0b46aa fix label manager options 2024-07-06 09:13:39 +03:00
Yuri Kuznetsov 7bcd347f2d link multiple id autocomple conflict fix 2024-07-05 19:32:13 +03:00
Yuri Kuznetsov a6db71957a input-text-block 2024-07-05 19:05:50 +03:00
Yuri Kuznetsov 1a5df659cf date field after/before in params 2024-07-05 18:43:58 +03:00
Yuri Kuznetsov 2219f52140 id field view 2024-07-05 12:58:55 +03:00
Yuri Kuznetsov 78dde9c7a1 record helper in modal record 2024-07-04 17:50:31 +03:00
Yuri Kuznetsov 3f0e2a242d Merge branch 'fix' 2024-07-04 09:21:04 +03:00
Yuri Kuznetsov 64aebdde6b icon color fix 2024-07-04 09:20:54 +03:00
Arkadiy Asuratov 24f6db674f suggest toTimestamp over getTimestamp 2024-07-03 18:45:55 +03:00
Arkadiy Asuratov bfc4fb7ca8 suggest toTimestamp over getTimestamp 2024-07-03 18:45:55 +03:00
Yuri Kuznetsov d46945dd2e change mode 2024-07-03 17:00:00 +03:00
Yuri Kuznetsov 51130d1aef fix 2024-07-03 16:33:43 +03:00
Yuri Kuznetsov 6eb71a789e foreign check 2024-07-03 12:06:26 +03:00
Yuri Kuznetsov 9c77169e6c foreign allow delete for admin if no record 2024-07-03 11:43:38 +03:00
Yuri Kuznetsov 26f1218240 duplicate source ID in save options 2024-07-02 19:14:46 +03:00
Yuri Kuznetsov 8b09a81237 Merge branch 'fix' 2024-07-01 16:45:19 +03:00
Yuri Kuznetsov f3ee5c654b 8.3.4 2024-07-01 16:35:35 +03:00
Yuri Kuznetsov 19c8fe9ac5 ref 2024-07-01 16:33:15 +03:00
Yuri Kuznetsov 81f45d5679 merge 2024-07-01 16:27:08 +03:00
Yuri Kuznetsov 1a413cb54e check failed code attempts in a separate hook 2024-07-01 16:23:37 +03:00
Yuri Kuznetsov 3ab2ffee3c back to edit after duplicate cancel 2024-06-30 10:56:42 +03:00
Yuri Kuznetsov 53a481622a address country duplicate check 2024-06-30 09:58:49 +03:00
Yuri Kuznetsov 7d72a7ff71 ref 2024-06-29 19:45:32 +03:00
Yuri Kuznetsov 314cc7f4c2 load empty link multiple names 2024-06-29 19:31:51 +03:00
Yuri Kuznetsov df1228f720 ref 2024-06-29 13:04:52 +03:00
Yuri Kuznetsov bbd071221a fix 2024-06-29 13:04:48 +03:00
Yuri Kuznetsov 9235a3cf79 unset data 2024-06-29 12:12:42 +03:00
Yuri Kuznetsov 75c966e4bb external account disable 2024-06-29 11:52:29 +03:00
Yuri Kuznetsov c298f5ec9a cleanup 2024-06-29 10:43:58 +03:00
Yuri Kuznetsov 94986d8835 ui fix 2024-06-29 10:28:42 +03:00
Yuri Kuznetsov ece8405b33 campaign unsubscribe change 2024-06-29 10:14:17 +03:00
Yuri Kuznetsov 0d2d708ba8 mass email post unsubscribe 2024-06-28 20:13:10 +03:00
Yuri Kuznetsov ef31f7ba08 Merge branch 'fix' 2024-06-28 19:21:07 +03:00
Yuri Kuznetsov 41bcaf50c4 fix attachment-multiple render 2024-06-28 18:55:21 +03:00
Yuri Kuznetsov dcd3fa0fc8 ref 2024-06-28 16:52:05 +03:00
Yuri Kuznetsov b48b9683ab comments 2024-06-28 11:15:59 +03:00
Yuri Kuznetsov 2548f396ef ref 2024-06-28 11:09:53 +03:00
Yuri Kuznetsov 0122b99a4b ouath timeout 2024-06-28 10:16:09 +03:00
Yuri Kuznetsov fb747d3f65 ref 2024-06-28 09:55:36 +03:00
Yuri Kuznetsov 41cb0de44e ref 2024-06-28 09:35:59 +03:00
Yuri Kuznetsov f7442be97b cs 2024-06-28 09:19:10 +03:00
Yuri Kuznetsov 20ccacddf3 ref 2024-06-28 09:18:07 +03:00
Yuri Kuznetsov 1ce56bb522 event date start colors 2024-06-27 17:15:02 +03:00
Yuri Kuznetsov f2330b9a51 date field style 2024-06-27 16:29:02 +03:00
Yuri Kuznetsov 0346a0023e Merge branch 'fix' 2024-06-27 16:04:54 +03:00
Yuri Kuznetsov bc7d9443b1 8.3.3 2024-06-27 15:54:59 +03:00
Yuri Kuznetsov 8188dc065b fix row actions returning undefined 2024-06-27 15:29:30 +03:00
Yuri Kuznetsov a2025d0a89 fix props not initiated 2024-06-27 15:17:04 +03:00
Yuri Kuznetsov c46933f427 Merge branch 'fix' 2024-06-27 11:53:23 +03:00
Yuri Kuznetsov 1d31637c2e migrate: rebuild before each script 2024-06-27 11:53:13 +03:00
Yuri Kuznetsov e26026c9ad migrate: rebuild before each script 2024-06-27 11:49:36 +03:00
Yuri Kuznetsov f9e4d1a953 cleanup 2024-06-27 10:43:50 +03:00
Yuri Kuznetsov 6f00a6b2e7 2fa code attempts period separate param 2024-06-27 10:34:55 +03:00
Yuri Kuznetsov a89860dc52 ref 2024-06-27 10:24:12 +03:00
Yuri Kuznetsov 626c23b1b5 ref 2024-06-27 10:01:45 +03:00
Yuri Kuznetsov 7513d05451 hide menu items in edit mode 2024-06-27 08:52:45 +03:00
Yuri Kuznetsov 5e7aabc46c ref, convert lead button improvement 2024-06-27 08:34:38 +03:00
Yuri Kuznetsov 740baffeb4 convert lead use record service 2024-06-27 08:26:56 +03:00
Yuri Kuznetsov 2ba808c371 auth limit denial reason check 2024-06-26 21:54:15 +03:00
Yuri Kuznetsov 8c7f9f43e4 convert lead require acl create 2024-06-26 16:42:16 +03:00
Yuri Kuznetsov 2df98585f9 lead convert: ability to select account 2024-06-26 16:38:27 +03:00
Yuri Kuznetsov 9d4266bed0 ref 2024-06-26 16:26:39 +03:00
Yuri Kuznetsov cb313cd7ef ref 2024-06-26 14:29:10 +03:00
Yuri Kuznetsov ce54d516e9 Merge branch 'fix' 2024-06-26 13:51:29 +03:00
Yuri Kuznetsov 6bce395daf fix dropdown empty 2024-06-26 13:51:19 +03:00
Yuri Kuznetsov b634daca6e cs 2024-06-26 13:33:59 +03:00
Yuri Kuznetsov 338e0bb9d4 deleteId param 2024-06-26 13:27:20 +03:00
Yuri Kuznetsov cc576a6af8 add user utility fields 2024-06-26 12:33:15 +03:00
Yuri Kuznetsov cbb0159d27 userNameRegularExpression readonly 2024-06-26 12:22:04 +03:00
Yuri Kuznetsov 515b43614b sanitizer lower case 2024-06-26 12:20:29 +03:00
Yuri Kuznetsov 736d23fa6d inline edit shortcut do not reset 2024-06-26 08:40:53 +03:00
Yuri Kuznetsov 7a096cdde4 Merge branch 'fix' 2024-06-26 08:05:49 +03:00
Yuri Kuznetsov 5b0787474e 8.3.2 2024-06-26 07:53:49 +03:00
Yuri Kuznetsov 8c87f20374 comment 2024-06-26 07:50:19 +03:00
Yuri Kuznetsov ba35115a48 inline edit: revert initial attributes on error 2024-06-26 07:44:44 +03:00
Yuri Kuznetsov 069010d0fe log opened only for system user 2024-06-26 07:22:09 +03:00
Yuri Kuznetsov fe5878fd99 2fa auth log records on fail 2024-06-25 20:39:44 +03:00
Yuri Kuznetsov ee37960259 mass action show/hide 2024-06-25 16:22:33 +03:00
Yuri Kuznetsov 3ce8fae228 cs ref 2024-06-25 15:37:00 +03:00
Yuri Kuznetsov 13ebc558e9 email dd item group index change 2024-06-25 15:03:53 +03:00
Yuri Kuznetsov 3ae342c275 mass actions groups 2024-06-25 15:00:25 +03:00
Yuri Kuznetsov a283305c7b fix test 2024-06-25 14:05:29 +03:00
Yuri Kuznetsov 839ceea142 root api endpoint fix type 2024-06-25 08:35:56 +03:00
Yuri Kuznetsov a6c698588c update bullbone 2024-06-24 18:59:23 +03:00
Yuri Kuznetsov 3007976506 fix test 2024-06-24 13:49:54 +03:00
Yuri Kuznetsov 8189f0eb2f wysiwyg: don't call getValueForDisplay 2024-06-24 13:27:42 +03:00
Yuri Kuznetsov 248063f327 activities, prepare for output 2024-06-24 13:23:07 +03:00
Yuri Kuznetsov 3a76807a2b parent field in activities and other dashlets 2024-06-24 13:12:29 +03:00
Yuri Kuznetsov 6e6d3e15ea emails archive 2024-06-24 12:44:35 +03:00
Yuri Kuznetsov eee086ef52 Merge branch 'fix' 2024-06-24 11:24:47 +03:00
Yuri Kuznetsov 9cce9d7347 comment 2024-06-24 11:16:45 +03:00
Yuri Kuznetsov 69d0dbbf1c email collapse fix 2024-06-24 11:09:29 +03:00
Yuri Kuznetsov 92695c0c39 email search cc 2024-06-24 10:56:51 +03:00
Yuri Kuznetsov 44346e962c Merge branch 'fix' 2024-06-24 10:54:58 +03:00
Yuri Kuznetsov 68ef9ce4ac disable email field layouts 2024-06-24 10:54:48 +03:00
Yuri Kuznetsov 83a415cb33 Merge branch 'fix' 2024-06-24 10:35:56 +03:00
Yuri Kuznetsov 163cf047e5 8.3.1 2024-06-24 10:25:00 +03:00
Yuri Kuznetsov 02efdf11f5 Merge branch 'fix' 2024-06-23 17:53:09 +03:00
Yuri Kuznetsov cc574afd3d labels 2024-06-23 17:52:57 +03:00
Yuri Kuznetsov 26a0c4b108 ref 2024-06-23 11:55:00 +03:00
Yuri Kuznetsov 77d76fe0ee bg lang 2024-06-23 11:19:19 +03:00
Yuri Kuznetsov caeaf46403 email show quote part 2024-06-23 11:05:47 +03:00
Yuri Kuznetsov c254f5cc0d Merge branch 'fix' 2024-06-23 10:52:27 +03:00
Yuri Kuznetsov e7ab75ec5a link-one fix 2024-06-23 10:47:07 +03:00
Yuri Kuznetsov 53c3f4b4c3 fix 2024-06-23 09:26:55 +03:00
Yuri Kuznetsov 53a12e17f8 Merge branch 'master' of https://github.com/espocrm/espocrm 2024-06-22 19:43:21 +03:00
Yuri Kuznetsov 2ab5489bec eml duplicate check 2024-06-22 19:43:11 +03:00
Yurii Kuznietsov a5c87272b1 Update CONTRIBUTING.md 2024-06-22 19:15:42 +03:00
Yurii Kuznietsov 130bae6a88 Update CONTRIBUTING.md 2024-06-22 15:36:57 +03:00
Yurii Kuznietsov c520a02ca5 Update CONTRIBUTING.md 2024-06-22 15:36:16 +03:00
Yuri Kuznetsov b8d5612d32 ref 2024-06-22 11:19:54 +03:00
Yuri Kuznetsov 6ecf5fcdd0 note avatar style fix 2024-06-22 11:13:23 +03:00
Yuri Kuznetsov 5a93722232 import EML 2024-06-22 09:56:29 +03:00
Yuri Kuznetsov d6035523e2 Merge branch 'fix' 2024-06-22 09:26:49 +03:00
Yuri Kuznetsov ff6983c9f3 fix wysiwyg sticky toolbar position 2024-06-22 09:26:40 +03:00
Yuri Kuznetsov e2673473c5 wysiwyg codeview not limited height 2024-06-22 09:09:42 +03:00
Yuri Kuznetsov e8bf70ab8e wysiwyg fullscreen codeview switch fix 2024-06-22 08:59:04 +03:00
Yuri Kuznetsov f459d5811d wysiwyg height not being changed fix 2024-06-22 08:45:10 +03:00
Yuri Kuznetsov 8b49e72f2b Merge branch 'fix' 2024-06-21 22:12:19 +03:00
Yuri Kuznetsov 53bf9b024a address field: support list-link template 2024-06-21 22:12:04 +03:00
Yuri Kuznetsov a1292cf933 navbar order change 2024-06-21 16:55:25 +03:00
Yuri Kuznetsov 5ccfd77669 ref 2024-06-21 16:52:40 +03:00
Yuri Kuznetsov 67a27ec21b Merge branch 'fix' 2024-06-21 16:27:43 +03:00
Yuri Kuznetsov 25aed1a1c4 style fix 2024-06-21 16:17:45 +03:00
Yuri Kuznetsov b1de15339a metadata navbar menu 2024-06-21 15:42:48 +03:00
Yuri Kuznetsov 3f819500d3 navbar item order, gaps 2024-06-21 13:56:06 +03:00
Yuri Kuznetsov 435be717c3 cleanup 2024-06-21 13:41:45 +03:00
Yuri Kuznetsov d2f4fbc59d Merge branch 'fix' 2024-06-21 13:14:47 +03:00
Yuri Kuznetsov 2589801993 css fix 2024-06-21 13:14:40 +03:00
Yuri Kuznetsov 9e0a77588d move navbar items to metadata 2024-06-21 13:06:29 +03:00
Yuri Kuznetsov 5f954c22da fix header dropdown on small screen 2024-06-21 12:41:36 +03:00
Yuri Kuznetsov 423e2ca544 fix favicon metadata path 2024-06-21 12:04:11 +03:00
Yuri Kuznetsov d033b26e57 Merge branch 'fix' 2024-06-21 09:40:43 +03:00
Yuri Kuznetsov 6176b8770f fulltext hyphen use natural language mode 2024-06-21 09:40:20 +03:00
Yuri Kuznetsov cbced73f6e history layout change 2024-06-20 22:03:43 +03:00
Yuri Kuznetsov 9168dd69f2 orm: mapper ref, fix wildcard in expression 2024-06-20 17:45:44 +03:00
Yuri Kuznetsov c69fc7f2c9 orm mapper: deleted check 2024-06-20 16:39:10 +03:00
Yuri Kuznetsov faebc13757 Merge branch 'fix' 2024-06-20 15:42:11 +03:00
Yuri Kuznetsov 8ab7452859 fix kanban fetch 2024-06-20 15:42:03 +03:00
Yuri Kuznetsov bd6e0023c3 kanban column show/hide 2024-06-20 13:01:50 +03:00
Yuri Kuznetsov 5f00c85882 cs 2024-06-20 11:48:37 +03:00
Yuri Kuznetsov 6c8cffeb2a nowrap 2024-06-20 08:38:21 +03:00
Yuri Kuznetsov 683eb5a491 Merge branch 'master' into version/8.4 2024-06-20 08:25:23 +03:00
Yuri Kuznetsov 07035bf8bf undo avatar 2024-06-20 08:19:34 +03:00
Yuri Kuznetsov 9e8df41174 check set held 2024-06-19 21:46:36 +03:00
Yuri Kuznetsov 64736349f0 ref 2024-06-19 21:44:15 +03:00
Yuri Kuznetsov 71f389703d remove action fix 2024-06-19 21:36:35 +03:00
Yuri Kuznetsov 669701c6fd Merge branch 'master' into version/8.4 2024-06-19 12:29:07 +03:00
Yuri Kuznetsov 02917943b6 quick view aux 2024-06-19 12:28:44 +03:00
Yuri Kuznetsov 7221963990 stream assign avatar 2024-06-19 12:27:33 +03:00
Yuri Kuznetsov 062043d5e1 Merge branch 'master' into version/8.4 2024-06-19 12:23:58 +03:00
Yuri Kuznetsov acf1833d9f fix tooltip 2024-06-19 11:54:13 +03:00
Yuri Kuznetsov 9c45902213 stream notes quick view aux 2024-06-19 08:40:56 +03:00
Yuri Kuznetsov f9674bd60c field view validation functions 2024-06-18 12:10:55 +03:00
Yuri Kuznetsov 831d840cc5 field view validation functions 2024-06-18 12:08:39 +03:00
Yuri Kuznetsov adc3df5144 Merge branch 'master' into version/8.4 2024-06-18 11:34:06 +03:00
Yuri Kuznetsov 3f6f718cd9 schema 2024-06-18 11:30:17 +03:00
Yuri Kuznetsov c35b5c5aa5 label 2024-06-18 11:29:01 +03:00
Yuri Kuznetsov 0050f44a8a lang 2024-06-18 11:08:44 +03:00
dependabot[bot] f86479afdc Bump ws from 8.13.0 to 8.17.1
Bumps [ws](https://github.com/websockets/ws) from 8.13.0 to 8.17.1.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.13.0...8.17.1)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-18 11:00:39 +03:00
Yuri Kuznetsov f4cbb5e56a avatar color, use read only state 2024-06-18 09:03:46 +03:00
Yuri Kuznetsov 6c8e9b129a avatar color attribute in avatar field 2024-06-18 08:54:05 +03:00
Yuri Kuznetsov c033cb171e avatar color combined field 2024-06-17 18:48:27 +03:00
Yuri Kuznetsov bdf7d56e0c phone validation ref 2024-06-17 17:13:59 +03:00
Yuri Kuznetsov 3e7326f605 ref 2024-06-17 16:56:13 +03:00
Yuri Kuznetsov 61e51bfb31 ref 2024-06-17 16:54:21 +03:00
Yuri Kuznetsov c35d934ba7 import array, trim 2024-06-17 13:23:04 +03:00
Yuri Kuznetsov 80fa391daa css fix 2024-06-17 12:51:28 +03:00
Yuri Kuznetsov 1092b17a13 orm mapper: check deleted when update 2024-06-17 12:50:10 +03:00
Yuri Kuznetsov 1de6568918 v 2024-06-17 09:09:27 +03:00
Yuri Kuznetsov ee7c7046ac wysiwyg escape insert link 2024-06-16 10:38:15 +03:00
Yuri Kuznetsov 19638dd649 activities panel update-all 2024-06-15 16:14:16 +03:00
Yuri Kuznetsov df30678484 css fix 2024-06-14 16:58:22 +03:00
dependabot[bot] b05697d874 Bump braces from 3.0.2 to 3.0.3
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-12 23:10:31 +03:00
Yuri Kuznetsov 91177fc0d2 schema 2024-06-12 11:58:28 +03:00
Yuri Kuznetsov e56c64dba4 schema 2024-06-12 09:15:35 +03:00
Yuri Kuznetsov 7ecc0dc6a9 upcoming activities order impr 2024-06-10 14:50:59 +03:00
Yuri Kuznetsov 808e2f8788 ref 2024-06-10 14:34:38 +03:00
Yuri Kuznetsov 7682539114 fix submit reminders 2024-06-10 12:15:29 +03:00
Yuri Kuznetsov 890649e46b detail modes 2024-06-10 11:02:29 +03:00
Yuri Kuznetsov 33d710d265 Merge branch 'master' into version/8.4 2024-06-10 09:49:37 +03:00
Yuri Kuznetsov 0558739e67 link multiple primary fix 2024-06-10 09:16:11 +03:00
Yuri Kuznetsov 552983ca33 ref 2024-06-09 18:33:14 +03:00
Yuri Kuznetsov d328872479 ref 2024-06-09 18:28:57 +03:00
Yuri Kuznetsov 6cee542972 change todo 2024-06-08 21:16:11 +03:00
Yuri Kuznetsov afb77424d4 array field: guard null item 2024-06-08 16:09:03 +03:00
Yuri Kuznetsov c7465970bd viewModeIconClassMap 2024-06-08 12:21:00 +03:00
Yuri Kuznetsov 4f6ae321b9 schema 2024-06-08 12:13:37 +03:00
Yuri Kuznetsov b92970992f ref 2024-06-08 11:50:46 +03:00
Yuri Kuznetsov 4923984e2d cleanup 2024-06-08 11:48:53 +03:00
Yuri Kuznetsov 071fb20f7b email insert data load additional fields 2024-06-08 09:51:16 +03:00
Yuri Kuznetsov 3e7d0f23c8 Merge branch 'fix' 2024-06-07 14:44:42 +03:00
Yuri Kuznetsov 268009e5f6 8.2.5 2024-06-07 14:35:06 +03:00
Yuri Kuznetsov 573d810d37 color changes 2024-06-06 20:58:51 +03:00
Yuri Kuznetsov b3f3226f55 metadata additional builders 2024-06-06 19:32:15 +03:00
Yuri Kuznetsov 511b7b0d9d Merge branch 'fix' 2024-06-06 13:58:48 +03:00
Yuri Kuznetsov 3e54b58c8c formula parser assign operator fix 2024-06-06 13:57:46 +03:00
Yuri Kuznetsov 92467afad3 formula parser assign operator fix 2024-06-06 13:51:57 +03:00
Yuri Kuznetsov a1c6004c4f user tabs fix 2024-06-06 11:29:17 +03:00
Yuri Kuznetsov 0d8943d86a cs 2024-06-06 11:17:00 +03:00
Yuri Kuznetsov 07ca2b6ceb user layout detail small change 2024-06-06 11:09:31 +03:00
Yuri Kuznetsov e019de24cc fieldNotMatchingPattern$phoneNumberLoose 2024-06-05 12:05:57 +03:00
Yuri Kuznetsov 7b417313a2 reminder validator ref 2024-06-04 20:50:48 +03:00
Yuri Kuznetsov cb6777d19b fix notification css 2024-06-04 20:38:31 +03:00
Yuri Kuznetsov 2790834c62 fix notification selector 2024-06-04 20:29:17 +03:00
Yuri Kuznetsov 9162b61a3f dynamic logic datetime second granuality 2024-06-04 12:06:53 +03:00
Yuri Kuznetsov 8488afb6f6 logo changes 2024-06-03 13:47:46 +03:00
Yuri Kuznetsov 1b518f4b6f style fix 2024-06-03 13:43:26 +03:00
Yuri Kuznetsov 92bad5240b calendar task fix 2024-06-03 13:40:29 +03:00
Yuri Kuznetsov 5f70d1b408 sent folder encoding 2024-06-03 11:27:42 +03:00
Yuri Kuznetsov 0941bd440e comment 2024-06-03 11:12:44 +03:00
Yuri Kuznetsov 2fabf4bfe1 EntityProvider getByClassName 2024-05-31 09:50:21 +03:00
Yurii Kuznietsov f9e13e8093 Update README.md 2024-05-30 20:54:11 +03:00
Yurii Kuznietsov d1f0fd4fb8 Update README.md 2024-05-30 20:53:41 +03:00
Yurii Kuznietsov 7c9ae7caa4 Update README.md 2024-05-30 20:01:09 +03:00
Yurii Kuznietsov 2adcab4417 Update README.md 2024-05-30 19:53:53 +03:00
Yurii Kuznietsov 2fd367cd7d Update README.md 2024-05-30 19:51:17 +03:00
Yurii Kuznietsov 82c74c23f9 Update README.md 2024-05-30 19:47:42 +03:00
Yurii Kuznietsov 9de57ba4b2 Update README.md 2024-05-30 19:41:22 +03:00
Yurii Kuznietsov 53d544a6d5 Update README.md 2024-05-30 19:40:26 +03:00
Yurii Kuznietsov 5f550f38f9 Update README.md 2024-05-30 19:39:56 +03:00
Yurii Kuznietsov 63a1c0be66 Update README.md 2024-05-30 19:36:49 +03:00
Yuri Kuznetsov a43903dfd2 task reminders 2024-05-30 09:34:54 +03:00
Yuri Kuznetsov ce369c774b ref 2024-05-30 09:34:54 +03:00
Yurii Kuznietsov 971543ab02 Update README.md 2024-05-29 21:51:00 +03:00
Yuri Kuznetsov bf8b9a7e16 sakura color changes 2024-05-29 14:15:23 +03:00
Yuri Kuznetsov d727cea9ee navbar color changes 2024-05-29 14:06:48 +03:00
Yuri Kuznetsov 373e05d219 navbar hover bg 2024-05-29 14:03:41 +03:00
Yuri Kuznetsov 8fa5cf4af1 color 2024-05-29 13:57:29 +03:00
Yuri Kuznetsov 776ae3ac9d radius change 2024-05-29 12:14:36 +03:00
Yuri Kuznetsov f3d9f93564 espo theme change 2024-05-29 12:13:34 +03:00
Yuri Kuznetsov d32995df9b hazyblue changes 2024-05-29 12:06:12 +03:00
Yuri Kuznetsov 6c8e5b2c92 calls scheduler panel 2024-05-29 11:54:39 +03:00
Yuri Kuznetsov b44e536e35 logo espo color 2024-05-29 10:32:55 +03:00
Yuri Kuznetsov 8e9045352d color change 2024-05-27 17:06:48 +03:00
Yuri Kuznetsov 97499626db links no spell check 2024-05-27 15:38:31 +03:00
Yuri Kuznetsov 65cf99faad color change 2024-05-27 15:23:41 +03:00
Yuri Kuznetsov 5719f29262 table-panel border radius fix 2024-05-27 10:45:44 +03:00
Yuri Kuznetsov 06811d9fcc link manager: remove dynamic logic 2024-05-27 10:18:48 +03:00
Yuri Kuznetsov 9000dba6fd default team read only for non admins 2024-05-26 10:12:24 +03:00
Yuri Kuznetsov b8734f68da ref 2024-05-24 17:47:29 +03:00
Yuri Kuznetsov a6eb683770 logo-espo 2024-05-24 16:22:32 +03:00
Yuri Kuznetsov 00a8c7326d calendar no select 2024-05-24 15:30:52 +03:00
Yuri Kuznetsov 8faa5b91e2 color fixes 2024-05-24 14:21:39 +03:00
Yuri Kuznetsov afeeba80f7 fix css 2024-05-24 14:16:44 +03:00
Yuri Kuznetsov b6daa5accb espo theme colors change 2024-05-23 18:15:48 +03:00
Yuri Kuznetsov 7bf5c9ba11 color change 2024-05-23 13:00:18 +03:00
Yuri Kuznetsov 2433da0db7 theme changes 2024-05-22 19:50:40 +03:00
Yuri Kuznetsov b820c880a7 theme changes 2024-05-22 16:38:28 +03:00
Yuri Kuznetsov c7c31ee73a theme change 2024-05-22 16:13:18 +03:00
Yuri Kuznetsov 18ec05f63e color change 2024-05-22 16:02:14 +03:00
Yuri Kuznetsov cf42c312cf fix style 2024-05-22 10:20:49 +03:00
Yuri Kuznetsov d76bb5677b color fix 2024-05-22 10:12:02 +03:00
Yuri Kuznetsov 9afaf54dea theme changes 2024-05-22 09:43:41 +03:00
Yuri Kuznetsov 99490fdf4c theme changes 2024-05-21 18:59:14 +03:00
Yuri Kuznetsov 244edbbcac cleanup 2024-05-21 14:59:54 +03:00
Yuri Kuznetsov 2fab584c60 color changes 2024-05-21 14:51:39 +03:00
Yuri Kuznetsov 9d797ae17a fix color 2024-05-21 14:43:44 +03:00
Yuri Kuznetsov 15860096d3 rename subscription table 2024-05-21 14:28:09 +03:00
Yuri Kuznetsov c0ac579b7f ref 2024-05-21 12:26:22 +03:00
Yuri Kuznetsov 50d5280d2c button style changes 2024-05-21 10:47:22 +03:00
Yuri Kuznetsov b1c5526286 btn color change 2024-05-21 10:41:48 +03:00
Yuri Kuznetsov 384902f349 violet theme button style change 2024-05-21 10:08:10 +03:00
Yuri Kuznetsov 86e75a4d47 color fix 2024-05-21 09:56:58 +03:00
Yuri Kuznetsov 0401f0cce6 fix overlapping 2024-05-21 09:52:48 +03:00
Yuri Kuznetsov 5b54f1b579 color change 2024-05-20 19:33:02 +03:00
Yuri Kuznetsov 62370b6c8d Merge branch 'fix' 2024-05-20 11:41:43 +03:00
Yuri Kuznetsov edc9d00be3 calendar all day fix 2024-05-20 11:41:31 +03:00
Yuri Kuznetsov 73d4250daa typo 2024-05-18 16:48:58 +03:00
Yuri Kuznetsov aa052560bf cs 2024-05-18 16:47:18 +03:00
Yuri Kuznetsov b35c2ca97f deprecation 2024-05-18 16:46:48 +03:00
Yuri Kuznetsov d2946cacfa fix stream scroll 2024-05-18 13:55:49 +03:00
Yuri Kuznetsov eaaceb74bd stored view impr 2024-05-18 11:47:18 +03:00
Yuri Kuznetsov 2138591b72 fix 2024-05-18 11:33:46 +03:00
Yuri Kuznetsov 3583b586c2 ref 2024-05-18 11:11:05 +03:00
Yuri Kuznetsov 965e5149f3 ref 2024-05-18 10:38:17 +03:00
Yuri Kuznetsov be899bc0e2 reset scroll 2024-05-18 10:33:15 +03:00
Yuri Kuznetsov e23e8854d7 cs 2024-05-18 09:59:14 +03:00
Yuri Kuznetsov 2b977e5456 record dashlets display records int 2024-05-18 09:38:38 +03:00
Yuri Kuznetsov 1c7f65db1a password settings layout change 2024-05-17 16:40:33 +03:00
Yuri Kuznetsov cdda1e0514 typo 2024-05-17 15:00:25 +03:00
Yuri Kuznetsov 9fc955d7b5 portal auth token control 2024-05-17 14:49:39 +03:00
Yuri Kuznetsov 0372374e1f convert lead ui impr 2024-05-17 13:05:48 +03:00
Yuri Kuznetsov c615f0d8db message 2024-05-17 12:52:57 +03:00
Yuri Kuznetsov f4b9b3fd81 ref 2024-05-17 12:52:50 +03:00
Yuri Kuznetsov da06eb9426 messages 2024-05-17 12:14:46 +03:00
Yuri Kuznetsov 2a3f3f2d04 update favicon 2024-05-17 11:28:48 +03:00
Yuri Kuznetsov 19a8535dbe favicon png 2024-05-17 11:23:28 +03:00
Yuri Kuznetsov 363a1c1b06 favicon 2024-05-17 09:48:29 +03:00
Yuri Kuznetsov 5c2b3f707b fix mail link surrounded by quotes 2024-05-17 09:27:04 +03:00
Yuri Kuznetsov 73f88bc097 fix messages 2024-05-16 16:00:20 +03:00
Yuri Kuznetsov 7ebcc6c71a kb active status list 2024-05-16 14:51:55 +03:00
Yuri Kuznetsov af3baef0cb campaign disable custom options 2024-05-16 14:41:46 +03:00
Yuri Kuznetsov 14ea8886f6 document active statuses 2024-05-16 13:57:20 +03:00
Yuri Kuznetsov 852977d5db schema 2024-05-16 13:50:30 +03:00
Yuri Kuznetsov 4178406453 ref 2024-05-16 13:49:09 +03:00
Yuri Kuznetsov 149649be09 fix 2024-05-16 12:58:15 +03:00
Yuri Kuznetsov 2a17155f96 force assignment notifications 2024-05-16 12:48:46 +03:00
Yuri Kuznetsov d9488ba71c ref 2024-05-16 12:42:43 +03:00
Yuri Kuznetsov 275c3aa492 remove addressCountryList param 2024-05-15 14:56:22 +03:00
Yuri Kuznetsov 9f5975eb5b autocomplete in modal fix 2024-05-15 14:52:44 +03:00
Yuri Kuznetsov a1d5174372 address country list 2024-05-15 14:12:29 +03:00
Yuri Kuznetsov 547b77423a varchar custom autocomplete lookup function 2024-05-15 12:56:40 +03:00
Yuri Kuznetsov a2c81e5891 orm reportsitory delete from db if no deleted 2024-05-15 10:49:36 +03:00
Yuri Kuznetsov f1070612b4 ref cs 2024-05-15 10:43:17 +03:00
Yuri Kuznetsov c5828a9feb schema 2024-05-15 10:30:10 +03:00
Yuri Kuznetsov 62b2314ff3 address fix params 2024-05-14 14:07:46 +03:00
Yuri Kuznetsov dce9c437bf ref 2024-05-14 11:52:26 +03:00
Yuri Kuznetsov e5a012cdc7 7_2 migration 2024-05-13 12:23:32 +03:00
Yuri Kuznetsov 17f18e36fd migration scripts 2024-05-13 10:56:52 +03:00
Yuri Kuznetsov e99fca0252 group email account exclude from reply 2024-05-13 10:01:50 +03:00
Yuri Kuznetsov 316a865d02 imap notify wait period 2024-05-12 16:28:25 +03:00
Yuri Kuznetsov 0f6049b36f notify when imap not connected 2024-05-12 12:33:58 +03:00
Yuri Kuznetsov 63771d37b3 email mark as read from list view 2024-05-11 13:28:21 +03:00
Yuri Kuznetsov 9e50367303 ref 2024-05-11 13:20:57 +03:00
Yuri Kuznetsov 71b0dfec04 added validation faulure explanations 2024-05-11 10:19:49 +03:00
Yuri Kuznetsov 90c692648d validation failures changes 2024-05-11 10:19:30 +03:00
Yuri Kuznetsov e968424b9d ref 2024-05-11 10:03:11 +03:00
Yuri Kuznetsov ed77c8758c imap error exception 2024-05-10 14:39:27 +03:00
Yuri Kuznetsov caa536f17a fix logging level 2024-05-10 14:05:32 +03:00
Yuri Kuznetsov 7463caf8ee ref 2024-05-10 13:48:22 +03:00
Yuri Kuznetsov d4096b8dfb stream view list modal pagination 2024-05-10 13:24:09 +03:00
Yuri Kuznetsov f73e4f2b47 Merge branch 'fix' 2024-05-10 12:52:40 +03:00
Yuri Kuznetsov fc93a3d029 fix empty template 2024-05-10 12:52:35 +03:00
Yuri Kuznetsov 0bcbfdd223 fix empty template 2024-05-10 12:51:53 +03:00
Yuri Kuznetsov 6b1f8de16a fix typo 2024-05-10 10:56:14 +03:00
Yuri Kuznetsov 64741be18b fix typo 2024-05-10 10:55:26 +03:00
Yuri Kuznetsov 8ad8a42ecd docs 2024-05-10 10:53:10 +03:00
Yuri Kuznetsov ead2d3829b docs 2024-05-10 10:51:12 +03:00
Yuri Kuznetsov 55be65d48c oidc authentication prompt param in UI 2024-05-10 10:17:25 +03:00
Yuri Kuznetsov ecee6e7477 authentication layout change 2024-05-10 10:01:15 +03:00
Yuri Kuznetsov c037870e7e tooltip 2024-05-10 09:53:09 +03:00
Yuri Kuznetsov e86c4a9b6b ip address whitelist 2024-05-09 19:21:19 +03:00
Yuri Kuznetsov 586e28bd26 ref 2024-05-09 17:05:47 +03:00
Yuri Kuznetsov 3ca7cdffcc fix message 2024-05-09 16:39:38 +03:00
Yuri Kuznetsov d36dc4bc1c fix docs 2024-05-09 16:38:32 +03:00
Yuri Kuznetsov ede53970bf ref 2024-05-09 16:37:43 +03:00
Yuri Kuznetsov 0348778bc1 ref 2024-05-09 16:19:48 +03:00
Yuri Kuznetsov 3e2a3597ad schema 2024-05-09 16:02:28 +03:00
Yuri Kuznetsov 1c5afb52b3 schema 2024-05-09 15:32:35 +03:00
Yuri Kuznetsov 8601c8fc24 color changes 2024-05-09 13:00:48 +03:00
Yuri Kuznetsov 5b3b7b583d color fix 2024-05-09 12:10:28 +03:00
Yuri Kuznetsov 9f0827e478 ref 2024-05-09 11:56:59 +03:00
Yuri Kuznetsov a0858acae3 color fix 2024-05-09 11:54:10 +03:00
Yuri Kuznetsov 49a49d6103 cs 2024-05-09 10:51:59 +03:00
Yuri Kuznetsov 7d9d248ab7 Merge branch 'fix' 2024-05-09 10:51:32 +03:00
Yuri Kuznetsov acc0b71a7a fix url decode 2024-05-09 10:50:41 +03:00
Yuri Kuznetsov 1f3a0bb5dd allow tilde in URL 2024-05-09 10:35:24 +03:00
Yuri Kuznetsov c5611a691f link multiple autocomplete fix 2024-05-08 21:19:21 +03:00
Yuri Kuznetsov 72927e34e2 autocomplete fix 2024-05-08 21:02:00 +03:00
Yuri Kuznetsov 6c2a114c47 ref 2024-05-08 12:53:15 +03:00
Yuri Kuznetsov edb5f30ddd Merge branch 'fix' 2024-05-08 11:41:47 +03:00
Yuri Kuznetsov 94881de082 8.2.4 2024-05-08 11:32:01 +03:00
Yuri Kuznetsov ce0baf450e light logo color change 2024-05-08 10:08:55 +03:00
Yuri Kuznetsov 3854141292 do not update pinned collection if the same 2024-05-08 09:33:06 +03:00
Yuri Kuznetsov 152bc76e57 sync pinned on websocket update 2024-05-08 09:28:56 +03:00
Yuri Kuznetsov 6d01717f7e style fix 2024-05-07 23:07:42 +03:00
Yuri Kuznetsov b026ff8066 pinned icon 2024-05-07 23:04:48 +03:00
Yuri Kuznetsov f9270a20d4 pin note check max count > 0 2024-05-07 22:46:15 +03:00
Yuri Kuznetsov 5196343479 pinned node sync more fields 2024-05-07 22:42:02 +03:00
Yuri Kuznetsov 3606051b91 entity manager columns swap 2024-05-07 19:41:23 +03:00
Yuri Kuznetsov 3fc4b487b3 pinned notes 2024-05-07 18:15:52 +03:00
Yuri Kuznetsov 692e0b43e3 ref 2024-05-07 11:38:18 +03:00
Yuri Kuznetsov c273e26cf9 colorpicker style improvements 2024-05-06 17:01:32 +03:00
Yuri Kuznetsov a26a48fac7 wysiwyg cell params 2024-05-06 15:40:37 +03:00
Yuri Kuznetsov 2a42ab8db5 wysiwyg table params 2024-05-06 14:34:54 +03:00
Yuri Kuznetsov 16d66aa642 ref 2024-05-05 10:08:54 +03:00
Yuri Kuznetsov 85d167aad4 Merge branch 'fix' 2024-05-04 23:03:14 +03:00
Yuri Kuznetsov cbec1cbbe5 fix parser 2024-05-04 23:00:46 +03:00
Yuri Kuznetsov 1509f8d18f exception levels 2024-05-04 14:57:12 +03:00
Yuri Kuznetsov e6d618f142 job fail critical log 2024-05-04 14:52:06 +03:00
Yuri Kuznetsov 455c51e806 ref 2024-05-04 14:47:39 +03:00
Yuri Kuznetsov d9f553d47f formula error fix 2024-05-04 14:39:31 +03:00
Yuri Kuznetsov 5dc08ecf05 fix formula errors 2024-05-04 14:38:43 +03:00
Yuri Kuznetsov 2d791010dc log previous 2024-05-04 14:38:11 +03:00
Yuri Kuznetsov cc8e2511e0 fix bubbling 2024-05-04 14:26:02 +03:00
Yuri Kuznetsov 92b0af358e fix log messages 2024-05-04 14:09:45 +03:00
Yuri Kuznetsov 9526614a3d fix message 2024-05-04 14:05:23 +03:00
Yuri Kuznetsov 9fca726329 fix legacy 2024-05-04 13:59:48 +03:00
Yuri Kuznetsov 83a9f53fe3 action history record bigint 2024-05-04 13:38:28 +03:00
Yuri Kuznetsov da27b68312 messages 2024-05-04 13:37:44 +03:00
Yuri Kuznetsov d2e90f7853 schema 2024-05-04 13:23:54 +03:00
Yuri Kuznetsov 0f7cd11ffe Merge branch 'f/log' 2024-05-04 13:09:11 +03:00
Yuri Kuznetsov b006349528 app log dev 2024-05-04 13:05:08 +03:00
Yuri Kuznetsov f03637ac64 type fix 2024-05-04 09:29:24 +03:00
Yuri Kuznetsov e0909af3fe app log dev 2024-05-03 18:33:47 +03:00
Yuri Kuznetsov 1ed38e28cd side panel date fields if no by field 2024-05-03 17:35:50 +03:00
Yuri Kuznetsov 567db6b204 linkOnlyNotLinked param 2024-05-03 13:05:17 +03:00
Yuri Kuznetsov 04689b8ed0 user attribute safe 2024-05-03 10:27:22 +03:00
Yuri Kuznetsov c007010a73 bar argument value exception message 2024-05-03 10:05:27 +03:00
Yuri Kuznetsov 498dd5478b ref 2024-05-03 09:52:59 +03:00
Yuri Kuznetsov 4e9e71b1bb userAttribute forbid list 2024-05-02 20:39:13 +03:00
Yuri Kuznetsov 06771f2f3e unsafe functions 2024-05-02 20:05:40 +03:00
Yuri Kuznetsov bafe2dd471 formula base64 functions 2024-05-02 18:39:54 +03:00
Yuri Kuznetsov 4591d8f634 formula acl functions 2024-05-02 18:03:09 +03:00
Yuri Kuznetsov f1e42f931b ref 2024-05-02 13:20:40 +03:00
Yuri Kuznetsov 8cf31f17cb email account active filter 2024-05-02 12:24:57 +03:00
Yuri Kuznetsov 40c8d0063d fill assigned user if read level is own 2024-05-02 10:59:58 +03:00
Yuri Kuznetsov 157f03b38f imap test connection error 2024-05-02 10:26:47 +03:00
Yuri Kuznetsov ffa5cf44ea load parent fields fix 2024-05-02 10:10:12 +03:00
Yuri Kuznetsov cc88ab9290 ref 2024-05-02 10:07:00 +03:00
dependabot[bot] 1a179c2373 Bump ejs from 3.1.8 to 3.1.10
Bumps [ejs](https://github.com/mde/ejs) from 3.1.8 to 3.1.10.
- [Release notes](https://github.com/mde/ejs/releases)
- [Commits](https://github.com/mde/ejs/compare/v3.1.8...v3.1.10)

---
updated-dependencies:
- dependency-name: ejs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-02 09:08:46 +03:00
Yuri Kuznetsov 066f815d0c dashblet dropdown overflow fix 2024-05-01 13:37:04 +03:00
Yuri Kuznetsov 8078043f3c ref 2024-05-01 13:03:39 +03:00
Yuri Kuznetsov 06de056bb5 fix kanban dropdown overflow 2024-05-01 12:59:33 +03:00
Yuri Kuznetsov 630bdd7885 change start img 2024-04-30 22:15:08 +03:00
Yuri Kuznetsov 847440ad6c change install image 2024-04-30 15:49:51 +03:00
Yuri Kuznetsov 0271deddde fix test 2024-04-30 14:25:46 +03:00
Yuri Kuznetsov 65a385dfcf Merge branch 'fix' 2024-04-30 14:18:11 +03:00
Yuri Kuznetsov 71dd872618 calendar duplicate event fix 2024-04-30 14:15:39 +03:00
Yuri Kuznetsov 8025c1e101 mention frontend fix 2024-04-30 12:02:48 +03:00
Yuri Kuznetsov fe15332531 ref 2024-04-29 19:25:16 +03:00
Yuri Kuznetsov 508699d0aa bool 2024-04-29 19:24:25 +03:00
Yuri Kuznetsov 0465f826f1 fix test 2024-04-29 15:26:21 +03:00
Yuri Kuznetsov f78d38d592 migration script for reminders 2024-04-28 20:09:53 +03:00
Yuri Kuznetsov db7142e4f9 reminded assigned users 2024-04-28 17:34:45 +03:00
Yuri Kuznetsov e1b4fd6bdb reminderMaxCount 2024-04-28 14:43:49 +03:00
Yuri Kuznetsov 483ffb91fa ref 2024-04-28 14:33:23 +03:00
Yuri Kuznetsov 76a5721c72 skip past reminders 2024-04-28 14:27:24 +03:00
Yuri Kuznetsov 27c5bec755 reminders only own user 2024-04-28 14:15:39 +03:00
Yuri Kuznetsov 8e3016e301 rename 2024-04-28 12:37:50 +03:00
Yuri Kuznetsov d93f08b18e reminder type fixes 2024-04-28 12:36:56 +03:00
Yuri Kuznetsov 701abe129b reminder links 2024-04-28 12:31:13 +03:00
Yuri Kuznetsov e585ef0a0d ref 2024-04-28 10:10:53 +03:00
Yuri Kuznetsov beaafccd2a log formatter interpolation. new line 2024-04-28 10:10:41 +03:00
Yuri Kuznetsov cad2e0078a log refactoring 2024-04-27 20:21:42 +03:00
Yuri Kuznetsov 3dc35d0f16 formula log functions 2024-04-27 17:17:22 +03:00
Yuri Kuznetsov 2d73c62936 ref 2024-04-27 12:48:36 +03:00
Yuri Kuznetsov dad5c3724e ref 2024-04-27 12:00:56 +03:00
Yuri Kuznetsov d0f3373515 ref 2024-04-26 13:37:03 +03:00
Yuri Kuznetsov bbe578ce0a ref 2024-04-26 11:54:11 +03:00
Yuri Kuznetsov 51a8a3a302 jsdocs deprecation 2024-04-26 11:25:19 +03:00
Yuri Kuznetsov e3d3cae647 Merge branch 'fix' 2024-04-26 11:09:17 +03:00
Yuri Kuznetsov bfed154feb language load off 2024-04-26 11:01:54 +03:00
Yuri Kuznetsov bbe9281cb0 css fix, border radius 2024-04-25 21:32:30 +03:00
Yuri Kuznetsov b2a6ababd2 note acl query with created at 2024-04-25 16:50:08 +03:00
Yuri Kuznetsov 343ad33a78 noteAclPeriod only admin 2024-04-25 16:35:41 +03:00
Yuri Kuznetsov 0a51e27d57 ref 2024-04-25 16:21:54 +03:00
Yuri Kuznetsov dc1dc5de9b noteAclLimit adminOnly 2024-04-25 16:12:30 +03:00
Yuri Kuznetsov 868aa64e51 ref docs 2024-04-25 16:07:03 +03:00
Yuri Kuznetsov 0a9c0dcb20 fix doc 2024-04-25 16:04:33 +03:00
Yuri Kuznetsov c1855e84a9 ref 2024-04-25 15:43:29 +03:00
Yuri Kuznetsov 4aa4b17c9f ref 2024-04-25 14:42:22 +03:00
Yuri Kuznetsov 689d59df2e ref 2024-04-25 14:26:02 +03:00
Yuri Kuznetsov 6fd55a6c2d quickSearchFullTextAppendWildcard 2024-04-25 13:40:25 +03:00
Yuri Kuznetsov ae141c2415 no distinct 2024-04-25 13:25:13 +03:00
Yuri Kuznetsov ab88abfb5e test filter applier ref and fix 2024-04-25 13:13:56 +03:00
Yuri Kuznetsov 90ab1de10f ref 2024-04-25 11:05:31 +03:00
Yuri Kuznetsov e3bf337a07 calendar restrict drop 2024-04-25 10:47:26 +03:00
Yuri Kuznetsov 69329a87db rename 2024-04-24 13:45:47 +03:00
Yuri Kuznetsov 663eacb962 formula single quote fix 2024-04-24 13:45:24 +03:00
Yuri Kuznetsov 38ce78fced formula fix string double backslash and quote 2024-04-24 13:33:52 +03:00
Yuri Kuznetsov ddf904fa0f formula backslash escaping 2024-04-24 13:22:22 +03:00
Yuri Kuznetsov eba3138d16 formula string backslashes 2024-04-23 21:51:23 +03:00
Yuri Kuznetsov 05227281e7 style fix 2024-04-23 19:08:52 +03:00
Yuri Kuznetsov 16672722d4 has-attachment change 2024-04-23 19:07:00 +03:00
Yuri Kuznetsov 7fcc80025c ref 2024-04-23 18:53:02 +03:00
Yuri Kuznetsov da264fcc71 navbar hide last divider 2024-04-23 18:49:42 +03:00
Yuri Kuznetsov 2b6bf419a1 fix add custom tabs 2024-04-23 18:10:50 +03:00
Yuri Kuznetsov 310f83e9c2 update navbar on preferences 2024-04-23 18:09:49 +03:00
Yuri Kuznetsov 45cdae005a add custom tabs 2024-04-23 17:43:09 +03:00
Yuri Kuznetsov 15b4ce9657 move migration 2024-04-23 17:09:26 +03:00
Yuri Kuznetsov 665dd2f242 mention permission 2024-04-23 16:58:46 +03:00
Yuri Kuznetsov ecde690c3a post field ref 2024-04-23 16:56:57 +03:00
Yuri Kuznetsov 90728dfc18 autocomplete abort last fetch 2024-04-23 16:05:53 +03:00
Yuri Kuznetsov d519318804 fix jsdocs 2024-04-23 15:45:15 +03:00
Yuri Kuznetsov 0d8a5ce5a1 skip full text search if request is too short and no operators 2024-04-23 15:07:39 +03:00
Yuri Kuznetsov 3dc29b1bae fix 2024-04-22 14:42:37 +03:00
Yuri Kuznetsov 0819b69d4a email inbox alias 2024-04-22 13:59:01 +03:00
Yuri Kuznetsov 8ac3d4e90b ref tests 2024-04-22 13:42:05 +03:00
Yuri Kuznetsov a577950cc0 lead capture loadAdditionalFieldsAfterUpdate 2024-04-22 13:29:28 +03:00
Yuri Kuznetsov 3cf6edd051 Merge branch 'fix' 2024-04-22 12:38:47 +03:00
Yuri Kuznetsov 5a19b90b13 fix role empty stream level on ui 2024-04-22 12:38:33 +03:00
Yuri Kuznetsov daa3980122 fix role empty stream level on ui 2024-04-22 12:38:12 +03:00
Yuri Kuznetsov bab460c521 account name audited 2024-04-21 16:38:39 +03:00
Yuri Kuznetsov 64b3f55d7a email filter lower 2024-04-21 14:38:54 +03:00
Yuri Kuznetsov ca053b57c5 orm: custom where value in complex expresssion, fix string expression unescaping 2024-04-21 14:37:40 +03:00
Yuri Kuznetsov a07f6b032a sanitizer suppress list, allow empty attachment 2024-04-20 21:47:38 +03:00
Yuri Kuznetsov ad0fbb577f ref 2024-04-20 21:34:49 +03:00
Yuri Kuznetsov df55323a04 attachment multiple audited 2024-04-20 20:54:48 +03:00
Yuri Kuznetsov a081237b2e link multiple maxCount 2024-04-20 20:50:03 +03:00
Yuri Kuznetsov b604501eba fix 2024-04-20 20:48:15 +03:00
Yuri Kuznetsov b4368862e0 fix readme 2024-04-20 18:06:28 +03:00
Yuri Kuznetsov 81043740dc fix readme 2024-04-20 18:02:24 +03:00
Yuri Kuznetsov b6beb39192 update readme 2024-04-20 18:01:05 +03:00
Yurii Kuznietsov 687f66908d Update feature_request.md 2024-04-20 16:44:36 +03:00
Yuri Kuznetsov a5ef17e06c login view setup handler 2024-04-20 15:42:45 +03:00
Yuri Kuznetsov cb8a43da52 cron/cache admin notifications 2024-04-20 15:28:53 +03:00
Yuri Kuznetsov a575a69704 move 2024-04-20 15:20:18 +03:00
Yuri Kuznetsov 7d6b1d7bcf glass theme style fix 2024-04-20 11:57:44 +03:00
Yuri Kuznetsov 31b875bbdd Merge branch 'fix' 2024-04-20 11:33:33 +03:00
Yuri Kuznetsov 33b0ca8824 fix add dashlet 2024-04-20 11:32:41 +03:00
Yuri Kuznetsov e7d388f55f navbar event off 2024-04-20 11:29:04 +03:00
Yuri Kuznetsov 559ef609e0 navbar resize fix 2024-04-20 11:19:23 +03:00
Yuri Kuznetsov c382cb0e7b npm updates 2024-04-20 10:54:39 +03:00
Yuri Kuznetsov 3912f937bd update summernote 2024-04-20 10:54:02 +03:00
Yuri Kuznetsov 7222cd6436 Merge branch 'fix' 2024-04-20 08:35:44 +03:00
Yuri Kuznetsov 540c58a564 fix add dashlet quick seach 2024-04-20 08:35:16 +03:00
Yuri Kuznetsov 6f7c1f72f6 fix add dashlet quick seach 2024-04-20 08:34:58 +03:00
Yuri Kuznetsov 5bf4ab368d ref 2024-04-19 14:55:49 +03:00
Yuri Kuznetsov 42645a9bc1 permission consts 2024-04-19 14:33:54 +03:00
Yuri Kuznetsov 5be0f5c71e cs 2024-04-19 14:18:51 +03:00
Yuri Kuznetsov f029675856 acl permission consts 2024-04-19 14:18:27 +03:00
Yuri Kuznetsov 3a1f5bec0f default team ui impr 2024-04-19 12:31:48 +03:00
Yuri Kuznetsov 1356e7b9f8 jsdoc 2024-04-19 12:30:19 +03:00
Yuri Kuznetsov ab10b6c036 validate user default team 2024-04-19 12:00:03 +03:00
Yuri Kuznetsov ed0e5112a3 disable layout for avatar fields 2024-04-19 11:46:25 +03:00
Yuri Kuznetsov 46da1bb8a0 permission string fix 2024-04-19 11:42:37 +03:00
Yuri Kuznetsov cd216007af assigned to self and default team if no assignment permission 2024-04-19 11:28:01 +03:00
Yuri Kuznetsov bd96cf9e73 Merge branch 'fix' 2024-04-19 10:42:18 +03:00
Yuri Kuznetsov 67c0be5699 8.2.3 2024-04-19 10:21:05 +03:00
Yuri Kuznetsov 695dc2eb42 record service create: keep new 2024-04-19 09:30:49 +03:00
Yuri Kuznetsov 516bd037e7 jsdoc 2024-04-18 19:46:18 +03:00
Yuri Kuznetsov f122d3d3bb jsdocs 2024-04-18 19:33:00 +03:00
Yuri Kuznetsov e7b64bcc6f jsdocs 2024-04-18 19:24:07 +03:00
Yuri Kuznetsov 3df7349d2b cs ref 2024-04-18 19:14:29 +03:00
Yuri Kuznetsov 47b6471717 jsdoc 2024-04-18 19:12:01 +03:00
Yuri Kuznetsov 95280e8f7b jsdocs 2024-04-18 18:58:17 +03:00
Yuri Kuznetsov 296f73a407 jsdoc 2024-04-18 18:47:29 +03:00
Yuri Kuznetsov 918faa340b jsdoc, cs 2024-04-18 18:38:26 +03:00
Yuri Kuznetsov 4526224ae4 avatar color 2024-04-18 16:50:31 +03:00
Yuri Kuznetsov e35b8e799f colorpicker field change 2024-04-18 16:49:49 +03:00
Yuri Kuznetsov 06fffdf1cf colorpicker change 2024-04-18 16:20:27 +03:00
Yuri Kuznetsov ac133fb257 dynamic logic changes 2024-04-18 15:56:26 +03:00
Yuri Kuznetsov 2ab3274768 formula parser +/- after operator 2024-04-18 14:20:00 +03:00
Yuri Kuznetsov b7f22352ed cs 2024-04-18 12:36:15 +03:00
Yuri Kuznetsov 7873340616 rename Ranges label 2024-04-18 12:00:06 +03:00
Yuri Kuznetsov 546a0440cd isFollowed bool 2024-04-18 11:01:49 +03:00
Yuri Kuznetsov 9783271a1d comment 2024-04-18 11:01:26 +03:00
Yuri Kuznetsov 35ad3177d3 cleanup 2024-04-18 10:47:02 +03:00
Yuri Kuznetsov 06b6be0c9e ref 2024-04-18 10:43:57 +03:00
Yuri Kuznetsov a390d71ef0 complex expr field impr 2024-04-18 10:36:09 +03:00
Yuri Kuznetsov 39dafddb4b field-manager impr 2024-04-18 10:35:59 +03:00
Yuri Kuznetsov 2706f8681f ref, comment 2024-04-18 10:05:07 +03:00
Yuri Kuznetsov 44babbc240 cleanup stars 2024-04-17 16:56:43 +03:00
Yuri Kuznetsov 2fe1403cdb schema 2024-04-17 16:06:51 +03:00
Yuri Kuznetsov 665bfc3649 comment 2024-04-17 16:03:22 +03:00
Yuri Kuznetsov dd3746d435 move to folder header text 2024-04-17 14:30:08 +03:00
Yuri Kuznetsov b881c11672 star limit 2024-04-17 14:19:40 +03:00
Yuri Kuznetsov 2c0370f546 on empty autocomplete promise 2024-04-17 11:51:23 +03:00
Yuri Kuznetsov 6e1b3c64ec starred filter in entity manager list 2024-04-17 10:40:40 +03:00
Yuri Kuznetsov 17c3fe5699 enable stars, schema 2024-04-17 10:27:56 +03:00
Yuri Kuznetsov 8d20ba1ffd stars dev and change 2024-04-17 10:10:22 +03:00
Yuri Kuznetsov 0d124269f0 star button wider 2024-04-17 09:55:24 +03:00
Yuri Kuznetsov 1fcf326ecc btn style fix 2024-04-17 09:21:07 +03:00
Yuri Kuznetsov 76397085a3 star dev 2024-04-16 19:20:59 +03:00
Yuri Kuznetsov 1b4bdfaa12 note email sent when from user 2024-04-16 14:50:45 +03:00
Yuri Kuznetsov 040e6ddeb7 cs, cleanup 2024-04-16 14:30:43 +03:00
Yuri Kuznetsov d148b53d52 schema 2024-04-16 14:11:29 +03:00
Yuri Kuznetsov 90f125567c autcomplete own teams 2024-04-16 14:10:04 +03:00
Yuri Kuznetsov 8cc72d1c7b empty query autocomplete fix, no value setting 2024-04-16 13:39:37 +03:00
Yuri Kuznetsov 747c1bbf12 getEmptyAutocompleteResult ref, fix 2024-04-16 12:51:12 +03:00
Yuri Kuznetsov 22b5f89991 docs fix 2024-04-16 12:45:33 +03:00
Yuri Kuznetsov d19de50f7f Merge branch 'fix' 2024-04-16 12:38:20 +03:00
Yuri Kuznetsov d1dd39bf5a email parent allow autocomplete on empty 2024-04-16 12:38:03 +03:00
Yuri Kuznetsov d07f5b8e18 email parent allow autocomplete on empty 2024-04-16 12:37:33 +03:00
Yuri Kuznetsov 9fa9440d7b cleanup 2024-04-16 10:30:52 +03:00
Yuri Kuznetsov 042b3ae66e do not log exception file/line if created with static 2024-04-16 09:58:59 +03:00
Yuri Kuznetsov c07004faf4 add method 2024-04-15 21:38:57 +03:00
Yuri Kuznetsov b3905eedf4 notification list ux impr 2024-04-15 20:27:14 +03:00
Yuri Kuznetsov 9cffccbe73 ref 2024-04-15 18:03:05 +03:00
Yuri Kuznetsov 43322186b5 acl getPermissionLevel 2024-04-15 18:01:55 +03:00
Yuri Kuznetsov a0ab01a77f ref 2024-04-15 17:57:24 +03:00
Yuri Kuznetsov d7b9b65990 application id in body tag 2024-04-15 16:33:11 +03:00
Yuri Kuznetsov e23a22d259 change app id 2024-04-15 16:28:56 +03:00
Yuri Kuznetsov 5d946a44f6 kanban isMuted 2024-04-15 13:23:41 +03:00
Yuri Kuznetsov 437f12245e date time field style fix 2024-04-15 12:49:59 +03:00
Yuri Kuznetsov b1cefdfbbe datetime input group style fix 2024-04-14 16:08:31 +03:00
Yuri Kuznetsov 857ee7fb98 style fix 2024-04-13 09:56:03 +03:00
Yuri Kuznetsov 3b68d3dd13 css fix 2024-04-13 09:46:12 +03:00
Yuri Kuznetsov 4125b4f9a0 colors 2024-04-11 17:33:03 +03:00
Yuri Kuznetsov 913cad075a Merge branch 'fix' 2024-04-11 17:31:31 +03:00
Yuri Kuznetsov 8a542adb7e calendar update class names 2024-04-11 17:31:14 +03:00
Yuri Kuznetsov 481f352f5a calendar update class names 2024-04-11 17:30:37 +03:00
Yuri Kuznetsov 8ce2c7a40b light theme calendar colors 2024-04-11 17:19:20 +03:00
Yuri Kuznetsov 8dda7cf2ea link parent list change 2024-04-11 14:52:44 +03:00
Yuri Kuznetsov 01eacf875d list link mode color default 2024-04-11 14:28:38 +03:00
Yuri Kuznetsov 6da9ac6af1 enum label type 2024-04-11 13:17:07 +03:00
Yuri Kuznetsov 418ad2776d wysiwyg attributes order preserving 2024-04-11 12:10:46 +03:00
Yuri Kuznetsov 2a2c07af87 cleanup 2024-04-11 11:55:15 +03:00
Yuri Kuznetsov ca898cf240 ref 2024-04-11 11:29:48 +03:00
Yuri Kuznetsov 05b6f83ba1 style fix 2024-04-11 11:00:39 +03:00
Yuri Kuznetsov 68ccaf8818 activities dashlet fix refresh 2024-04-11 10:53:59 +03:00
Yuri Kuznetsov 162e0ab546 ref 2024-04-11 10:32:59 +03:00
Yuri Kuznetsov 4c9df4da1b activities panel changes 2024-04-11 10:26:39 +03:00
Yuri Kuznetsov d1c2e82abc wysiwyg fetch empty as null 2024-04-10 23:08:10 +03:00
Yuri Kuznetsov c054cde199 currency fix 2024-04-10 23:03:16 +03:00
Yuri Kuznetsov f113905edd cleanup 2024-04-10 22:50:45 +03:00
Yuri Kuznetsov b4e2b91c31 ref 2024-04-10 22:48:11 +03:00
Yuri Kuznetsov 3568345343 no join loader load all links 2024-04-10 14:35:32 +03:00
Yuri Kuznetsov 4f1223e9a0 Merge branch 'fix' 2024-04-10 13:23:38 +03:00
Yuri Kuznetsov c6a172a6d0 style fix 2024-04-10 13:23:27 +03:00
Yuri Kuznetsov 1da70019fb fix tests 2024-04-10 11:09:43 +03:00
Yuri Kuznetsov a84962d96d formula func variable aware 2024-04-10 10:54:33 +03:00
Yuri Kuznetsov 98253fb0a8 customizable check 2024-04-10 10:09:01 +03:00
Yuri Kuznetsov 7887e4c7e3 style fix 2024-04-09 21:23:28 +03:00
Yuri Kuznetsov abe383d532 call rows actions fix 2024-04-09 20:56:51 +03:00
Yuri Kuznetsov a8078c3fc6 row actions dividers 2024-04-09 20:35:36 +03:00
Yuri Kuznetsov 9fd07d2fab notify usage 2024-04-09 19:42:36 +03:00
Yuri Kuznetsov f42fbd0098 ref 2024-04-09 19:40:38 +03:00
Yuri Kuznetsov d431699dbe css fix 2024-04-09 16:51:50 +03:00
Yuri Kuznetsov 9b883d3e59 convert currency item order 2024-04-09 16:45:39 +03:00
Yuri Kuznetsov 955e53304f modal dropdown dividers 2024-04-09 16:14:26 +03:00
Yuri Kuznetsov ed7085e952 detail view dropdown dividers 2024-04-09 15:55:46 +03:00
Yuri Kuznetsov e274907260 schema 2024-04-09 15:29:00 +03:00
Yuri Kuznetsov 96520162d3 link load names test 2024-04-09 13:34:41 +03:00
Yuri Kuznetsov bb091a0301 foreign attributes unset and foregn names load 2024-04-09 13:07:45 +03:00
Yuri Kuznetsov 7253082497 console command allowed flags 2024-04-09 11:34:16 +03:00
Yuri Kuznetsov 0e446c6f08 console allowed options 2024-04-09 11:25:24 +03:00
Yuri Kuznetsov 7af208c2a0 after-upgrade pass isUpgrade 2024-04-09 10:49:00 +03:00
Yuri Kuznetsov 60e0c03a1e Merge branch 'fix' 2024-04-09 10:43:12 +03:00
Yuri Kuznetsov 89f75bfb37 oidc prevent errors when user is not allowed 2024-04-09 09:24:23 +03:00
Yuri Kuznetsov b83a66cf49 bg lang 2024-04-08 12:51:16 +03:00
Yuri Kuznetsov 3cd6deb732 pagination previous step and modal detail pagination fix 2024-04-08 12:07:14 +03:00
Yuri Kuznetsov 59fff4cc80 fix detail pagination 2024-04-08 11:40:57 +03:00
Yuri Kuznetsov 35e7b5c5ee move 2024-04-07 18:50:17 +03:00
Yuri Kuznetsov e7415a2317 ref 2024-04-07 18:44:44 +03:00
Yuri Kuznetsov 69c5af4d97 Merge branch 'fix' 2024-04-07 18:31:07 +03:00
Yuri Kuznetsov dca1d34685 reminder usersColumns check 2024-04-07 18:30:55 +03:00
Yuri Kuznetsov 105922b026 email create filter from address 2024-04-07 17:18:48 +03:00
Yuri Kuznetsov 1dd6ea1bac jsdocs 2024-04-07 17:06:16 +03:00
Yuri Kuznetsov 54f1fb27a1 email filter change layout 2024-04-07 15:13:43 +03:00
Yuri Kuznetsov 27353b1fbf attendeeLinkMap 2024-04-07 13:26:37 +03:00
Yuri Kuznetsov afc34a9730 add docs 2024-04-07 11:36:09 +03:00
Yuri Kuznetsov 5b4e3cbc66 cleanup 2024-04-07 11:27:21 +03:00
Yuri Kuznetsov 5b763b7519 rename 2024-04-07 11:27:01 +03:00
Yuri Kuznetsov fb07cec466 fix 2024-04-07 11:26:20 +03:00
Yuri Kuznetsov 04ea13d8e2 list view docs 2024-04-07 11:26:15 +03:00
Yuri Kuznetsov 9208775f34 list view focus 2024-04-06 18:04:14 +03:00
Yuri Kuznetsov 308c480317 migrations beta versions 2024-04-06 16:16:36 +03:00
Yuri Kuznetsov 85af141f9f Merge branch 'f/migrations' 2024-04-06 12:46:04 +03:00
Yuri Kuznetsov d7a88b382d migrations fix and dev 2024-04-06 12:45:55 +03:00
Yuri Kuznetsov 8133682288 ref 2024-04-06 09:58:48 +03:00
Yuri Kuznetsov 0b035b89c0 user fetch issue 2024-04-06 09:55:56 +03:00
Yuri Kuznetsov d5930542d9 ref 2024-04-06 09:51:56 +03:00
Yuri Kuznetsov 831dfc5bfd fix sndAccessInfo fetch 2024-04-06 09:51:48 +03:00
Yuri Kuznetsov 078808604c ref 2024-04-06 09:38:39 +03:00
Yuri Kuznetsov 35f8c5652a avater flicker fix 2024-04-06 09:35:25 +03:00
Yuri Kuznetsov 14d8f88985 ref 2024-04-06 09:32:17 +03:00
Yuri Kuznetsov 2ae0a118b6 disable note in link manager 2024-04-05 23:11:44 +03:00
Yuri Kuznetsov 1a51759b0f fix user acl 2024-04-05 20:18:21 +03:00
Yuri Kuznetsov acc2eb1a55 migrations dev 2024-04-05 15:48:20 +03:00
Yuri Kuznetsov e513a72504 migratinos dev 2024-04-05 14:58:53 +03:00
Yuri Kuznetsov a8fa8bfa00 ref 2024-04-05 14:14:31 +03:00
Yuri Kuznetsov e8473e5ce0 migrations dev 2024-04-05 13:34:49 +03:00
Yuri Kuznetsov 5a6583b93b migrations dev 2024-04-05 13:19:43 +03:00
Yuri Kuznetsov 977b9adeff migrations dev 2024-04-05 12:17:00 +03:00
Yuri Kuznetsov 382ba22cd7 fix command test 2024-04-05 11:25:23 +03:00
Yuri Kuznetsov 03e16b81e5 upgrade ref and test fixes 2024-04-05 11:21:09 +03:00
Yuri Kuznetsov 455bb2a298 migrations ref 2024-04-05 11:01:02 +03:00
Yuri Kuznetsov 0b19bd4bb5 upgrade ref 2024-04-05 10:47:10 +03:00
Yuri Kuznetsov 48b0063c69 middle table prefix server side check 2024-04-05 09:51:22 +03:00
Yuri Kuznetsov 9395fa886e grid layout css fix 2024-04-05 09:39:22 +03:00
Yuri Kuznetsov a6c95ff0d5 layouts title 2024-04-05 09:37:10 +03:00
Yuri Kuznetsov 0a115a3603 command noSystemUser param 2024-04-04 20:53:33 +03:00
Yuri Kuznetsov fcb3158a06 readme 2024-04-04 20:27:47 +03:00
Yuri Kuznetsov 77f60734f6 upgrade ref 2024-04-04 16:19:56 +03:00
Eymen Elkum 1c10ddd7b8 add missing & to google static url 2024-04-04 13:53:06 +03:00
Yuri Kuznetsov d8dd048a89 migrations dev 2024-04-04 13:26:32 +03:00
Yuri Kuznetsov 42812f8bb2 ref 2024-04-04 11:42:07 +03:00
Yuri Kuznetsov b5a9690b63 Merge branch 'fix' 2024-04-04 10:44:59 +03:00
Yuri Kuznetsov b741898ce9 Merge branch 'fix' 2024-04-03 14:44:58 +03:00
Yuri Kuznetsov b89c28fe96 Merge branch 'fix' 2024-04-02 17:32:42 +03:00
Yuri Kuznetsov 1c68c805aa Merge branch 'fix' 2024-04-02 15:42:14 +03:00
Yuri Kuznetsov cef7a919b8 suppress inspection 2024-04-02 14:39:22 +03:00
Yuri Kuznetsov 3117db022f type fix 2024-04-02 14:38:40 +03:00
Yuri Kuznetsov 7b642e40b0 phone ext langth frontend validation 2024-04-02 14:21:53 +03:00
Yuri Kuznetsov 89ff3dcf15 phone number extensions 2024-04-02 13:47:23 +03:00
Yuri Kuznetsov 79c8d25a80 comments 2024-04-01 19:44:47 +03:00
Yuri Kuznetsov 58d926be82 orm base entity writtenMap change 2024-04-01 19:40:04 +03:00
Yuri Kuznetsov 3044f83690 orderDisabled parameter 2024-04-01 17:05:57 +03:00
Yuri Kuznetsov f2d5b2685e jsdoc 2024-04-01 16:10:27 +03:00
Yuri Kuznetsov 17abe18d01 validation before check create access 2024-04-01 15:32:07 +03:00
Yuri Kuznetsov 09880ff8f1 import error file and line 2024-04-01 15:23:14 +03:00
Yuri Kuznetsov 23f4686577 send invitation check status backend 2024-04-01 13:22:12 +03:00
1274 changed files with 41113 additions and 17834 deletions
+7 -3
View File
@@ -1,14 +1,18 @@
## Issues
When reporting a possible bug, provide detail steps so that we will be able
to reproduce the issue. Try not to use phrases like "very big bug",
to reproduce the issue. Steps to reproduce should be clear and unambiguous. Try not to use phrases like "very big bug",
"huge issue", "useless feature", etc. No need to use exclamation marks as well.
Steps to reproduce should be clear and unambiguous.
Note that we don't provide developer help or any kind of support on GitHub.
For this, please use our [forum](https://forum.espocrm.com).
If you are very new to EspoCRM, it's probable that an issue you ran into is not a bug.
Consider creating a topic on our [forum](https://forum.espocrm.com/forum/general) instead.
The issue tracker is for the benefit of the EspoCRM project. The project maintainers are going to handle issues in the project's best interest.
The maintainers have right to close issues without explanation.
## Pull Requests
We are open for contributions that are bug fixes and small improvements. If you would like to contribute something that is not a small fix, please reach out to maintainers before submitting your PR (by creating a GitHub issue).
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: Feature request
about: Feature requests are not desired at the moment. Need to polish the system. For high-level features, consider creating feature requests on the forum. For low-level (framework) here on GitHub.
about: For high-level features, create feature requests on our forum. For low-level (framework) here on GitHub.
title: ''
labels: ''
assignees: ''
-2
View File
@@ -17,8 +17,6 @@
<codeStyleSettings language="PHP">
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
<option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
<option name="CATCH_ON_NEW_LINE" value="true" />
<option name="FINALLY_ON_NEW_LINE" value="true" />
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
<option name="ALIGN_MULTILINE_FOR" value="false" />
<option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="true" />
+1
View File
@@ -3,6 +3,7 @@
<option name="myName" value="Project Default" />
<inspection_tool class="DuplicatedCode" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="ES6ConvertLetToConst" enabled="true" level="WEAK WARNING" enabled_by_default="true" editorAttributes="INFO_ATTRIBUTES" />
<inspection_tool class="HtmlUnknownAnchorTarget" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="JSIgnoredPromiseFromCall" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PhpDocMissingThrowsInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PhpDocSignatureIsNotCompleteInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
+3 -1
View File
@@ -134,9 +134,11 @@ module.exports = grunt => {
src: [
'build/tmp/custom/Espo/Custom/*',
'!build/tmp/custom/Espo/Custom/.htaccess',
'!build/tmp/custom/Espo/Modules',
'build/tmp/custom/Espo/Modules/*',
'!build/tmp/custom/Espo/Modules/.htaccess',
'build/tmp/install/config.php',
'build/tmp/vendor/*/*/.git',
'build/tmp/custom/Espo/Custom/*',
'build/tmp/client/custom/*',
'!build/tmp/client/custom/modules',
'build/tmp/client/custom/modules/*',
+41 -19
View File
@@ -2,37 +2,41 @@
[![PHPStan level 8](https://img.shields.io/badge/PHPStan-level%208-brightgreen)](#espocrm)
[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 and a REST API
backend written in PHP.
[Download](https://www.espocrm.com/download/) the latest release from our website. Release notes
and release packages are available at [Releases](https://github.com/espocrm/espocrm/releases) on GitHub.
[EspoCRM](https://www.espocrm.com) is a free, open-source CRM platform designed to help organizations build and maintain strong customer relationships.
It provides a wide range of tools to store, organize, and manage leads, contacts, sales opportunities, marketing campaigns,
support cases, and more all business information in a simple and intuitive interface.
![Screenshot](https://user-images.githubusercontent.com/1006792/226094559-995dfd2a-a18f-4619-a21b-79a4e671990a.png)
### Architecture
EspoCRM is a web application with a frontend designed as a single-page application and a REST API
backend written in PHP.
### Demo
You can try the CRM on the online [demo](https://www.espocrm.com/demo/).
You can try the CRM on an online [demo](https://www.espocrm.com/demo/).
### Requirements
* PHP 8.1 - 8.3;
* MySQL 5.7 (and later), or MariaDB 10.2 (and later);
* PostgreSQL 15 (and later) (yet experimental, officially supported soon).
* PostgreSQL 15 (and later) (beta, official support soon).
For more information about server configuration see [this article](https://docs.espocrm.com/administration/server-configuration/).
For more information about server configuration, see [this article](https://docs.espocrm.com/administration/server-configuration/).
### Documentation
### Why EspoCRM?
See the [documentation](https://docs.espocrm.com) for administrators, users and developers.
* **Open-source transparency**. EspoCRMs source code is open and accessible, so anyone can inspect it and see how data is being managed within the CRM.
* **Customization freedom**. You can develop features, create custom entities, fields, relationships, buttons to make the CRM fit your specific needs.
* **Clean user interface**. EspoCRM has a uncluttered, minimalist and very fast user inteface that is easy to navigate and has a short learning curve.
* **Straightforward REST API**. It can be easily integrated with other applications using a REST API.
### Bug reporting
### Who is EspoCRM for?
Create a [GitHub issue](https://github.com/espocrm/espocrm/issues/new/choose) or post on our [forum](https://forum.espocrm.com/forum/bug-reports).
* **Startups, small & medium-sized businesses**. Its an affordable solution that is flexible and fully customizable.
* **Developers & tech enthusiasts**. You can extend functionalities, build extensions, and create custom integrations.
* **Anyone seeking a free CRM**. If you're looking for a user-friendly and secure CRM platform, it can be a good option.
### Installing stable version
@@ -43,11 +47,29 @@ See installation instructions:
* [Installation with Docker](https://docs.espocrm.com/administration/docker/installation/)
* [Installation with Traefik](https://docs.espocrm.com/administration/docker/traefik/)
### Download
[Download](https://www.espocrm.com/download/) the latest release from our website. You can also download the latest and previous release packages from GitHub [releases](https://github.com/espocrm/espocrm/releases).
### Release notes
Release notes are available at GitHub [releases](https://github.com/espocrm/espocrm/releases).
### Documentation
See the [documentation](https://docs.espocrm.com) for administrators, users and developers.
### Bug reporting
Create a [GitHub issue](https://github.com/espocrm/espocrm/issues/new/choose) or post on our [forum](https://forum.espocrm.com/forum/bug-reports).
### Development
See the [developer documentation](https://docs.espocrm.com/development/).
We highly recommend using IDE for development. The backend codebase follows SOLID principles, utilizes interfaces, static typing and generics. We recommend to start learning EspoCRM from the Dependency Injection article in the documentation.
We highly recommend using an IDE for development. The backend codebase follows SOLID principles, utilizes interfaces, static typing and generics. We recommend to start learning EspoCRM from the Dependency Injection article in the documentation.
Metadata plays an integral role in the EspoCRM application. All possible parameters are described with a JSON Schema, meaning you will have autocompletion in the IDE. You can also find the full metadata reference in the documentation.
### Community & Support
@@ -55,11 +77,11 @@ If you have a question regarding some features, need help or customizations, wan
### License
EspoCRM is published under the GNU AGPLv3 [license](https://raw.githubusercontent.com/espocrm/espocrm/master/LICENSE.txt).
EspoCRM is an open-source project licensed under [GNU AGPLv3](https://raw.githubusercontent.com/espocrm/espocrm/master/LICENSE.txt).
### Contributing
Before we can merge your pull request, you need to accept our CLA [here](https://github.com/espocrm/cla). See [contributing guidelines](https://github.com/espocrm/espocrm/blob/master/.github/CONTRIBUTING.md).
Before we can merge your pull request, you need to accept our CLA [here](https://github.com/espocrm/cla). See the [contributing guidelines](https://github.com/espocrm/espocrm/blob/master/.github/CONTRIBUTING.md).
Branches:
@@ -0,0 +1,47 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\Acl\ImportEml;
use Espo\Core\Acl\AccessCreateChecker;
use Espo\Core\Acl\ScopeData;
use Espo\Entities\User;
class AccessChecker implements AccessCreateChecker
{
public function check(User $user, ScopeData $data): bool
{
return $data->isTrue();
}
public function checkCreate(User $user, ScopeData $data): bool
{
return $data->isTrue();
}
}
@@ -29,6 +29,7 @@
namespace Espo\Classes\Acl\Note;
use Espo\Core\Acl\Permission;
use Espo\Core\Acl\Table;
use Espo\Entities\Note;
use Espo\Entities\User;
@@ -143,7 +144,7 @@ class AccessChecker implements AccessEntityCREDChecker
}
if ($entity->getTargetType() === Note::TARGET_PORTALS) {
return $this->aclManager->getPermissionLevel($user, 'portal') === Table::LEVEL_YES;
return $this->aclManager->getPermissionLevel($user, Permission::PORTAL) === Table::LEVEL_YES;
}
return false;
@@ -29,6 +29,7 @@
namespace Espo\Classes\Acl\Portal;
use Espo\Core\Acl\Permission;
use Espo\Entities\Portal;
use Espo\Entities\User;
use Espo\Core\Acl\AccessEntityCREDChecker;
@@ -45,18 +46,12 @@ class AccessChecker implements AccessEntityCREDChecker
{
use DefaultAccessCheckerDependency;
private DefaultAccessChecker $defaultAccessChecker;
private AclManager $aclManager;
public function __construct(DefaultAccessChecker $defaultAccessChecker, AclManager $aclManager)
{
$this->defaultAccessChecker = $defaultAccessChecker;
$this->aclManager = $aclManager;
}
public function __construct(private DefaultAccessChecker $defaultAccessChecker, private AclManager $aclManager)
{}
public function check(User $user, ScopeData $data): bool
{
$level = $this->aclManager->getPermissionLevel($user, 'portal');
$level = $this->aclManager->getPermissionLevel($user, Permission::PORTAL);
return $level === Table::LEVEL_YES;
}
@@ -29,6 +29,7 @@
namespace Espo\Classes\Acl\User;
use Espo\Core\Acl\Permission;
use Espo\Entities\User;
use Espo\ORM\Entity;
use Espo\Core\Acl\AccessEntityCREDSChecker;
@@ -60,8 +61,6 @@ class AccessChecker implements AccessEntityCREDSChecker
return false;
}
/** @var User $entity */
if ($entity->isSuperAdmin() && !$user->isSuperAdmin()) {
return false;
}
@@ -71,10 +70,8 @@ class AccessChecker implements AccessEntityCREDSChecker
public function checkEntityRead(User $user, Entity $entity, ScopeData $data): bool
{
/** @var User $entity */
if ($entity->isPortal()) {
if ($this->aclManager->getPermissionLevel($user, 'portal') === Table::LEVEL_YES) {
if ($this->aclManager->getPermissionLevel($user, Permission::PORTAL) === Table::LEVEL_YES) {
return true;
}
@@ -90,8 +87,6 @@ class AccessChecker implements AccessEntityCREDSChecker
public function checkEntityEdit(User $user, Entity $entity, ScopeData $data): bool
{
/** @var User $entity */
if ($entity->isSystem()) {
return false;
}
@@ -111,8 +106,6 @@ class AccessChecker implements AccessEntityCREDSChecker
public function checkEntityDelete(User $user, Entity $entity, ScopeData $data): bool
{
/** @var User $entity */
if (!$user->isAdmin()) {
return false;
}
@@ -130,8 +123,7 @@ class AccessChecker implements AccessEntityCREDSChecker
public function checkEntityStream(User $user, Entity $entity, ScopeData $data): bool
{
/** @var User $entity */
return $this->aclManager->checkUserPermission($user, $entity, 'user');
/** @noinspection PhpRedundantOptionalArgumentInspection */
return $this->aclManager->checkUserPermission($user, $entity, Permission::USER);
}
}
@@ -0,0 +1,48 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\AppParams;
use Espo\Core\Utils\Address\CountryDataProvider;
use Espo\Tools\App\AppParam;
class AddressCountryData implements AppParam
{
public function __construct(
private CountryDataProvider $provider
) {}
/**
* @return array{list: string[], preferredList: string[]}
*/
public function get(): array
{
return $this->provider->get();
}
}
@@ -0,0 +1,65 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\Cleanup;
use Espo\Core\Cleanup\Cleanup;
use Espo\Core\Field\DateTime;
use Espo\Core\Utils\Config;
use Espo\Entities\AppLogRecord;
use Espo\ORM\EntityManager;
use Espo\ORM\Query\DeleteBuilder;
class AppLog implements Cleanup
{
private const PERIOD = '30 days';
public function __construct(
private EntityManager $entityManager,
private Config $config
) {}
public function process(): void
{
$query = DeleteBuilder::create()
->from(AppLogRecord::ENTITY_TYPE)
->where(['createdAt<' => $this->getBefore()->toString()])
->build();
$this->entityManager->getQueryExecutor()->execute($query);
}
private function getBefore(): DateTime
{
/** @var string $period */
$period = $this->config->get('cleanupAppLogPeriod') ?? self::PERIOD;
return DateTime::createNow()->modify('-' . $period);
}
}
+137
View File
@@ -0,0 +1,137 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\Cleanup;
use Espo\Core\Cleanup\Cleanup;
use Espo\Core\Utils\Acl\UserAclManagerProvider;
use Espo\Entities\StarSubscription;
use Espo\Entities\User;
use Espo\ORM\EntityManager;
use Espo\ORM\Query\DeleteBuilder;
use Espo\Tools\Stars\StarService;
/**
* @noinspection PhpUnused
*/
class Stars implements Cleanup
{
public function __construct(
private EntityManager $entityManager,
private UserAclManagerProvider $userAclManagerProvider,
private StarService $service
) {}
public function process(): void
{
foreach ($this->getEntityTypeList() as $entityType) {
$this->processEntityType($entityType);
}
}
/**
* @return string[]
*/
private function getEntityTypeList(): array
{
$groups = $this->entityManager->getRDBRepositoryByClass(StarSubscription::class)
->group('entityType')
->select('entityType')
->find();
$list = [];
foreach ($groups as $group) {
$list[] = $group->get('entityType');
}
return $list;
}
private function processEntityType(string $entityType): void
{
if (
!$this->service->isEnabled($entityType) ||
!$this->entityManager->hasRepository($entityType)
) {
$deleteQuery = DeleteBuilder::create()
->from(StarSubscription::ENTITY_TYPE)
->where(['entityType' => $entityType])
->build();
$this->entityManager->getQueryExecutor()->execute($deleteQuery);
return;
}
$stars = $this->entityManager
->getRDBRepositoryByClass(StarSubscription::class)
->where(['entityType' => $entityType])
->sth()
->find();
foreach ($stars as $star) {
$entityId = $star->get('entityId');
$userId = $star->get('userId');
if ($userId === null || $entityId === null) {
continue;
}
$entity = $this->entityManager->getEntityById($entityType, $entityId);
$user = $this->entityManager->getRDBRepositoryByClass(User::class)->getById($userId);
if (!$entity || !$user) {
$this->unstar($userId, $entityType, $entityId);
continue;
}
$aclManager = $this->userAclManagerProvider->get($user);
if (!$aclManager->checkEntityRead($user, $entity)) {
$this->unstar($userId, $entityType, $entityId);
}
}
}
private function unstar(string $userId, string $entityType, string $entityId): void
{
$deleteQuery = DeleteBuilder::create()
->from(StarSubscription::ENTITY_TYPE)
->where([
'userId' => $userId,
'entityType' => $entityType,
'entityId' => $entityId,
])
->build();
$this->entityManager->getQueryExecutor()->execute($deleteQuery);
}
}
@@ -33,7 +33,7 @@ use Espo\Core\Cleanup\Cleanup;
use Espo\Core\Field\DateTime;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Metadata;
use Espo\Entities\Subscription;
use Espo\Entities\StreamSubscription;
use Espo\ORM\EntityManager;
use Espo\ORM\Query\Part\Condition as Cond;
@@ -41,19 +41,11 @@ class Subscribers implements Cleanup
{
private const PERIOD = '2 months';
private Metadata $metadata;
private EntityManager $entityManager;
private Config $config;
public function __construct(
Metadata $metadata,
EntityManager $entityManager,
Config $config
) {
$this->metadata = $metadata;
$this->entityManager = $entityManager;
$this->config = $config;
}
private Metadata $metadata,
private EntityManager $entityManager,
private Config $config
) {}
public function process(): void
{
@@ -105,7 +97,7 @@ class Subscribers implements Cleanup
$query = $this->entityManager
->getQueryBuilder()
->delete()
->from(Subscription::ENTITY_TYPE, 'subscription')
->from(StreamSubscription::ENTITY_TYPE, 'subscription')
->join(
$entityType,
'entity',
@@ -0,0 +1,66 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\FieldProcessing\Email;
use Espo\Core\FieldProcessing\Loader;
use Espo\Core\FieldProcessing\Loader\Params;
use Espo\Entities\Email;
use Espo\Entities\EmailFolder;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
/**
* @implements Loader<Email>
*/
class FolderDataLoader implements Loader
{
public function __construct(private EntityManager $entityManager) {}
public function process(Entity $entity, Params $params): void
{
$folderId = $entity->get(Email::USERS_COLUMN_FOLDER_ID);
if (!$folderId) {
return;
}
$folder = $this->entityManager
->getRDBRepositoryByClass(EmailFolder::class)
->select(['id', 'name'])
->where(['id' => $folderId])
->findOne();
if (!$folder) {
return;
}
$entity->set('folderName', $folder->getName());
}
}
@@ -41,14 +41,10 @@ use Espo\Entities\User;
*/
class UserColumnsLoader implements Loader
{
private EntityManager $entityManager;
private User $user;
public function __construct(EntityManager $entityManager, User $user)
{
$this->entityManager = $entityManager;
$this->user = $user;
}
public function __construct(
private EntityManager $entityManager,
private User $user
) {}
public function process(Entity $entity, Params $params): void
{
@@ -58,6 +54,7 @@ class UserColumnsLoader implements Loader
Email::USERS_COLUMN_IS_READ,
Email::USERS_COLUMN_IS_IMPORTANT,
Email::USERS_COLUMN_IN_TRASH,
Email::USERS_COLUMN_IN_ARCHIVE,
])
->where([
'deleted' => false,
@@ -70,6 +67,7 @@ class UserColumnsLoader implements Loader
$entity->set(Email::USERS_COLUMN_IS_READ, null);
$entity->clear(Email::USERS_COLUMN_IS_IMPORTANT);
$entity->clear(Email::USERS_COLUMN_IN_TRASH);
$entity->clear(Email::USERS_COLUMN_IN_ARCHIVE);
return;
}
@@ -78,6 +76,8 @@ class UserColumnsLoader implements Loader
Email::USERS_COLUMN_IS_READ => $emailUser->get(Email::USERS_COLUMN_IS_READ),
Email::USERS_COLUMN_IS_IMPORTANT => $emailUser->get(Email::USERS_COLUMN_IS_IMPORTANT),
Email::USERS_COLUMN_IN_TRASH => $emailUser->get(Email::USERS_COLUMN_IN_TRASH),
Email::USERS_COLUMN_IN_ARCHIVE => $emailUser->get(Email::USERS_COLUMN_IN_ARCHIVE),
'isUsersSent' => $entity->getSentBy()?->getId() === $this->user->getId(),
]);
}
}
@@ -0,0 +1,27 @@
<?php
/**LICENSE**/
namespace Espo\Classes\FieldProcessing\InboundEmail;
use Espo\Core\FieldProcessing\Loader;
use Espo\Core\FieldProcessing\Loader\Params;
use Espo\Core\Utils\Config;
use Espo\Entities\InboundEmail;
use Espo\ORM\Entity;
/**
* @implements Loader<InboundEmail>
*/
class IsSystemLoader implements Loader
{
public function __construct(
private Config $config,
) {}
public function process(Entity $entity, Params $params): void
{
$isSystem = $entity->getEmailAddress() === $this->config->get('outboundEmailFromAddress');
$entity->set('isSystem', $isSystem);
}
}
@@ -0,0 +1,56 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\FieldSanitizers;
use Espo\Core\FieldSanitize\Sanitizer;
use Espo\Core\FieldSanitize\Sanitizer\Data;
/**
* @noinspection PhpUnused
*/
class StringLowerCase implements Sanitizer
{
public function sanitize(Data $data, string $field): void
{
if (!$data->has($field)) {
return;
}
$value = $data->get($field);
if (!is_string($value)) {
return;
}
$value = mb_strtolower($value);
$data->set($field, $value);
}
}
@@ -0,0 +1,56 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\FieldSanitizers;
use Espo\Core\FieldSanitize\Sanitizer;
use Espo\Core\FieldSanitize\Sanitizer\Data;
/**
* @noinspection PhpUnused
*/
class StringUpperCase implements Sanitizer
{
public function sanitize(Data $data, string $field): void
{
if (!$data->has($field)) {
return;
}
$value = $data->get($field);
if (!is_string($value)) {
return;
}
$value = mb_strtoupper($value);
$data->set($field, $value);
}
}
@@ -29,16 +29,54 @@
namespace Espo\Classes\FieldValidators;
use Doctrine\DBAL\Types\Types;
use Espo\ORM\Defs;
use Espo\ORM\Entity;
use stdClass;
class IntType
{
public function __construct(
private Defs $defs,
) {}
public function checkRequired(Entity $entity, string $field): bool
{
return $this->isNotEmpty($entity, $field);
}
/** @noinspection PhpUnused */
public function checkRangeInternal(Entity $entity, string $field): bool
{
$value = $entity->get($field);
if ($value === null) {
return true;
}
$dbType = $this->defs
->getEntity($entity->getEntityType())
->tryGetAttribute($field)
?->getParam('dbType') ?? Types::INTEGER;
$ranges = [
Types::INTEGER => [-2147483648, 2147483647],
Types::SMALLINT => [-32768, 32767],
];
$range = $ranges[$dbType] ?? null;
if (!$range) {
return true;
}
if ($value < $range[0] || $value > $range[1]) {
return false;
}
return true;
}
/**
* @param mixed $validationValue
* @noinspection PhpUnused
@@ -36,20 +36,17 @@ use Espo\Core\ORM\Entity as CoreEntity;
use stdClass;
/**
* @noinspection PhpUnused
*/
class LinkMultipleType
{
private Metadata $metadata;
private Defs $defs;
private const COLUMN_TYPE_ENUM = 'enum';
private const COLUMN_TYPE_VARCHAR = 'varchar';
private const COLUMN_TYPE_BOOL = 'bool';
public function __construct(Metadata $metadata, Defs $defs)
{
$this->metadata = $metadata;
$this->defs = $defs;
}
public function __construct(private Metadata $metadata, private Defs $defs)
{}
public function checkRequired(Entity $entity, string $field): bool
{
@@ -63,6 +60,7 @@ class LinkMultipleType
return count($idList) > 0;
}
/** @noinspection PhpUnused */
public function checkPattern(Entity $entity, string $field): bool
{
/** @var ?mixed[] $idList */
@@ -93,6 +91,27 @@ class LinkMultipleType
return true;
}
/** @noinspection PhpUnused */
public function checkMaxCount(Entity $entity, string $field, ?int $maxCount): bool
{
if ($maxCount === null) {
return true;
}
$list = $entity->get($field . 'Ids');
if (!is_array($list)) {
return true;
}
if (count($list) > $maxCount) {
return false;
}
return true;
}
/** @noinspection PhpUnused */
public function checkColumnsValid(Entity $entity, string $field): bool
{
if (!$entity instanceof CoreEntity) {
@@ -31,6 +31,7 @@ namespace Espo\Classes\FieldValidators;
use Brick\PhoneNumber\PhoneNumber;
use Brick\PhoneNumber\PhoneNumberParseException;
use Espo\Core\PhoneNumber\Util;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Metadata;
use Espo\ORM\Defs;
@@ -203,6 +204,22 @@ class PhoneType
return true;
}
$ext = null;
if ($this->config->get('phoneNumberExtensions')) {
[$number, $ext] = Util::splitExtension($number);
}
if ($ext) {
if (!preg_match('/[0-9]+/', $ext)) {
return false;
}
if (strlen($ext) > 6) {
return false;
}
}
try {
$numberObj = PhoneNumber::parse($number);
}
@@ -0,0 +1,90 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\FieldValidators\Settings\AuthIpAddressWhitelist;
use Espo\Core\FieldValidation\Validator;
use Espo\Core\FieldValidation\Validator\Data;
use Espo\Core\FieldValidation\Validator\Failure;
use Espo\ORM\Entity;
/**
* @implements Validator<Entity>
*/
class Valid implements Validator
{
public function validate(Entity $entity, string $field, Data $data): ?Failure
{
$list = $entity->get($field);
if (!is_array($list)) {
return null;
}
foreach ($list as $item) {
if (!is_string($item)) {
continue;
}
if (!$this->isValid($item)) {
return Failure::create();
}
}
return null;
}
private function isValid(string $item): bool
{
$address = $item;
if (count(explode('/', $item)) > 1) {
[$address, $mask] = explode('/', $item, 2);
if (!is_numeric($mask)) {
return false;
}
$mask = (int) $mask;
if ($mask < 0 || $mask > 128) {
return false;
}
}
if (
filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false &&
filter_var($address, FILTER_VALIDATE_IP) === false
) {
return false;
}
return true;
}
}
@@ -0,0 +1,55 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\FieldValidators\User\DefaultTeam;
use Espo\Core\FieldValidation\Validator;
use Espo\Core\FieldValidation\Validator\Data;
use Espo\Core\FieldValidation\Validator\Failure;
use Espo\Entities\User;
use Espo\ORM\Entity;
/**
* @implements Validator<User>
*/
class IsUserTeam implements Validator
{
public function validate(Entity $entity, string $field, Data $data): ?Failure
{
if (!$entity->getDefaultTeam()) {
return null;
}
if (in_array($entity->getDefaultTeam()->getId(), $entity->getTeamIdList())) {
return null;
}
return Failure::create();
}
}
@@ -30,6 +30,7 @@
namespace Espo\Classes\Jobs;
use Espo\Entities\AuthToken;
use Espo\Entities\Portal;
use Espo\Core\Job\JobDataLess;
use Espo\Core\ORM\EntityManager;
use Espo\Core\Utils\Config;
@@ -50,27 +51,69 @@ class AuthTokenControl implements JobDataLess
public function run(): void
{
$authTokenLifetime = (int) ($this->config->get('authTokenLifetime', 0) * 60);
$authTokenMaxIdleTime = (int) ($this->config->get('authTokenMaxIdleTime', 0) * 60);
$lifetime = (int) ($this->config->get('authTokenLifetime', 0) * 60);
$maxIdleTime = (int) ($this->config->get('authTokenMaxIdleTime', 0) * 60);
if (!$authTokenLifetime && !$authTokenMaxIdleTime) {
$portalIds = [];
/** @var iterable<Portal> $portals */
$portals = $this->entityManager
->getRDBRepositoryByClass(Portal::class)
->find();
foreach ($portals as $portal) {
$portalIds[] = $portal->getId();
}
$this->process(null, $lifetime, $maxIdleTime, $portalIds);
foreach ($portals as $portal) {
$itemLifetime = $portal->get('authTokenLifetime') !== null ?
(int) ($portal->get('authTokenLifetime') * 60) :
$lifetime;
$itemMaxIdleTime = $portal->get('authTokenMaxIdleTime') !== null ?
(int) ($portal->get('authTokenMaxIdleTime') * 60) :
$maxIdleTime;
$this->process($portal->getId(), $itemLifetime, $itemMaxIdleTime);
}
}
/**
* @param string[] $ignorePortalIds
*/
private function process(?string $portalId, int $lifetime, int $maxIdleTime, array $ignorePortalIds = []): void
{
if (!$lifetime && !$maxIdleTime) {
return;
}
$whereClause = [
'isActive' => true,
];
$whereClause = ['isActive' => true];
if ($authTokenLifetime) {
if ($portalId) {
$whereClause['portalId'] = $portalId;
}
if (!$portalId && $ignorePortalIds !== []) {
$whereClause[] = [
'OR' => [
['portalId' => null],
['portalId!=' => $ignorePortalIds],
]
];
}
if ($lifetime) {
$dt = new DateTime();
$dt->modify("-$authTokenLifetime minutes");
$dt->modify("-$lifetime minutes");
$whereClause['createdAt<'] = $dt->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT);
}
if ($authTokenMaxIdleTime) {
if ($maxIdleTime) {
$dt = new DateTime();
$dt->modify("-$authTokenMaxIdleTime minutes");
$dt->modify("-$maxIdleTime minutes");
$whereClause['lastAccess<'] = $dt->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT);
}
@@ -29,39 +29,31 @@
namespace Espo\Classes\Jobs;
use Espo\Core\Exceptions\Error;
use Espo\Core\Mail\Account\PersonalAccount\Service;
use Espo\Core\Job\Job;
use Espo\Core\Job\Job\Data;
use RuntimeException;
use Throwable;
class CheckEmailAccounts implements Job
{
private $service;
public function __construct(Service $service)
{
$this->service = $service;
}
public function __construct(private Service $service)
{}
public function run(Data $data): void
{
$targetId = $data->getTargetId();
if (!$targetId) {
throw new Error("No target.");
throw new RuntimeException("No target.");
}
try {
$this->service->fetch($targetId);
}
catch (Throwable $e) {
throw new Error(
'Job CheckEmailAccounts ' . $targetId . ': [' . $e->getCode() . '] ' . $e->getMessage() . ' ' .
$e->getFile() . ':' . $e->getLine()
);
throw new RuntimeException("CheckInboundEmails job failed, $targetId; {$e->getMessage()}", 0, $e);
}
}
}
@@ -29,37 +29,31 @@
namespace Espo\Classes\Jobs;
use Espo\Core\Exceptions\Error;
use Espo\Core\Mail\Account\GroupAccount\Service;
use Espo\Core\Job\Job;
use Espo\Core\Job\Job\Data;
use RuntimeException;
use Throwable;
class CheckInboundEmails implements Job
{
private $service;
public function __construct(Service $service)
{
$this->service = $service;
}
public function __construct(private Service $service)
{}
public function run(Data $data): void
{
$targetId = $data->getTargetId();
if (!$targetId) {
throw new Error("No target.");
throw new RuntimeException("No target.");
}
try {
$this->service->fetch($targetId);
}
catch (Throwable $e) {
throw new Error(
'Job CheckInboundEmails ' . $targetId . ': [' . $e->getCode() . '] ' .$e->getMessage()
);
throw new RuntimeException("CheckInboundEmails job failed, $targetId; {$e->getMessage()}", 0, $e);
}
}
}
@@ -30,6 +30,7 @@
namespace Espo\Classes\MassAction\Email;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\MassAction\Data;
use Espo\Core\MassAction\MassAction;
@@ -44,11 +45,10 @@ use Espo\ORM\EntityManager;
use Espo\Tools\Email\Folder;
use Espo\Tools\Email\InboxService as EmailService;
use Exception;
use RuntimeException;
class MoveToFolder implements MassAction
{
private const FOLDER_INBOX = Folder::INBOX;
public function __construct(
private QueryBuilder $queryBuilder,
private EntityManager $entityManager,
@@ -68,7 +68,11 @@ class MoveToFolder implements MassAction
throw new BadRequest("No folder ID.");
}
if ($folderId !== self::FOLDER_INBOX && !str_starts_with($folderId, 'group:')) {
if (
$folderId !== Folder::INBOX &&
$folderId !== Folder::ARCHIVE &&
!str_starts_with($folderId, 'group:')
) {
$folder = $this->entityManager
->getRDBRepositoryByClass(EmailFolder::class)
->where([
@@ -93,7 +97,12 @@ class MoveToFolder implements MassAction
}
}
$query = $this->queryBuilder->build($params);
try {
$query = $this->queryBuilder->build($params);
}
catch (BadRequest|Forbidden $e) {
throw new RuntimeException($e->getMessage());
}
$collection = $this->entityManager
->getRDBRepositoryByClass(Email::class)
@@ -31,6 +31,7 @@ namespace Espo\Classes\MassAction\User;
use Espo\Core\Acl;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\MassAction\Actions\MassDelete as MassDeleteOriginal;
use Espo\Core\MassAction\Data;
@@ -59,18 +60,19 @@ class MassDelete implements MassAction
/**
* @throws Forbidden
* @throws BadRequest
* @throws Error
*/
public function process(Params $params, Data $data): Result
{
$entityType = $params->getEntityType();
if (!$this->acl->check($entityType, Acl\Table::ACTION_DELETE)) {
throw new Forbidden("No delete access for '{$entityType}'.");
throw new Forbidden("No delete access for '$entityType'.");
}
if (
!$params->hasIds() &&
$this->acl->getPermissionLevel('massUpdatePermission') !== Acl\Table::LEVEL_YES
$this->acl->getPermissionLevel(Acl\Permission::MASS_UPDATE) !== Acl\Table::LEVEL_YES
) {
throw new Forbidden("No mass-update permission.");
}
@@ -51,7 +51,7 @@ use Espo\Tools\MassUpdate\Data as MassUpdateData;
class MassUpdate implements MassAction
{
private const PERMISSION = 'massUpdatePermission';
private const PERMISSION = Acl\Permission::MASS_UPDATE;
/** @var string[] */
private array $notAllowedAttributeList = [
@@ -0,0 +1,70 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\RecordHooks\AddressCountry;
use Espo\Core\Exceptions\ConflictSilent;
use Espo\Core\Exceptions\Error\Body;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Entities\AddressCountry;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
/**
* @implements SaveHook<AddressCountry>
*/
class BeforeSave implements SaveHook
{
public function __construct(
private EntityManager $entityManager,
) {}
public function process(Entity $entity): void
{
$where = ['name' => $entity->getName()];
if (!$entity->isNew()) {
$where['id!='] = $entity->getId();
}
$one = $this->entityManager
->getRDBRepositoryByClass(AddressCountry::class)
->where($where)
->findOne();
if (!$one) {
return;
}
throw ConflictSilent::createWithBody(
'duplicateError',
Body::create()->withMessageTranslation('duplicateConflict')
);
}
}
@@ -0,0 +1,101 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\RecordHooks\Email;
use Espo\Core\Acl;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Mail\Account\SendingAccountProvider;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Core\Utils\Config;
use Espo\Entities\Email;
use Espo\Entities\User;
use Espo\ORM\Entity;
/**
* @implements SaveHook<Email>
*/
class CheckFromAddress implements SaveHook
{
public function __construct(
private User $user,
private SendingAccountProvider $sendingAccountProvider,
private Config $config,
private Acl $acl,
) {}
public function process(Entity $entity): void
{
if ($this->user->isAdmin()) {
return;
}
$fromAddress = $entity->getFromAddress();
// Should be after 'getFromAddress'.
if (!$entity->isAttributeChanged('from')) {
return;
}
if (!$fromAddress) {
throw new BadRequest("No 'from' address");
}
if ($this->acl->checkScope('Import')) {
return;
}
$fromAddress = strtolower($fromAddress);
foreach ($this->user->getEmailAddressGroup()->getAddressList() as $address) {
if ($fromAddress === strtolower($address)) {
return;
}
}
if ($this->sendingAccountProvider->getShared($this->user, $fromAddress)) {
return;
}
$system = $this->sendingAccountProvider->getSystem();
if (
$system &&
$this->config->get('outboundEmailIsShared') &&
$system->getEmailAddress()
) {
if ($fromAddress === strtolower($system->getEmailAddress())) {
return;
}
}
throw new Forbidden("Not allowed 'from' address.");
}
}
@@ -30,6 +30,7 @@
namespace Espo\Classes\RecordHooks\Note;
use Espo\Core\Acl;
use Espo\Core\Acl\Permission;
use Espo\Core\Acl\Table as AclTable;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Forbidden;
@@ -90,7 +91,7 @@ class AssignmentCheck implements SaveHook
}
}
$messagePermission = $this->acl->getPermissionLevel('message');
$messagePermission = $this->acl->getPermissionLevel(Permission::MESSAGE);
if ($messagePermission === AclTable::LEVEL_NO) {
if (
@@ -126,14 +127,14 @@ class AssignmentCheck implements SaveHook
throw new BadRequest("No portal IDs.");
}
if ($this->acl->getPermissionLevel('portal') !== AclTable::LEVEL_YES) {
if ($this->acl->getPermissionLevel(Permission::PORTAL) !== AclTable::LEVEL_YES) {
throw new Forbidden('Not permitted to post to portal users.');
}
}
if (
$targetType === Note::TARGET_USERS &&
$this->acl->getPermissionLevel('portal') !== AclTable::LEVEL_YES
$this->acl->getPermissionLevel(Permission::PORTAL) !== AclTable::LEVEL_YES
) {
if ($hasPortalTargetUser) {
throw new Forbidden('Not permitted to post to portal users.');
@@ -70,6 +70,7 @@ class BeforeCreate implements SaveHook
$targetType = $entity->getTargetType();
$entity->clear('isPinned');
$entity->clear('isGlobal');
switch ($targetType) {
@@ -87,7 +88,7 @@ class BeforeCreate implements SaveHook
$entity->clear('usersIds');
$entity->clear('teamsIds');
$entity->clear('portalsIds');
$entity->set('usersIds', [$this->user->getId()]);
$entity->setUsersIds([$this->user->getId()]);
$entity->set('isForSelf', true);
break;
@@ -32,7 +32,6 @@ namespace Espo\Classes\RecordHooks\Note;
use Espo\Core\Exceptions\ForbiddenSilent;
use Espo\Core\Record\Hook\SaveHook;
use Espo\Entities\Note;
use Espo\Entities\User;
use Espo\ORM\Entity;
use Espo\Tools\Stream\NoteUtil;
@@ -43,18 +42,27 @@ use Espo\Tools\Stream\NoteUtil;
class BeforeUpdate implements SaveHook
{
public function __construct(
private User $user,
private NoteUtil $noteUtil
private NoteUtil $noteUtil,
) {}
public function process(Entity $entity): void
{
if (!$this->isEditableType($entity)) {
throw new ForbiddenSilent("Note is not editable.");
}
if ($entity->isPost()) {
$this->noteUtil->handlePostText($entity);
}
if (!$entity->isPost() && !$this->user->isAdmin()) {
throw new ForbiddenSilent("Only 'Post' type allowed.");
if (!$entity->isPost()) {
$entity->clear('post');
$entity->clear('attachmentsIds');
}
}
private function isEditableType(Note $entity): bool
{
return $entity->getType() == Note::TYPE_POST;
}
}
@@ -0,0 +1,45 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\Select\AddressCountry;
use Espo\Core\Select\Order\Item;
use Espo\Core\Select\Order\Orderer;
use Espo\ORM\Query\Part\Order;
use Espo\ORM\Query\SelectBuilder;
class PreferredNameOrderer implements Orderer
{
public function apply(SelectBuilder $queryBuilder, Item $item): void
{
$queryBuilder
->order('isPreferred', $item->getOrder() === Order::ASC ? Order::DESC : Order::ASC)
->order('name', $item->getOrder());
}
}
@@ -0,0 +1,49 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\Select\AppLogRecord\PrimaryFilters;
use Espo\Core\Select\Primary\Filter;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
use Psr\Log\LogLevel;
class Errors implements Filter
{
public function apply(QueryBuilder $queryBuilder): void
{
$queryBuilder->where([
'level' => [
ucfirst(LogLevel::ERROR),
ucfirst(LogLevel::EMERGENCY),
ucfirst(LogLevel::CRITICAL),
ucfirst(LogLevel::ALERT),
]
]);
}
}
@@ -32,6 +32,7 @@ namespace Espo\Classes\Select\Email\AccessControlFilters;
use Espo\Core\Select\AccessControl\Filter;
use Espo\Classes\Select\Email\Helpers\JoinHelper;
use Espo\Entities\Email;
use Espo\Entities\User;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
@@ -46,6 +47,6 @@ class OnlyOwn implements Filter
{
$this->joinHelper->joinEmailUser($queryBuilder, $this->user->getId());
$queryBuilder->where(['emailUser.userId' => $this->user->getId()]);
$queryBuilder->where([Email::ALIAS_INBOX . '.userId' => $this->user->getId()]);
}
}
@@ -52,15 +52,15 @@ class OnlyTeam implements Filter
'entityTeam.entityType' => Email::ENTITY_TYPE,
'entityTeam.deleted' => false,
])
->leftJoin(Email::RELATIONSHIP_EMAIL_USER, 'emailUser', [
'emailUser.emailId:' => 'id',
'emailUser.deleted' => false,
'emailUser.userId' => $this->user->getId(),
->leftJoin(Email::RELATIONSHIP_EMAIL_USER, Email::ALIAS_INBOX, [
Email::ALIAS_INBOX . '.emailId:' => 'id',
Email::ALIAS_INBOX . '.deleted' => false,
Email::ALIAS_INBOX . '.userId' => $this->user->getId(),
])
->where([
'OR' => [
'entityTeam.teamId' => $this->user->getTeamIdList(),
'emailUser.userId' => $this->user->getId(),
Email::ALIAS_INBOX . '.userId' => $this->user->getId(),
]
])
->build();
@@ -31,6 +31,7 @@ namespace Espo\Classes\Select\Email\AccessControlFilters;
use Espo\Core\Select\AccessControl\Filter;
use Espo\Classes\Select\Email\Helpers\JoinHelper;
use Espo\Entities\Email;
use Espo\Entities\User;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
@@ -46,10 +47,9 @@ class PortalOnlyAccount implements Filter
$queryBuilder->distinct();
$orGroup = [
'emailUser.userId' => $this->user->getId(),
Email::ALIAS_INBOX . '.userId' => $this->user->getId(),
];
/** @var string[] $accountIdList */
$accountIdList = $this->user->getLinkMultipleIdList('accounts');
if (count($accountIdList)) {
@@ -31,6 +31,7 @@ namespace Espo\Classes\Select\Email\AccessControlFilters;
use Espo\Core\Select\AccessControl\Filter;
use Espo\Classes\Select\Email\Helpers\JoinHelper;
use Espo\Entities\Email;
use Espo\Entities\User;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
@@ -46,7 +47,7 @@ class PortalOnlyContact implements Filter
$queryBuilder->distinct();
$orGroup = [
'emailUser.userId' => $this->user->getId(),
Email::ALIAS_INBOX . '.userId' => $this->user->getId(),
];
$contactId = $this->user->get('contactId');
@@ -76,9 +76,9 @@ class Main implements AdditionalApplier
return;
}
if ($this->checkApplyDateSentIndex($queryBuilder, $searchParams)) {
/*if ($this->checkApplyDateSentIndex($queryBuilder, $searchParams)) {
$queryBuilder->useIndex('dateSent');
}
}*/
}
private function joinEmailUser(SelectBuilder $queryBuilder): void
@@ -93,11 +93,12 @@ class Main implements AdditionalApplier
Email::USERS_COLUMN_IS_READ,
Email::USERS_COLUMN_IS_IMPORTANT,
Email::USERS_COLUMN_IN_TRASH,
Email::USERS_COLUMN_IN_ARCHIVE,
Email::USERS_COLUMN_FOLDER_ID,
];
foreach ($itemList as $item) {
$queryBuilder->select('emailUser.' . $item, $item);
$queryBuilder->select(Email::ALIAS_INBOX . '.' . $item, $item);
}
}
@@ -116,7 +117,7 @@ class Main implements AdditionalApplier
return null;
}
private function checkApplyDateSentIndex(SelectBuilder $queryBuilder, SearchParams $searchParams): bool
/*private function checkApplyDateSentIndex(SelectBuilder $queryBuilder, SearchParams $searchParams): bool
{
if ($searchParams->getTextFilter()) {
return false;
@@ -149,5 +150,5 @@ class Main implements AdditionalApplier
}
return true;
}
}*/
}
@@ -31,6 +31,7 @@ namespace Espo\Classes\Select\Email\BoolFilters;
use Espo\Classes\Select\Email\Helpers\JoinHelper;
use Espo\Core\Select\Bool\Filter;
use Espo\Entities\Email;
use Espo\Entities\User;
use Espo\ORM\Query\Part\Where\OrGroupBuilder;
use Espo\ORM\Query\Part\WhereClause;
@@ -46,7 +47,7 @@ class OnlyMy implements Filter
$this->joinHelper->joinEmailUser($queryBuilder, $this->user->getId());
$item = WhereClause::fromRaw([
'emailUser.userId' => $this->user->getId(),
Email::ALIAS_INBOX . '.userId' => $this->user->getId(),
]);
$orGroupBuilder->add($item);
@@ -36,14 +36,14 @@ class JoinHelper
{
public function joinEmailUser(QueryBuilder $queryBuilder, string $userId): void
{
if ($queryBuilder->hasLeftJoinAlias('emailUser')) {
if ($queryBuilder->hasLeftJoinAlias(Email::ALIAS_INBOX)) {
return;
}
$queryBuilder->leftJoin(Email::RELATIONSHIP_EMAIL_USER, 'emailUser', [
'emailUser.emailId:' => 'id',
'emailUser.deleted' => false,
'emailUser.userId' => $userId,
$queryBuilder->leftJoin(Email::RELATIONSHIP_EMAIL_USER, Email::ALIAS_INBOX, [
Email::ALIAS_INBOX . '.emailId:' => 'id',
Email::ALIAS_INBOX . '.deleted' => false,
Email::ALIAS_INBOX . '.userId' => $userId,
]);
}
}
@@ -0,0 +1,83 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\Select\Email\Where\ItemConverters;
use Espo\Core\Select\Helpers\RandomStringGenerator;
use Espo\Core\Select\Where\Item;
use Espo\Core\Select\Where\ItemConverter;
use Espo\Classes\Select\Email\Helpers\EmailAddressHelper;
use Espo\ORM\Query\Part\WhereClause;
use Espo\ORM\Query\Part\WhereItem as WhereClauseItem;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
class CcEquals implements ItemConverter
{
public function __construct(
private EmailAddressHelper $emailAddressHelper,
private RandomStringGenerator $randomStringGenerator
) {}
public function convert(QueryBuilder $queryBuilder, Item $item): WhereClauseItem
{
$value = $item->getValue();
if (!$value) {
return WhereClause::fromRaw([
'id' => null,
]);
}
$emailAddressId = $this->emailAddressHelper->getEmailAddressIdByValue($value);
if (!$emailAddressId) {
return WhereClause::fromRaw([
'id' => null,
]);
}
$queryBuilder->distinct();
$alias = 'emailEmailAddress' . $this->randomStringGenerator->generate();
$queryBuilder->leftJoin(
'EmailEmailAddress',
$alias,
[
'emailId:' => 'id',
'deleted' => false,
]
);
return WhereClause::fromRaw([
$alias . '.emailAddressId' => $emailAddressId,
$alias . '.addressType' => 'cc',
]);
}
}
@@ -0,0 +1,54 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\Select\Email\Where\ItemConverters;
use Espo\Core\Select\Where\Item;
use Espo\Core\Select\Where\ItemConverter;
use Espo\Classes\Select\Email\Helpers\JoinHelper;
use Espo\Entities\Email;
use Espo\Entities\User;
use Espo\ORM\Query\Part\WhereClause;
use Espo\ORM\Query\Part\WhereItem as WhereClauseItem;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
class InArchiveIsFalse implements ItemConverter
{
public function __construct(private User $user, private JoinHelper $joinHelper)
{}
public function convert(QueryBuilder $queryBuilder, Item $item): WhereClauseItem
{
$this->joinHelper->joinEmailUser($queryBuilder, $this->user->getId());
return WhereClause::fromRaw([
Email::ALIAS_INBOX . '.inArchive' => false,
]);
}
}
@@ -0,0 +1,54 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\Select\Email\Where\ItemConverters;
use Espo\Core\Select\Where\Item;
use Espo\Core\Select\Where\ItemConverter;
use Espo\Classes\Select\Email\Helpers\JoinHelper;
use Espo\Entities\Email;
use Espo\Entities\User;
use Espo\ORM\Query\Part\WhereClause;
use Espo\ORM\Query\Part\WhereItem as WhereClauseItem;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
class InArchiveIsTrue implements ItemConverter
{
public function __construct(private User $user, private JoinHelper $joinHelper)
{}
public function convert(QueryBuilder $queryBuilder, Item $item): WhereClauseItem
{
$this->joinHelper->joinEmailUser($queryBuilder, $this->user->getId());
return WhereClause::fromRaw([
Email::ALIAS_INBOX . '.inArchive' => true,
]);
}
}
@@ -41,6 +41,9 @@ use Espo\Entities\User;
use Espo\Classes\Select\Email\Helpers\JoinHelper;
use Espo\Tools\Email\Folder;
/**
* @noinspection PhpUnused
*/
class InFolder implements ItemConverter
{
public function __construct(
@@ -59,19 +62,21 @@ class InFolder implements ItemConverter
Folder::IMPORTANT => $this->convertImportant($queryBuilder),
Folder::SENT => $this->convertSent($queryBuilder),
Folder::TRASH => $this->convertTrash($queryBuilder),
Folder::DRAFTS => $this->convertDraft($queryBuilder),
Folder::ARCHIVE => $this->convertArchive($queryBuilder),
Folder::DRAFTS => $this->convertDraft(),
default => $this->convertFolderId($queryBuilder, $folderId),
};
}
protected function convertInbox(QueryBuilder $queryBuilder): WhereClauseItem
private function convertInbox(QueryBuilder $queryBuilder): WhereClauseItem
{
$this->joinEmailUser($queryBuilder);
$whereClause = [
'emailUser.inTrash' => false,
'emailUser.folderId' => null,
'emailUser.userId' => $this->user->getId(),
Email::ALIAS_INBOX . '.inTrash' => false,
Email::ALIAS_INBOX . '.inArchive' => false,
Email::ALIAS_INBOX . '.folderId' => null,
Email::ALIAS_INBOX . '.userId' => $this->user->getId(),
[
'status' => [
Email::STATUS_ARCHIVED,
@@ -83,7 +88,7 @@ class InFolder implements ItemConverter
$emailAddressIdList = $this->getEmailAddressIdList();
if (!empty($emailAddressIdList)) {
if ($emailAddressIdList !== []) {
$whereClause['fromEmailAddressId!='] = $emailAddressIdList;
$whereClause[] = [
@@ -92,8 +97,7 @@ class InFolder implements ItemConverter
'createdById!=' => $this->user->getId(),
],
];
}
else {
} else {
$whereClause[] = [
'status' => Email::STATUS_ARCHIVED,
'createdById!=' => $this->user->getId(),
@@ -103,7 +107,7 @@ class InFolder implements ItemConverter
return WhereClause::fromRaw($whereClause);
}
protected function convertSent(QueryBuilder $queryBuilder): WhereClauseItem
private function convertSent(QueryBuilder $queryBuilder): WhereClauseItem
{
$this->joinEmailUser($queryBuilder);
@@ -118,31 +122,41 @@ class InFolder implements ItemConverter
[
'status!=' => Email::STATUS_DRAFT,
],
'emailUser.inTrash' => false,
Email::ALIAS_INBOX . '.inTrash' => false,
]);
}
protected function convertImportant(QueryBuilder $queryBuilder): WhereClauseItem
private function convertImportant(QueryBuilder $queryBuilder): WhereClauseItem
{
$this->joinEmailUser($queryBuilder);
return WhereClause::fromRaw([
'emailUser.userId' => $this->user->getId(),
'emailUser.isImportant' => true,
Email::ALIAS_INBOX . '.userId' => $this->user->getId(),
Email::ALIAS_INBOX . '.isImportant' => true,
]);
}
protected function convertTrash(QueryBuilder $queryBuilder): WhereClauseItem
private function convertTrash(QueryBuilder $queryBuilder): WhereClauseItem
{
$this->joinEmailUser($queryBuilder);
return WhereClause::fromRaw([
'emailUser.userId' => $this->user->getId(),
'emailUser.inTrash' => true,
Email::ALIAS_INBOX . '.userId' => $this->user->getId(),
Email::ALIAS_INBOX . '.inTrash' => true,
]);
}
protected function convertDraft(QueryBuilder $queryBuilder): WhereClauseItem
private function convertArchive(QueryBuilder $queryBuilder): WhereClauseItem
{
$this->joinEmailUser($queryBuilder);
return WhereClause::fromRaw([
Email::ALIAS_INBOX . '.userId' => $this->user->getId(),
Email::ALIAS_INBOX . '.inArchive' => true,
]);
}
private function convertDraft(): WhereClauseItem
{
return WhereClause::fromRaw([
'status' => Email::STATUS_DRAFT,
@@ -150,7 +164,7 @@ class InFolder implements ItemConverter
]);
}
protected function convertFolderId(QueryBuilder $queryBuilder, string $folderId): WhereClauseItem
private function convertFolderId(QueryBuilder $queryBuilder, string $folderId): WhereClauseItem
{
$this->joinEmailUser($queryBuilder);
@@ -164,15 +178,17 @@ class InFolder implements ItemConverter
return WhereClause::fromRaw([
'groupFolderId' => $groupFolderId,
'OR' => [
'emailUser.id' => null,
'emailUser.inTrash' => false,
Email::ALIAS_INBOX . '.id' => null,
Email::ALIAS_INBOX . '.inTrash' => false,
Email::ALIAS_INBOX . '.inArchive' => false,
]
]);
}
return WhereClause::fromRaw([
'emailUser.inTrash' => false,
'emailUser.folderId' => $folderId,
Email::ALIAS_INBOX . '.inTrash' => false,
Email::ALIAS_INBOX . '.inArchive' => false,
Email::ALIAS_INBOX . '.folderId' => $folderId,
'groupFolderId' => null,
]);
}
@@ -32,6 +32,7 @@ namespace Espo\Classes\Select\Email\Where\ItemConverters;
use Espo\Core\Select\Where\Item;
use Espo\Core\Select\Where\ItemConverter;
use Espo\Classes\Select\Email\Helpers\JoinHelper;
use Espo\Entities\Email;
use Espo\Entities\User;
use Espo\ORM\Query\Part\WhereClause;
use Espo\ORM\Query\Part\WhereItem as WhereClauseItem;
@@ -47,7 +48,7 @@ class InTrashIsFalse implements ItemConverter
$this->joinHelper->joinEmailUser($queryBuilder, $this->user->getId());
return WhereClause::fromRaw([
'emailUser.inTrash' => false,
Email::ALIAS_INBOX . '.inTrash' => false,
]);
}
}
@@ -32,6 +32,7 @@ namespace Espo\Classes\Select\Email\Where\ItemConverters;
use Espo\Core\Select\Where\Item;
use Espo\Core\Select\Where\ItemConverter;
use Espo\Classes\Select\Email\Helpers\JoinHelper;
use Espo\Entities\Email;
use Espo\Entities\User;
use Espo\ORM\Query\Part\WhereClause;
use Espo\ORM\Query\Part\WhereItem as WhereClauseItem;
@@ -47,7 +48,7 @@ class InTrashIsTrue implements ItemConverter
$this->joinHelper->joinEmailUser($queryBuilder, $this->user->getId());
return WhereClause::fromRaw([
'emailUser.inTrash' => true,
Email::ALIAS_INBOX . '.inTrash' => true,
]);
}
}
@@ -32,6 +32,7 @@ namespace Espo\Classes\Select\Email\Where\ItemConverters;
use Espo\Core\Select\Where\Item;
use Espo\Core\Select\Where\ItemConverter;
use Espo\Classes\Select\Email\Helpers\JoinHelper;
use Espo\Entities\Email;
use Espo\Entities\User;
use Espo\ORM\Query\Part\WhereClause;
use Espo\ORM\Query\Part\WhereItem as WhereClauseItem;
@@ -47,7 +48,7 @@ class IsImportantIsFalse implements ItemConverter
$this->joinHelper->joinEmailUser($queryBuilder, $this->user->getId());
return WhereClause::fromRaw([
'emailUser.isImportant' => false,
Email::ALIAS_INBOX . '.isImportant' => false,
]);
}
}
@@ -32,6 +32,7 @@ namespace Espo\Classes\Select\Email\Where\ItemConverters;
use Espo\Core\Select\Where\Item;
use Espo\Core\Select\Where\ItemConverter;
use Espo\Classes\Select\Email\Helpers\JoinHelper;
use Espo\Entities\Email;
use Espo\Entities\User;
use Espo\ORM\Query\Part\WhereClause;
use Espo\ORM\Query\Part\WhereItem as WhereClauseItem;
@@ -47,7 +48,7 @@ class IsImportantIsTrue implements ItemConverter
$this->joinHelper->joinEmailUser($queryBuilder, $this->user->getId());
return WhereClause::fromRaw([
'emailUser.isImportant' => true,
Email::ALIAS_INBOX . '.isImportant' => true,
]);
}
}
@@ -32,6 +32,7 @@ namespace Espo\Classes\Select\Email\Where\ItemConverters;
use Espo\Core\Select\Where\Item;
use Espo\Core\Select\Where\ItemConverter;
use Espo\Classes\Select\Email\Helpers\JoinHelper;
use Espo\Entities\Email;
use Espo\Entities\User;
use Espo\ORM\Query\Part\WhereClause;
use Espo\ORM\Query\Part\WhereItem as WhereClauseItem;
@@ -47,7 +48,7 @@ class IsNotReadIsFalse implements ItemConverter
$this->joinHelper->joinEmailUser($queryBuilder, $this->user->getId());
return WhereClause::fromRaw([
'emailUser.isRead' => true,
Email::ALIAS_INBOX . '.isRead' => true,
]);
}
}
@@ -32,6 +32,7 @@ namespace Espo\Classes\Select\Email\Where\ItemConverters;
use Espo\Core\Select\Where\Item;
use Espo\Core\Select\Where\ItemConverter;
use Espo\Classes\Select\Email\Helpers\JoinHelper;
use Espo\Entities\Email;
use Espo\Entities\User;
use Espo\ORM\Query\Part\WhereClause;
use Espo\ORM\Query\Part\WhereItem as WhereClauseItem;
@@ -47,7 +48,7 @@ class IsNotReadIsTrue implements ItemConverter
$this->joinHelper->joinEmailUser($queryBuilder, $this->user->getId());
return WhereClause::fromRaw([
'emailUser.isRead' => false,
Email::ALIAS_INBOX . '.isRead' => false,
'OR' => [
'sentById' => null,
'sentById!=' => $this->user->getId()
@@ -0,0 +1,42 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\Select\EmailAccount\PrimaryFilters;
use Espo\Core\Select\Primary\Filter;
use Espo\Entities\EmailFilter;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
class Active implements Filter
{
public function apply(QueryBuilder $queryBuilder): void
{
$queryBuilder->where(['status' => EmailFilter::STATUS_ACTIVE]);
}
}
@@ -29,6 +29,7 @@
namespace Espo\Classes\Select\User\AccessControlFilters;
use Espo\Core\Acl\Permission;
use Espo\ORM\Query\SelectBuilder;
use Espo\Core\Acl\Table;
use Espo\Core\AclManager;
@@ -51,7 +52,7 @@ class Mandatory implements Filter
]);
}
if ($this->aclManager->getPermissionLevel($this->user, 'portalPermission') !== Table::LEVEL_YES) {
if ($this->aclManager->getPermissionLevel($this->user, Permission::PORTAL) !== Table::LEVEL_YES) {
$queryBuilder->where([
'OR' => [
'type!=' => User::TYPE_PORTAL,
@@ -29,6 +29,7 @@
namespace Espo\Classes\Select\User\AccessControlFilters;
use Espo\Core\Acl\Permission;
use Espo\ORM\Query\SelectBuilder;
use Espo\Core\Acl\Table;
use Espo\Core\AclManager;
@@ -43,7 +44,7 @@ class OnlyOwn implements Filter
public function apply(SelectBuilder $queryBuilder): void
{
if ($this->aclManager->getPermissionLevel($this->user, 'portalPermission') === Table::LEVEL_YES) {
if ($this->aclManager->getPermissionLevel($this->user, Permission::PORTAL) === Table::LEVEL_YES) {
$queryBuilder->where([
'OR' => [
'id' => $this->user->getId(),
@@ -51,7 +51,7 @@ class OnlyTeam implements Filter
'id' => $this->user->getId(),
];
if ($this->aclManager->getPermissionLevel($this->user, 'portalPermission') === Table::LEVEL_YES) {
if ($this->aclManager->getPermissionLevel($this->user, 'portal') === Table::LEVEL_YES) {
$orGroup['type'] = User::TYPE_PORTAL;
}
@@ -0,0 +1,47 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\Select\User\BoolFilters;
use Espo\Core\Select\Bool\Filter;
use Espo\Entities\User;
use Espo\ORM\Query\Part\Where\OrGroupBuilder;
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
class OnlyMe implements Filter
{
public function __construct(
private User $user
) {}
public function apply(QueryBuilder $queryBuilder, OrGroupBuilder $orGroupBuilder): void
{
$queryBuilder->where(['id' => $this->user->getId()]);
}
}
@@ -165,7 +165,7 @@ class GoogleMaps implements Helper
$url = "https://maps.googleapis.com/maps/api/staticmap?" .
'center=' . $addressEncoded .
'format=' . $format .
'&format=' . $format .
'&size=' . $size .
'&key=' . $apiKey;
@@ -0,0 +1,50 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Controllers;
use Espo\Core\Controllers\RecordBase;
use Espo\Tools\Address\CountryDefaultsPopulator;
class AddressCountry extends RecordBase
{
protected function checkAccess(): bool
{
return $this->user->isAdmin();
}
public function postActionPopulateDefaults(): bool
{
$populate = $this->injectableFactory->create(CountryDefaultsPopulator::class);
$populate->populate();
return true;
}
}
+2 -2
View File
@@ -36,7 +36,7 @@ use Espo\Core\Container;
use Espo\Core\DataManager;
use Espo\Core\Api\Request;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\AdminNotificationManager;
use Espo\Tools\AdminNotifications\Manager;
use Espo\Core\Utils\SystemRequirements;
use Espo\Core\Utils\ScheduledJob;
use Espo\Core\Upgrades\UpgradeManager;
@@ -51,7 +51,7 @@ class Admin
private Container $container,
private Config $config,
private User $user,
private AdminNotificationManager $adminNotificationManager,
private Manager $adminNotificationManager,
private SystemRequirements $systemRequirements,
private ScheduledJob $scheduledJob,
private DataManager $dataManager
+6 -2
View File
@@ -29,10 +29,14 @@
namespace Espo\Controllers;
use Espo\Core\Api\Request;
use Espo\Core\Api\Response;
use Espo\Core\Utils\Json;
class ApiIndex
{
public function getActionIndex(): string
public function getActionIndex(Request $request, Response $response): void
{
return "EspoCRM REST API";
$response->writeBody(Json::encode("EspoCRM REST API"));
}
}
@@ -0,0 +1,76 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero 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;
use Espo\Core\Controllers\Record;
use Espo\Core\Api\Request;
use Espo\Core\Api\Response;
use stdClass;
class AppLogRecord extends Record
{
protected function checkAccess(): bool
{
if (!$this->user->isAdmin()) {
return false;
}
if (!$this->config->get('restrictedMode')) {
return true;
}
if ($this->config->get('appLogAdminAllowed')) {
return true;
}
return $this->user->isSuperAdmin();
}
public function postActionCreate(Request $request, Response $response): stdClass
{
throw new Forbidden();
}
public function putActionUpdate(Request $request, Response $response): stdClass
{
throw new Forbidden();
}
public function postActionCreateLink(Request $request): bool
{
throw new Forbidden();
}
public function deleteActionRemoveLink(Request $request): bool
{
throw new Forbidden();
}
}
+10 -1
View File
@@ -36,17 +36,26 @@ use Espo\Core\Acl;
use Espo\Core\Api\Request;
use Espo\Core\Api\Response;
use Espo\Core\Exceptions\NotFound;
use Espo\Tools\DataPrivacy\Erasor;
class DataPrivacy
{
/**
* @throws Forbidden
*/
public function __construct(private Erasor $erasor, private Acl $acl)
{
if ($this->acl->getPermissionLevel('dataPrivacyPermission') === Acl\Table::LEVEL_NO) {
if ($this->acl->getPermissionLevel(Acl\Permission::DATA_PRIVACY) === Acl\Table::LEVEL_NO) {
throw new Forbidden();
}
}
/**
* @throws BadRequest
* @throws Forbidden
* @throws NotFound
*/
public function postActionErase(Request $request, Response $response): void
{
$data = $request->getParsedBody();
@@ -35,6 +35,7 @@ use Espo\Core\Mail\Account\Storage\Params as StorageParams;
use Espo\Core\Controllers\Record;
use Espo\Core\Api\Request;
use Espo\Core\Mail\Exceptions\ImapError;
class InboundEmail extends Record
{
@@ -45,7 +46,8 @@ class InboundEmail extends Record
/**
* @return string[]
* @throws \Espo\Core\Exceptions\Error
* @throws Error
* @throws ImapError
*/
public function postActionGetFolders(Request $request): array
{
+2 -1
View File
@@ -29,6 +29,7 @@
namespace Espo\Controllers;
use Espo\Core\Acl\Permission;
use Espo\Core\Acl\Table;
use Espo\Core\Controllers\Record;
@@ -36,7 +37,7 @@ class Portal extends Record
{
protected function checkAccess(): bool
{
$level = $this->acl->getPermissionLevel('portal');
$level = $this->acl->getPermissionLevel(Permission::PORTAL);
return $level === Table::LEVEL_YES;
}
+18 -9
View File
@@ -68,19 +68,28 @@ class Stream
throw new BadRequest();
}
if ($id === null && $scope !== UserEntity::ENTITY_TYPE) {
throw new BadRequest("No ID.");
}
$searchParams = $this->fetchSearchParams($request);
$result = $scope === UserEntity::ENTITY_TYPE ?
$this->userRecordService->find($id, $searchParams) :
$this->service->find($scope, $id ?? '', $searchParams);
if ($scope === UserEntity::ENTITY_TYPE) {
$collection = $this->userRecordService->find($id, $searchParams);
return (object) [
'total' => $collection->getTotal(),
'list' => $collection->getValueMapList(),
];
}
if ($id === null) {
throw new BadRequest();
}
$collection = $this->service->find($scope, $id, $searchParams);
$pinnedCollection = $this->service->getPinned($scope, $id);
return (object) [
'total' => $result->getTotal(),
'list' => $result->getValueMapList(),
'total' => $collection->getTotal(),
'list' => $collection->getValueMapList(),
'pinnedList' => $pinnedCollection->getValueMapList(),
];
}
+2 -1
View File
@@ -31,6 +31,7 @@ namespace Espo\Core;
use Espo\Core\Acl\Exceptions\NotImplemented;
use Espo\Core\Acl\GlobalRestriction;
use Espo\Core\Acl\Permission;
use Espo\Core\Acl\Table;
use Espo\ORM\Entity;
@@ -283,7 +284,7 @@ class Acl
*
* @param User|string $target User entity or user ID.
*/
public function checkUserPermission($target, string $permissionType = 'user'): bool
public function checkUserPermission($target, string $permissionType = Permission::USER): bool
{
return $this->aclManager->checkUserPermission($this->user, $target, $permissionType);
}
@@ -30,11 +30,8 @@
namespace Espo\Core\Acl\AccessChecker\AccessCheckers;
use Espo\Entities\User;
use Espo\ORM\Entity;
use Espo\Core\Utils\Metadata;
use Espo\Core\Acl\DefaultAccessChecker;
use Espo\Core\Acl\Traits\DefaultAccessCheckerDependency;
use Espo\Core\Acl\AccessEntityCreateChecker;
@@ -67,17 +64,12 @@ class Foreign implements
{
use DefaultAccessCheckerDependency;
private Metadata $metadata;
private EntityManager $entityManager;
public function __construct(
Metadata $metadata,
private Metadata $metadata,
DefaultAccessChecker $defaultAccessChecker,
EntityManager $entityManager
private EntityManager $entityManager
) {
$this->metadata = $metadata;
$this->defaultAccessChecker = $defaultAccessChecker;
$this->entityManager = $entityManager;
}
private function getForeignEntity(Entity $entity): ?Entity
@@ -121,7 +113,9 @@ class Foreign implements
return false;
}
return $this->defaultAccessChecker->checkEntityCreate($user, $entity, $data);
// @todo Check parent 'edit' access.
return $this->defaultAccessChecker->checkEntityCreate($user, $foreign, $data);
}
public function checkEntityRead(User $user, Entity $entity, ScopeData $data): bool
@@ -132,7 +126,7 @@ class Foreign implements
return false;
}
return $this->defaultAccessChecker->checkEntityRead($user, $entity, $data);
return $this->defaultAccessChecker->checkEntityRead($user, $foreign, $data);
}
public function checkEntityEdit(User $user, Entity $entity, ScopeData $data): bool
@@ -143,7 +137,7 @@ class Foreign implements
return false;
}
return $this->defaultAccessChecker->checkEntityEdit($user, $entity, $data);
return $this->defaultAccessChecker->checkEntityEdit($user, $foreign, $data);
}
public function checkEntityDelete(User $user, Entity $entity, ScopeData $data): bool
@@ -151,10 +145,14 @@ class Foreign implements
$foreign = $this->getForeignEntity($entity);
if (!$foreign) {
if ($user->isAdmin()) {
return true;
}
return false;
}
return $this->defaultAccessChecker->checkEntityDelete($user, $entity, $data);
return $this->defaultAccessChecker->checkEntityDelete($user, $foreign, $data);
}
public function checkEntityStream(User $user, Entity $entity, ScopeData $data): bool
@@ -165,6 +163,6 @@ class Foreign implements
return false;
}
return $this->defaultAccessChecker->checkEntityStream($user, $entity, $data);
return $this->defaultAccessChecker->checkEntityStream($user, $foreign, $data);
}
}
@@ -87,7 +87,7 @@ class DefaultAccessChecker implements
return $this->scopeChecker->check($data, $action, $checkerData);
}
private function checkScope(User $user, ScopeData $data, ?string $action = null): bool
private function checkScope(ScopeData $data, ?string $action = null): bool
{
$checkerData = ScopeCheckerData
::createBuilder()
@@ -100,27 +100,27 @@ class DefaultAccessChecker implements
public function check(User $user, ScopeData $data): bool
{
return $this->checkScope($user, $data);
return $this->checkScope($data);
}
public function checkCreate(User $user, ScopeData $data): bool
{
return $this->checkScope($user, $data, Table::ACTION_CREATE);
return $this->checkScope($data, Table::ACTION_CREATE);
}
public function checkRead(User $user, ScopeData $data): bool
{
return $this->checkScope($user, $data, Table::ACTION_READ);
return $this->checkScope($data, Table::ACTION_READ);
}
public function checkEdit(User $user, ScopeData $data): bool
{
return $this->checkScope($user, $data, Table::ACTION_EDIT);
return $this->checkScope($data, Table::ACTION_EDIT);
}
public function checkDelete(User $user, ScopeData $data): bool
{
if ($this->checkScope($user, $data, Table::ACTION_DELETE)) {
if ($this->checkScope($data, Table::ACTION_DELETE)) {
return true;
}
@@ -137,7 +137,7 @@ class DefaultAccessChecker implements
public function checkStream(User $user, ScopeData $data): bool
{
return $this->checkScope($user, $data, Table::ACTION_STREAM);
return $this->checkScope($data, Table::ACTION_STREAM);
}
public function checkEntityCreate(User $user, Entity $entity, ScopeData $data): bool
@@ -99,7 +99,7 @@ class DefaultAssignmentChecker implements AssignmentChecker
return false;
}
$assignmentPermission = $this->aclManager->getPermissionLevel($user, 'assignmentPermission');
$assignmentPermission = $this->aclManager->getPermissionLevel($user, Permission::ASSIGNMENT);
if (
$assignmentPermission === Table::LEVEL_YES ||
@@ -157,7 +157,7 @@ class DefaultAssignmentChecker implements AssignmentChecker
protected function isPermittedTeams(User $user, Entity $entity): bool
{
$assignmentPermission = $this->aclManager->getPermissionLevel($user, 'assignmentPermission');
$assignmentPermission = $this->aclManager->getPermissionLevel($user, Permission::ASSIGNMENT);
if (!in_array($assignmentPermission, [Table::LEVEL_TEAM, Table::LEVEL_NO])) {
return true;
@@ -219,7 +219,7 @@ class DefaultAssignmentChecker implements AssignmentChecker
private function isPermittedTeamsEmpty(User $user, CoreEntity $entity): bool
{
$assignmentPermission = $this->aclManager->getPermissionLevel($user, 'assignmentPermission');
$assignmentPermission = $this->aclManager->getPermissionLevel($user, Permission::ASSIGNMENT);
if ($assignmentPermission !== Table::LEVEL_TEAM) {
return true;
@@ -259,7 +259,7 @@ class DefaultAssignmentChecker implements AssignmentChecker
return false;
}
$assignmentPermission = $this->aclManager->getPermissionLevel($user, 'assignmentPermission');
$assignmentPermission = $this->aclManager->getPermissionLevel($user, Permission::ASSIGNMENT);
if (
$assignmentPermission === Table::LEVEL_YES ||
@@ -81,7 +81,6 @@ class DefaultOwnershipChecker implements OwnershipOwnChecker, OwnershipTeamCheck
return false;
}
/** @var string[] $userTeamIdList */
$userTeamIdList = $user->getLinkMultipleIdList(self::FIELD_TEAMS);
if (
@@ -53,7 +53,7 @@ class OwnerUserFieldProvider
/**
* Get an entity field that stores an owner-user (or multiple users).
* Must be link or linkMulitple field. NULL means no owner.
* Must be a link or linkMultiple field. NULL means no owner.
*/
public function get(string $entityType): ?string
{
+43
View File
@@ -0,0 +1,43 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Acl;
class Permission
{
public const ASSIGNMENT = 'assignment';
public const USER = 'user';
public const PORTAL = 'portal';
public const MASS_UPDATE = 'massUpdate';
public const EXPORT = 'export';
public const AUDIT = 'audit';
public const DATA_PRIVACY = 'dataPrivacy';
public const MESSAGE = 'message';
public const MENTION = 'mention';
}
@@ -52,9 +52,7 @@ class DefaultTable implements Table
protected string $type = 'acl';
protected string $defaultAclType = 'recordAllTeamOwnNo';
/**
* @var string[]
*/
/** @var string[] */
private $actionList = [
self::ACTION_READ,
self::ACTION_STREAM,
@@ -63,16 +61,12 @@ class DefaultTable implements Table
self::ACTION_CREATE,
];
/**
* @var string[]
*/
/** @var string[] */
private $booleanActionList = [
self::ACTION_CREATE,
];
/**
* @var string[]
*/
/** @var string[] */
protected $levelList = [
self::LEVEL_YES,
self::LEVEL_ALL,
@@ -81,30 +75,23 @@ class DefaultTable implements Table
self::LEVEL_NO,
];
/**
* @var string[]
*/
/** @var string[] */
private $fieldActionList = [
self::ACTION_READ,
self::ACTION_EDIT,
];
/**
* @var string[]
*/
/** @var string[] */
protected $fieldLevelList = [
self::LEVEL_YES,
self::LEVEL_NO,
];
private stdClass $data;
private string $cacheKey;
/**
* @var string[]
*/
/** @var string[] */
private $valuePermissionList = [];
private ScopeDataResolver $scopeDataResolver;
public function __construct(
private RoleListProvider $roleListProvider,
@@ -112,7 +99,7 @@ class DefaultTable implements Table
protected User $user,
Config $config,
protected Metadata $metadata,
DataCache $dataCache
DataCache $dataCache,
) {
$this->data = (object) [
@@ -135,14 +122,15 @@ class DefaultTable implements Table
$cachedData = $dataCache->get($this->cacheKey);
$this->data = $cachedData;
}
else {
} else {
$this->load();
if ($config->get('useCache')) {
$dataCache->store($this->cacheKey, $this->data);
}
}
$this->scopeDataResolver = new ScopeDataResolver($this);
}
/**
@@ -156,11 +144,7 @@ class DefaultTable implements Table
$data = $this->data->scopes->$scope;
if (is_string($data)) {
return $this->getScopeData($data);
}
return ScopeData::fromRaw($data);
return $this->scopeDataResolver->resolve($data);
}
/**
@@ -0,0 +1,66 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Acl\Table;
use Espo\Core\Acl\ScopeData;
use Espo\Core\Acl\Table;
/**
* @internal
*/
class ScopeDataResolver
{
public function __construct(
private Table $table,
) {}
public function resolve(mixed $data): ScopeData
{
if (!is_string($data)) {
return ScopeData::fromRaw($data);
}
$foreignScope = $data;
$isBoolean = false;
if (str_starts_with($data, 'boolean:')) {
[, $foreignScope] = explode(':', $data, 2);
$isBoolean = true;
}
$scopeData = $this->table->getScopeData($foreignScope);
if ($isBoolean && !$scopeData->isBoolean()) {
return ScopeData::fromRaw(true);
}
return $scopeData;
}
}
+3 -2
View File
@@ -29,6 +29,7 @@
namespace Espo\Core;
use Espo\Core\Acl\Permission;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
use Espo\Entities\User;
@@ -69,7 +70,7 @@ use InvalidArgumentException;
*/
class AclManager
{
protected const PERMISSION_ASSIGNMENT = 'assignment';
protected const PERMISSION_ASSIGNMENT = Permission::ASSIGNMENT;
/** @var array<string, AccessChecker> */
private $accessCheckerHashMap = [];
@@ -572,7 +573,7 @@ class AclManager
*
* @param User|string $target User entity or user ID.
*/
public function checkUserPermission(User $user, $target, string $permissionType = 'user'): bool
public function checkUserPermission(User $user, $target, string $permissionType = Permission::USER): bool
{
$permission = $this->getPermissionLevel($user, $permissionType);
+22 -11
View File
@@ -29,6 +29,7 @@
namespace Espo\Core\Api;
use Espo\Core\Authentication\HeaderKey;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\ServiceUnavailable;
use Espo\Core\Exceptions\Forbidden;
@@ -47,8 +48,6 @@ use Exception;
*/
class Auth
{
private const HEADER_ESPO_AUTHORIZATION = 'Espo-Authorization';
public function __construct(
private Log $log,
private Authentication $authentication,
@@ -195,8 +194,12 @@ class Auth
throw new BadRequest("Auth: Bad authorization string provided.");
}
/** @var array{string, string} */
return explode(':', $stringDecoded, 2);
[$username, $password] = explode(':', $stringDecoded, 2);
$username = trim($username);
$password = trim($password);
return [$username, $password];
}
private function handleSecondStepRequired(Response $response, Result $result): void
@@ -237,7 +240,9 @@ class Auth
$response->writeBody($e->getBody());
}
$this->log->notice("Auth: " . $e->getMessage());
if ($e->getMessage()) {
$this->log->notice("Auth exception: {message}", ['message' => $e->getMessage()]);
}
return;
}
@@ -269,7 +274,7 @@ class Auth
private function obtainAuthenticationMethodFromRequest(Request $request): ?string
{
if ($request->hasHeader(self::HEADER_ESPO_AUTHORIZATION)) {
if ($request->hasHeader(HeaderKey::AUTHORIZATION)) {
return null;
}
@@ -299,12 +304,10 @@ class Auth
*/
private function obtainUsernamePasswordFromRequest(Request $request): array
{
if ($request->hasHeader(self::HEADER_ESPO_AUTHORIZATION)) {
[$username, $password] = $this->decodeAuthorizationString(
$request->getHeader(self::HEADER_ESPO_AUTHORIZATION) ?? ''
);
if ($request->hasHeader(HeaderKey::AUTHORIZATION)) {
$headerValue = $request->getHeader(HeaderKey::AUTHORIZATION) ?? '';
return [$username, $password];
return $this->decodeAuthorizationString($headerValue);
}
if (
@@ -314,6 +317,14 @@ class Auth
$username = $request->getServerParam('PHP_AUTH_USER');
$password = $request->getServerParam('PHP_AUTH_PW');
if (is_string($username)) {
$username = trim($username);
}
if (is_string($password)) {
$password = trim($password);
}
return [$username, $password];
}
+37 -31
View File
@@ -37,9 +37,11 @@ use Espo\Core\Exceptions\HasBody;
use Espo\Core\Exceptions\HasLogLevel;
use Espo\Core\Exceptions\HasLogMessage;
use Espo\Core\Exceptions\NotFound;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Log;
use LogicException;
use Psr\Log\LogLevel;
use RuntimeException;
use Throwable;
/**
@@ -80,7 +82,7 @@ class ErrorOutput
NotFound::class,
];
public function __construct(private Log $log, private Config $config)
public function __construct(private Log $log)
{}
public function process(
@@ -112,6 +114,11 @@ class ErrorOutput
): void {
$message = $exception->getMessage();
if ($exception->getPrevious() && $exception->getPrevious()->getMessage()) {
$message .= " " . $exception->getPrevious()->getMessage();
}
$statusCode = $exception->getCode();
if ($exception instanceof HasLogMessage) {
@@ -122,30 +129,12 @@ class ErrorOutput
$this->processRoute($route, $request, $exception);
}
$logLevel = $exception instanceof HasLogLevel ?
$exception->getLogLevel() :
Log::LEVEL_ERROR;
$level = $this->getLevel($exception);
$messageLineFile =
'line: ' . $exception->getLine() . ', ' .
'file: ' . $exception->getFile();
$logMessageItemList = [];
if ($message) {
$logMessageItemList[] = "$message";
}
$logMessageItemList[] = $request->getMethod() . ' ' . $request->getResourcePath();
$logMessageItemList[] = $messageLineFile;
$logMessage = "($statusCode) " . implode("; ", $logMessageItemList);
if ($this->toPrintTrace()) {
$logMessage .= " :: " . $exception->getTraceAsString();
}
$this->log->log($logLevel, $logMessage);
$this->log->log($level, $message, [
'exception' => $exception,
'request' => $request,
]);
if (!in_array($statusCode, $this->allowedStatusCodeList)) {
$statusCode = 500;
@@ -224,6 +213,11 @@ class ErrorOutput
$requestBodyString = $this->clearPasswords($request->getBodyContents() ?? '');
$message = $exception->getMessage();
if ($exception->getPrevious() && $exception->getPrevious()->getMessage()) {
$message .= " " . $exception->getPrevious()->getMessage();
}
$statusCode = $exception->getCode();
$routeParams = $request->getRouteParams();
@@ -250,12 +244,7 @@ class ErrorOutput
$logMessage .= implode("; ", $logMessageItemList);
$this->log->log('debug', $logMessage);
}
private function toPrintTrace(): bool
{
return (bool) $this->config->get('logger.printTrace');
$this->log->debug($logMessage);
}
private function toPrintExceptionStatusReason(Throwable $exception): bool
@@ -269,4 +258,21 @@ class ErrorOutput
return false;
}
private function getLevel(Throwable $exception): string
{
if ($exception instanceof HasLogLevel) {
return $exception->getLogLevel();
}
if ($exception instanceof LogicException) {
return LogLevel::ALERT;
}
if ($exception instanceof RuntimeException) {
return LogLevel::CRITICAL;
}
return LogLevel::ERROR;
}
}
@@ -40,7 +40,6 @@ use Exception;
class Command implements Runner
{
use Cli;
use SetupSystemUser;
public function __construct(private ConsoleCommandManager $commandManager)
{}
@@ -67,9 +67,7 @@ class Authentication
{
private const LOGOUT_USERNAME = '**logout';
private const HEADER_ESPO_AUTHORIZATION = 'Espo-Authorization';
private const HEADER_CREATE_TOKEN_SECRET = 'Espo-Authorization-Create-Token-Secret';
private const HEADER_BY_TOKEN = 'Espo-Authorization-By-Token';
private const HEADER_ANOTHER_USER = 'X-Another-User';
private const HEADER_LOGOUT_REDIRECT_URL = 'X-Logout-Redirect-Url';
@@ -95,6 +93,7 @@ class Authentication
* Process logging in.
*
* @throws ServiceUnavailable
* @throws Forbidden
*/
public function login(AuthenticationData $data, Request $request, Response $response): Result
{
@@ -107,16 +106,21 @@ class Authentication
$method &&
!$this->configDataProvider->authenticationMethodIsApi($method)
) {
$this->log
->warning("AUTH: Trying to use not allowed authentication method '$method'.");
$this->log->warning("Auth: Trying to use not allowed authentication method '{method}'.", [
'method' => $method,
]);
return $this->processFail(Result::fail(FailReason::METHOD_NOT_ALLOWED), $data, $request);
}
$this->hookManager->processBeforeLogin($data, $request);
try {
$this->hookManager->processBeforeLogin($data, $request);
} catch (Forbidden $e) {
$this->processForbidden($e);
}
if (!$method && $password === null) {
$this->log->error("AUTH: Trying to login w/o password.");
$this->log->error("Auth: Trying to login w/o password.");
return Result::fail(FailReason::NO_PASSWORD);
}
@@ -149,7 +153,7 @@ class Authentication
}
}
$byTokenAndUsername = $request->getHeader(self::HEADER_BY_TOKEN) === 'true';
$byTokenAndUsername = $request->getHeader(HeaderKey::AUTHORIZATION_BY_TOKEN) === 'true';
if ($method && $byTokenAndUsername) {
return Result::fail(FailReason::DISCREPANT_DATA);
@@ -157,7 +161,9 @@ class Authentication
if (($byTokenAndUsername || $byTokenOnly) && !$authToken) {
if ($username) {
$this->log->info("AUTH: Trying to login as user '$username' by token but token is not found.");
$this->log->info("Auth: Trying to login as user '{username}' by token but token is not found.", [
'username' => $username,
]);
}
return $this->processFail(Result::fail(FailReason::TOKEN_NOT_FOUND), $data, $request);
@@ -213,15 +219,7 @@ class Authentication
return $this->processFail(Result::fail(FailReason::DENIED), $data, $request);
}
if ($this->isPortal()) {
$user->set('portalId', $this->getPortal()->getId());
}
if (!$this->isPortal()) {
$user->loadLinkMultipleField('teams');
}
$user->set('ipAddress', $this->util->obtainIpFromRequest($request));
$this->prepareUser($user, $request);
[$loggedUser, $anotherUserFailReason] = $this->getLoggedUser($request, $user);
@@ -234,6 +232,7 @@ class Authentication
$this->applicationUser->setUser($loggedUser);
if (
!$result->bypassSecondStep() &&
!$result->isSecondStepRequired() &&
!$authToken &&
$this->configDataProvider->isTwoFactorEnabled()
@@ -241,13 +240,19 @@ class Authentication
$result = $this->processTwoFactor($result, $request);
if ($result->isFail()) {
return $this->processFail($result, $data, $request);
return $this->processTwoFactorFail($result, $data, $request, $authLogRecord);
}
}
try {
$this->hookManager->processOnLogin($result, $data, $request);
} catch (Forbidden $e) {
$this->processForbidden($e, $authLogRecord);
}
if (
!$result->isSecondStepRequired() &&
$request->getHeader(self::HEADER_ESPO_AUTHORIZATION)
$request->getHeader(HeaderKey::AUTHORIZATION)
) {
$authToken = $this->processAuthTokenFinal(
$authToken,
@@ -344,13 +349,13 @@ class Authentication
private function processAuthTokenCheck(AuthToken $authToken): bool
{
if ($this->isPortal() && $authToken->getPortalId() !== $this->getPortal()->getId()) {
$this->log->info("AUTH: Trying to login to portal with a token not related to portal.");
$this->log->info("Auth: Trying to login to portal with a token not related to portal.");
return false;
}
if (!$this->isPortal() && $authToken->getPortalId()) {
$this->log->info("AUTH: Trying to login to crm with a token related to portal.");
$this->log->info("Auth: Trying to login to crm with a token related to portal.");
return false;
}
@@ -361,8 +366,9 @@ class Authentication
private function processUserCheck(User $user, ?AuthLogRecord $authLogRecord): bool
{
if (!$user->isActive()) {
$this->log
->info("AUTH: Trying to login as user '" . $user->getUserName() . "' which is not active.");
$this->log->info("Auth: Trying to login as user '{username}' which is not active.", [
'username' => $user->getUserName(),
]);
$this->logDenied($authLogRecord, AuthLogRecord::DENIAL_REASON_INACTIVE_USER);
@@ -370,8 +376,9 @@ class Authentication
}
if ($user->isSystem()) {
$this->log
->info("AUTH: Trying to login to crm as a system user '{$user->getUserName()}'.");
$this->log->info("Auth: Trying to login to crm as a system user '{username}'.", [
'username' => $user->getUserName(),
]);
$this->logDenied($authLogRecord, AuthLogRecord::DENIAL_REASON_IS_SYSTEM_USER);
@@ -379,8 +386,9 @@ class Authentication
}
if (!$user->isAdmin() && !$this->isPortal() && $user->isPortal()) {
$this->log
->info("AUTH: Trying to login to crm as a portal user '" . $user->getUserName() . "'.");
$this->log->info("Auth: Trying to login to crm as a portal user '{username}'.", [
'username' => $user->getUserName(),
]);
$this->logDenied($authLogRecord, AuthLogRecord::DENIAL_REASON_IS_PORTAL_USER);
@@ -388,8 +396,9 @@ class Authentication
}
if ($this->isPortal() && !$user->isPortal()) {
$this->log->info(
"AUTH: Trying to login to portal as user '" . $user->getUserName() . "' which is not portal user.");
$this->log->info("Auth: Trying to login to portal as user '{username}' which is not portal user.", [
'username' => $user->getUserName(),
]);
$this->logDenied($authLogRecord, AuthLogRecord::DENIAL_REASON_IS_NOT_PORTAL_USER);
@@ -403,9 +412,12 @@ class Authentication
->isRelated($user);
if (!$isPortalRelatedToUser) {
$this->log->info(
"AUTH: Trying to login to portal as user '" . $user->getUserName() . "' ".
"which is portal user but does not belong to portal.");
$msg = "Auth: Trying to login to portal as user '{username}' " .
"which is portal user but does not belong to portal.";
$this->log->info($msg, [
'username' => $user->getUserName(),
]);
$this->logDenied($authLogRecord, AuthLogRecord::DENIAL_REASON_USER_IS_NOT_IN_PORTAL);
@@ -771,4 +783,56 @@ class Authentication
return [$loggedUser, null];
}
private function prepareUser(User $user, Request $request): void
{
if ($this->isPortal()) {
$user->set('portalId', $this->getPortal()->getId());
}
if (!$this->isPortal()) {
$user->loadLinkMultipleField('teams');
}
$user->set('ipAddress', $this->util->obtainIpFromRequest($request));
}
/**
* @throws Forbidden
*/
private function processForbidden(Forbidden $exception, ?AuthLogRecord $authLogRecord = null): never
{
$this->log->warning('Auth: Forbidden. {message}', [
'message' => $exception->getMessage(),
'exception' => $exception,
]);
if ($authLogRecord) {
$authLogRecord
->setIsDenied()
->setDenialReason(AuthLogRecord::DENIAL_REASON_FORBIDDEN);
$this->entityManager->saveEntity($authLogRecord);
}
throw new Forbidden();
}
private function processTwoFactorFail(
Result $result,
AuthenticationData $data,
Request $request,
?AuthLogRecord $authLogRecord
): Result {
if ($authLogRecord) {
$authLogRecord
->setIsDenied()
->setDenialReason(AuthLogRecord::DENIAL_REASON_WRONG_CODE);
$this->entityManager->saveEntity($authLogRecord);
}
return $this->processFail($result, $data, $request);
}
}
@@ -37,6 +37,7 @@ use Espo\Core\Utils\Metadata;
class ConfigDataProvider
{
private const FAILED_ATTEMPTS_PERIOD = '60 seconds';
private const FAILED_CODE_ATTEMPTS_PERIOD = '5 minutes';
private const MAX_FAILED_ATTEMPT_NUMBER = 10;
public function __construct(private Config $config, private Metadata $metadata)
@@ -50,6 +51,14 @@ class ConfigDataProvider
return $this->config->get('authFailedAttemptsPeriod', self::FAILED_ATTEMPTS_PERIOD);
}
/**
* A period for max failed 2FA code attempts checking.
*/
public function getFailedCodeAttemptsPeriod(): string
{
return $this->config->get('authFailedCodeAttemptsPeriod', self::FAILED_CODE_ATTEMPTS_PERIOD);
}
/**
* Max failed log in attempts.
*/
@@ -147,4 +156,25 @@ class ConfigDataProvider
return $list;
}
public function ipAddressCheck(): bool
{
return (bool) $this->config->get('authIpAddressCheck');
}
/**
* @return string[]
*/
public function getIpAddressWhitelist(): array
{
return $this->config->get('authIpAddressWhitelist') ?? [];
}
/**
* @return string[]
*/
public function getIpAddressCheckExcludedUserIdList(): array
{
return $this->config->get('authIpAddressCheckExcludedUsersIds') ?? [];
}
}
@@ -0,0 +1,37 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Authentication;
class HeaderKey
{
public const AUTHORIZATION_BY_TOKEN = 'Espo-Authorization-By-Token';
public const AUTHORIZATION_CODE = 'Espo-Authorization-Code';
public const AUTHORIZATION = 'Espo-Authorization';
}
@@ -36,11 +36,12 @@ use Espo\Core\Exceptions\ServiceUnavailable;
/**
* Before logging in, before credentials are checked.
*
* @throws Forbidden
* @throws ServiceUnavailable
*/
interface BeforeLogin
{
/**
* @throws Forbidden
* @throws ServiceUnavailable
*/
public function process(AuthenticationData $data, Request $request): void;
}
@@ -30,12 +30,12 @@
namespace Espo\Core\Authentication\Hook\Hooks;
use Espo\Core\Api\Util;
use Espo\Core\Authentication\HeaderKey;
use Espo\Core\Authentication\Hook\BeforeLogin;
use Espo\Core\Authentication\AuthenticationData;
use Espo\Core\Api\Request;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Authentication\ConfigDataProvider;
use Espo\Core\Utils\Log;
use Espo\ORM\EntityManager;
use Espo\Entities\AuthLogRecord;
@@ -51,7 +51,6 @@ class FailedAttemptsLimit implements BeforeLogin
public function __construct(
private ConfigDataProvider $configDataProvider,
private EntityManager $entityManager,
private Log $log,
private Util $util
) {}
@@ -60,34 +59,20 @@ class FailedAttemptsLimit implements BeforeLogin
*/
public function process(AuthenticationData $data, Request $request): void
{
$isByTokenOnly = !$data->getMethod() && $request->getHeader('Espo-Authorization-By-Token') === 'true';
$isByTokenOnly = !$data->getMethod() && $request->getHeader(HeaderKey::AUTHORIZATION_BY_TOKEN) === 'true';
if ($isByTokenOnly) {
return;
}
if ($this->configDataProvider->isAuthLogDisabled()) {
if ($isByTokenOnly || $this->configDataProvider->isAuthLogDisabled()) {
return;
}
$failedAttemptsPeriod = $this->configDataProvider->getFailedAttemptsPeriod();
$maxFailedAttempts = $this->configDataProvider->getMaxFailedAttemptNumber();
$requestTime = intval($request->getServerParam('REQUEST_TIME_FLOAT'));
try {
$requestTimeFrom = (new DateTime('@' . $requestTime))->modify('-' . $failedAttemptsPeriod);
}
catch (Exception $e) {
throw new RuntimeException($e->getMessage());
}
$ip = $this->util->obtainIpFromRequest($request);
$ipAddress = $this->util->obtainIpFromRequest($request);
$where = [
'requestTime>' => $requestTimeFrom->format('U'),
'ipAddress' => $ip,
'requestTime>' => $this->getTimeFrom($request, $failedAttemptsPeriod)->format('U'),
'isDenied' => true,
'ipAddress' => $ipAddress,
];
$wasFailed = (bool) $this->entityManager
@@ -105,12 +90,24 @@ class FailedAttemptsLimit implements BeforeLogin
->where($where)
->count();
if ($failAttemptCount <= $maxFailedAttempts) {
if ($failAttemptCount <= $this->configDataProvider->getMaxFailedAttemptNumber()) {
return;
}
$this->log->warning("AUTH: Max failed login attempts exceeded for IP '$ip'.");
throw new Forbidden("Max failed login attempts exceeded for IP address $ipAddress.");
}
throw new Forbidden("Max failed login attempts exceeded.");
private function getTimeFrom(Request $request, string $failedAttemptsPeriod): DateTime
{
$requestTime = intval($request->getServerParam('REQUEST_TIME_FLOAT'));
try {
$requestTimeFrom = (new DateTime('@' . $requestTime))->modify('-' . $failedAttemptsPeriod);
}
catch (Exception $e) {
throw new RuntimeException($e->getMessage());
}
return $requestTimeFrom;
}
}
@@ -0,0 +1,118 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Authentication\Hook\Hooks;
use Espo\Core\Api\Request;
use Espo\Core\Authentication\AuthenticationData;
use Espo\Core\Authentication\ConfigDataProvider;
use Espo\Core\Authentication\Hook\BeforeLogin;
use Espo\Core\Exceptions\Forbidden;
use Espo\Entities\AuthLogRecord;
use Espo\ORM\EntityManager;
use DateTime;
use Exception;
use RuntimeException;
/**
* @noinspection PhpUnused
*/
class FailedCodeAttemptsLimit implements BeforeLogin
{
public function __construct(
private ConfigDataProvider $configDataProvider,
private EntityManager $entityManager,
) {}
/**
* @throws Forbidden
*/
public function process(AuthenticationData $data, Request $request): void
{
if (
$request->getHeader('Espo-Authorization-Code') === null ||
$this->configDataProvider->isAuthLogDisabled()
) {
return;
}
$isByTokenOnly = !$data->getMethod() && $request->getHeader('Espo-Authorization-By-Token') === 'true';
if ($isByTokenOnly) {
return;
}
$failedAttemptsPeriod = $this->configDataProvider->getFailedCodeAttemptsPeriod();
$where = [
'requestTime>' => $this->getTimeFrom($request, $failedAttemptsPeriod)->format('U'),
'isDenied' => true,
'username' => $data->getUsername(),
'denialReason' => AuthLogRecord::DENIAL_REASON_WRONG_CODE,
];
$wasFailed = (bool) $this->entityManager
->getRDBRepository(AuthLogRecord::ENTITY_TYPE)
->select(['id'])
->where($where)
->findOne();
if (!$wasFailed) {
return;
}
$failAttemptCount = $this->entityManager
->getRDBRepository(AuthLogRecord::ENTITY_TYPE)
->where($where)
->count();
if ($failAttemptCount <= $this->configDataProvider->getMaxFailedAttemptNumber()) {
return;
}
$username = $data->getUsername() ?? '';
throw new Forbidden("Max failed 2FA login attempts exceeded for username '$username'.");
}
private function getTimeFrom(Request $request, string $failedAttemptsPeriod): DateTime
{
$requestTime = intval($request->getServerParam('REQUEST_TIME_FLOAT'));
try {
$requestTimeFrom = (new DateTime('@' . $requestTime))->modify('-' . $failedAttemptsPeriod);
}
catch (Exception $e) {
throw new RuntimeException($e->getMessage());
}
return $requestTimeFrom;
}
}
@@ -0,0 +1,87 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Authentication\Hook\Hooks;
use Espo\Core\Api\Request;
use Espo\Core\Api\Util;
use Espo\Core\Authentication\AuthenticationData;
use Espo\Core\Authentication\ConfigDataProvider;
use Espo\Core\Authentication\Hook\OnLogin;
use Espo\Core\Authentication\Result;
use Espo\Core\Authentication\Util\IpAddressUtil;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Utils\Config;
class IpAddressWhitelist implements OnLogin
{
public function __construct(
private ConfigDataProvider $configDataProvider,
private Util $util,
private Config $config,
private IpAddressUtil $ipAddressUtil
) {}
public function process(Result $result, AuthenticationData $data, Request $request): void
{
if (!$this->configDataProvider->ipAddressCheck()) {
return;
}
$ipAddress = $this->util->obtainIpFromRequest($request);
if (
$ipAddress &&
$this->ipAddressUtil->isInWhitelist($ipAddress, $this->configDataProvider->getIpAddressWhitelist())
) {
return;
}
$user = $result->getUser();
if ($user && $user->isPortal()) {
return;
}
if ($user && $user->isSuperAdmin() && $this->config->get('restrictedMode')) {
return;
}
if (
$user &&
in_array($user->getId(), $this->configDataProvider->getIpAddressCheckExcludedUserIdList())
) {
return;
}
$username = $user ? $user->getUserName() : '?';
throw new Forbidden("Not allowed IP address $ipAddress, user: $username.");
}
}
@@ -29,6 +29,8 @@
namespace Espo\Core\Authentication\Hook;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\ServiceUnavailable;
use Espo\Core\Utils\Metadata;
use Espo\Core\InjectableFactory;
use Espo\Core\Authentication\AuthenticationData;
@@ -41,6 +43,10 @@ class Manager
public function __construct(private Metadata $metadata, private InjectableFactory $injectableFactory)
{}
/**
* @throws ServiceUnavailable
* @throws Forbidden
*/
public function processBeforeLogin(AuthenticationData $data, Request $request): void
{
foreach ($this->getBeforeLoginHookList() as $hook) {
@@ -48,6 +54,16 @@ class Manager
}
}
/**
* @throws Forbidden
*/
public function processOnLogin(Result $result, AuthenticationData $data, Request $request): void
{
foreach ($this->getOnLoginHookList() as $hook) {
$hook->process($result, $data, $request);
}
}
public function processOnFail(Result $result, AuthenticationData $data, Request $request): void
{
foreach ($this->getOnFailHookList() as $hook) {
@@ -102,6 +118,21 @@ class Manager
return $list;
}
/**
* @return OnLogin[]
*/
private function getOnLoginHookList(): array
{
$list = [];
foreach ($this->getHookClassNameList('onLogin') as $className) {
/** @var class-string<OnLogin> $className */
$list[] = $this->injectableFactory->create($className);
}
return $list;
}
/**
* @return OnResult[]
*/
@@ -0,0 +1,46 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://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 Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Authentication\Hook;
use Espo\Core\Authentication\AuthenticationData;
use Espo\Core\Api\Request;
use Espo\Core\Authentication\Result;
use Espo\Core\Exceptions\Forbidden;
/**
* Once a login result is ready.
*/
interface OnLogin
{
/**
* @throws Forbidden
*/
public function process(Result $result, AuthenticationData $data, Request $request): void;
}
@@ -132,9 +132,8 @@ class LdapLogin implements Login
if ($user) {
return Result::success($user);
}
else {
return Result::fail(FailReason::WRONG_CREDENTIALS);
}
return Result::fail(FailReason::WRONG_CREDENTIALS);
}
if (!$password || $username == '**logout') {
@@ -158,9 +157,10 @@ class LdapLogin implements Login
catch (Exception $e) {
$options = $this->utils->getLdapClientOptions();
$this->log->error(
'LDAP: Could not connect to LDAP server [' . $options['host'] . '], details: ' . $e->getMessage()
);
$this->log->error("LDAP: Could not connect to LDAP server host. {message}", [
'host' => $options['host'],
'message' => $e->getMessage()
]);
/** @var string $username */
@@ -170,7 +170,9 @@ class LdapLogin implements Login
return Result::fail();
}
$this->log->info('LDAP: Administrator [' . $username . '] was logged in by Espo method.');
$this->log->info("LDAP: Administrator '{username}' was logged in by Espo method.", [
'username' => $username,
]);
}
$userDn = null;
@@ -182,15 +184,16 @@ class LdapLogin implements Login
$userDn = $this->findLdapUserDnByUsername($username);
}
catch (Exception $e) {
$this->log->error(
'Error while finding DN for [' . $username . '], details: ' . $e->getMessage() . '.'
);
$this->log->error("Error while finding DN for '{username}'. {message}", [
'username' => $username,
'message' => $e->getMessage(),
]);
}
if (!isset($userDn)) {
$this->log->error(
'LDAP: Authentication failed for user [' . $username . '], details: user is not found.'
);
$this->log->error("LDAP: Authentication failed for '{username}'; user is not found.", [
'username' => $username,
]);
$adminUser = $this->adminLogin($username, $password);
@@ -198,18 +201,24 @@ class LdapLogin implements Login
return Result::fail();
}
$this->log->info('LDAP: Administrator [' . $username . '] was logged in by Espo method.');
$this->log->info("LDAP: Administrator '{username}' was logged in by Espo method.", [
'username' => $username,
]);
}
$this->log->debug('User [' . $username . '] is found with this DN ['.$userDn.'].');
$this->log->debug("User '{username}' with DN '{dn}' is found .", [
'username' => $username,
'dn' => $userDn,
]);
try {
$ldapClient->bind($userDn, $password);
}
catch (Exception $e) {
$this->log->error(
'LDAP: Authentication failed for user [' . $username . '], details: ' . $e->getMessage()
);
$this->log->error("LDAP: Authentication failed for '{username}'. {message}", [
'username' => $username,
'message' => $e->getMessage(),
]);
return Result::fail();
}
@@ -229,9 +238,9 @@ class LdapLogin implements Login
if (!isset($user)) {
if (!$this->utils->getOption('createEspoUser')) {
$this->log->warning(
"LDAP: Authentication success for user $username, but user is not created in EspoCRM."
);
$this->log->warning("LDAP: '{username}' authenticated, but user is not created in Espo.", [
'username' => $username,
]);
return Result::fail(FailReason::USER_NOT_FOUND);
}
@@ -259,7 +268,7 @@ class LdapLogin implements Login
$this->client = $this->clientFactory->create($options);
}
catch (Exception $e) {
$this->log->error('LDAP error: ' . $e->getMessage());
$this->log->error("LDAP error. {message}", ['message' => $e->getMessage()]);
}
}
@@ -290,7 +299,10 @@ class LdapLogin implements Login
if (strtolower($username) !== strtolower($tokenUsername)) {
$ip = $this->util->obtainIpFromRequest($request);
$this->log->alert('Unauthorized access attempt for user [' . $username . '] from IP [' . $ip . ']');
$this->log->alert("Unauthorized access attempt for user '{username}' from IP '{ip}'.", [
'username' => $username,
'ip' => $ip,
]);
return null;
}
@@ -325,11 +337,11 @@ class LdapLogin implements Login
*/
private function createUser(array $userData, bool $isPortal = false): ?User
{
$this->log->info('Creating new user...');
$this->log->info("LDAP: Creating new user.");
$data = [];
$this->log->debug('LDAP: user data: ' . print_r($userData, true));
$this->log->debug("LDAP: user data: {userData}", ['userData' => print_r($userData, true)]);
$ldapFields = $this->loadFields('ldap');
@@ -337,7 +349,10 @@ class LdapLogin implements Login
$ldap = strtolower($ldap);
if (isset($userData[$ldap][0])) {
$this->log->debug('LDAP: Create a user with [' . $espo . '] = [' . $userData[$ldap][0] . '].');
$this->log->debug("LDAP: Create a user with [{user1}] = [{user2}].", [
'user1' => $espo,
'user2' => $userData[$ldap][0],
]);
$data[$espo] = $userData[$ldap][0];
}
@@ -410,7 +425,7 @@ class LdapLogin implements Login
/** @noinspection PhpRedundantOptionalArgumentInspection */
$result = $ldapClient->search($searchString, null, Ldap::SEARCH_SCOPE_SUB);
$this->log->debug('LDAP: user search string: "' . $searchString . '"');
$this->log->debug("LDAP: user search string: {string}.", ['string' => $searchString]);
foreach ($result as $item) {
return $item["dn"];
@@ -70,10 +70,16 @@ class Hmac implements Login
$string = $request->getMethod() . ' ' . $request->getResourcePath();
// To become a legacy.
if ($hash === ApiKey::hash($secretKey, $string)) {
return Result::success($user);
}
// As of v8.4.1.
if ($hash === hash_hmac('sha256', $string, $secretKey)) {
return Result::success($user);
}
return Result::fail(FailReason::HASH_NOT_MATCHED);
}
}
@@ -43,7 +43,7 @@ use Espo\ORM\EntityManager;
/**
* Compatible only with default Espo auth tokens.
*
* @todo Use a token-sessionId map to retrieve tokens.
* @todo Use a token-sessionId map to retrieve tokens. Send sid claim in id_token.
*/
class BackchannelLogout
{
@@ -214,7 +214,7 @@ class ConfigDataProvider
public function getAuthorizationPrompt(): string
{
return $this->config->get('oidcAuthorizationPrompt') ?? 'consent';
return $this->object->get('oidcAuthorizationPrompt') ?? 'consent';
}
public function getAuthorizationMaxAge(): ?int
@@ -152,7 +152,7 @@ class Login implements LoginInterface
return Result::fail(FailReason::USER_NOT_FOUND);
}
return Result::success($user);
return Result::success($user)->withBypassSecondStep();
}
private function loginFallback(Data $data, Request $request): Result
@@ -51,6 +51,10 @@ class DefaultUserProvider implements UserProvider
{
$user = $this->findUser($payload);
if ($user === false) {
return null;
}
if ($user) {
$this->syncUser($user, $payload);
@@ -60,7 +64,10 @@ class DefaultUserProvider implements UserProvider
return $this->tryToCreateUser($payload);
}
private function findUser(Payload $payload): ?User
/**
* @return User|false|null
*/
private function findUser(Payload $payload): User|bool|null
{
$usernameClaim = $this->configDataProvider->getUsernameClaim();
@@ -82,24 +89,26 @@ class DefaultUserProvider implements UserProvider
return null;
}
if (!$user->isActive()) {
return null;
}
$userId = $user->getId();
if (!$user->isActive()) {
$this->log->info("Oidc: User $userId found but it's not active.");
return false;
}
$isPortal = $this->applicationState->isPortal();
if (!$isPortal && !$user->isRegular() && !$user->isAdmin()) {
$this->log->info("Oidc: User $userId found but it's neither regular user not admin.");
$this->log->info("Oidc: User $userId found but it's neither regular user nor admin.");
return null;
return false;
}
if ($isPortal && !$user->isPortal()) {
$this->log->info("Oidc: User $userId found but it's not portal user.");
return null;
return false;
}
if ($isPortal) {
@@ -108,20 +117,20 @@ class DefaultUserProvider implements UserProvider
if (!$user->getPortals()->hasId($portalId)) {
$this->log->info("Oidc: User $userId found but it's not related to current portal.");
return null;
return false;
}
}
if ($user->isSuperAdmin()) {
$this->log->info("Oidc: User $userId found but it's super-admin, not allowed.");
return null;
return false;
}
if ($user->isAdmin() && !$this->configDataProvider->allowAdminUser()) {
$this->log->info("Oidc: User $userId found but it's admin, not allowed.");
return null;
return false;
}
return $user;
@@ -51,6 +51,7 @@ class Result
private ?string $token = null;
private ?string $view = null;
private ?string $failReason = null;
private bool $bypassSecondStep = false;
private ?Data $data;
private function __construct(string $status, ?User $user = null, ?Data $data = null)
@@ -105,13 +106,23 @@ class Result
}
/**
* Second step is required. E.g. for 2FA.
* The second step is required.
*/
public function isSecondStepRequired(): bool
{
return $this->status === self::STATUS_SECOND_STEP_REQUIRED;
}
/**
* To bypass the second step.
*
* @since 8.4.0
*/
public function bypassSecondStep(): bool
{
return $this->bypassSecondStep;
}
/**
* Login is failed.
*/
@@ -183,4 +194,17 @@ class Result
{
return $this->failReason;
}
/**
* Clone with bypass second step.
*
* @since 8.4.0
*/
public function withBypassSecondStep(bool $bypassSecondStep = true): self
{
$obj = clone $this;
$obj->bypassSecondStep = $bypassSecondStep;
return $obj;
}
}
@@ -29,6 +29,7 @@
namespace Espo\Core\Authentication\TwoFactor\Email;
use Espo\Core\Authentication\HeaderKey;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Mail\Exceptions\SendingError;
use Espo\Core\Utils\Log;
@@ -56,7 +57,7 @@ class EmailLogin implements Login
public function login(Result $result, Request $request): Result
{
$code = $request->getHeader('Espo-Authorization-Code');
$code = $request->getHeader(HeaderKey::AUTHORIZATION_CODE);
$user = $result->getUser();
@@ -29,6 +29,7 @@
namespace Espo\Core\Authentication\TwoFactor\Sms;
use Espo\Core\Authentication\HeaderKey;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Utils\Log;
use Espo\ORM\EntityManager;
@@ -55,7 +56,7 @@ class SmsLogin implements Login
public function login(Result $result, Request $request): Result
{
$code = $request->getHeader('Espo-Authorization-Code');
$code = $request->getHeader(HeaderKey::AUTHORIZATION_CODE);
$user = $result->getUser();
@@ -29,6 +29,7 @@
namespace Espo\Core\Authentication\TwoFactor\Totp;
use Espo\Core\Authentication\HeaderKey;
use Espo\ORM\EntityManager;
use Espo\Entities\User;
use Espo\Entities\UserData;
@@ -55,7 +56,7 @@ class TotpLogin implements Login
public function login(Result $result, Request $request): Result
{
$code = $request->getHeader('Espo-Authorization-Code');
$code = $request->getHeader(HeaderKey::AUTHORIZATION_CODE);
$user = $result->getUser();

Some files were not shown because too many files have changed in this diff Show More