Compare commits

...

1911 Commits

Author SHA1 Message Date
FreeScout
adc9921108 Add vendor files to dist 2024-08-27 04:52:34 -07:00
FreeScout
f326b14776 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-08-27 04:52:00 -07:00
FreeScout
afaf1f53ab v1.8.151 2024-08-27 04:50:57 -07:00
FreeScout
23bb50eb86 Fix out of memory on Status page when there are many background jobs - closes #4197 2024-08-27 03:58:19 -07:00
FreeScout
39824c876d Fix Undefined array key 1 error on fetching emails - closes #4195 2024-08-26 22:22:06 -07:00
FreeScout
ca1a7d0900 Fix non-multipart emails contain headers in the bodies - closes #4181 2024-08-24 03:02:23 -07:00
FreeScout
351eff9b80 Fix checking POP3 connection when PHP Request Shutdown: Unexpected characters at end of address error occurs 2024-08-23 22:48:46 -07:00
FreeScout
e037011c55 Added Kazakh language 2024-08-16 09:10:07 -07:00
FreeScout
c0ce07a2b9 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-08-15 20:10:13 -07:00
FreeScout
94f11a9155 v1.8.150 2024-08-15 20:09:47 -07:00
FreeScout
dff234ffbe Allow to disable quotes around SINCE date in IMAP search query via APP_SINCE_WITHOUT_QUOTES_ON_FETCHING env parameter - closes #4175 2024-08-15 20:07:55 -07:00
FreeScout
223b4d12dc Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-08-14 09:51:38 -07:00
FreeScout
b33fb5e1d3 v1.8.149 2024-08-14 09:51:26 -07:00
FreeScout
3511e7acb2 Fix configuring MS365 SMTP via oAuth - closes #4179 2024-08-13 22:23:07 -07:00
FreeScout
be4a1234c3 Improve Webklex/php-imap memory usage 2024-08-13 11:24:45 -07:00
FreeScout
823014d4e9 Call parseRawBody() in Webklex Message if body has not been parsed yet 2024-08-13 11:08:16 -07:00
FreeScout
310d3290fe Do not add quotes around the date in IMAP SINCE query - closes #4175 2024-08-11 03:44:03 -07:00
FreeScout
07765cf8ff Populate variables in custom Mailbox names inside emails sent to customers - closes #4169 2024-08-08 05:00:40 -07:00
FreeScout
c4a6585cc7 Add vendor files to dist 2024-08-08 04:17:10 -07:00
FreeScout
d99d991650 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-08-08 04:16:35 -07:00
FreeScout
6e8682e693 v1.8.148 2024-08-08 04:15:59 -07:00
FreeScout
87eab76574 Set MS365 SMTP credentials automatically only when outlook.office365.com SMTP host is set - closes #4151 2024-08-08 04:00:35 -07:00
FreeScout
a011948149 Make toSql() comparison compatible with PostgreSQL - closes #4167 2024-08-07 03:56:31 -07:00
FreeScout
b9391d87dc Leave other emails unread when an exception happens during fetching of one of them - closes #4159 2024-08-02 23:32:49 -07:00
FreeScout
7e7f0a0d10 Leave other emails unread when an exception happens during fetching of one of them - closes #4159 2024-08-01 23:34:38 -07:00
FreeScout
8e62d70986 Correctly processs invalid dates when fetching emails - closes #4159 2024-08-01 21:22:33 -07:00
FreeScout
02e5fa8535 Correctly process invalid mail Reply-To headers - closes #4158 2024-08-01 21:01:42 -07:00
FreeScout
2dc8d061e1 Prefill SMTP username and password for MS365 if those fields are empty - closes #4151 2024-07-30 22:21:34 -07:00
FreeScout
efca2acbe2 Log Helper::getRemoteFileContents() errors 2024-07-22 09:50:01 -07:00
FreeScout
029e5c8a1f Execute hooks when performing conversations bulk delete to Deleted folder - closes #4141 2024-07-22 06:30:26 -07:00
FreeScout
7383b95864 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-07-21 06:19:34 -07:00
FreeScout
5be99f03f3 v1.8.147 2024-07-21 06:19:27 -07:00
FreeScout
da522d8d58 Fix Disconnect link for MS365 in Fetching settings - closes #4136 2024-07-18 02:30:49 -07:00
FreeScout
60ac03df04 Improve App Key check in tools.php 2024-07-17 09:38:38 -07:00
FreeScout
5a22011fe6 Improve sanitizing Path to PHP in tools.php 2024-07-17 09:37:21 -07:00
FreeScout
09f4908def Fix Call to a member function getFirstName() on null in notification.blade.php - closes #4132 2024-07-16 22:17:20 -07:00
FreeScout
8172db9cdb Improve sanitizing Path to PHP in tools.php 2024-07-16 22:12:57 -07:00
FreeScout
05e7e1e739 Improve sanitizing Path to PHP in tools.php 2024-07-16 07:03:27 -07:00
FreeScout
b2c72775dd Merge pull request #4130 from IchHabeHunger54/master
Add conversation->changeSubject() and conversation.subject_changed action
2024-07-16 13:55:52 +03:00
Neustifter, David
9eb7457097 Add Conversation->changeSubject() and conversation.subject_changed action 2024-07-16 12:47:07 +02:00
FreeScout
0b7d1dc06f Allow padding-left and simiar CSS properties when purifying HTML 2024-07-11 00:53:42 -07:00
FreeScout
2f9d1bf92e Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-07-08 01:44:11 -07:00
FreeScout
de405fe60f v1.8.146 2024-07-08 01:44:01 -07:00
FreeScout
9bb3ee9bc1 Use only forward slashes in attachment URLs - closes #4106 2024-07-04 21:04:52 -07:00
FreeScout
5ab6a42808 Fallback to getPathname() for uploaded files - closes #4105 2024-07-04 21:03:14 -07:00
FreeScout
9b5286efca Fallback to getPathname() for uploaded files - closes #41054105 2024-07-04 20:59:32 -07:00
FreeScout
78e1067c07 Do not duplicate recipient in conversation CC field - closes #4112 2024-07-03 08:24:55 -07:00
FreeScout
c19c4ee217 Fix conversation draft not visible in Drafts after undoing new conversation - closes #4112 2024-07-03 07:55:25 -07:00
FreeScout
5ef26eaf32 Fix error message when can not fetch specific email vai MailHelper::fetchMessage() - closes #4097 2024-07-03 04:37:07 -07:00
FreeScout
16843c9d6a Fix Datatables sort_desc.png not found - closes #4107 2024-07-03 00:45:22 -07:00
FreeScout
a21f5c9d53 Merge branch 'master' of github.com:freescout-helpdesk/freescout 2024-07-02 12:02:26 -07:00
FreeScout
97cea6cdef Fix Undefined array key auth_mode on sending emails - closes #4103 2024-07-02 12:02:09 -07:00
FreeScout
12c6552259 Merge pull request #4099 from gabllux/master
Handle null values on outOauthEnabled() - closes #4094
2024-07-02 20:54:56 +02:00
gabllux
04aaeb4cc1 Handle null values on outOauthEnabled() - closes https://github.com/freescout-help-desk/freescout/issues/4094 2024-06-25 21:33:01 +02:00
FreeScout
7c3e57aa57 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-06-19 11:39:48 -07:00
FreeScout
f90ba3d6d6 v1.8.145 2024-06-19 11:39:29 -07:00
FreeScout
4fe2a9333e Improve SMTP oAuth implementation - closes #4084 2024-06-19 09:47:03 -07:00
FreeScout
bd59fa5ef5 Mention posix_isatty() function in \Helper::checkRequiredFunctions() 2024-06-19 04:24:00 -07:00
FreeScout
989c729834 Add created_at index to threads table - closes #4069 2024-06-18 02:26:10 -07:00
FreeScout
a57a424067 Update readme 2024-06-18 00:46:15 -07:00
FreeScout
755e4f0cd7 Add vendor files to dist 2024-06-17 22:12:38 -07:00
FreeScout
a2a0539316 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-06-17 22:11:43 -07:00
FreeScout
e8975d04d6 v1.8.144 2024-06-17 22:11:34 -07:00
FreeScout
d6eac44275 Show more detailed errors when IMAP authentication fails 2024-06-17 10:54:23 -07:00
FreeScout
ea39e24727 Rename STARTTLS Encryption method in Fetching settings 2024-06-17 06:31:18 -07:00
FreeScout
5a290b3ad3 Allow to enable debug in Status - Tools - Fetching section 2024-06-16 02:21:06 -07:00
FreeScout
ce523c51e7 Show debug log when testing fetching 2024-06-16 02:05:42 -07:00
FreeScout
5bcde678d1 Update repo URL in self-updater 2024-06-15 08:30:44 -07:00
FreeScout
98e74e53f2 Update repo link 2024-06-14 00:36:27 -07:00
FreeScout
bde2da820c Update repo URL in self-updater 2024-06-14 00:06:10 -07:00
FreeScout
4b2dd40d99 Update also inactive modules via freescout:module-update command 2024-06-13 03:22:17 -07:00
FreeScout
4d7f8d1f50 Microsoft Exchange oAuth for SMTP - closes #4072 2024-06-12 22:21:03 -07:00
FreeScout
8510ee8224 Microsoft Exchange oAuth for SMTP - closes #4072 2024-06-12 20:45:58 -07:00
FreeScout
7e8e514e7b Microsoft Exchange oAuth for SMTP - closes #4072 2024-06-12 11:45:08 -07:00
FreeScout
fb3b0d3a49 Update readme 2024-06-11 22:18:50 -07:00
FreeScout
aaacc6d3fa Fix Call to undefined function get_current_user() error on Status page - closes #4067 2024-06-10 22:31:29 -07:00
FreeScout
3c199f697c Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-06-10 10:25:57 -07:00
FreeScout
5c01c3793c v1.8.143 2024-06-10 10:25:45 -07:00
FreeScout
0ff901ef32 Fix Undefined variable $customer in thread.php - closes #4066 2024-06-10 10:24:36 -07:00
FreeScout
08a5d5f6ff Add MailHelper::parseEml() function 2024-06-10 04:42:42 -07:00
FreeScout
1fd00def95 Allow to call \Webklex\PHPIMAP\Message::make() without specifying uid 2024-06-10 01:27:24 -07:00
FreeScout
21a73fa5e6 Add vendor files to dist 2024-06-09 09:37:24 -07:00
FreeScout
c9b2c3cc36 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-06-09 09:36:57 -07:00
FreeScout
4ee876c134 v1.8.142 2024-06-09 09:36:48 -07:00
FreeScout
c1c713ef15 Show From for emails received from customers with multiple emails - closes #4059 2024-06-06 11:52:59 -07:00
FreeScout
b926cd5cf2 Do not show Mailbox is empty error when fetching via POP3 - closes #4060 2024-06-06 11:44:21 -07:00
FreeScout
325343c2cf Fix sending a new conversation to the customer with multiple emails - closes #4057 2024-06-05 04:00:26 -07:00
FreeScout
d9502ff348 Fix error in module:list command - closes #4056 2024-06-05 00:00:37 -07:00
FreeScout
585f8f49a7 Sort modules list in the Deactivate License dialog - closes #4055 2024-06-03 22:45:54 -07:00
FreeScout
505db999ad Fix mb_convert_encoding() invalid encoding error on fetching - closes #4051 2024-06-01 08:55:33 -07:00
FreeScout
d402dd5164 Catch mb_convert_encoding() error on fetching - closes #4051 2024-06-01 08:36:57 -07:00
FreeScout
a93cc2a08f Improve Gmail reply separation on fetching 2024-06-01 03:34:03 -07:00
FreeScout
0ac8685c63 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-05-30 04:55:43 -07:00
FreeScout
754b2fc40c v1.8.141 2024-05-30 04:55:26 -07:00
FreeScout
c32a68ed93 Remove All from current mailbox link from conversation page - closes #4041 2024-05-27 01:30:51 -07:00
FreeScout
c269311ef5 Skip auto-replies sent to the email notification on behalf of a user - #4035 2024-05-27 01:24:35 -07:00
FreeScout
04c28a9637 Disable Check Connection button on changing mailbox Fetching settings 2024-05-22 22:33:24 -07:00
FreeScout
6830a78e23 Show exclamation icon in mailbox connection settings tabs 2024-05-22 22:04:29 -07:00
FreeScout
3a4921344b Show exclamation icon in mailbox connection settings tabs 2024-05-22 22:02:03 -07:00
FreeScout
063b77f3df Fix IMAP folders not being retrieved in some cases 2024-05-22 21:50:26 -07:00
FreeScout
61e5f1fde5 Fix fetching emails forwarded using @fwd command - closes #4036 2024-05-21 11:36:15 -07:00
FreeScout
4f253bfab1 Fix reteiving the original message in Show Original window for moved conversations 2024-05-21 10:33:16 -07:00
FreeScout
c88ca44ff1 Find prev_thead iteratively looking in-reply-to and references #4026 from Som-Energia/master
Find prev_thead iteratively looking in-reply-to and references
2024-05-16 09:01:22 +03:00
FreeScout
2d871f2759 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-05-15 22:57:28 -07:00
FreeScout
2a0bac500c v1.8.140 2024-05-15 22:57:20 -07:00
FreeScout
cedf4de2ba Update TestWebklex command 2024-05-15 22:46:16 -07:00
Àngel Fernández
d9e1a7ae98 Find prev_thead iteratively looking in-reply-to and references 2024-05-15 18:15:31 +02:00
FreeScout
80118d579d Update general_help_request.md 2024-05-15 07:13:47 -07:00
FreeScout
0299c8be99 Switch to the new fetching library 2024-05-10 22:46:33 -07:00
FreeScout
70a7ec54ea Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-05-10 22:15:09 -07:00
FreeScout
4876d37287 v1.8.139 2024-05-10 22:14:53 -07:00
FreeScout
9894ca78f0 app.csp_custom env parameter 2024-05-09 06:34:05 -07:00
FreeScout
99a4b4b4e1 Remove unwanted tags from HTML when editing threads - GHSA-985r-6qfc-hg8m 2024-05-06 00:52:36 -07:00
FreeScout
1169f5bae9 Strip unwanted tags from mailbox Auto Reply 2024-05-06 00:16:53 -07:00
FreeScout
a59619c153 Strip unwanted tags from mailbox signature when saving it 2024-05-06 00:14:00 -07:00
FreeScout
c4932c4c8e Fix CC and BCC fields when cloning a conversation - closes #4011 2024-05-05 09:44:35 -07:00
FreeScout
cb31699a6e Update Polish translation 2024-05-05 00:08:47 -07:00
FreeScout
41e6e10241 Update Polish translation 2024-05-05 00:07:28 -07:00
FreeScout
ce5c85f592 Remove FORM tags from HTML when editing threads - GHSA-985r-6qfc-hg8m 2024-05-04 23:15:05 -07:00
FreeScout
2614514bc6 Do not allow Prototype Pollution via getQueryParam() function - GHSA-rx6j-4c33-9h3r 2024-05-04 22:59:35 -07:00
FreeScout
a50c261f13 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-04-26 06:41:18 -07:00
FreeScout
b474193f97 v1.8.138 2024-04-26 06:41:10 -07:00
FreeScout
27a4f2e8ff Fix cron URL link - closes #4000 2024-04-26 03:53:37 -07:00
FreeScout
17024f814a Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-04-22 09:35:48 -07:00
FreeScout
d18cc23149 v1.8.137 2024-04-22 09:35:36 -07:00
FreeScout
047111ffbb Fix Unknown hashing algorithm crc32c error in Webklex/php-imap library - closes #3991 2024-04-22 06:51:49 -07:00
FreeScout
8c5dbb4197 Fetch emails sent to multiple mailboxes on a per mailbox basis - closes #3941 2024-04-22 01:36:46 -07:00
FreeScout
7a6cbab8af Do not show closed conversations in non-standard folders - closes #3988 2024-04-20 23:17:20 -07:00
FreeScout
add6f179a4 Add folder.only_assigned_condition hook - closes #701 2024-04-20 22:27:13 -07:00
FreeScout
29d8c41395 Fix browser push notifications sent not to all users - closes #3880 2024-04-19 07:31:00 -07:00
FreeScout
0ecfa248ee Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-04-18 23:35:34 -07:00
FreeScout
748c7c5de1 v1.8.136 2024-04-18 23:34:12 -07:00
FreeScout
2b5be510b7 Update Dutch translation 2024-04-16 06:34:24 -07:00
FreeScout
99ef4e6899 Do not run scheduled commands when running \Artisan::call() function - closes #3970 2024-04-15 07:17:25 -07:00
FreeScout
59b86a882e Fix attachments not being shown when re-opening a new conversation draft - closes #3957 2024-04-12 22:12:10 -07:00
FreeScout
a6a729189c Show system date and time on Status page instead of current user time 2024-04-12 22:03:48 -07:00
FreeScout
7829e9a3ba Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-04-12 21:53:28 -07:00
FreeScout
a2e27f3ced v1.8.135 2024-04-12 21:53:13 -07:00
FreeScout
9175ce3646 Fix Parse error in Attachment.php - closes #3965 2024-04-12 21:52:35 -07:00
FreeScout
0dbdf401eb Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-04-12 11:20:10 -07:00
FreeScout
db84078093 v1.8.134 2024-04-12 11:19:57 -07:00
FreeScout
a0ab8069d2 Fix Header::decode() is not supported error in Webklex/php-imap - closes #3960 2024-04-12 11:18:45 -07:00
FreeScout
ea068dc23e Update Readme 2024-04-11 23:33:16 -07:00
FreeScout
26756cc687 Add vendor files to dist 2024-04-11 23:17:54 -07:00
FreeScout
171a4e3b48 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-04-11 23:17:21 -07:00
FreeScout
bfd5aef4d9 v1.8.133 2024-04-11 23:17:11 -07:00
FreeScout
b3c2a48eec Fix an error when new conversation draft is being discarded by other user - closes #3951 2024-04-11 23:15:26 -07:00
FreeScout
848cc9e43f Fix To field not appearing after discarding forwarding draft - closes #3950 2024-04-10 23:10:27 -07:00
FreeScout
f61230ff35 Update Polish translation 2024-04-10 22:54:15 -07:00
FreeScout
268740a142 Fix an issue with removing attachments when replying 2024-04-10 06:40:48 -07:00
FreeScout
abf8d32ff5 When forwarding a conversation do not include attachments from drafts 2024-04-10 06:39:15 -07:00
FreeScout
b769aec7e8 Fix attachments not being sent when forwarding a conversation 2024-04-10 06:29:10 -07:00
FreeScout
84832f011a Fix attachments being duplicated when discarding forwarding draft - closes #3949 2024-04-10 06:28:34 -07:00
FreeScout
b1dc766ffb Make updates in Webklex/php-imap library 2024-04-10 05:35:06 -07:00
FreeScout
3274b35468 Do not mark thread as containing attachments when only embedded images are present in the email 2024-04-09 07:06:47 -07:00
FreeScout
ad76e2d3ce Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-04-09 03:19:17 -07:00
FreeScout
aec78d147a v1.8.132 2024-04-09 03:19:03 -07:00
FreeScout
9333111a99 Fix some emails shown incorrectly - closes #3907 2024-04-08 11:21:52 -07:00
FreeScout
1f0d192fd5 Fix Call to a member function getFullName() on null error in notification template - closes #3942 2024-04-07 09:27:04 -07:00
FreeScout
21f04259cb Fix Call to a member function getFullName() on null error in notification template - closes #3942 2024-04-07 09:10:29 -07:00
FreeScout
a62eb20f26 Update ru.json 2024-04-06 23:16:01 -07:00
FreeScout
b813a4af9d Update ru.json #3945 from ViktorOn/master
Update ru.json
2024-04-07 08:51:13 +03:00
Vik
09ca411903 Update ru.json
Lots of edits
2024-04-07 00:37:14 +03:00
FreeScout
59c256f8cc Fix duplicated folder names when getting IMAP folders - closes #3937 2024-04-05 07:15:56 -07:00
FreeScout
c0e2a8a6fb Fix Passing null error in symfony/http-foundation/Request.php - closes #3940 2024-04-05 04:23:26 -07:00
FreeScout
c15278d3c6 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-04-04 22:21:07 -07:00
FreeScout
7cf3c67432 v1.8.131 2024-04-04 22:20:55 -07:00
FreeScout
1ef3796709 Fix attachment IDs when checking max message size - closes #3939 2024-04-04 11:24:33 -07:00
FreeScout
d3527f2aee Allow limiting command freescout:fetch-emails to specific mailboxes #3932 from ohader/fetch-emails-mailbox
Allow limiting command freescout:fetch-emails to specific mailboxes
2024-04-03 21:10:58 +03:00
Oliver Hader
31b3dff98e Allow limiting command freescout:fetch-emails to specific mailboxes
This change introduces an optional command option `mailboxes`, which
can be used to limit fetching messages to particular mailbox ids.

The following example fetches all messages of the last 500 days
for the specific mailboxes 5 and 7 only. By omitting the new
`mailboxes` option, any message of any mailbox would have been fetched.

```
php artisan freescout:fetch-emails --mailboxes=5,7 --unseen=0 --days=500
```
2024-04-03 20:06:51 +02:00
FreeScout
1b9cf02f1a Fix saving into Sent IMAP folder message with attachments - closes #3934 2024-04-03 10:06:38 -07:00
FreeScout
a9963c2f43 Fix saving into Sent IMAP folder message with attachments - closes #3934 2024-04-03 01:19:22 -07:00
FreeScout
b9bdf92065 Update Polish translation 2024-04-01 23:05:26 -07:00
FreeScout
b66b8f1880 Fix To and From headers when saving outgoing emails to the Sent IMAP folder - closes #3900 2024-04-01 23:04:01 -07:00
FreeScout
356b211754 Allow fetching seen/read messages via scheduler #3931 from ohader/schedule-fetch-unseen
Allow fetching seen/read messages via scheduler
2024-04-02 08:48:26 +03:00
FreeScout
833389c1fc Fix retrieving IMAP subfolders - closes #3933 2024-04-01 22:46:13 -07:00
Oliver Hader
f5e30cdff3 Allow fetching seen/read messages via scheduler
Usually the `schedule:run` command invokes `freescout:fetch-emails`,
but only fetches unseen (not read by anybody else, yet). This change
allows to configure this behavior and to fetch seen/read messages.

The new option `app.fetch_unseen` can be configured via `.env`:

```
APP_FETCH_UNSEEN=0
```
2024-04-01 14:54:58 +02:00
FreeScout
50f6cd743a Make params optional in Helper::setGuzzleDefaultOptions() #3926 from Lars-/master
Make params optional in Helper::setGuzzleDefaultOptions()
2024-03-30 08:10:49 +03:00
Lars Jansen
c4c6e861b5 fix: optional params in setGuzzleDefaultOptions 2024-03-30 06:02:34 +01:00
FreeScout
cac293b75b Use encrypted attachment IDs when uploading attachments 2024-03-29 09:37:56 -07:00
FreeScout
2c39ba9d9e Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-03-29 07:36:35 -07:00
FreeScout
a1602fa57a v1.8.130 2024-03-29 07:36:25 -07:00
FreeScout
f6f1adc9f4 Use encrypted attachment IDs when uploading attachments 2024-03-29 07:35:38 -07:00
FreeScout
9fbbba817e Fix conv_type_custom error - closes #3923 2024-03-29 07:34:49 -07:00
FreeScout
d973a8a2ca Update Japanese translation 2024-03-29 06:41:48 -07:00
FreeScout
70cc044e57 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-03-29 06:36:58 -07:00
FreeScout
f7498e5ba2 v1.8.129 2024-03-29 06:36:06 -07:00
FreeScout
e103599984 Helper encrypt and decypt with password functions 2024-03-29 06:34:40 -07:00
FreeScout
40abe0b311 Update Japanese translation 2024-03-28 23:02:19 -07:00
FreeScout
8b9e514ce9 Add Custom conversation type #3918 from Lars-/master
Add Custom conversation type
2024-03-28 11:26:34 +03:00
Lars Jansen
582f3a6ae4 feat: new conversation type: custom 2024-03-28 09:08:41 +01:00
Lars Jansen
5589c0a80f Merge remote-tracking branch 'Github/master' 2024-03-28 09:08:10 +01:00
FreeScout
71c67ad637 Re-sort strings in translation files 2024-03-26 23:07:15 -07:00
FreeScout
1c7aff0d5b Fix deleting conversations after sorting them - closes #3901 2024-03-25 23:50:32 -07:00
FreeScout
9b359abcce Add sender name to From field when saving email to the Sent folder - closes #3900 2024-03-25 00:24:28 -07:00
FreeScout
67de1109fe Add vendor files to dist 2024-03-22 00:11:37 -07:00
FreeScout
3489567cde Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-03-22 00:11:04 -07:00
FreeScout
d69e6c3501 v1.8.128 2024-03-22 00:10:55 -07:00
FreeScout
01dec5160e Exclude duplicate INBOX. folders when retreiving folders 2024-03-21 23:40:54 -07:00
FreeScout
90c80c9946 Fix removing comments when purifying HTML - closes #3894 2024-03-21 02:48:15 -07:00
FreeScout
0b9ca610b7 Fix Passing null to parameter error in HTMLPurifier - closes #3893 2024-03-20 23:07:07 -07:00
FreeScout
2a2782bc95 Fix conversations bulk actions when page parameter is set in the URL 2024-03-19 23:18:02 -07:00
FreeScout
3b6b02ed19 Fix conversation bulk actions after sorting conversations - closes #3884 2024-03-19 23:08:38 -07:00
FreeScout
443ae7f40b Fix long fetch command being killed - closes #3844 2024-03-19 06:45:05 -07:00
FreeScout
4c91ac44e1 Sanitize Path to PHP in tools.php 2024-03-19 04:47:43 -07:00
FreeScout
42dc98fa45 Strip script tags from mailbox signature 2024-03-19 03:15:37 -07:00
FreeScout
e88bec86fb Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-03-18 00:25:56 -07:00
FreeScout
20c8ef7383 v1.8.127 2024-03-18 00:25:48 -07:00
FreeScout
ff09db6244 Upgrade DataTables to 1.13.11 2024-03-18 00:11:45 -07:00
FreeScout
c15a00d603 Properly process situation when an attachment can not be saved to the disc - closes #3871 2024-03-13 07:14:24 -07:00
FreeScout
b18cf31bdc Show extended editor toolbar when editing threads - closes #3866 2024-03-12 00:37:51 -07:00
FreeScout
3ee1a96bde Allow <center> element when purifying thread content 2024-03-12 00:27:39 -07:00
FreeScout
8c470d0e4e Remove [if mso] from thread content when purifying 2024-03-12 00:26:43 -07:00
FreeScout
0826102f6e Preserve elements inside [if mso] when purifying thread content 2024-03-12 00:13:27 -07:00
FreeScout
2ff52a0359 Use latest PHP version for linting #3864 from fulldecent/patch-15
Use latest PHP version for linting
2024-03-12 09:54:41 +03:00
William Entriken
8d70999498 Use latest PHP version for linting 2024-03-10 13:25:44 -04:00
FreeScout
f6a4581dad Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-03-08 06:49:51 -08:00
FreeScout
2d74977ed3 v1.8.126 2024-03-08 06:49:39 -08:00
FreeScout
8b06724b67 Fix an issue with updating modules 2024-03-08 06:48:51 -08:00
FreeScout
c4755fa920 Disable Undo reply feature for chat conversations in Chat Mode - closes #3856 2024-03-08 06:17:44 -08:00
FreeScout
36f146a71d Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-03-08 01:17:16 -08:00
FreeScout
a5515d8520 v1.8.125 2024-03-08 01:16:49 -08:00
FreeScout
33514c6e25 Fixed an issue with opening Closed folder - closes #3858 2024-03-08 01:16:14 -08:00
FreeScout
d8a582aaa5 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-03-08 00:23:41 -08:00
FreeScout
a428d33964 v1.8.124 2024-03-08 00:23:25 -08:00
FreeScout
33639a8955 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-03-08 00:09:52 -08:00
FreeScout
4021f07aec v1.8.123 2024-03-08 00:09:43 -08:00
FreeScout
67bd456b63 Update Readme 2024-03-07 22:08:26 -08:00
FreeScout
78cc5877a3 Update readme 2024-03-07 10:43:17 -08:00
FreeScout
c4bc505e03 Accessibility of exact date in conversation table - closes #3829 2024-03-07 10:41:43 -08:00
FreeScout
ad372590a9 Allowing updates for third party modules #3851 from Lars-/master
Allowing updates for third party modules
2024-03-07 13:35:03 +03:00
Lars Jansen
2771d526be chore: added comments 2024-03-07 11:24:04 +01:00
Lars Jansen
263ec4d266 fix: code styling 2024-03-07 11:03:46 +01:00
FreeScout
f86876328e Accessibility of exact date in conversation table - closes #3829 2024-03-06 21:55:33 -08:00
Lars Jansen
e47167b17e feat: allow updates for external modules 2024-03-05 20:46:43 +01:00
Lars Jansen
5c32cf69e6 fix: typo 2024-03-05 15:39:31 +01:00
Lars Jansen
fd46017c45 feat: add author to module card if available 2024-03-05 15:39:26 +01:00
FreeScout
95d3cca660 Do not save SMTP username and password in Send Log status message 2024-03-05 06:39:24 -08:00
FreeScout
1fcb5560f0 Fix Passing null to parameter error in Helper::stripTags() - closes #3849 2024-03-05 01:30:31 -08:00
FreeScout
06b3dc27d2 Fix creating conversations by a user seeing only assigned conversations - closes #3843 2024-03-04 05:36:57 -08:00
FreeScout
d2b683368c If user can see only assigned conversations send him notifications only for assigned to him conversations - closes #3843 2024-03-02 21:32:19 -08:00
FreeScout
6b397ccd3f Fix creating conversations by a user seeing only assigned conversations - closes #3843 2024-03-02 21:09:50 -08:00
FreeScout
32006fe8e1 Fix adding notes to Spam conversations 2024-03-02 04:58:52 -08:00
FreeScout
911e157322 Fix showing only assigned to the user tickets - closes #3843 2024-03-02 04:44:23 -08:00
FreeScout
100b4062bf Fix Numeric value out of range error in search on PostgreSQL - closes #3831 2024-02-27 23:04:44 -08:00
FreeScout
b27e2b7a1c Update Czech translation 2024-02-24 06:50:52 -08:00
FreeScout
7085009667 Update Spanish translation 2024-02-24 06:24:34 -08:00
FreeScout
4b6ceab070 Fix word plus colon mistakenly converted to URL in editor - closes #3822 2024-02-24 03:54:38 -08:00
FreeScout
49af472617 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-02-23 06:52:13 -08:00
FreeScout
d7aeb0d3b3 v1.8.122 2024-02-23 06:52:03 -08:00
FreeScout
35f8b775f9 Fix an issue with minify.config.php - closes #3818 2024-02-23 06:49:37 -08:00
FreeScout
079df0d6a6 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-02-23 03:26:42 -08:00
FreeScout
36442cb3a2 v1.8.121 2024-02-23 03:26:32 -08:00
FreeScout
217cd9183e Add Turkish language 2024-02-20 03:12:36 -08:00
FreeScout
e9c2eaebce Fix showing scaled image in threads - closes #3798 2024-02-20 03:00:48 -08:00
FreeScout
954c790827 Update it.json #3755 from PeopleInside/patch-1
Update it.json
2024-02-20 11:53:55 +03:00
FreeScout
2d2b476688 Remove APP_URL from minify.config.php to avoid CORS issues on custom domains 2024-02-20 00:21:20 -08:00
FreeScout
ab58217cab Improve sorting elements on mobile devices - closes #3800 2024-02-18 22:29:13 -08:00
FreeScout
6f0b05c1f6 Improve searching customers by phone number when creating Phone conversations - closes #3786 2024-02-18 08:54:17 -08:00
FreeScout
58d35098c0 Allow to set session.same_site via SESSION_SAME_SITE env parameter - closes #3023 2024-02-18 05:54:38 -08:00
FreeScout
44e71e58e6 Remove 255 symbols limit from mailbox Alises 2024-02-18 05:36:30 -08:00
FreeScout
0e516bc407 Allow style attribute for tr in HTML purifier - closes #3799 2024-02-18 05:28:06 -08:00
Marco Borla
9fc7b9d755 Update it.json
Fix Soggetto is Oggetto.

Subject = Oggetto
2024-02-02 10:46:40 +01:00
FreeScout
e1ff8fb520 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-02-01 17:35:42 -08:00
FreeScout
adf308ac9f v1.8.120 2024-02-01 17:35:31 -08:00
FreeScout
7b6abe858c Fix an issue with multiple emails being sent to customers - closes #3632 2024-01-31 23:17:48 -08:00
FreeScout
6937346e5a Show .parsley-required in red color 2024-01-31 23:10:08 -08:00
FreeScout
fc1a354072 Update Italian translation #3739 from floatingpurr/fix-transalation
✨ Update Italian translation
2024-01-26 19:41:15 +03:00
Andrea Mignone
82464e4129 ✨ Modify Italian translation 2024-01-26 15:29:04 +01:00
FreeScout
1a126ad743 Add parameter to open tracking URL 2024-01-26 06:01:43 -08:00
FreeScout
9558194bf7 Fix redeclaration of let DragDropTouch on Translate page - closes #3717 2024-01-26 00:51:00 -08:00
FreeScout
8aa2f0c018 Improve contrast on on unread notifications 2024-01-24 22:26:11 -08:00
FreeScout
d6de310363 Improve contrast on on unread notifications #3728 from mokkin/patch-1
Improve contrast on on unread notifications
2024-01-25 09:17:59 +03:00
mokkin
1fdddb25ec improve contrast on on unread notifications
fixes https://github.com/freescout-helpdesk/freescout/issues/3693
2024-01-23 17:15:15 +01:00
FreeScout
a78f6f5ce3 Add vendor files to dist 2024-01-23 02:01:04 -08:00
FreeScout
2528727d81 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-01-23 02:00:40 -08:00
FreeScout
97bb4dd956 v1.8.119 2024-01-23 02:00:23 -08:00
FreeScout
3c6819c9e7 Fix scrolling conversations list on mobile devices - closes #3724 2024-01-23 01:59:33 -08:00
FreeScout
bb2fc15c07 Fix error in HTMLPurifier/Length.php - closes #3721 2024-01-23 00:45:39 -08:00
FreeScout
fb129df215 Fix Bootstrap submenu after applying DragDropTouch Polyfill - closes #3708 2024-01-22 01:05:29 -08:00
FreeScout
050ccb921e Fix Trailing data error on PostgreSQL - closes #3702 2024-01-20 22:05:39 -08:00
FreeScout
9b9aae4c0e Add vendor files to dist 2024-01-19 23:40:40 -08:00
FreeScout
263569b163 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-01-19 23:40:11 -08:00
FreeScout
2702232eea v1.8.118 2024-01-19 23:40:02 -08:00
FreeScout
8f802b4d38 Update install.sh 2024-01-19 23:38:24 -08:00
FreeScout
00b4aabb32 Update Slovak translation #3692 from jozefrebjak/sk-lang-update
Update Slovak translation
2024-01-20 10:31:24 +03:00
Jozef Rebjak
8ea84dca94 lang: update slovak translation 2024-01-16 15:02:22 +01:00
FreeScout
bc45b779ef Fix Use of undefined constant FT_UID in Webklex/laravel-imap - closes #2604 2024-01-15 19:06:51 -08:00
FreeScout
c72286623f Fix Use of undefined constant FT_UID in Webklex/laravel-imap - closes #2604 2024-01-11 21:56:49 -08:00
FreeScout
1d3c97fae5 Do not show Delete button in bulk actions if user does not have corresponding permission - closes #3680 2024-01-11 10:35:19 -08:00
FreeScout
17e22b8b93 Improve HTML purification - closes #3664 2024-01-11 10:24:46 -08:00
FreeScout
e32714ebaf Make html5sortable.js work on mobile devices - closes #3671 2024-01-09 23:33:53 -08:00
FreeScout
acd81548e6 Make summernote compatible with jQuery v3.6.0 - closes #3666 2024-01-08 09:23:07 -08:00
FreeScout
2d71860fa1 Fix counter not aligned in Dashboard - closes #3665 2024-01-08 04:16:19 -08:00
FreeScout
6930924cc1 Update German translation 2024-01-06 21:02:28 -08:00
FreeScout
e5ce253daf Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-01-06 04:08:07 -08:00
FreeScout
b9cfe0e300 v1.8.117 2024-01-06 04:07:56 -08:00
FreeScout
af91c8c9e3 Fix Call to undefined method getHashedRelySeparator() 2024-01-06 04:06:14 -08:00
FreeScout
08e50e3643 Localize calendar 2024-01-06 03:55:51 -08:00
FreeScout
885001ec97 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-01-05 20:47:49 -08:00
FreeScout
b2dae09cf1 Improve fetching email in Show Original window - closes #3658 2024-01-05 10:08:21 -08:00
FreeScout
c55d94d969 Add alternative reply separation method - closes #3648 2024-01-05 08:35:15 -08:00
FreeScout
7d035f5604 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2024-01-05 08:20:36 -08:00
FreeScout
a5f49080a7 v1.8.116 2024-01-05 08:20:21 -08:00
FreeScout
8a52a85ca3 Replace shell_exec() with Helper::shellExec() 2024-01-05 03:28:30 -08:00
FreeScout
2bfdc99f89 Add alternative reply separation method - closes #3648 2024-01-04 02:03:36 -08:00
FreeScout
e5da621736 Copy attachments when creating a new conversation from some message - closes #3647 2024-01-03 23:57:07 -08:00
FreeScout
359f6c455c Add alternative reply separation method - closes #3648 2024-01-03 22:22:59 -08:00
FreeScout
e929af5c99 Upgrade Bootstrap to v3.4.1 2024-01-03 03:17:36 -08:00
FreeScout
191e61400d Upgrade jQuery to v3.6.0 2024-01-03 03:15:52 -08:00
FreeScout
d311b40faa Allow to filter conversation by Assignee - closes #3641 2024-01-02 03:31:34 -08:00
FreeScout
7c7fc41b2c Add America/Santiago timezone - closes #3639 2023-12-31 07:48:11 -08:00
FreeScout
5ab0f84d41 Refine the Italian translation #3634 from floatingpurr/fix-translation
Refine the Italian translation
2023-12-27 20:44:16 +03:00
Andrea Mignone
5917b190eb Refine and make consistent Italian translation 2023-12-27 18:40:18 +01:00
FreeScout
828c7b814c Show SMTP communication details when testing mail sending - closes #3626 2023-12-27 02:12:06 -08:00
FreeScout
6d3f1bbee8 Show SMTP communication details when testing mail sending - closes #3626 2023-12-27 01:04:03 -08:00
FreeScout
85954ab7cc Add vendor files to dist 2023-12-26 21:49:22 -08:00
FreeScout
822b2f4ae7 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-12-26 21:48:38 -08:00
FreeScout
ed6911a8dc v1.8.115 2023-12-26 21:30:05 -08:00
FreeScout
d8cfe15373 Improve linkify algorithm 2023-12-23 01:50:22 -08:00
FreeScout
dc4d6d2671 Add StartTLS encryption option for Fetching Email when Webklex/php-imap is used - closes #3624 2023-12-22 03:27:03 -08:00
FreeScout
eb6b30864f Improve the message shown when IMAP folder can not be found during fetching 2023-12-22 00:25:26 -08:00
FreeScout
1618bb1266 Allow to fetch emails from nested folders - closes #3623 2023-12-21 23:11:39 -08:00
FreeScout
54e5aa28bd Do not set customer last name when it was manually removed - closes #3622 2023-12-21 00:55:22 -08:00
FreeScout
4e72e2ea50 Fix null error in locale_options template - closes #3620 2023-12-20 03:41:08 -08:00
FreeScout
134608625f Fix Packets out of order error - closes #3619 2023-12-20 03:34:55 -08:00
FreeScout
11fbaa6be8 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-12-19 21:59:34 -08:00
FreeScout
377dd08c96 v1.8.114 2023-12-19 21:59:03 -08:00
FreeScout
6a9dfe6dd1 Fix email send loop - closes #3607 2023-12-19 21:30:02 -08:00
FreeScout
59da4e4f99 Fix email send loop - closes #3607 2023-12-19 09:37:58 -08:00
FreeScout
6d917f82d7 Add as CC a customer who was in CC and replied to the conversation - #3613 2023-12-16 21:15:02 -08:00
FreeScout
15780467eb Do not strip HTML tags in plain text emails - closes #3614 2023-12-16 04:09:15 -08:00
FreeScout
1a53871517 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-12-14 23:58:28 -08:00
FreeScout
ef0af3a494 v1.8.113 2023-12-14 23:58:15 -08:00
FreeScout
ab854befd1 Helper::sanitizeDatepickerDatetime() function 2023-12-14 21:58:12 -08:00
FreeScout
3d522c4f2e Show notice on System Status page is CloudFlare is used 2023-12-13 09:32:18 -08:00
FreeScout
7908da3721 Allow to translate English texts - closes #3599 2023-12-13 06:00:12 -08:00
FreeScout
f9a3354741 Fix user reply to a notification being incorrectly cropped - closes #3580 2023-12-12 09:57:03 -08:00
FreeScout
fbf4d3b355 Improve SendReplyToCustomer background jobs monitoring - closes #3590 2023-12-12 03:43:31 -08:00
FreeScout
f1839bcfbf Fix user reply to a notification being incorrectly cropped - closes #3580 2023-12-11 10:23:17 -08:00
FreeScout
895af2d7ad Make sure thread_id is null for an attachment when linking attachment to a thread 2023-12-11 06:09:38 -08:00
FreeScout
b5510da291 Do not remove double dots from attachment names - closes #3592 2023-12-09 10:40:25 -08:00
FreeScout
3083738d00 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-12-08 02:45:34 -08:00
FreeScout
ea84fe82dd v1.8.112 2023-12-08 02:45:08 -08:00
FreeScout
ea8b7507e4 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-12-07 23:35:26 -08:00
FreeScout
7dfd170983 Allow .PEM insertion for MySQL connections #3582 from crixle/master
Allow .PEM insertion for MySQL connections
2023-12-06 11:53:58 +03:00
Crixle
06c7e709e6 Allow .PEM insertion for MySQL connections
Required for PaaS DB servers that require SSL.
2023-12-05 17:22:06 -05:00
FreeScout
e0f10670d7 Improve linkify algorithm - closes #3402 2023-12-03 02:27:03 -08:00
FreeScout
b33d2dff4a Fix Call to undefined function shell_exec() on Status page - closes #3570 2023-12-02 21:32:27 -08:00
FreeScout
2f1b9ebe0c conversation.is_user_assignee hook - closes #3568 2023-12-02 04:18:52 -08:00
FreeScout
9b024bda31 Do not change customer when CCed email replies to a conversation - closes #3551 2023-12-02 00:46:47 -08:00
FreeScout
606dcf17c9 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-12-01 21:20:17 -08:00
FreeScout
2fb0c29705 v1.8.110 2023-12-01 21:20:07 -08:00
FreeScout
d673a7006b Fix Select None button - closes #3546 2023-11-24 21:11:49 -08:00
FreeScout
fbe3a795df Merge multiple conversations at once - closes #3516 2023-11-23 23:04:51 -08:00
FreeScout
0c614c3f7e Make error message in logs more descriptive when SendReplyToCustomer job can not save outgoing reply to the IMAP folder 2023-11-22 23:52:36 -08:00
FreeScout
ce4bebb8b4 Autosave new conversation draft when choosing recipient - closes #3538 2023-11-22 21:23:15 -08:00
FreeScout
a8b4a581e4 Update Readme 2023-11-21 23:21:39 -08:00
FreeScout
7d5cc80898 Update readme 2023-11-21 23:18:19 -08:00
FreeScout
7fa6003156 Update Portuguese translation 2023-11-21 21:47:51 -08:00
FreeScout
87cdff17b8 Improve linkify algorithm - closes #3402 2023-11-19 23:06:52 -08:00
FreeScout
4fbe3a3340 Allow to select multiple conversations using SHIFT button - closes #1312 2023-11-19 22:02:36 -08:00
FreeScout
c2a8894d9f Dynamically load customer info when creating new conversation - closes #2402 2023-11-18 04:02:19 -08:00
FreeScout
4bb7e224d7 Show customer info when opening new conversation draft - closes #2402 2023-11-18 01:02:00 -08:00
FreeScout
c24efff92f Check pcntl_signal() function on System Status page in the console PHP 2023-11-17 22:28:01 -08:00
FreeScout
9dc73c29ff Set min-height for .container 2023-11-17 22:16:21 -08:00
FreeScout
c2d599b546 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-11-17 21:20:09 -08:00
FreeScout
574d1d1d69 v1.8.109 2023-11-17 21:19:57 -08:00
FreeScout
f3ff95cc57 Allow to set SMTP timeout via MAIL_SMTP_TIMEOUT .env parameter 2023-11-17 05:30:47 -08:00
FreeScout
4a2f4fdadc Allow to set SMTP timeout via MAIL_TIMEOUT .env parameter 2023-11-16 21:56:58 -08:00
FreeScout
c2bd39a65f Mark email as read when saving to IMAP Folder with Webklex/PHPIMAP - closes #3533 2023-11-16 10:54:52 -08:00
FreeScout
529b141f1b Fix URLs for attachments with percents - closes #3530 2023-11-15 21:16:46 -08:00
FreeScout
de643ee51b Remove max length limitation from mailbox Aliases field - closes #3525 2023-11-13 22:01:59 -08:00
FreeScout
1dd59bc2b6 Fix displaying SVG images in the uploaded folder on Apache - closes #3522 2023-11-12 22:45:55 -08:00
FreeScout
8b80bd4119 Allow to set table prefix via .env DB_TABLE_PREFIX parameter - closes #3523 2023-11-12 22:23:13 -08:00
FreeScout
0ca0ea0e93 Check pcntl_signal() function on System Status page - closes #3515 2023-11-12 22:18:54 -08:00
FreeScout
4f15b2f273 Allow to retry thread sending from conversation - closes #3517 2023-11-09 05:31:47 -08:00
FreeScout
63e2384907 PHP IMAP: Only explode the tag line if it contains a space - closes #3503 2023-11-08 09:21:41 -08:00
FreeScout
e9b66ff7fd Allow to pass custom message to \Helper::denyAccess() 2023-11-08 09:21:15 -08:00
FreeScout
0eff48267f Fix cicle between two tickets with older newer buttons - closes #3506 2023-11-07 22:04:53 -08:00
FreeScout
4c9f247cf6 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-11-06 23:50:12 -08:00
FreeScout
5aa7552232 v1.8.108 2023-11-06 23:49:59 -08:00
FreeScout
0dd1a7308a Fixed saving outgoing replies to the IMAP Sent folder with non-ASCII name 2023-11-06 23:48:51 -08:00
FreeScout
315aeafd24 Fix fetching emails from non-ascii imap folders - closes #3502 2023-11-06 23:33:47 -08:00
FreeScout
74fd2dcf5e Fix Passing null to parameter error in User.php - closes #3501 2023-11-06 05:20:33 -08:00
FreeScout
da66ab2fc6 Add html5sortable.js script 2023-11-04 21:49:58 -07:00
FreeScout
4835fba6dc Fix an issue with phone customers always shown in the search - closes #3499 2023-11-04 05:02:48 -07:00
FreeScout
c28b663f56 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-11-03 23:48:43 -07:00
FreeScout
cc2ece53ee v1.8.107 2023-11-03 23:48:35 -07:00
FreeScout
ccc23e6e52 Update install.sh 2023-11-03 23:47:50 -07:00
FreeScout
cee5c16aa1 Adjust installer styles 2023-11-03 23:34:05 -07:00
FreeScout
84db98515d Make install.sh script compatible with Debian 2023-11-03 23:33:48 -07:00
FreeScout
4a24df3395 Fix installing helpdesk without HTTPS via install.sh script 2023-11-03 23:33:10 -07:00
FreeScout
f21054f17f Helper::sqlSanitizeString() function 2023-11-03 03:13:10 -07:00
FreeScout
e12824008f Update general_help_request.md 2023-11-01 10:45:00 -07:00
FreeScout
0c24e04b8c Improve font color sanitizing when purifying thread content - closes #3492 2023-11-01 10:23:33 -07:00
FreeScout
e66aae1209 Set max length for attribtues before saving a user to avoid String data right truncated error on PostgreSQL - closes #3489 2023-11-01 06:07:03 -07:00
FreeScout
e652a1eced Set system timezone by default for users created outside of web interface - closes #3490 2023-10-31 23:20:44 -07:00
FreeScout
bbfbd43105 Fix incorrect redirect when replying to the last active conversation - closes #3486 2023-10-31 07:10:05 -07:00
FreeScout
62bc9643fd .link-grey-blue style 2023-10-31 07:08:24 -07:00
FreeScout
a36bbb2ffa Update Polish translation 2023-10-31 00:31:28 -07:00
FreeScout
c58e2403ca Add dashboard.heading_append hook 2023-10-28 22:07:23 -07:00
FreeScout
926327156d Dynamically expand Logs Monitoring settings in Alerts 2023-10-27 22:19:32 -07:00
FreeScout
45a7f9ae65 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-10-27 01:22:55 -07:00
FreeScout
26536b965c v1.8.106 2023-10-27 01:22:47 -07:00
FreeScout
896de29721 Add Max Message Size option to General Settings - closes #3479 2023-10-26 02:01:17 -07:00
FreeScout
fdafd95584 Fix: support overriding of viewable attachments from .env #3481 from InteractiveWebdesign/patch-1
Fix: support overriding of viewable attachments from .env
2023-10-26 10:42:53 +03:00
Interactive-Webdesign
e0f31775e5 fix: support overriding of viewable attachments from .env 2023-10-26 09:38:49 +02:00
FreeScout
67a67aef88 Show All from current mailbox link in conversations - closes #3480 2023-10-25 22:19:41 -07:00
FreeScout
0c28152d43 Fix multiplying attachments when forwarding a conversation 2023-10-24 22:20:46 -07:00
FreeScout
a361b3ba41 Fetch emails into several mailboxes when customer replies - closes #3473 2023-10-24 11:12:56 -07:00
FreeScout
20fe99a820 Fix multiplying attachments when forwarding a conversation 2023-10-23 22:58:40 -07:00
FreeScout
d380d5dd0e Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-10-22 23:41:30 -07:00
FreeScout
7df8b05e00 v1.8.105 2023-10-22 23:41:23 -07:00
FreeScout
7708fa86bd Allow to set APP_CLOUDFLARE_IS_USED=true for proper client IP detection when CloudFlare is used - closes #3467 2023-10-22 23:34:01 -07:00
FreeScout
ccb865821d Improve converting URLs into clickable links - closes #3464 2023-10-18 01:41:35 -07:00
FreeScout
7ff10f5510 Allow more attributes when purifying HTML - closes #3463 2023-10-18 00:58:33 -07:00
FreeScout
fce9f83459 Add X-Frame-Options header 2023-10-17 23:20:30 -07:00
FreeScout
74609b0705 Add client_max_body_size to nginx config in install.sh - closes #3453 2023-10-15 00:52:43 -07:00
FreeScout
5efc10ede6 Chat Mode 2023-10-14 06:38:55 -07:00
FreeScout
99bab479e0 Fix an issue when support agent replies to the email notifications and conversation moved to another mailbox - closes #3455 2023-10-12 23:15:16 -07:00
FreeScout
70f11046f1 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-10-12 21:54:05 -07:00
FreeScout
6094bddf6a v1.8.104 2023-10-12 21:53:43 -07:00
FreeScout
490d571f3c Add icon to chat conversations tag 2023-10-12 01:50:03 -07:00
FreeScout
18dc66f319 Update install.sh 2023-10-10 00:05:24 -07:00
FreeScout
ba174ed435 Fix apache headers module not found - closes #3450 2023-10-09 22:29:59 -07:00
FreeScout
33723056ec Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-10-08 11:11:13 -07:00
FreeScout
5f9c615ac5 v1.8.103 2023-10-08 11:11:04 -07:00
FreeScout
76b3776d79 Fix CSP error on module install - closes #3445 2023-10-08 11:09:51 -07:00
FreeScout
b3f2cddd2d Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-10-07 22:02:17 -07:00
FreeScout
d9dac047a0 v1.8.102 2023-10-07 22:01:58 -07:00
FreeScout
a863377597 Enable Content Security Policy 2023-10-07 22:00:57 -07:00
FreeScout
5420cd415b Do not add paperclip to conversations when email contains only embedded images and do not show embedded images as attachments in conversations 2023-10-07 03:53:24 -07:00
FreeScout
1d79d7fe00 Save base64 data images as attachments on fetching 2023-10-05 23:44:42 -07:00
FreeScout
fa6bc246de Add vendor files to dist 2023-10-04 22:20:25 -07:00
FreeScout
c9d5f9f7fa Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-10-04 22:18:40 -07:00
FreeScout
b0df2d6898 v1.8.101 2023-10-04 22:18:18 -07:00
FreeScout
a0f4c6b6cd Fix error in PostgreSqlPlatform.php on PHP 8.2 with PostgreSQL 2023-10-04 04:06:19 -07:00
FreeScout
309775cce2 Update test-pgsql.yml 2023-10-04 03:16:47 -07:00
FreeScout
12dcdfbf4a Update test-pgsql.yml 2023-10-04 03:05:17 -07:00
FreeScout
1fc7042545 Test Application (with PostgreSQL) 2023-10-04 03:01:23 -07:00
FreeScout
9089aba931 Update email.reply_to_customer.subject hook 2023-10-02 04:48:35 -07:00
FreeScout
b12c6102c6 Update test.yml 2023-10-02 01:04:04 -07:00
FreeScout
5548148150 Update user getAuthToken() 2023-10-01 23:48:46 -07:00
FreeScout
aff6e4acb0 CSP compatibility 2023-09-30 10:33:41 -07:00
FreeScout
1e88014f38 Configure CSP 2023-09-30 07:38:06 -07:00
FreeScout
5be74723d5 Configure CSP 2023-09-30 03:04:11 -07:00
FreeScout
f0ee5ee2ae Configure CSP 2023-09-29 09:54:13 -07:00
FreeScout
1daa34f604 Improve uploads folder security - closes #3428 2023-09-27 10:16:34 -07:00
FreeScout
258b774098 Run post update after modules in update.sh #3425 from webmaster777/post-update-after-modules
Run post update after modules in update.sh
2023-09-27 19:52:23 +03:00
FreeScout
1b6f9e4f09 Improve uploads folder security - closes #3428 2023-09-27 07:15:24 -07:00
Meindert-Jan Kroese
2598263cab run post_update.sh at the very end of updating (incl modules) 2023-09-26 14:31:04 +02:00
FreeScout
98314a9a26 Update German translation 2023-09-26 00:54:27 -07:00
FreeScout
20074845de Remove phpstan 2023-09-23 23:04:58 -07:00
FreeScout
d0b1c816eb Update lint-php.yml 2023-09-23 22:54:56 -07:00
FreeScout
5f1a59f7b3 Update phpcs and phpstan 2023-09-23 22:18:14 -07:00
FreeScout
c7644e214d Update analyse-php.yml 2023-09-23 11:40:59 -07:00
FreeScout
b6d026a2b1 Update analyse-php.yml 2023-09-23 11:37:02 -07:00
FreeScout
23c6cb33a3 Update analyse-php.yml 2023-09-23 11:29:44 -07:00
FreeScout
e68e8ef165 analyse-php.yml 2023-09-23 11:14:31 -07:00
FreeScout
912b805777 lint-js.yml 2023-09-23 11:02:51 -07:00
FreeScout
cacdf09649 lint-php.yml 2023-09-23 10:59:56 -07:00
FreeScout
b32320d59c Fix db seed error on PHP 8.2 2023-09-23 08:48:25 -07:00
FreeScout
0e2cc687d4 Rename mysql_testing DB to testing 2023-09-23 08:26:31 -07:00
FreeScout
1bc82611e9 Commit tests/Feature/ folder 2023-09-23 08:23:45 -07:00
FreeScout
6bb27b4e6f Update test.yml 2023-09-23 08:15:20 -07:00
FreeScout
3a6d5b3e34 Configure mysql_testing in database.php 2023-09-23 08:09:42 -07:00
FreeScout
33ccc85fbf Update test.yml 2023-09-23 07:47:40 -07:00
FreeScout
a21a7dbeda Update test.yml 2023-09-23 07:36:17 -07:00
FreeScout
718dc2c691 Update test.yml 2023-09-23 07:34:25 -07:00
FreeScout
7ca5a92e9f Update test.yml 2023-09-23 07:30:24 -07:00
FreeScout
7a9ecfe329 Update test.yml 2023-09-23 07:25:00 -07:00
FreeScout
ffd299f369 Update phpunit/phpunit to 9.5.28 2023-09-23 06:32:51 -07:00
FreeScout
fbf2f1f063 Add vendor files to dist 2023-09-23 03:18:04 -07:00
FreeScout
66c1ca83d9 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-09-23 03:10:53 -07:00
FreeScout
a7e542b7b2 v1.8.100 2023-09-23 03:10:22 -07:00
FreeScout
90d73053f1 Fix discarding new phone conversation draft - closes #3407 2023-09-22 23:56:37 -07:00
FreeScout
900f259620 Remove yarn.lock 2023-09-22 23:46:20 -07:00
FreeScout
b7367f89e2 Merge pull request #3408 from baldugus/master
Add filter `fetch_emails.mailbox_to_save_message`
2023-09-23 09:43:10 +03:00
FreeScout
35ba1d2ca5 Improve accessibility from philippkraft/master
Improve accessibility - addendum to issue #1150
2023-09-23 09:40:22 +03:00
FreeScout
cf072514a4 Fix OS Command Injection issue in laravel/framework 2023-09-22 23:24:08 -07:00
FreeScout
6133547617 Fix Possibility for Denial of Service by overwriting PHP files with language exports in barryvdh/laravel-translation-manager 2023-09-22 23:15:15 -07:00
FreeScout
d62bf49eb9 Fix CVE-2023-35169 issue in webklex/laravel-imap 2023-09-22 23:09:29 -07:00
FreeScout
7b32dde0cf Fix CVE-2023-35169 issue in webklex/php-imap 2023-09-22 23:04:14 -07:00
FreeScout
8c7e53aef8 Fix the issue causing long fetching process to be killed before it finishes - closes #3417 2023-09-22 21:59:00 -07:00
FreeScout
2d9e9091b4 Fix the issue causing long fetching process to be killed before it finishes - closes #3417 2023-09-22 10:48:21 -07:00
FreeScout
8363650304 Fix Cookie serialization vulnerability in laravel/framework 2023-09-22 07:06:43 -07:00
FreeScout
21d86327b9 Fix Guard bypass in Eloquent models in laravel/framework 2023-09-22 04:52:43 -07:00
FreeScout
1e871813f8 Fix CVE-2021-43808 issue in laravel/framework 2023-09-22 04:31:41 -07:00
FreeScout
822fb85f1c Fix RCE vulnerability in cookie session driver in laravel/framework 2023-09-22 02:07:50 -07:00
Philipp Kraft
ae0e931588 Merge branch 'freescout-helpdesk:master' into master 2023-09-22 10:29:24 +02:00
FreeScout
6bb91df75f Fix CVE-2019-18887 issue in symfony/http-kernel 2023-09-22 01:10:19 -07:00
FreeScout
9c1c180619 Fix CVE-2022-24894 issue in symfony/http-kernel 2023-09-22 01:01:59 -07:00
Gustavo Balduino
4b27fe0f82 Add filter fetch_emails.mailbox_to_save_message 2023-09-20 16:12:51 -03:00
FreeScout
aab5869dc9 Run test workflow on push to dist branch 2023-09-17 02:54:02 -07:00
FreeScout
c6b50b2ccf Fix CVE-2019-18888 issue in symfony/http-foundation 2023-09-17 02:49:35 -07:00
FreeScout
ba8296ef50 Fix CVE-2019-10913 issue in symfony/http-foundation 2023-09-17 01:44:45 -07:00
FreeScout
5b5df2e51e Upgrade league/flysystem to 1.1.4 2023-09-17 01:34:37 -07:00
FreeScout
73c039147e Upgrade guzzlehttp/guzzle to 6.5.8 2023-09-17 01:20:35 -07:00
FreeScout
51ef78c2af Upgrade erusev/parsedown to 1.7.2 2023-09-17 00:33:09 -07:00
FreeScout
42423bdf61 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-09-15 07:52:40 -07:00
FreeScout
35e2769ba3 Add freescout:module-update command and --yes flag to tools/update.sh - closes #3384 2023-09-15 07:52:14 -07:00
FreeScout
dc3e56d44b Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-09-15 07:50:28 -07:00
FreeScout
4d59b4b463 v1.8.99 2023-09-15 07:50:00 -07:00
FreeScout
ae9f1f8b0d Update install script path in continuous integration test 2023-09-15 07:43:29 -07:00
FreeScout
e30b9ed175 Create continuous integration test #3395 from fulldecent/patch-13
Create continuous integration test
2023-09-15 17:41:19 +03:00
FreeScout
d06dbcb99d Add freescout:module-update command and --yes flag to tools/update.sh - closes #3384 2023-09-15 07:37:53 -07:00
FreeScout
70af65c51f Add tools/install.sh 2023-09-14 22:10:37 -07:00
William Entriken
d288f3319a Create continuous integration test 2023-09-14 21:43:04 -04:00
FreeScout
96b1ee7b66 Properly process situation when application can not parse mail date header - closes #3394 2023-09-14 05:51:15 -07:00
FreeScout
89b37e778a Add freescout:module-update command and --yes flag to tools/update.sh - closes #3384 2023-09-14 00:13:51 -07:00
FreeScout
a72b859f05 freescout:module-update command - closes #3384 2023-09-13 04:49:02 -07:00
FreeScout
9d1c463247 Update yarn.lock 2023-09-13 04:25:43 -07:00
FreeScout
62c7bed6bb Allow to update all modules at once 2023-09-13 01:43:56 -07:00
FreeScout
3338ea6510 App\Module::updateModule() function 2023-09-13 00:46:49 -07:00
FreeScout
b6c015e3da Remove Mail Date and Time option from Mail Settings - closes #3380 2023-09-11 22:23:22 -07:00
FreeScout
547b05dcae Update package.json 2023-09-11 22:17:20 -07:00
FreeScout
ee7a2e692b Improve attachment names sanitizing - closes #3377 2023-09-11 22:15:10 -07:00
FreeScout
d52161b44b Improve attachment names sanitizing - closes #3377 2023-09-11 10:15:55 -07:00
FreeScout
9b5bf06a29 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-09-11 09:25:25 -07:00
FreeScout
189942c45a v1.8.98 2023-09-11 09:24:40 -07:00
FreeScout
8c68731283 Skip module symlink check and creation on open_basedir restriction in effect error - closes #3375 2023-09-11 06:54:48 -07:00
FreeScout
ec24787b9b Skip module symlink check and creation on open_basedir restriction in effect error - closes #3375 2023-09-11 06:42:01 -07:00
FreeScout
c220b8be8e Set default CURLOPT_CONNECTTIMEOUT value from config 2023-09-09 06:47:20 -07:00
FreeScout
550d8cc457 Fix No query results for model error on Status page - closes #3366 2023-09-09 03:57:27 -07:00
FreeScout
2de52008a2 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-09-08 10:32:38 -07:00
FreeScout
4354742d3f v1.8.97 2023-09-08 10:32:12 -07:00
FreeScout
5e1bf39839 Fix error on System Status page - closes #3368 2023-09-08 09:50:24 -07:00
FreeScout
74ab043a30 Automatically fix module symlink when module Public folder is a broken symlink 2023-09-08 06:22:00 -07:00
FreeScout
2abe265046 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-09-08 04:37:02 -07:00
FreeScout
d4f3cd4e6c v1.8.96 2023-09-08 04:36:27 -07:00
FreeScout
0c5a50c34e Remove trailing slash from host name when generating URLs 2023-09-08 01:28:51 -07:00
Philipp Kraft
dfab29bbbc Aria markup for thread options, addendum to #1150
Screen readers do not find the thread-options dropdown, because correct aria markup is missing. Added role, aria-haspopup, and aria-expanded attributes to the dropdown toggle caret
2023-09-08 09:36:57 +02:00
FreeScout
4bbbab7fa1 Automatically check invalid or missing modules symlinks on Modules and System Status pages 2023-09-07 00:12:34 -07:00
FreeScout
4c0b5d1d58 Automatically check invalid or missing modules symlinks on Modules and System Status pages 2023-09-06 23:38:00 -07:00
FreeScout
9f99e784f4 Move star mark when merging conversations - closes #3351 2023-09-06 03:28:55 -07:00
FreeScout
8c673d5242 Do not insert new line in the editor when pasting content - closes #3319 2023-09-05 23:27:01 -07:00
FreeScout
6852d7e14d Update Spanish translation #3343 from Pablohn26/patch-3
Update Spanish translation
2023-09-05 17:31:34 +03:00
FreeScout
204808032e Show SMTP 5xx errors immediately in the conversation - closes #3339 2023-09-05 07:30:19 -07:00
Pablo Hinojosa
8952cdb813 Update es.json
Improve Spanish translation. 
* Adding `¿` and `¡` marks
* Fixing some words without accent mark
2023-09-05 16:17:30 +02:00
FreeScout
bab3eb1e9f Show SMTP Queue ID in Outgoing Emails for sent emails - closes #3330 2023-09-05 04:44:34 -07:00
FreeScout
900bdfb199 Cancel SendReplyToCustomer background jobs when undoing replies - closes #3300 2023-09-05 00:33:52 -07:00
FreeScout
097f404d94 Fix Allow to reply from aliases option being reset when mailbox settings saved by non-admin - closes #3337 2023-09-04 23:30:50 -07:00
FreeScout
f796523e3b Set CURLOPT_SSL_VERIFYPEER option to false for curl and Guzzle requests - closes #3293 2023-09-04 23:24:51 -07:00
FreeScout
44477d7b1a Check symlink function on Status page - closes #3335 2023-09-03 23:49:38 -07:00
FreeScout
f3592d1d60 Fix errors on Status page - closes #3334 2023-09-03 23:35:29 -07:00
FreeScout
b935e20d63 Fix errors on Status page - closes #3334 2023-09-03 23:34:34 -07:00
FreeScout
0e4c8415a1 CustomerChannel::getChannels() function 2023-09-03 08:31:02 -07:00
FreeScout
b342f14bf9 Show DB info and missing migrations on Status page - closes #3326 2023-09-03 03:54:07 -07:00
FreeScout
b8489059f3 Show link to conversation and logs in SendReplyToCustomer jobs on System page - closes #3331 2023-09-03 00:45:01 -07:00
FreeScout
ebfd86bcb9 Show link to conversation and logs in SendReplyToCustomer jobs on System page - closes #3331 2023-09-03 00:39:29 -07:00
FreeScout
1e37ca57bc Store for the future an attempt to get SMTP Queue ID - closes #3330 2023-09-02 23:22:36 -07:00
FreeScout
eabd501dee Update readme 2023-09-02 00:04:35 -07:00
FreeScout
252de7a589 Fix variable name in saveUserThread() on fetching - closes #3325 2023-09-01 06:53:22 -07:00
FreeScout
9be2c1df5a Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-09-01 03:11:57 -07:00
FreeScout
a35c78d796 v1.8.95 2023-09-01 03:11:03 -07:00
FreeScout
43a8a759af Add GET page parameter when navigating over conversations - closes #3323 2023-09-01 00:50:37 -07:00
FreeScout
92abebaffa Show correct 12 or 24 hour format in date pickers - closes #3322 2023-08-31 23:53:57 -07:00
FreeScout
3b9b149555 Fix Invalid text representation error on PostgreSQL when saving mailbox permissions - closes #3315 2023-08-30 00:29:27 -07:00
FreeScout
8b3433b4ca Improve mail subject decoding - closes #3312 2023-08-28 23:29:54 -07:00
FreeScout
b02e034621 Fix migration creating customer_channel table - closes #3309 2023-08-28 22:48:59 -07:00
FreeScout
196e484448 Add Mail Date and Time option allowing to use date from mail headers when fetching emails - closes #3297 2023-08-28 00:15:59 -07:00
FreeScout
0cd3952d52 Repalce with empty string non-replaced vars in mailbox custom from name - closes #1031 2023-08-27 07:33:48 -07:00
FreeScout
3640508db9 Fix summernote adding new lines when pasting HTML - closes #3264 2023-08-26 09:17:00 -07:00
FreeScout
e75dcaf282 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-08-25 21:58:56 -07:00
FreeScout
a7b6b94612 v1.8.94 2023-08-25 21:58:19 -07:00
FreeScout
6ea0ea4619 Remove old records from notifications table - closes #3294 2023-08-23 10:44:58 -07:00
FreeScout
0c71e9cf5f Fix module translation file never imported under certain condition #3289 from totake0224/master
Fix module translation file never imported under certain condition
2023-08-22 09:48:03 +03:00
totake0224
348fbb237b set $locale before preg_match.because if former foreach ended continue condition. module translation file would be never importerd. 2023-08-22 15:35:15 +09:00
FreeScout
e2808ced1a Add exclude_channel parameter to customer->findCustomersBySocialProfile() 2023-08-21 06:11:52 -07:00
FreeScout
30b481597b Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-08-20 23:29:39 -07:00
FreeScout
c31685b062 v1.8.93 2023-08-20 23:29:29 -07:00
FreeScout
8dbe489d60 Fix Unassigned and Assigned folders - closes #3287 2023-08-20 23:28:42 -07:00
FreeScout
b11ffe9afa Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-08-20 22:33:03 -07:00
FreeScout
789da461f9 v1.8.92 2023-08-20 22:32:30 -07:00
FreeScout
fb3d1752dc Add an option allowing to restrict users to see only tickets assigned to themselves - closes #701 2023-08-20 03:29:46 -07:00
FreeScout
5219afb13f Add Chinese Simplified language 2023-08-20 00:59:58 -07:00
FreeScout
71e0888c65 Customer findCustomersBySocialProfile() function 2023-08-20 00:03:47 -07:00
FreeScout
e5456c8ca3 Functions for working with customer_channel table 2023-08-19 23:08:03 -07:00
FreeScout
9987ba2fbb In the chat conversation reply area show all buttons after codeview 2023-08-19 23:06:15 -07:00
FreeScout
8ea51c3e41 Update Danish translation 2023-08-19 09:38:24 -07:00
FreeScout
fbd934d750 Store customer to channel mapping in customer_channel table 2023-08-19 08:15:47 -07:00
FreeScout
63d06ee32a Show an error message to support agent if an emial has not been sent after 1 hour - closes #3268 2023-08-18 23:52:54 -07:00
FreeScout
561f1deb07 Add vendor files to dist 2023-08-18 00:04:44 -07:00
FreeScout
584d84475a Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-08-18 00:02:31 -07:00
FreeScout
c9e56723fb v1.8.91 2023-08-18 00:01:48 -07:00
FreeScout
b5b6e42c2d Allow to send by email to the customer threads in a phone conversation - closes #3270 2023-08-17 01:26:11 -07:00
FreeScout
14e56cd04f Fix Passing null to parameter in HTMLPurifier/Lexer.php - closes #3277 2023-08-16 22:34:47 -07:00
FreeScout
ecfbdd29a7 Speed up Eventy event firing 2023-08-14 23:30:37 -07:00
FreeScout
321a87c407 Speed up Eventy event firing #3263 from OttoWinter/optimize-eventy
Speed up Eventy event firing
2023-08-15 09:14:11 +03:00
Otto Winter
4403fa2238 Add missed change 2023-08-14 21:55:40 +02:00
Otto Winter
f2623f0253 Optimize eventy 2023-08-14 20:58:42 +02:00
FreeScout
36f16d9f9b Fix routes var in ModuleLaroute.php 2023-08-14 06:01:02 -07:00
FreeScout
097d5e3f98 Condense reply toolbar 2023-08-14 01:47:30 -07:00
FreeScout
87267638f7 Update Czech translation 2023-08-13 06:22:31 -07:00
FreeScout
6bcc9b0cc5 Update Polish translation 2023-08-13 03:37:42 -07:00
FreeScout
40fc845461 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-08-11 22:03:54 -07:00
FreeScout
18017c73ce v1.8.90 2023-08-11 22:03:21 -07:00
FreeScout
9e69ae6ec0 Allow to use vars in mailbox Custom From Name - closes #1031 2023-08-11 07:28:43 -07:00
FreeScout
0cb4bbcfe2 Improve subject and attachment name decoding - closes #3248 2023-08-08 22:20:00 -07:00
FreeScout
6a2f5657a5 Add app.curl_user_agent parameter 2023-08-06 03:25:30 -07:00
FreeScout
624d84be41 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-08-05 23:10:33 -07:00
FreeScout
8045da1f96 v1.8.89 2023-08-05 23:10:05 -07:00
FreeScout
482f428a17 Fix preg_match() Passing null to parameter in Helper.php - closes #3239 2023-08-05 23:08:59 -07:00
FreeScout
663908d17e Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-08-05 11:07:03 -07:00
FreeScout
3c61ff6895 v1.8.88 2023-08-05 11:06:26 -07:00
FreeScout
be504fc15c Fix shell_exec() error on Status page - closes #3239 2023-08-05 11:04:58 -07:00
FreeScout
60c2bd4808 Take into account not only created_at but also thread ID when sorting threads - closes #3240 2023-08-05 04:05:16 -07:00
FreeScout
03c584a5c3 Fix an issue with the customer reply connected to the wrong conversation - closes #3231 2023-08-04 06:47:25 -07:00
FreeScout
3570c48420 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-08-03 23:22:23 -07:00
FreeScout
aac5df9748 v1.8.87 2023-08-03 23:21:49 -07:00
FreeScout
884fa08355 Save .ICS attachments having no name as calendar.ics - closes #1412 2023-07-31 22:08:37 -07:00
FreeScout
59dbc520b9 Update Italian translation 2023-07-31 21:46:18 -07:00
FreeScout
8075bf14ec Save CC and BCC when saving outgoing email to the Sent IMAP folder - closes #3228 2023-07-31 04:26:16 -07:00
FreeScout
8e0d993596 Save CC and BCC when saving outgoing email to the Sent IMAP folder - closes #3228 2023-07-31 04:23:53 -07:00
FreeScout
6c87713751 Improve subject decoding - closes #3196 2023-07-18 06:55:38 -07:00
FreeScout
43ecd35402 Improve subject decoding 2023-07-18 06:39:49 -07:00
FreeScout
424c89e2c8 Allow to view .json, .diff and .patch attachments - closes #3192 2023-07-17 23:12:12 -07:00
FreeScout
744ccef561 Check pcntl extension in the console version of PHP on Status page - closes #3190 2023-07-17 22:48:18 -07:00
FreeScout
7f877ee636 Make sure that conversation subject has String type when fetching emails 2023-07-17 10:31:37 -07:00
FreeScout
542d6dc061 Remove FUNDING.yml 2023-07-17 09:56:43 -07:00
FreeScout
bd1bdc03c2 Make sure that conversation subject has String type when fetching emails 2023-07-17 06:22:12 -07:00
FreeScout
b0be55ef2e Update Readme 2023-07-17 03:09:16 -07:00
FreeScout
6f332d8c2d Update readme 2023-07-17 03:06:05 -07:00
FreeScout
cf2f488aaf Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-07-17 01:40:15 -07:00
FreeScout
8c91df4319 v1.8.86 2023-07-17 01:39:12 -07:00
FreeScout
807eba8d98 Do not delete the message when another user discards a draft of the reply which just has been sent - closes #2873 2023-07-17 01:04:22 -07:00
FreeScout
71fb3b1313 Allow to set trusted proxies via .env file APP_TRUSTED_PROXIES parameter - closes #1994 2023-07-17 00:12:00 -07:00
FreeScout
2b9f56b456 Helper::getClientIp() function 2023-07-15 23:20:47 -07:00
FreeScout
9266e5c207 Allow to show custom message on 403 error page 2023-07-15 23:02:56 -07:00
FreeScout
ffe2adc4c2 Login hooks 2023-07-15 06:11:45 -07:00
FreeScout
9b6fa32db5 Fix References header in emails to customers - closes #3175 2023-07-14 10:24:07 -07:00
FreeScout
fdea5e302f Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-07-14 06:40:55 -07:00
FreeScout
4fbd276fff v1.8.85 2023-07-14 06:40:19 -07:00
FreeScout
90536704a3 Improve iso-2022-jp subjects and attachment names decoding - closes #3185 2023-07-14 04:28:10 -07:00
FreeScout
f8787c67df Update \MailHelper::decodeSubject() 2023-07-14 03:27:06 -07:00
FreeScout
2cea3a6585 Do not create empty conversation when same email is being fetched at the same time - #3186 2023-07-14 03:25:08 -07:00
FreeScout
61edffd0b6 Fix Attempt to read property id on null in reply_fancy - closes #3182 2023-07-13 22:57:42 -07:00
FreeScout
6494ca8c26 List all Message-IDs in References header in replies to customers - closes #3175 2023-07-12 11:05:30 -07:00
FreeScout
480448ff79 Improve suject decoding - closes #3177 2023-07-12 07:18:34 -07:00
FreeScout
8139f91db2 Update Russian translation 2023-07-12 01:21:42 -07:00
FreeScout
3d7f424b28 Limit the number of displayed non writable cache files on Status page - closes #3165 2023-07-09 22:48:33 -07:00
FreeScout
9e514447e6 Merge pull request #3172 from sulemaanhamza/master
Fixed undefined variable bug in the FollowerObserver
2023-07-08 12:15:22 +03:00
suleman
06ed4c8da7 Fixed undefined variable bug in FollowerObserver 2023-07-08 13:47:59 +05:00
FreeScout
072c3f8be9 Add vendor files to dist 2023-07-07 00:28:42 -07:00
FreeScout
37b158e99b Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-07-07 00:26:34 -07:00
FreeScout
a327c0b321 v1.8.84 2023-07-07 00:25:55 -07:00
FreeScout
d57dc285df Improve subject decoding - closes #3167 2023-07-06 23:45:46 -07:00
FreeScout
e29cfc7b88 Do not encode HTML entities in outoging plain text emails - closes #3159 2023-07-06 02:56:11 -07:00
FreeScout
08c7a4033e Update German translation 2023-07-06 02:36:32 -07:00
FreeScout
b9dd2b7cbc Fix GuzzleHttp\Psr7\Uri::withScheme() error on PHP 7.1 - closes #3158 2023-07-05 00:20:32 -07:00
FreeScout
e45eccd64c Do not try to resend email notifications on received bounce emails - closes #3156 2023-07-04 23:25:57 -07:00
FreeScout
c93411e330 Update message shown when no activations left for the license key 2023-07-04 23:18:55 -07:00
FreeScout
550e2d30cc Fix PHP version check in tools.php - closes #3162 2023-07-04 22:50:19 -07:00
FreeScout
48e4cfadfd Check pcntl extension status in System to avoid Undefined constant SIGKILL error 2023-07-04 00:57:25 -07:00
FreeScout
c43e41c076 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-06-30 20:37:11 -07:00
FreeScout
45af81513c v1.8.83 2023-06-30 20:36:24 -07:00
FreeScout
5244b5c8f0 Fix retrieveing attachments - closes #3154 2023-06-30 20:35:10 -07:00
FreeScout
ff36b2fef1 Add vendor files to dist 2023-06-30 00:40:52 -07:00
FreeScout
b27997db2c Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-06-30 00:39:35 -07:00
FreeScout
b0b9a621a1 v1.8.82 2023-06-30 00:38:47 -07:00
FreeScout
f851e70d28 Make using imap_utf8_to_mutf7() function optional in Webklex/laravel-imap library - closes #3152 2023-06-30 00:36:05 -07:00
FreeScout
09a5d1520d email.reply_to_customer.threads hook 2023-06-29 00:48:39 -07:00
FreeScout
7ba097e34c Update NL translation 2023-06-28 23:07:47 -07:00
FreeScout
2141e1f4da Preserve new lines when replacing vars in the signature - closes #3146 2023-06-28 07:20:12 -07:00
FreeScout
9c0fd7b027 Preserve new lines when replacing vars in the signature - closes #3146 2023-06-28 07:10:14 -07:00
FreeScout
fa9289293b Improve subject decoding 2023-06-27 23:36:16 -07:00
FreeScout
3092424315 Fixed saving data into Timestamp field on PostgreSQL 2023-06-27 01:14:05 -07:00
FreeScout
2abe327e90 Fix Undefined property: Webklex\IMAP\Attachment::config - closes #3142 2023-06-26 23:24:11 -07:00
FreeScout
47ebc2f694 Improve decoding strings in Webklex/laravel-imap library 2023-06-26 11:33:57 -07:00
FreeScout
9312036f69 Improve decoding strings in Webklex/laravel-imap library 2023-06-26 07:10:19 -07:00
FreeScout
5732b8456f Update readme 2023-06-25 06:07:21 -07:00
FreeScout
7fd01b7e40 Show proper error when module license key is invalid 2023-06-23 22:21:12 -07:00
FreeScout
fc72dfc870 Show proper error when module license key is invalid 2023-06-23 12:10:04 -07:00
FreeScout
24dc14c44a Add vendor files to dist 2023-06-22 00:14:37 -07:00
FreeScout
1e291d4180 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-06-22 00:11:31 -07:00
FreeScout
1af3310e0b v1.8.81 2023-06-22 00:10:52 -07:00
FreeScout
e90c1838f1 Change iso-2022-jp encoding to iso-2022-jp-ms when decoding data in Webklex/php-imap library - closes #3122 2023-06-22 00:05:10 -07:00
FreeScout
88932bfc67 Fix decoding sender name in iso-2022-jp encoding - closes #3089 2023-06-19 06:47:56 -07:00
FreeScout
17205d39d2 Improve decoding attachment names with Webklex/PHP-IMAP library - closes #2753 2023-06-19 06:25:08 -07:00
FreeScout
9512104536 Improve decoding attachment names with Webklex/PHP-IMAP library - closes #2753 2023-06-19 05:06:47 -07:00
FreeScout
4eabdc489f Improve body encoding conversion on fetchign - closes #3089 2023-06-19 02:13:21 -07:00
FreeScout
fd9865b368 Revert formatEmailList() update - closes #3118 2023-06-19 01:49:45 -07:00
FreeScout
c5a8dca36f Prevent Attempt to read property mail on fetching - closes #3118 2023-06-19 00:33:40 -07:00
FreeScout
49d0a2aaf0 Improve subject decoding - closes #3089 2023-06-15 05:40:47 -07:00
FreeScout
b5b941bf73 Do not use @unknown email from Reply-To on fetching - closes #3101 2023-06-14 22:34:15 -07:00
FreeScout
6cb39bd6ee Improve email validation: do not allow @unknown emails - closes #3101 2023-06-13 23:18:26 -07:00
FreeScout
b6b1292589 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-06-05 10:32:38 -07:00
FreeScout
2d4e4e71ab v1.8.80 2023-06-05 10:32:08 -07:00
FreeScout
8194d416c6 Fix an issue with HTTPS redirect - closes #3075, #3070 2023-06-05 10:30:13 -07:00
FreeScout
d3aa94a922 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-06-03 02:33:01 -07:00
FreeScout
0d0ca9a73a v1.8.79 2023-06-03 02:32:27 -07:00
FreeScout
f096e93a09 Fix error on Users page when user first name is empty - closes #2972 2023-06-02 03:06:45 -07:00
FreeScout
678c9b029b Improve subject decoding with iconv_mime_decode() - closes #3066 2023-06-01 02:28:45 -07:00
FreeScout
98d5986dde Fix module activation issue when /public/modules symlink exists but leads to wrong path 2023-06-01 00:42:49 -07:00
FreeScout
56f00fe6a6 Fix module activation issue when /public/modules symlink exists but leads to wrong path 2023-06-01 00:04:56 -07:00
FreeScout
3214bbc427 Do not try to send email again if mail server replies: message file too big - closes #3065 2023-05-31 23:44:24 -07:00
FreeScout
6e52046032 Decode subjects using iconv_mime_decode() in Webklex/php-imap library - closes #3039 2023-05-31 02:00:21 -07:00
FreeScout
9d4c43f96d Fix Undefined array key 0 error in Subscription class - closes #3059 2023-05-31 00:01:11 -07:00
FreeScout
0ef404f3ef Replace base64 images in messages with attachment URLs - closes #3057 2023-05-30 23:53:20 -07:00
FreeScout
ac76251b75 Update German translation 2023-05-30 07:29:53 -07:00
FreeScout
c97610bd97 Show translated filter names in the Search - closes #3058 2023-05-30 07:29:05 -07:00
FreeScout
722349f586 Fix subject with colon being truncated with Webklex/php-imap library - closes #2964 2023-05-30 00:36:26 -07:00
FreeScout
5aabac7b4f Remove app.force_https parameter and determine protocol from app.url parameter - closes #3053 2023-05-28 00:35:51 -07:00
FreeScout
ee2269fdf1 Add vendor files to dist 2023-05-28 00:09:31 -07:00
FreeScout
adb1e309e3 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-05-28 00:07:57 -07:00
FreeScout
a5f77e7846 v1.8.78 2023-05-28 00:07:36 -07:00
FreeScout
4711ea8c7f Update German translation 2023-05-27 06:59:47 -07:00
FreeScout
769529e129 Fix saving OpenOffice attachments - closes #3048 2023-05-24 23:07:09 -07:00
FreeScout
5b0f1f974c Show links hrefs only in thread content in the printed version - closes #3045 2023-05-23 22:53:58 -07:00
FreeScout
cf22374926 Allow to specify unlimited number of recipients in new conversation draft - closes #3036 2023-05-22 07:28:46 -07:00
FreeScout
56a52c6237 Do not show attachment URLs in printed version - closes #3035 2023-05-22 00:42:34 -07:00
FreeScout
04fd44f8c2 Set customer First and Last Name max length to 255 symbols - closes #3033 2023-05-22 00:32:13 -07:00
FreeScout
a43729e9c1 Allow to set same_site to none in config/session.php - closes #3023 2023-05-17 23:32:29 -07:00
FreeScout
84314598d2 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-05-14 10:13:00 -07:00
FreeScout
9991c368cf v1.8.77 2023-05-14 10:12:29 -07:00
FreeScout
2dfe5e6b64 Fix minutes formatting in 12-hour clock mode - closes #2997 2023-05-14 10:09:52 -07:00
FreeScout
57b67838f8 Add customer.deleting hook #3003 from Lars-/master
Add customer.deleting hook
2023-05-11 11:15:59 +03:00
Lars Jansen
f4f49735be feat: add customer deleting event
This commit adds a new method to the CustomerObserver class that triggers an event when a customer is being deleted.
2023-05-11 10:10:20 +02:00
FreeScout
1a76549c2e Properly thread into conversations replies from Jira - #2927 2023-05-10 23:37:50 -07:00
FreeScout
02a1f4a89c Merge pull request #3001 from avenjamin/master
Unfollow followed conversation when re/assigned
2023-05-11 09:23:16 +03:00
Avenjamin
cc7e6aa9ec unfollow followed conversation when re/assigned
If a user is "following" a conversation and the conversation is then assigned to them they no longer need to follow it.
2023-05-11 09:41:52 +10:00
FreeScout
b78838ca23 Fix updating custom folders counters - closes #2998 2023-05-10 03:21:40 -07:00
FreeScout
a54b6a4195 Add vendor files to dist 2023-05-09 22:29:56 -07:00
FreeScout
14e03bf903 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-05-09 22:27:17 -07:00
FreeScout
dc8f2c53c9 v1.8.76 2023-05-09 22:26:15 -07:00
FreeScout
fe1563fb03 Fix Passing null error in emails template - closes #2989 2023-05-09 06:47:06 -07:00
FreeScout
82628607d6 Allow to reply from alises option for mailboxes - closes #2988 2023-05-09 06:43:30 -07:00
FreeScout
1a9b178f67 Fix showing mailbox mute status for non-admin users - closes #2992 2023-05-09 06:22:43 -07:00
FreeScout
db4f8ed299 Add PHP and FreeScout versions to General Help Request template 2023-05-07 23:32:25 -07:00
FreeScout
0318f3f785 Limit Insert variable dropdown width to avoid issues in Firefox - closes #2966 2023-05-07 23:28:39 -07:00
FreeScout
60914553c3 Improve subject decoding - closes #2987 2023-05-07 23:18:57 -07:00
FreeScout
939c2c52fa Improve sanitizing SVG files 2023-05-06 09:02:18 -07:00
FreeScout
f8aba65578 Improve sanitizing SVG files 2023-05-06 07:07:44 -07:00
FreeScout
8fc1f7668b Improve displaying tables in conversations - closes #2969 2023-05-06 04:52:30 -07:00
FreeScout
18ad759d43 Improve background to background-color conversion - closes #2969 2023-05-06 01:14:35 -07:00
FreeScout
550ca6f7dd Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-05-05 21:57:58 -07:00
FreeScout
e81829a203 v1.8.75 2023-05-05 21:57:25 -07:00
FreeScout
257dfb8474 Allow to update folders counters in background 2023-05-05 07:06:17 -07:00
FreeScout
6a8be9661d Allow to update folders counters in background #2982 from Lars-/master
Allow to update folders counters in background
2023-05-05 16:47:49 +03:00
Lars Jansen
b51bffa173 fix: performance gain by updating folder counters in a job 2023-05-05 15:03:29 +02:00
FreeScout
56db1b8023 Take into account mailbox From Name settings when sending a reply to customer from mailbox alias - closes #2976 2023-05-04 23:18:49 -07:00
FreeScout
6c949b18af Fix an issue with a note being created instead of reply in phone conversation - closes #2953 2023-05-04 22:52:41 -07:00
FreeScout
b776827cfb Remove default SMTP host and port from config - closes #2978 2023-05-04 21:42:53 -07:00
FreeScout
951d0f322b Use iconv_mime_decode() instead of imap_utf8() for UTF-8 encoded subjects - #2965 2023-05-03 23:00:03 -07:00
FreeScout
1288670a72 Automatically load new notes when viewing a conversation - closes #2961 2023-05-02 22:50:47 -07:00
FreeScout
1398f02865 Restart swiftmailer after each email sent to remove temp files - closes #2949 2023-05-02 05:48:48 -07:00
FreeScout
3c3f25adce Fix seconds format in translated dates 2023-05-02 01:45:00 -07:00
FreeScout
2fe0f518be Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-05-02 01:35:30 -07:00
FreeScout
a67f35e659 v1.8.74 2023-05-02 01:34:59 -07:00
FreeScout
e071abda6e Show dates translated into selected language - closes #2950 2023-05-02 01:34:15 -07:00
FreeScout
37421bd258 Show dates translated into selected language - closes #2950 2023-05-01 23:26:06 -07:00
FreeScout
f2f9ac533d Do not show From (Alias) field when creating a phone conversation 2023-04-27 22:14:45 -07:00
FreeScout
02a58f07a5 Show dates translated into selected language - closes #2950 2023-04-26 22:18:28 -07:00
FreeScout
34eb6bf84c Show dates translated into selected language - closes #2950 2023-04-26 22:06:56 -07:00
FreeScout
7e49207ad9 Show dates translated into selected language - closes #2950 2023-04-26 10:51:44 -07:00
FreeScout
2f21645b39 Show dates translated into selected language - closes #2950 2023-04-26 06:44:21 -07:00
FreeScout
3572c95448 Show dates translated into selected language - closes #2950 2023-04-26 05:30:57 -07:00
FreeScout
3892d12a1f Fix wrong messages order in the email to customer in some cases - closes #2938 2023-04-25 23:20:55 -07:00
FreeScout
9973837e63 Clean temp files daily instead of weekly - closes #2949 2023-04-25 22:43:14 -07:00
FreeScout
e896a79dac Allow to select mailbox alias in From field when creating a new conversation - closes #656 2023-04-24 23:17:45 -07:00
FreeScout
309b832f6c Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-04-23 07:52:10 -07:00
FreeScout
eca72d744e v1.8.73 2023-04-23 07:51:10 -07:00
FreeScout
e63ea84226 Fix replying to email notifications by users - closes #2941 2023-04-23 07:49:32 -07:00
FreeScout
c8322ada6f Updaet Czech translation 2023-04-21 22:12:46 -07:00
FreeScout
67e73fcbc3 Add vendor files to dist 2023-04-20 22:52:00 -07:00
FreeScout
e2f51b074e Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-04-20 22:50:44 -07:00
FreeScout
10b8ae0e74 v1.8.72 2023-04-20 22:50:13 -07:00
FreeScout
a484cf8236 If Email notifications are disabled use Browser notifications settings to show Bell notifications in the top menu - closes #1074 2023-04-19 23:51:27 -07:00
FreeScout
a94f97e5a2 Improve @fwd email detection - closes #2929 2023-04-19 22:56:49 -07:00
FreeScout
ca48719464 Sanitize mailbox email when creating or updating the mailbox - closes #9154 2023-04-18 22:19:58 -07:00
FreeScout
adc3b832b8 Add Norwegian translation 2023-04-18 01:30:33 -07:00
FreeScout
ec89f0e57d Sanitize mailbox email when creating or updating the mailbox - closes #9154 2023-04-18 00:51:31 -07:00
FreeScout
65060592b4 Allow to reply from Mailbox Aliases - closes #656 2023-04-17 07:30:52 -07:00
FreeScout
c329dd5f20 Allow to change user photo size via APP_USER_PHOTO_SIZE .env file variable 2023-04-15 00:33:33 -07:00
FreeScout
59f918f099 Allow to change customer photo size via APP_CUSTOMER_PHOTO_SIZE .env variable - closes #2919 2023-04-15 00:22:23 -07:00
FreeScout
f818134c2d Use proxy setting from .env when checking and downloading new release - closes #2911 2023-04-14 01:00:55 -07:00
FreeScout
a3fa817a37 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-04-12 06:23:47 -07:00
FreeScout
24201fa0fb v1.8.71 2023-04-12 06:23:14 -07:00
FreeScout
aba3421a1d Pass threads_count to reply_email.before_signature and reply_email.after_signature hooks - closes #2908 2023-04-11 06:28:09 -07:00
FreeScout
ead9233560 Update Polish translation 2023-04-10 22:51:33 -07:00
FreeScout
3a09c344e5 Update Italian translation 2023-04-09 01:45:35 -07:00
FreeScout
51174a75bb Improve PHP version check in tools.php - closes #2901 2023-04-06 02:42:03 -07:00
FreeScout
0715e452fc Update pt-BR translation 2023-04-05 07:09:19 -07:00
FreeScout
6fdb19a86a Rename menu hook into menu.mailbox_single.after_name 2023-04-05 07:08:15 -07:00
FreeScout
71301ad1fe Add vendor files to dist 2023-04-04 03:07:24 -07:00
FreeScout
ae67a1375c Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-04-04 03:03:03 -07:00
FreeScout
b30b48f16d v1.8.70 2023-04-04 02:59:48 -07:00
FreeScout
cc0f86ab53 Escape link text in the editor 2023-04-04 02:49:27 -07:00
FreeScout
6b1b061b26 Take into account proxy settings from .env in Helper::getRemoteFileContents() 2023-04-03 22:55:51 -07:00
FreeScout
87e427ccf4 Add Norwegian language 2023-04-01 06:54:53 -07:00
FreeScout
35d29eb216 Change freescout:send-monitor description - closes #2881 2023-04-01 06:27:19 -07:00
FreeScout
b2bc4cabce Update Russian translation 2023-04-01 05:44:15 -07:00
FreeScout
c8f5198c06 Fix selecting conversations via long press in mobile app - closes #2868 2023-04-01 03:14:12 -07:00
FreeScout
fb4ab34e2a Change freescout:send-monitor description - closes #2881 2023-04-01 02:56:49 -07:00
FreeScout
6c24b618bb Retry background job button - closes #2881 2023-04-01 02:53:28 -07:00
FreeScout
c0904f07e9 Change queue:work check interval - closes #2876 2023-04-01 02:47:23 -07:00
FreeScout
90f1c0a737 Retry background job button - closes #2881 2023-04-01 02:38:30 -07:00
FreeScout
00893a00a4 Open links in another tab 2023-03-31 00:01:18 -07:00
FreeScout
df54432000 Improve Filesystem.php security on Windows 2023-03-30 23:35:14 -07:00
FreeScout
0d1a05edbb Revert conversation preview after undoing sending a reply - closes #2874 2023-03-30 23:18:26 -07:00
FreeScout
b5f93ed153 Update readme 2023-03-30 22:25:04 -07:00
FreeScout
7aa8617ff6 Remove SMTP mail service providers info from mailbox Sending Emails page - #2862 2023-03-30 22:19:53 -07:00
FreeScout
3144fbf7b2 Format dates in search 2023-03-22 11:22:24 -07:00
FreeScout
15787aaa0f Helper::sqlLikeOperator() function 2023-03-19 22:20:11 -07:00
FreeScout
5999ac2944 Fix typo in check-requirements command 2023-03-19 22:19:33 -07:00
FreeScout
788a793292 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-03-17 22:01:57 -07:00
FreeScout
4036bb6de3 v1.8.69 2023-03-17 22:01:26 -07:00
FreeScout
c9c698da03 Revert the change in Reply-To header processing on fetching - closes #2833 2023-03-17 21:58:38 -07:00
FreeScout
a76c512a1e Fix empty body issue when fetching emails via Webklex/php-imap library - closes #1972 2023-03-17 07:21:49 -07:00
FreeScout
867876c1b7 Fix in Docker mode checking queue:work status on Status page and killing background processes that stuck 2023-03-17 05:15:18 -07:00
FreeScout
8f809a53d9 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-03-16 23:53:32 -07:00
FreeScout
f93154cbb4 v1.8.68 2023-03-16 23:51:12 -07:00
FreeScout
a454ded9d2 Merge branch 'master' of github.com:freescout-helpdesk/freescout 2023-03-16 23:49:20 -07:00
FreeScout
478aa72f0c Update conversation preview when deleting a note - closes #2832 2023-03-16 23:46:14 -07:00
FreeScout
8b8e49f552 Fix popover z-index - closes #2829 2023-03-16 05:31:30 -07:00
FreeScout
3eb0e9213c Add menu.mailbox.before_name and after_name actions for only one mailbox #2827 from avenjamin/master
Add menu.mailbox.before_name and after_name actions for only one mailbox
2023-03-16 09:33:56 +03:00
FreeScout
b4e6089f61 Allow single backslashes in .env file - closes #2822 2023-03-15 23:31:15 -07:00
FreeScout
924473b254 Do not use Reply-To as a sender email for auto-responders - closes #2826 2023-03-15 23:14:48 -07:00
FreeScout
29177f5c1c Accept forwarded emails with just Fwd: subject 2023-03-15 11:17:42 -07:00
Avenjamin
309b355d41 Add menu.mailbox.before_name and after_name actions when only one mailbox
If there's only one mailbox the existing before_name and after_name action's didn't apply.
2023-03-16 00:27:52 +11:00
FreeScout
fd05a47156 Substitute customer in the worwarded email only if there is @fwd in the email - closes #2813 2023-03-14 23:52:11 -07:00
FreeScout
dc685bbafb Create vendor/natxet/cssmin/src folder in composer.json for Docker - closes #2583 2023-03-14 22:49:08 -07:00
FreeScout
5901132900 Allow single backslashes in .env file - closes #2822 2023-03-14 07:42:41 -07:00
FreeScout
f996d024e7 Use curl instead of file_get_contents() to load remote files 2023-03-13 23:19:59 -07:00
FreeScout
33a05b7176 mail.fetch_message.imap_folders hook - closes #2820 2023-03-13 01:33:34 -07:00
FreeScout
929811b154 Add menu.mailbox.after_name hook #2816 from avenjamin/master
Add menu.mailbox.after_name hook
2023-03-10 15:09:36 +03:00
Avenjamin
47eaa65056 Add menu.mailbox.after_name @action
Add @action to allow customisation *after* the mailbox name in the mailbox menu. e.g. add unassigned conversation count.
2023-03-10 22:59:58 +11:00
FreeScout
1bc5f606aa Do not substitute the customer in forwarded by support agent email if the email starts with @fwd - closes #2813 2023-03-10 03:16:21 -08:00
FreeScout
8c214c7d90 Update Czech translation 2023-03-09 21:55:06 -08:00
FreeScout
c1de256ae7 Fix Typos #2812 from avenjamin/master
Fix Typos
2023-03-10 08:53:19 +03:00
Avenjamin
6362f905aa Revert getAssesibleFolders typo
Ignore getAssesibleFolders typo as it's used by various modules
2023-03-09 23:26:30 +11:00
Avenjamin
a3356cb76b Fix Typos
fix various typos
2023-03-09 23:03:46 +11:00
FreeScout
8c0afd34c1 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-03-09 00:25:23 -08:00
FreeScout
6252e73127 v1.8.67 2023-03-09 00:24:56 -08:00
FreeScout
3aaed00349 Update Helper::2808() warning message 2023-03-08 23:57:28 -08:00
FreeScout
5b2ff85187 Update Helper::2808() warning message 2023-03-08 23:54:29 -08:00
FreeScout
992c25e8c8 Show an alert in the mailbox to all users if there are problems with emails queue processing - closes #2808 2023-03-08 23:53:16 -08:00
FreeScout
5adb116141 Send alerts to activated admins only 2023-03-08 06:05:17 -08:00
FreeScout
05c76c6584 Detect incoming message as a reply when original email sent to multiple mailboxes - closes #2807 2023-03-08 04:33:12 -08:00
FreeScout
863863a7ac .link-active style 2023-03-08 04:32:05 -08:00
FreeScout
b144823cd5 thread.info.prepend 2023-03-08 04:31:45 -08:00
FreeScout
1c968bda9d conversation.view.threads hook 2023-03-08 04:31:33 -08:00
FreeScout
fb6b31106d Clean updater folder before installing a new app version to avoid installing some random release 2023-03-04 00:04:43 -08:00
FreeScout
bde0b18824 Add vendor files to dist 2023-03-03 22:06:52 -08:00
FreeScout
44084b4f1d Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-03-03 22:05:26 -08:00
FreeScout
29423e03de v1.8.66 2023-03-03 22:04:59 -08:00
FreeScout
4141758ee4 Fix Mailbox name is not valid mUTF-7 error for Webklex/laravel-imap - closes #2793 2023-03-03 05:56:16 -08:00
FreeScout
0f3aeab653 Webklex/php-imap Fix not all attachments are pushed to the collection 2023-02-28 22:44:25 -08:00
FreeScout
a87a37ca54 Webklex/php-imap Prevent the structure parsing from parsing an empty part 2023-02-28 22:40:27 -08:00
FreeScout
cd096eacbf Webklex/php-imap Prevent infinite loop in ImapProtocol 2023-02-28 22:27:23 -08:00
FreeScout
fd40451ef0 Improved retrieving original message with wekblex/php-imap library 2023-02-28 22:15:03 -08:00
FreeScout
0845cd1982 Fix attachment names encoded in windows-1258 in webklex/laravel-imap - closes #2782 2023-02-28 09:44:31 -08:00
FreeScout
133341c379 Update Czech translation 2023-02-28 06:04:14 -08:00
FreeScout
4a27791524 Fix attachment names encoded in windows-1258 in webklex/laravel-imap - closes #2782 2023-02-28 06:03:39 -08:00
FreeScout
3767e0bfd2 Add Finnish language 2023-02-27 23:43:05 -08:00
FreeScout
e0c7370395 Add Finnish language 2023-02-27 23:42:15 -08:00
FreeScout
a4aa6abc9e When deleting an email from customer do not create a new customer if there are no conversations with such email - closes #2767 2023-02-27 23:38:08 -08:00
FreeScout
7b5572f5de Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-02-26 23:28:39 -08:00
FreeScout
46cb52bd2b v1.8.65 2023-02-26 23:28:14 -08:00
FreeScout
996758f6d1 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-02-26 23:24:18 -08:00
FreeScout
4952912e16 v1.8.64 2023-02-26 23:24:03 -08:00
FreeScout
b9366bc3f2 Fix Undefined array key argv error 2023-02-26 23:22:52 -08:00
FreeScout
e21e48626d Optimize queue:work monitoring 2023-02-26 22:54:16 -08:00
FreeScout
313cd61975 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-02-26 22:28:05 -08:00
FreeScout
fb71bef6f6 v1.8.63 2023-02-26 22:27:42 -08:00
FreeScout
717ab19c07 Fix creating a new conversation with Send and go to next active option - closes #2769 2023-02-26 22:25:16 -08:00
FreeScout
95bf16da60 Fix undefined variable To on fetching forwarded email - closes #2771 2023-02-26 19:59:42 -08:00
FreeScout
7664faaf44 Kill fetch-emails commands running for too long - closes #2489, #2762 2023-02-26 09:51:05 -08:00
FreeScout
48736ea1d0 Add vendor files to dist 2023-02-25 21:14:43 -08:00
FreeScout
4ace088465 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-02-25 21:10:58 -08:00
FreeScout
57b92938fb v1.8.62 2023-02-25 21:10:38 -08:00
FreeScout
09b5605d04 Do not show message ID in the Gmail snippet preview - closes #2752 2023-02-25 03:21:26 -08:00
FreeScout
1d3aaca944 Do not convert phone conversation into email conversation when adding a note - closes #2766 2023-02-24 22:35:28 -08:00
FreeScout
1ecf3ae23d Fixed an issue with email notifications being sent to the user when they should not be sent - closes #2764 2023-02-24 22:15:41 -08:00
FreeScout
924b47635d Create a conversation on behalf of a customer when support agent forwards an email only when it is sent to one address - closes #2761 2023-02-24 21:06:10 -08:00
FreeScout
a62da2c890 Improve detecting customer email when fetching emails forwarded by support agents - closes #2763 2023-02-23 04:31:43 -08:00
FreeScout
0653551d93 Fix incorrect customer email detection when fetching forwarded email - closes #2763 2023-02-22 23:11:47 -08:00
FreeScout
f581f52a6f Do not add convesations count to the title when saving a draft 2023-02-22 22:48:52 -08:00
FreeScout
323ce17ef1 Fix saving outgoing emails to the IMAP folder with new Webklex IMAP library 2023-02-22 22:43:36 -08:00
FreeScout
4abe0200fb Fix retrieving folders list in mailbox Fetching settings with new Webklex IMAP library 2023-02-22 22:17:37 -08:00
FreeScout
fa5c4f906f Rename Fetching Interval option into Fetching Interval 2023-02-22 21:58:42 -08:00
FreeScout
fe63a665d1 Do not add Re: when forwarding - closes #2759 2023-02-22 04:34:46 -08:00
FreeScout
73bac1622a Do not add Re: to the email saved in IMAP folder when creating new conversation or forwarding - closes #2509 2023-02-22 04:11:06 -08:00
FreeScout
0b51653658 Fix undefined attachment names with webklex/php-imap library - closes #2753 2023-02-21 22:41:44 -08:00
FreeScout
94807fd30a layout.favicon hook - closes #2754 2023-02-21 22:13:02 -08:00
FreeScout
074d8d6c13 Take into account proxy settings in Helper::downloadRemoteFile() - closes #2755 2023-02-21 21:25:31 -08:00
FreeScout
59f510dfa9 Set closed_at when creating a conversation and mailbox Status After Replying is Closed - closes #2751 2023-02-21 06:19:15 -08:00
FreeScout
7828e6e284 Fix Send and stay on the page option when creating a new conversation 2023-02-21 06:07:58 -08:00
FreeScout
92ef753708 Update NL translation 2023-02-21 00:56:26 -08:00
FreeScout
62dae9a141 Update NL translation 2023-02-21 00:55:50 -08:00
FreeScout
24c9eb7185 When an attachment can not be saved to the IMAP folder - save an email without attachments - closes #2749 2023-02-21 00:52:53 -08:00
FreeScout
470bb614b9 Fix apple-touch-icon.png on subfolder installation 2023-02-20 00:07:09 -08:00
FreeScout
2a52506a2c Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-02-19 23:43:35 -08:00
FreeScout
9557f2d06d v1.8.61 2023-02-19 23:43:11 -08:00
FreeScout
947d693a15 Add favicon to the layout - closes #2746 2023-02-19 23:42:08 -08:00
FreeScout
fa58d144b9 Fix manifest and mask-icon path for subfolder installation - closes #2746 2023-02-19 23:22:25 -08:00
FreeScout
136304ce8b Shorten text in the conversations table footer - closes #2743 2023-02-19 06:01:04 -08:00
FreeScout
16f5a6dce1 When user replies to the email notification send reply to CC emails too - closes #2740 2023-02-19 00:41:56 -08:00
FreeScout
ff6d7ce7e4 Fix Logs date format display issue with 12hr date format 2023-02-18 23:24:17 -08:00
FreeScout
1123d22446 When user replies to the email notification send reply to CC emails too - closes #2740 2023-02-18 22:10:45 -08:00
FreeScout
adc39bd9fe Improve attachments mime type detection when attachment is created from remote URL 2023-02-18 03:08:36 -08:00
FreeScout
dea7ec57f3 Do not write a notice to the log when incoming email date can not be parsed - closes #2734 2023-02-16 22:51:01 -08:00
FreeScout
3ad7123361 Fix moving conversations when merging phone customers - closes #2722 2023-02-16 22:39:16 -08:00
FreeScout
eb1b6aad4a Improve attachments mime type detection when attachment is created from remote URL 2023-02-15 23:52:25 -08:00
FreeScout
9dfad3b5cf Fix an issue when a module could not be updated due to existing Public symlink - closes #2709 2023-02-15 23:03:59 -08:00
FreeScout
4eadecc08f Delete records from followers table when the user is deleted from DB 2023-02-15 03:52:36 -08:00
FreeScout
7875faa8fd Delete records from followers table when conversation is deleted - closes #2732 2023-02-15 03:45:07 -08:00
FreeScout
35725c2718 Show extra instructions when invitation email to user can not be sent - #2730 2023-02-14 22:00:06 -08:00
FreeScout
c05722b074 mailbox.permission_users hook 2023-02-14 06:29:40 -08:00
FreeScout
8efe05e524 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-02-14 01:03:14 -08:00
FreeScout
8971b0fa5a v1.8.60 2023-02-14 01:02:21 -08:00
FreeScout
4f338b4c5f Fix passing null to strtr() in Helper::textPreview() - closes #2720 2023-02-14 00:31:44 -08:00
FreeScout
e6ae2e6f43 Add vendor files to dist 2023-02-13 23:02:02 -08:00
FreeScout
69bb79ebe8 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-02-13 22:59:48 -08:00
FreeScout
8fe5ca71cc v1.8.59 2023-02-13 22:59:09 -08:00
FreeScout
2fa094c88b Add theme-color meta filter #2719 from avenjamin/master
Add theme-color meta filter @filter
2023-02-14 08:24:31 +03:00
Avenjamin
84ab443080 Add theme-color <meta> tag @filter (typo-fixed) 2023-02-14 12:19:21 +11:00
Avenjamin
969e27a14b Add theme-color meta tag @filter
Allow overriding of the theme-color <meta> tag
2023-02-14 12:17:29 +11:00
FreeScout
6b914bae88 Fix Newer conversation button - closes #2716 2023-02-13 05:51:02 -08:00
FreeScout
562235ed8e Make validation of SMTP SSL/TLS certificate optional - closes #2714 2023-02-12 23:59:16 -08:00
FreeScout
c109a0abd6 Replace new lines with spaces in conversation previews - closes #2711 2023-02-12 00:14:53 -08:00
FreeScout
3786da3544 Set timeouts for curl and GuzzleHttp requests 2023-02-10 23:53:12 -08:00
FreeScout
a537c43c80 Do not detect image src as a customer when fetching email forwarded by support agent - closes #2672 2023-02-10 21:06:19 -08:00
FreeScout
961cadb566 Add vendor files to dist 2023-02-09 22:28:11 -08:00
FreeScout
c65f1b9295 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-02-09 22:26:03 -08:00
FreeScout
c06826bea8 v1.8.58 2023-02-09 22:25:34 -08:00
FreeScout
ea0ae16271 Save attachments when saving outgoing emails into IMAP Sent folder - closes #2699 2023-02-09 04:02:50 -08:00
FreeScout
6260c7ab82 Update Readme 2023-02-09 02:22:34 -08:00
FreeScout
2afbb0b9e2 Send alerts to all admins instead of just the first one - closes #2686 2023-02-07 22:04:06 -08:00
FreeScout
e17db42460 Add attachment paperclip to the conversation when merging - closes #2681 2023-02-06 22:35:07 -08:00
FreeScout
ef28be6ddc Make link popover disappear when the editor looses focus - closes #2678 2023-02-06 02:26:53 -08:00
FreeScout
9f57e7afa8 Merge branch 'master' of github.com:freescout-helpdesk/freescout 2023-02-06 02:26:01 -08:00
FreeScout
6a18af92d4 Fix Typos #2677 from avenjamin/master
Fix Typos
2023-02-06 13:08:23 +03:00
Avenjamin
bfc358e1a2 Fix Typos
occured => occurred
non-npublished => non-published
DEactivated => Deactivated
2023-02-06 19:45:38 +11:00
FreeScout
affe1f0fb4 Update Polish translation 2023-02-04 21:39:50 -08:00
FreeScout
7bdf758797 pt_PT language in Carbon - closes #2670 2023-02-04 21:35:06 -08:00
FreeScout
6102bda402 Add vendor files to dist 2023-02-03 00:02:49 -08:00
FreeScout
a3bbc66718 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-02-03 00:01:05 -08:00
FreeScout
3aa687ee40 v1.8.57 2023-02-03 00:00:28 -08:00
FreeScout
e2c92b03b6 Fix populating the subject when creating a New Conversation from existing message - closes #2662 2023-02-02 23:34:46 -08:00
FreeScout
27deddef1e Make cards wider - #2663 2023-02-02 05:18:48 -08:00
FreeScout
022d7e3c4a Fix making conversation Unassigned when deleting a user - closes #2660 2023-02-01 22:43:43 -08:00
FreeScout
5004da8b3c Fix some bugs when sending emails separatately to recipients - closes #2661 2023-02-01 22:19:43 -08:00
FreeScout
1b31d796d6 Fix fetching emails to multiple mailboxes at once via Cc - closes #2659 2023-02-01 09:51:33 -08:00
FreeScout
6fa54dcfa3 Remove jobs.send_notification_to_users.users hook 2023-02-01 09:29:33 -08:00
FreeScout
5fa9412f9b Sort translations when publishing 2023-02-01 05:59:25 -08:00
FreeScout
9ce7a6fcc8 Decrease Starred folder counter when discarding starred draft 2023-02-01 04:09:39 -08:00
FreeScout
71134c2627 Fix Error occured error when saving draft of a phone conversation without a customer 2023-02-01 03:40:47 -08:00
FreeScout
709c65563b Add vendor files to dist 2023-02-01 00:26:07 -08:00
FreeScout
f5f88f01a7 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-02-01 00:23:40 -08:00
FreeScout
0ec060590e v1.8.56 2023-02-01 00:23:19 -08:00
FreeScout
74aa88fd83 Import emails sent to multiple mailboxes in Bcc - closes #2652 2023-01-31 23:59:13 -08:00
FreeScout
ce6764bf26 Check conversation type when processing reply to the phone conversation 2023-01-31 22:01:39 -08:00
FreeScout
c73878f849 Add extra options to Fetch Mail Schedule - closes #2489 2023-01-31 03:46:09 -08:00
FreeScout
97aa0b7b62 jobs.send_notification_to_users.users hook 2023-01-31 01:04:26 -08:00
FreeScout
a81aa48e0e Fix deprecation errors in Illuminate/Config/Repository.php - closes #2636 2023-01-30 23:08:55 -08:00
FreeScout
d416cf05c2 Set timeouts for email sending jobs to avoid queue:work getting stuck 2023-01-30 10:31:40 -08:00
FreeScout
8be46e6ab0 Do not add multiple App\\Jobs\\RestartQueueWorker jobs when restarting queue:work 2023-01-30 03:45:36 -08:00
FreeScout
fe03177a80 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-01-30 00:44:13 -08:00
FreeScout
e9c191eb7c v1.8.55 2023-01-30 00:43:49 -08:00
FreeScout
443d0464d1 Fetch original message in the Show Original window only for customer messages 2023-01-30 00:19:08 -08:00
FreeScout
5f817043ef Fix sending reply to the customer when replying to the phone conversation - closes #2627, #2637 2023-01-30 00:07:54 -08:00
FreeScout
d465237218 Fix /img/loading.gif not found - closes #2644 2023-01-29 23:11:27 -08:00
FreeScout
2a389c5034 Change extension if uploaded PDF file contents JavaScript 2023-01-29 23:00:11 -08:00
FreeScout
e183baad7f Fix original message not being fetched in Show Original window - closes #2638 2023-01-29 21:58:04 -08:00
FreeScout
bbefab738a Fix Undefined variable $result error on fetching original message - closes #2639 2023-01-29 21:25:50 -08:00
FreeScout
93e48db1d6 Remove script tags from uploaded SVG images 2023-01-26 23:30:25 -08:00
FreeScout
23d45109e2 Escape modal titles 2023-01-26 22:30:20 -08:00
FreeScout
b0ffdfb268 Add vendor files to dist 2023-01-26 04:31:52 -08:00
FreeScout
1a75b85e9a Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-01-26 04:29:46 -08:00
FreeScout
6342169f5c v1.8.54 2023-01-26 04:28:51 -08:00
FreeScout
7f0bb19134 Fix determining previous Message-ID - closes #2086 2023-01-26 04:27:08 -08:00
FreeScout
c729873e5e Fix webklex/php-imap issue with email body contained in the attachment called underfined - #1972 2023-01-26 04:22:17 -08:00
FreeScout
82180fb512 Allow to switch to the new webklex library for fetching emails via APP_NEW_FETCHING_LIBRARY env variable 2023-01-26 03:14:19 -08:00
FreeScout
f241cdbd15 Fix img proportions on purifying HTML 2023-01-26 00:46:12 -08:00
FreeScout
5ca4f39da4 Add vendor files to dist 2023-01-25 21:52:59 -08:00
FreeScout
475561cbf5 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-01-25 21:51:46 -08:00
FreeScout
71437f2778 Update composer.json 2023-01-25 21:51:33 -08:00
FreeScout
1a52f6e7c0 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-01-25 21:29:27 -08:00
FreeScout
cd8eb51131 v1.8.53 2023-01-25 21:29:00 -08:00
FreeScout
399ede2977 Update German translation 2023-01-25 21:26:59 -08:00
FreeScout
5edb1e6e9a Make HTMLPurifier PHP 8.2 compatible - closes #2623 2023-01-25 03:13:47 -08:00
FreeScout
98a391165e send_reply.before_save_forwarded_thread hook 2023-01-25 00:17:40 -08:00
FreeScout
357a9b6bfa Remove collapse and hidden classes from tables when purifying HTML - closes #2610 2023-01-25 00:06:52 -08:00
FreeScout
9c727926bc Remove collapse and hidden classes from tables when purifying HTML - closes #2610 2023-01-24 23:46:30 -08:00
FreeScout
e3ad881f5d View only allowed attachment mime types 2023-01-24 23:05:37 -08:00
FreeScout
5a26c18e84 Show corresponding message when Show Original window can not load email via IMAP - closes #2608 2023-01-24 22:26:39 -08:00
FreeScout
aea2d9794a Improve Show Original window performance - closes #2600 2023-01-24 22:06:42 -08:00
FreeScout
a3d5018c25 Do now show zero conversations in the title - closes #2613 2023-01-24 03:11:39 -08:00
FreeScout
170c4de21f Replace background: with background-color: when purifying HTML - closes #2560 2023-01-23 22:40:38 -08:00
FreeScout
78c271834b Update German translation 2023-01-23 00:54:08 -08:00
FreeScout
25f418f345 Do not distort image proportions when image is scaled using HTML.MaxImgLength - closes #2599 2023-01-23 00:39:40 -08:00
FreeScout
3c934d03af Make Swift/EmbeddedFile PHP 8.2 compatible 2023-01-22 22:51:28 -08:00
FreeScout
0d56f442ae Remove creating vendor/natxet/cssmin/src folder from composer.json 2023-01-19 21:21:44 -08:00
FreeScout
706e3fea74 Add vendor files to dist 2023-01-19 05:32:05 -08:00
FreeScout
b504bf0ecd Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-01-19 05:28:54 -08:00
FreeScout
bf8ac489f0 v1.8.52 2023-01-19 05:28:02 -08:00
FreeScout
a336c5b84f Set permissions set to photo folders to 0775 2023-01-19 05:25:18 -08:00
FreeScout
76cb0d7c60 Fix missing vendor/natxet/cssmin/src folder in docker - #2583 2023-01-19 05:23:57 -08:00
FreeScout
04cd790fb1 Make RachidLaasri LaravelInstaller PHP 8.2 compatible 2023-01-19 05:17:40 -08:00
FreeScout
78b52bfb81 Fix installation after adding RestartQueueWorker::dispatch() to CleaCache 2023-01-19 05:08:02 -08:00
FreeScout
c33b770b2e Keep spaces after tags - closes #2577 2023-01-19 03:13:58 -08:00
FreeScout
b1187cbe4c Add .sidebar-no-email class if mailbox has no email 2023-01-19 01:58:32 -08:00
FreeScout
acd7e4956d Update number of active conversation in the title when new conversation arrives - closes #2576 2023-01-19 01:14:33 -08:00
FreeScout
8c28e9a2e9 Make sure Str::replaceFirst() always returns a string 2023-01-18 23:38:21 -08:00
FreeScout
3f6a9c4981 Improve temp file name generation and cleaning 2023-01-18 22:57:06 -08:00
FreeScout
898c972759 Update path to LazyOpenStream.php in composer.json 2023-01-18 22:14:06 -08:00
FreeScout
6ab04ba685 Add vendor files to dist 2023-01-18 03:37:09 -08:00
FreeScout
57f768e8ef Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-01-18 03:34:13 -08:00
FreeScout
9686f12567 v1.8.51 2023-01-18 03:33:51 -08:00
FreeScout
9f0dd50969 Fix an issue when background job (queue:work) not executing 2023-01-18 03:15:17 -08:00
FreeScout
5a1c1bc648 Add small delay between connections when fetching emails to avoid mail server connections refusals - closes #2563 2023-01-18 00:06:06 -08:00
FreeScout
13889a2a84 Add slashes to models in migrations 2023-01-17 23:49:37 -08:00
FreeScout
52d02a9f62 Fix an issue when background job (queue:work) was not executed 2023-01-17 23:21:10 -08:00
FreeScout
0ca22719d8 Make sure fetching is executed even if previous command is killed or throuws an error and does not remove the mutex 2023-01-17 22:49:04 -08:00
FreeScout
3d83d7757f Automatically clean /tmp folder 2023-01-17 22:06:35 -08:00
FreeScout
ef3d8224de Update readme 2023-01-17 03:03:45 -08:00
FreeScout
b4933c20b2 Fix saving recipient when restoring draft of a forwarded message 2023-01-17 00:12:15 -08:00
FreeScout
5fb6eb9457 Fix duplicating CC and BCC when restorign a draft of a conversation with CC - clsoes #2555 2023-01-16 23:55:26 -08:00
FreeScout
d53b6bf2fc Fix restoring draf after Undo reply 2023-01-16 23:30:31 -08:00
FreeScout
ee332c38f4 Remove line from translations 2023-01-16 22:29:59 -08:00
FreeScout
07223f4c1b Update French translation 2023-01-16 22:25:01 -08:00
FreeScout
13c21868fb Fix CSS minification on PHP 8.2 2023-01-16 06:07:57 -08:00
FreeScout
27c31ad650 Update PL translation 2023-01-13 23:06:24 -08:00
FreeScout
6fba4d334b Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-01-13 21:47:46 -08:00
FreeScout
c42b9fa0cb 1.8.50 2023-01-13 21:47:27 -08:00
FreeScout
6f99b2e76b Fix 2022_12_18_010101_set_user_type_field migration causing installation issues 2023-01-13 21:46:02 -08:00
FreeScout
4ef9574b56 Show Deleted folder right afer Closed folder - closes #2530 2023-01-13 21:29:53 -08:00
FreeScout
36cca92666 freescout:check-requriements command 2023-01-13 05:18:47 -08:00
FreeScout
6a3443c600 freescout:check-requriements command 2023-01-13 03:46:55 -08:00
FreeScout
542646e71a Allow to add custom headers to outgoing emails via APP_CUSTOM_MAIL_HEADERS env variable - closes #2546 2023-01-12 23:49:36 -08:00
FreeScout
a830e0440b Fix sending emails via sendmail on PHP 8.2 2023-01-12 23:36:59 -08:00
FreeScout
f1bce10ef9 Fix sending emails via mail() function on PHP 8.2 2023-01-12 23:32:08 -08:00
FreeScout
6b25822135 Hooks for general settings 2023-01-12 23:04:49 -08:00
FreeScout
279f054a2c Remove recommendation to use mail() function to send system emails from Mail Settings 2023-01-12 03:52:25 -08:00
FreeScout
3163a4103e Set proper conversation type when switching from phone to email convesation - closes #2545 2023-01-12 01:13:15 -08:00
FreeScout
609400b98f Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-01-11 23:07:44 -08:00
FreeScout
5668a300e1 1.8.49 2023-01-11 23:07:08 -08:00
FreeScout
809f9cfe68 Fix an error when a user is trying to access settings of non-accessible mailbox 2023-01-11 22:56:51 -08:00
FreeScout
af6e3112d2 Do not require to enter password if user has automatically generated password - closes #2538 2023-01-11 08:42:15 -08:00
FreeScout
da3f934d35 If --no-interaction flag is passed to schedule:run the script will not run queue:work daemon - closes #2507 2023-01-10 04:37:42 -08:00
FreeScout
4f52aed496 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-01-10 00:36:52 -08:00
FreeScout
7124aa2197 v1.8.48 2023-01-10 00:36:06 -08:00
FreeScout
bc7b8f40b5 conversations.empty_folder hook 2023-01-10 00:18:28 -08:00
FreeScout
4c804ca361 Make thread_id parameter optional in Attachment->duplicate() 2023-01-09 23:56:23 -08:00
FreeScout
bd08536e54 Run RestartQueueWorker when clearing cache in order to stop queue:work to restart it when cache is cleared 2023-01-09 10:25:06 -08:00
FreeScout
eafc3d5750 Allow to add attachments to editor not only in the reply form 2023-01-09 09:18:31 -08:00
FreeScout
f9958ef526 Add comments to Kernel.php 2023-01-09 09:16:45 -08:00
FreeScout
8a4649ceae Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-01-07 22:00:27 -08:00
FreeScout
132a2e28d9 v1.8.47 2023-01-07 21:59:39 -08:00
FreeScout
5b1385849f Allow to convert phone conversations into email conversations by adding email to the customer and replying 2023-01-07 21:36:46 -08:00
FreeScout
db6248ad37 Fix retrieving email from forwarded email - closes #2517 2023-01-07 02:06:31 -08:00
FreeScout
112ea66d7f Show strikethrough text in conversations - closes #2504 2023-01-05 03:11:11 -08:00
FreeScout
ab3f7a0c1e Update Czech translation 2023-01-05 01:04:08 -08:00
FreeScout
5bcfd0c726 Fix placeholder in the alert email - closes #2508 2023-01-04 02:27:22 -08:00
FreeScout
328afcbb18 Fix sending emails with attachments on PHP 8.2 - closes #2505 2023-01-04 02:18:02 -08:00
FreeScout
ec548bdaab Do not check entire HTML when processing forwarded emails - closes #2503 2023-01-04 00:57:48 -08:00
FreeScout
15f1534197 Add vendor files to dist 2023-01-02 22:47:05 -08:00
FreeScout
a04537d4ef Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2023-01-02 22:45:51 -08:00
FreeScout
f90e48bd5d v1.8.46 2023-01-02 22:45:29 -08:00
FreeScout
31760e281b Update Czech translation 2023-01-02 22:01:20 -08:00
FreeScout
e41e098994 Fix passing null to parameter error in SwiftException.php - closes #2499 2023-01-02 21:59:37 -08:00
FreeScout
88b514dbff Improve creating tickets from forwarded emails - closes #2497 2022-12-31 05:33:06 -08:00
FreeScout
8c71c7900d Add 307 response code to Helper::downloadRemoteFileAsTmp() 2022-12-29 02:04:16 -08:00
FreeScout
54c1786435 Add exclude_hidden parameter to Mailbox::usersAssignable() 2022-12-28 10:34:00 -08:00
FreeScout
121953dc2e Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-12-25 22:22:52 -08:00
FreeScout
6b0adf31be 1.8.45 2022-12-25 22:22:16 -08:00
FreeScout
6f32bb0637 Customer::byPhone() method 2022-12-25 22:18:59 -08:00
FreeScout
f18d4f0048 Improve searching customers by phone numbers 2022-12-25 21:50:10 -08:00
FreeScout
f5aba6a012 Update customer observer 2022-12-25 09:16:24 -08:00
FreeScout
2d507d498d Additionally store phone number in numeric form for customers 2022-12-25 06:33:24 -08:00
FreeScout
e90e6e4e71 Allow to disable autoreply using ar_off meta key 2022-12-24 21:58:54 -08:00
FreeScout
059b0f44dd Update link in Spread the Word footer 2022-12-23 21:54:43 -08:00
FreeScout
0df3a7ad55 Add X-Auto-Response-Suppress header to user notification emails to avoid Exchange out-of-office autoreplys - closes #2488 2022-12-23 21:18:42 -08:00
FreeScout
554a1093fb DB_PGSQL_SSLMODE env variablle - closes #2485 2022-12-23 21:07:12 -08:00
FreeScout
4e027c715f Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-12-21 03:15:07 -08:00
FreeScout
3c9e2d9b3d v1.8.44 2022-12-21 03:14:47 -08:00
FreeScout
27e1aaa57f Fix set_user_type_field migration - closes #2484 2022-12-21 02:15:18 -08:00
FreeScout
29d6502c0c Fetch forwarded by support agent customer emails as tickets from customers - closes #290 2022-12-20 22:18:30 -08:00
FreeScout
28241c8e55 Pass mailbox to conversations_table from search template - closes #769 2022-12-20 04:38:54 -08:00
FreeScout
8ab1bec608 Pass mailbox to conversations_table from search template - closes #769 2022-12-20 04:27:41 -08:00
FreeScout
216ca83c22 Add vendor files to dist 2022-12-20 03:22:40 -08:00
FreeScout
256fefac59 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-12-20 03:20:15 -08:00
FreeScout
90f1349bd6 v1.8.43 2022-12-20 03:19:49 -08:00
FreeScout
e4ba051b49 Make Swiftmailer PHP 8.2 compatible 2022-12-20 00:53:41 -08:00
FreeScout
f61c482775 Fix Callables of the form Swift_SmtpTransport, Swift_Transport_EsmtpTransport::__construct are deprecated on PHP 8.2 2022-12-20 00:49:41 -08:00
FreeScout
a0384118db Button to delete all conversations from Spam - closes #1756 2022-12-20 00:06:19 -08:00
FreeScout
64b1c2bbb0 Save text after editing the code in the editor without switching back to the editor - #1705 2022-12-19 23:26:37 -08:00
FreeScout
a0b53946a0 Fetch forwarded by support agent customer emails as tickets from customers - closes #290 2022-12-19 23:03:00 -08:00
FreeScout
b39cbe7402 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-12-19 02:31:16 -08:00
FreeScout
2c3f195b96 v1.8.42 2022-12-19 02:30:56 -08:00
FreeScout
d258d04042 Fix sendin New Conversation 2022-12-19 02:30:02 -08:00
FreeScout
aa7ff00380 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-12-18 21:52:57 -08:00
FreeScout
1d6502112d 1.8.41 2022-12-18 21:49:54 -08:00
FreeScout
901fac8f89 Update PL translation 2022-12-18 21:47:48 -08:00
FreeScout
7afc9a0674 Fix error when conversation customer is changed and deleted after that - closes #2467 2022-12-18 21:46:29 -08:00
FreeScout
151f42c15d User type field 2022-12-17 22:28:44 -08:00
FreeScout
d6ef9ddc37 Add meta column to conversations table 2022-12-17 22:20:43 -08:00
FreeScout
1e39e35251 Update Readme 2022-12-16 09:36:20 -08:00
FreeScout
3181cba783 Add isset() check to SettingsController 2022-12-16 05:40:11 -08:00
FreeScout
ac19634172 In SendReplyToCustomer listener do not include messages added after the event has been fired - closes #2462 2022-12-15 22:32:36 -08:00
FreeScout
5271ba6fbf Update general_help_request.md 2022-12-15 00:43:40 -08:00
FreeScout
58e389a095 Update general_help_request.md 2022-12-14 23:47:09 -08:00
FreeScout
4894f115e0 Add vendor files to dist 2022-12-14 23:19:43 -08:00
FreeScout
f3f77c9ab5 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-12-14 23:18:32 -08:00
FreeScout
b9ebb84c0b v1.8.40 2022-12-14 23:18:15 -08:00
FreeScout
763337e7cd Prevent note remembering after sending a note and clicking Back in the browser - closes #1434 2022-12-14 22:34:54 -08:00
FreeScout
527e8241a3 Clone attachments when cloning conversations - closes #2465 2022-12-14 22:03:45 -08:00
FreeScout
154966ca8d Fix preg_replace_callback() issue on PHP 8.1 in Helper::linkify() - closes #2464 2022-12-14 21:49:10 -08:00
FreeScout
4b5d0b8232 Replace mb_convert_encoding HTML-ENTITIES in FetchEmails - closes #2461 2022-12-13 23:37:58 -08:00
FreeScout
2b87f01f90 Make javoscript/laravel-macroable-models compatible with PHP 8.2 2022-12-13 22:40:57 -08:00
FreeScout
db78c777ac Replace mb_convert_encoding HTML-ENTITIES in FetchEmails - closes #2461 2022-12-13 22:28:28 -08:00
FreeScout
85cc9924b5 Check if attachment exists on disk when sending a reply to a customer to avoid sending issues 2022-12-13 04:36:53 -08:00
FreeScout
ce33f8fbc7 Update French translation 2022-12-13 02:19:40 -08:00
FreeScout
ad63faf6ff Allow to pass an extra parameter to User::nonDeleted() function 2022-12-13 02:17:11 -08:00
FreeScout
8b5ca811dc Allow to pass an extra parameter to User::nonDeleted() function 2022-12-13 00:32:13 -08:00
FreeScout
47d988aa97 Add vendor files to dist 2022-12-12 23:22:21 -08:00
FreeScout
9ee76235fa Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-12-12 23:20:18 -08:00
FreeScout
492788cca1 v1.8.39 2022-12-12 23:19:56 -08:00
FreeScout
f85c5f3969 Add data-mime attribute to attachments li element 2022-12-12 23:18:29 -08:00
FreeScout
2fbed04583 Attachment size to upper case 2022-12-12 00:45:55 -08:00
FreeScout
e4a7cd6ffd Fix site.webmanifest 401 error if FreeScout is closed with Basic authentication 2022-12-11 22:43:23 -08:00
FreeScout
097d1b7ea4 Add .attachment-link class to attachments 2022-12-11 22:42:33 -08:00
FreeScout
5e38dc9abd Fix an issue when customer was using FreeScout and reply sometimes could be connected to the wrong conversation - closes #2375 2022-12-11 05:30:13 -08:00
FreeScout
47e716239e PHP 8.2 compatibility 2022-12-10 22:20:21 -08:00
FreeScout
3a33cc03a0 .padding-right-0 class 2022-12-10 22:18:04 -08:00
FreeScout
d34f98f8db Show To dropdown when replying to a conversation with changed customer 2022-12-10 04:27:24 -08:00
FreeScout
632f3b9d4e Replace soft hyphens in attachments files - closes #2448 2022-12-10 00:42:42 -08:00
FreeScout
23c8107463 PHP 8.2 compatibility 2022-12-09 23:58:10 -08:00
FreeScout
2f79060158 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-12-09 21:52:14 -08:00
FreeScout
e107c41ce9 v1.8.38 2022-12-09 21:51:53 -08:00
FreeScout
ddab271fce Rename mailbox.before_name hooks 2022-12-09 03:31:27 -08:00
FreeScout
13428f1526 Allow to show errors in the mailbox settings form via hooks 2022-12-09 03:30:25 -08:00
FreeScout
d02ce264ea Rename mailbox.before_name hooks 2022-12-08 22:55:30 -08:00
FreeScout
367b79687e Created Followers observer 2022-12-07 22:57:12 -08:00
FreeScout
0201523656 Jump to the conversation when searching by conversation number 2022-12-07 22:34:54 -08:00
FreeScout
96b0c17bc8 Rename MYSQL_ATTR_SSL_CA attr into DB_MYSQL_ATTR_SSL_CA 2022-12-07 21:22:37 -08:00
FreeScout
38fcc96c23 Add config to connect mysql/mariadb over TLS #2446 from mzch/master
Add config to connect mysql/mariadb over TLS
2022-12-07 17:32:26 +03:00
Koichi MATSUMOTO
f10b8e1526 Add config to connect mysql/mariadb over TLS
Add config to connect mysql/mariadb over TLS.
2022-12-07 23:26:53 +09:00
FreeScout
1d57be2fa0 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-12-07 06:04:53 -08:00
FreeScout
353774b83f v1.8.37 2022-12-07 06:04:13 -08:00
FreeScout
76e29a0b3d Add auto-reply header to Mail::isAutoResponder() - closes #2445 2022-12-07 06:02:39 -08:00
FreeScout
5e1537a217 Add auto-reply header to Mail::isAutoResponder() - closes #2445 2022-12-07 05:59:27 -08:00
FreeScout
368c6fdd64 Use case insensitive search for customers in PostgreSQL 2022-12-07 05:36:31 -08:00
FreeScout
b460bf2c14 Fix searching by customer name 2022-12-06 22:59:19 -08:00
FreeScout
d2c2b1837c Allow to sort search results by columns 2022-12-06 22:51:16 -08:00
FreeScout
66d45bf7f6 Entities hooks 2022-12-06 22:39:42 -08:00
FreeScout
ae861da89d Search hooks 2022-12-06 22:39:27 -08:00
FreeScout
c38438c317 Attachment observer 2022-12-06 22:38:24 -08:00
FreeScout
c8737b859a Make search case insensitive on PostgreSQL 2022-12-02 20:54:08 -08:00
FreeScout
3448df8087 Fix search when searching in a specific mailbox 2022-12-02 05:54:54 -08:00
FreeScout
76b32e28f5 Change ilike to like in the search 2022-12-02 05:41:56 -08:00
FreeScout
34883489c9 Search improvements #2434 from calebbaker194/master
Search improvements
2022-12-02 16:36:54 +03:00
FreeScout
c32c06819d New customer and thread functions 2022-12-01 22:32:59 -08:00
FreeScout
eb17935278 Update readme 2022-12-01 22:32:46 -08:00
Caleb
3914c87c71 Search improvments
This will allow searching by customer first and last name to find threads or customers. It also ignores cases on more things.
2022-12-01 08:21:25 -06:00
FreeScout
cc3f44d5e7 Allow users delete their notes 2022-11-30 05:51:40 -08:00
FreeScout
9238f97b87 Fix Call to a member function getFirstName() on null - closes #2432 2022-11-30 05:37:25 -08:00
FreeScout
8fc5372c91 Improve Clone Conversation implementation 2022-11-30 03:29:13 -08:00
FreeScout
00cb9bde18 Add possibility to clone a conversation #2430 from kovoks/master
Add possibility to clone a conversation.
2022-11-30 11:33:35 +03:00
FreeScout
29408872ee Remove sass installer files 2022-11-29 23:04:51 -08:00
FreeScout
fc4cf0e482 Update readme 2022-11-29 22:56:45 -08:00
Tom Albers
6e60f4b0dd Add possibility to clone a conversation. 2022-11-29 20:30:27 +01:00
FreeScout
440177eaa5 Add vendor files to dist 2022-11-27 05:51:52 -08:00
FreeScout
ce09e3e287 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-11-27 05:48:58 -08:00
FreeScout
40b61379fa v1.8.36 2022-11-27 05:48:00 -08:00
FreeScout
69329ce86d getMainPhoneValue() and getMainWebsite() method for Customer 2022-11-27 05:46:30 -08:00
FreeScout
eff45a28a4 Remove limitation on database connection parameters length on installation - closes #2418 2022-11-27 05:46:01 -08:00
FreeScout
d721a49ab4 Update javascript hook 2022-11-27 00:34:46 -08:00
FreeScout
96d31fc723 Do no show conversations from all mailboxes when the users has no mailboxes assigned - closes #2409 2022-11-25 22:06:56 -08:00
FreeScout
270e0b5da8 Fix PHP 81 error in FormatsMessages.php 2022-11-25 22:01:51 -08:00
FreeScout
8f23ba8872 Fix PHP81 error on saving mailbox outgoing settings - closes #2401 2022-11-25 21:53:30 -08:00
FreeScout
260671922b Change undefined attachment name to RFC822.eml for Webklex php-imap - closes #2385 2022-11-22 00:54:32 -08:00
FreeScout
4c3fe87a5f Fix PostgreSQL migration on PHP8.1 - closes #2401 2022-11-22 00:52:14 -08:00
FreeScout
bb912ca149 Fix Passing null to parameter in Translate page - closes #2398 2022-11-18 21:09:41 -08:00
FreeScout
98e0ae0f05 Do not log empty email body notice 2022-11-17 00:35:25 -08:00
FreeScout
7813210078 Add vendor files to dist 2022-11-16 23:30:17 -08:00
FreeScout
c51ee20010 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-11-16 23:27:22 -08:00
FreeScout
d11c09b3de v1.8.35 2022-11-16 23:26:41 -08:00
FreeScout
4a723d4abb Update webklex php-imap lirary to the latest version to fix empty body on MS365 - closes #1972 2022-11-16 23:25:07 -08:00
FreeScout
eb42948fa0 Save summernote cursor positino on reply area blur 2022-11-16 22:25:08 -08:00
FreeScout
481970fd65 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-11-16 08:56:07 -08:00
FreeScout
a9b84ac58d v1.8.34 2022-11-16 08:55:35 -08:00
FreeScout
4be8d83314 Fix To, Cc, Bcc fields - closes #2388 2022-11-16 08:54:39 -08:00
FreeScout
a7489e8679 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-11-16 04:04:38 -08:00
FreeScout
593439faf5 v1.8.33 2022-11-16 04:03:59 -08:00
FreeScout
6500b9e510 Fix Passing null to parameter to nl2br in FetchEmail - closes #2387 2022-11-16 03:42:08 -08:00
FreeScout
a9f055dc21 Do not crop mailbox dropdown menu - closes #2252 2022-11-15 02:23:22 -08:00
FreeScout
0d78f5db73 Save summernote cursor positino on reply area blur 2022-11-13 22:07:04 -08:00
FreeScout
429575f5f8 Fix Passing null to parameter in Helper::linkify() 2022-11-13 21:59:42 -08:00
FreeScout
36c6d3418e Do not allow to search conversations and customers in mailboxes to which the user have no access - closes #2370 2022-11-12 23:47:02 -08:00
FreeScout
b3ba614d9f Update Polish translation 2022-11-09 23:07:54 -08:00
FreeScout
587e836031 Remove new lines from envelope when saving outgoing email - closes #2376 2022-11-09 23:07:03 -08:00
FreeScout
747a13ac8c Show base64 data images in conversations 2022-11-09 22:54:35 -08:00
FreeScout
2ef4bc40a5 Show base64 data images in conversations - closes #2355 2022-11-09 22:53:08 -08:00
FreeScout
9570399ab9 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-11-09 02:18:31 -08:00
FreeScout
ee68d9b149 v1.8.32 2022-11-09 02:17:35 -08:00
FreeScout
645ac3187a Allow to prefill the subjection via GET parameter when creating a conversation - closes #2360 2022-11-09 00:43:54 -08:00
FreeScout
765e9a4e08 Do not allow to search conversations and customers in mailboxes to which the user have no access - closes #2370 2022-11-09 00:34:34 -08:00
FreeScout
3c33a6900e Search for tickets assigned to Anyone #2368 from Xuf00/unassigned-search-patch
Search for tickets assigned to Anyone - fixes #2367
2022-11-09 11:01:38 +03:00
FreeScout
0424a3869f French translation corrections #2366 from e-rwan/master
French translation corrections
2022-11-09 11:00:39 +03:00
Jack Johnson
a17716f2e4 Add support for searching for tickets that are assigned to the "Anybody" user 2022-11-06 20:21:41 +00:00
e-rwan
ac39d3385b Update fr.json 2022-11-04 16:31:30 +01:00
e-rwan
520a386717 Update fr.json 2022-11-04 16:29:44 +01:00
e-rwan
acda86eedf Update fr.json 2022-11-04 16:26:47 +01:00
e-rwan
c865af543b Update fr.json 2022-11-04 13:12:43 +01:00
FreeScout
1739602bdf Conversation table hooks 2022-10-26 23:26:20 -07:00
FreeScout
e2a5e5c021 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-10-25 00:27:33 -07:00
FreeScout
201d5a6f86 v1.8.31 2022-10-25 00:26:49 -07:00
FreeScout
13bca95e3c View Details button for failed jobs - closes #2044 2022-10-25 00:23:15 -07:00
FreeScout
54d4ceadd1 Improve email validation - closes #2338 2022-10-24 22:50:21 -07:00
FreeScout
05be407f37 Improve email validation - closes #2338 2022-10-24 04:35:23 -07:00
FreeScout
8b23ece688 Fix passing null parameter to Mail::hasVars() - closes #2335 2022-10-23 23:06:26 -07:00
FreeScout
460bc17e36 Fix Attempt to assign property user_updated_at on null 2022-10-23 07:03:02 -07:00
FreeScout
70fdbcdb66 Do not limit line length in getBodyAsText() 2022-10-15 01:31:55 -07:00
FreeScout
7b8a202131 Hide from Assign list users who are hidden in mailbox settings - closes #2327 2022-10-15 00:34:45 -07:00
FreeScout
b38a7f49fd Set system timezone for new users upon creation - closes #2312 2022-10-15 00:28:53 -07:00
FreeScout
ae384e035b Update general_help_request.md 2022-10-15 00:20:34 -07:00
FreeScout
25c43247cf Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-10-13 04:27:46 -07:00
FreeScout
5e6d6f0d2c v1.8.30 2022-10-13 04:26:05 -07:00
FreeScout
883c0696e7 editorProcessInsertVar() function 2022-10-13 04:22:34 -07:00
FreeScout
2d7fc4deb5 Allow to pass modal options to triggerModal() function 2022-10-13 01:29:33 -07:00
FreeScout
e1008951b5 Add Greek reply separator #2319 from fulldecent/patch-11
Add Greek reply separator, fixes #882
2022-10-12 08:27:56 +03:00
William Entriken
659382d41b Add localized separator, fixes #882 2022-10-11 14:33:53 -04:00
FreeScout
41a0e0f205 Preserve table classes when filtering email content - closes #2308 2022-10-07 06:41:24 -07:00
FreeScout
098b830a08 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-09-28 22:17:00 -07:00
FreeScout
ce05a79574 v1.8.29 2022-09-28 22:16:19 -07:00
FreeScout
d41000fb8d Improve including email address of original sender when forwarding conversation - closes #2285 2022-09-28 10:56:33 -07:00
FreeScout
e93bf6a121 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-09-27 07:06:39 -07:00
FreeScout
21427aa533 v1.8.28 2022-09-27 07:06:05 -07:00
FreeScout
ba397bbff9 Allow to forward chat conversations - closes #2280 2022-09-27 07:04:31 -07:00
FreeScout
49331ccf19 Rename /tools/pre_upgrade.sh into /tools/pre_update.sh and add post_update.sh - closes #2282 2022-09-27 06:58:11 -07:00
FreeScout
80498f7e66 Update eneral_help_request.md 2022-09-26 23:38:18 -07:00
FreeScout
c5128990bd Fix grammar application better #2278 from fulldecent/fix-grammar-application-better
Fix grammar application better
2022-09-27 09:19:48 +03:00
William Entriken
5e700de7fd Grammar fix 2022-09-26 15:31:12 -03:00
FreeScout
380ea308b3 Include emailaddress of original sender when forwarding conversation - closes #1236 2022-09-25 00:01:54 -07:00
FreeScout
0bbdd3eeeb Shorten forward_ meta fields 2022-09-24 23:37:42 -07:00
FreeScout
ff98192d92 notifications_table.td hook 2022-09-24 06:44:00 -07:00
FreeScout
25c832ffae flash_messages.flashes hook 2022-09-24 06:43:43 -07:00
FreeScout
da75d733d5 .subs-cb style for subscriptions table 2022-09-24 06:35:55 -07:00
FreeScout
03fd7c33ce notifications_table.th and notifications_table.td hooks 2022-09-23 02:01:13 -07:00
FreeScout
a0e169e92c conversation.status_changing hook 2022-09-23 01:20:31 -07:00
FreeScout
ae6fa8e7b2 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-09-22 05:21:21 -07:00
FreeScout
bf20ea5647 v1.8.27 2022-09-22 05:19:37 -07:00
FreeScout
6f4dd9b457 user.edit.phone_append hook 2022-09-22 05:18:26 -07:00
FreeScout
03a2e9789d Add Azerbaijani language to the Translate page - closes #2269 2022-09-22 02:40:26 -07:00
FreeScout
b0f8f9ebc3 Improve placing an outgoing email to the IMAP folder - closes #1892 2022-09-22 02:37:00 -07:00
FreeScout
fb47dd5c88 Update Russian translation 2022-09-22 02:07:57 -07:00
FreeScout
36ee900d8a Rename PublicController into OpenController to fix translating - closes #2268 2022-09-22 01:52:52 -07:00
FreeScout
fde6f0d0af Add alt attribute to images in the editor - closes #2265 2022-09-22 00:56:41 -07:00
FreeScout
168c61e4b0 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-09-21 04:18:20 -07:00
FreeScout
cc38f1482c v1.8.26 2022-09-21 04:17:40 -07:00
FreeScout
83392b81a5 url_generator.app_url hook 2022-09-21 00:30:29 -07:00
FreeScout
4617b50bf0 Follow redirects in Helper::downloadRemoteFileAsTmp() 2022-09-19 23:17:27 -07:00
FreeScout
0cc2787d3c Fix setting a conversation Preview when a conversation is created outside of web interface - closes #2254 2022-09-17 06:55:20 -07:00
FreeScout
3796cff48d Allow all users to mute notifications from particular mailboxes 2022-09-17 05:36:42 -07:00
FreeScout
350b09a77c Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-09-17 02:42:27 -07:00
FreeScout
28c3bb7053 v1.8.25 2022-09-17 02:41:44 -07:00
FreeScout
66fa46f937 conversation.state_changed hook 2022-09-17 02:39:41 -07:00
FreeScout
e639827cbe Adjust parseDateToCarbon() helper function 2022-09-16 22:15:19 -07:00
FreeScout
45e992d65b Fix unchecking all default subscriptions - closes #2247 2022-09-16 07:53:39 -07:00
FreeScout
ebcd11aae9 Avoid fatal error when conversation status is zero - closes #2237 2022-09-16 06:43:43 -07:00
FreeScout
b65373b837 APP_PROXY variable - closes #2242 2022-09-16 06:33:47 -07:00
FreeScout
9dbc09cccd Add iconv to the list of required extensions - closes #2236 2022-09-16 06:21:09 -07:00
FreeScout
3ba5b6b727 Remove white color background from #conv-layout- elements 2022-09-14 07:35:02 -07:00
FreeScout
137a714e06 Set fixed white color for .fs-tag 2022-09-14 07:23:23 -07:00
FreeScout
cc8f94fcff Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-09-14 04:19:42 -07:00
FreeScout
4e6fd62f62 v1.8.24 2022-09-14 04:19:00 -07:00
FreeScout
29dfd29937 Fix Passing null to parameter in http-foundation/Response.php - closes #2223 2022-09-14 01:47:26 -07:00
FreeScout
a7cc9b4145 Fix strtolower: Passing null to parameter in swift MimePart.php 2022-09-14 00:45:55 -07:00
FreeScout
c3fc12f773 mail.reapply_mail_config hook 2022-09-14 00:08:28 -07:00
FreeScout
6056b05b0f Adjust conversations navigation colspan 2022-09-12 10:25:51 -07:00
FreeScout
b395320ffd Make conversation history table wider in customer profile #2224 from fulldecent/patch-9
Make conversation history table wider in customer profile, Fix #61
2022-09-12 19:45:56 +03:00
William Entriken
6cae742920 Fix #61 2022-09-12 12:07:14 -04:00
FreeScout
37997583c9 deleteCustomer() method 2022-09-12 00:25:17 -07:00
FreeScout
990f05bc7d body.class hook 2022-09-11 01:01:22 -07:00
FreeScout
e130611866 deleteCookie() funciton 2022-09-11 00:00:35 -07:00
FreeScout
c3fe512dac Fetch emails by bunches in order to allow fetch large amounts of emails - closes #2198 2022-09-09 04:58:26 -07:00
FreeScout
8443713d48 Allowing multiple summernote hints #2202 from FreeScout-shop/master
Allowing multiple summernote hints
2022-09-09 11:44:29 +03:00
FreeScout
b850065299 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-09-09 00:40:31 -07:00
FreeScout
58720634cc v1.8.23 2022-09-09 00:39:43 -07:00
FreeScout
ddd853d5dc Fix searching convesations in the Merge dialog window 2022-09-09 00:35:19 -07:00
FreeScout
09c1bfa20a Fix searching conversations by number 2022-09-09 00:30:48 -07:00
RienNeVaPlus
0cfd2ba284 Initialize summernote.options.hint as [] 2022-09-07 06:08:05 +02:00
FreeScout
a32e4bb3a4 Preserve column sorting when using conversations navigation - closes #2191 2022-09-03 22:13:32 -07:00
FreeScout
56905d3187 conversation.prepend_send_dropdown hook 2022-09-03 22:12:28 -07:00
FreeScout
b7e83c7fd6 Follow a conversation when replying to a conversation assigned to someone else 2022-09-02 23:38:53 -07:00
FreeScout
8b6afcefd5 Show bell icon in the subject of conversations you are following 2022-09-02 23:10:27 -07:00
FreeScout
9e71085823 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-09-02 22:25:19 -07:00
FreeScout
d5ea944357 v1.8.22 2022-09-02 22:24:43 -07:00
FreeScout
483abee1ae user.non_deleted_condition hook 2022-09-02 22:17:03 -07:00
FreeScout
244224ac9b Take into account mailbox Default Assignee option when user replies to the notification by email - #2153 2022-09-02 09:59:07 -07:00
FreeScout
0916597990 Merge pull request #2169 from ilyakonovalenko/master
New action before_prev_convs per issue #2167 + An ability to keep current status and assignee per issue #2180
2022-09-02 17:39:20 +03:00
FreeScout
685b5abe03 Show proper error message when trying to forward a conversation without specifying a To address - closes #2189 2022-09-02 07:09:54 -07:00
FreeScout
1535fc69dc Check fpassthru() function in System Status - closes #597 2022-09-02 06:49:29 -07:00
FreeScout
d7a6260e2c Change lockout timeout to 10 minutes on Too many login attempts - closes #2135 2022-09-02 06:47:07 -07:00
FreeScout
584cf3bcea Fix decoding binary encoded emails - clsoes #2186 2022-09-02 06:36:41 -07:00
FreeScout
2c5be3a77e Check required functions in the installer - closes #2187 2022-09-02 04:47:07 -07:00
Илья Коноваленко
5de121a000 Added an ability to keep current conversation status and assignee when replying via both FreeScout and email 2022-08-30 12:54:52 +05:00
Илья Коноваленко
bd8e80510e Adding new action before_prev_convs per issue #2167 2022-08-20 00:28:46 +05:00
FreeScout
b235016207 Fix locking out - closes #2135 2022-08-18 00:51:40 -07:00
FreeScout
5befe9f1d7 Improve coding standard of x_embed commit 2022-08-17 23:48:47 -07:00
FreeScout
26b50243af Fixes for UI issues for conversation display via x_embed #2156 from vtq221/master
Changes for #2141 - Fixes for UI issues for conversation display via x_embed
2022-08-18 09:42:13 +03:00
FreeScout
70a3e0e500 Update notifications_table.general.append hook 2022-08-17 23:35:40 -07:00
FreeScout
97dbbd22b0 Update Danish translation 2022-08-17 02:51:36 -07:00
FreeScout
4bc8741dbc Set default empty preview in Conversation 2022-08-17 02:50:59 -07:00
vtq
20ade30be8 Changes for #2141 - fixes for UI issues for conversation display in kanban 2022-08-14 11:09:14 +10:00
FreeScout
5a8bcaf0ac Make conversation number equal to conversation ID by default - closes #2076 2022-08-01 08:04:22 -07:00
FreeScout
87dfcb8ed0 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-08-01 04:41:01 -07:00
FreeScout
f54e9e9e5d v1.8.21 2022-08-01 04:40:22 -07:00
FreeScout
28e75dbfbc Fix adding Re: to the subject - closes #2125 2022-08-01 04:39:18 -07:00
FreeScout
407c4a499d Fix modules list if author URL is empty on PHP 8.1 - closes #2127 2022-08-01 01:23:22 -07:00
FreeScout
9fe3b80fbf Corrected default customer avatar image URL to work with subdirectory #2122 from virtiqa/master
Corrected default customer avatar image URL to work with subdirectory
2022-08-01 08:59:59 +03:00
vtq
ef0fbccb12 Corrected default customer avatar image URL to work with subdirectory installs #2121 2022-07-28 20:06:13 +10:00
FreeScout
7a0921765a Removed trailing slash from tracking pixel route #2112 from virtiqa/master
Fix for #2111 removed trailing slash from tracking pixel route
2022-07-25 13:26:11 +03:00
vtq
ee2d752e25 Fix for #2111 removed trailing slash from tracking pixel route 2022-07-25 15:58:45 +10:00
FreeScout
5363501c98 Do not log in user automatically after resettings the password 2022-07-24 06:54:38 -07:00
FreeScout
aedb19901b Allow to delete mailboxes with unlimited number of conversations - closes #2089 2022-07-24 06:28:32 -07:00
FreeScout
44a1103210 Allow to delete unlimited number of conversations from Trash 2022-07-24 06:22:56 -07:00
FreeScout
78a1c8d249 Allow to fetch emails via POP3 2022-07-24 03:35:32 -07:00
FreeScout
495704cf41 Update Spanish translation 2022-07-24 03:24:40 -07:00
FreeScout
4698605a70 Default folder INBOX (POP3 fix) #2093 from PysX/master
Default folder INBOX (POP3 fix)
2022-07-24 12:30:28 +03:00
Cédric NOËL
2b0a31008b Default folder INBOX 2022-07-12 15:59:36 +02:00
FreeScout
4c4db724a5 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-07-06 10:57:59 -07:00
FreeScout
c4f5c234ae v1.8.20 2022-07-06 10:57:28 -07:00
FreeScout
70034bab61 Allow to bypass caching in Option::get() 2022-07-02 22:10:13 -07:00
FreeScout
eed54ed526 Merge branch 'master' of github.com:freescout-helpdesk/freescout 2022-07-01 23:04:59 -07:00
FreeScout
7728658175 Small optimizations to better style dashboard cards and sidebar folders #2073 from FreeScout-shop/master
Small optimizations to better style dashboard cards and sidebar folders
2022-07-02 09:04:07 +03:00
RienNeVaPlus
3f133c868d Add icon as class name and $active_count as data attribute 2022-07-01 06:34:58 +02:00
RienNeVaPlus
029d26b5af Add data-mailbox-id attribute to dash card 2022-07-01 06:33:03 +02:00
FreeScout
d0084862b7 Rename mailboxes.update_after_signature hook into mailbox.update.after_signature 2022-06-30 04:46:32 -07:00
FreeScout
5ded7d579a When emptying trash update starred conversations counter 2022-06-30 01:04:17 -07:00
FreeScout
758033a230 When deleting conversation forever delete them also from conversation_folder table 2022-06-30 01:03:40 -07:00
FreeScout
e8ae4e306f Update label text in tools.php 2022-06-30 00:16:03 -07:00
FreeScout
a3318986d7 Updated apple-touch-icon #2065 from ybizeul/new-apple-touch-icon
Updated apple-touch-icon
2022-06-30 10:05:39 +03:00
FreeScout
1c98d1602b Fix error in isLocaleRtl() if app.locales_rtl is empty - closes #2072 2022-06-30 00:04:28 -07:00
Yann Bizeul
2db5177e23 Updated apple-touch-icon 2022-06-27 07:44:18 +02:00
FreeScout
ebdb9f8979 Update Readme 2022-06-21 23:10:57 -07:00
FreeScout
fe4c69e3a9 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-06-20 00:13:22 -07:00
FreeScout
d961797c40 v1.8.19 2022-06-20 00:12:17 -07:00
FreeScout
b8dd8d30a6 Allow to log in in the mobile app when the root is redirecting - closes #2039 2022-06-19 23:55:22 -07:00
FreeScout
213fb80f95 Preservce code blocks when editing threads 2022-06-19 06:00:58 -07:00
FreeScout
7531eb4d64 Update Czech translation 2022-06-18 03:23:23 -07:00
FreeScout
f7c9d08b6c Pass conversation to conversation.is_in_folder_allowed hook 2022-06-17 22:29:31 -07:00
FreeScout
31995613ba mailbox.incoming_settings_before_save hook 2022-06-17 10:49:40 -07:00
FreeScout
529df15375 fetch_emails.after_set_seen hook 2022-06-17 10:48:59 -07:00
FreeScout
abfbf9959a Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-06-14 23:43:31 -07:00
FreeScout
9caa1e10cc v1.8.18 2022-06-14 23:43:03 -07:00
FreeScout
81139ab8b2 Adjust customer form and snippet 2022-06-14 23:41:56 -07:00
FreeScout
5f0510484e Enhancement of customer form #2034 from nykopol/master
Enhancement of customer form
2022-06-15 09:24:03 +03:00
FreeScout
c87b3a97d7 Do not send notifications to deleted users 2022-06-14 00:18:50 -07:00
FreeScout
74712cf2d9 search.assignees filter 2022-06-14 00:18:35 -07:00
FreeScout
40e0776219 Update subscription hooks 2022-06-10 02:39:19 -07:00
FreeScout
dc1d3ec5da Add EMAIL_DELETED_SUFFIX constatnt to User 2022-06-09 23:54:29 -07:00
FreeScout
d72bc81563 mailbox.permissions.before_access_settings hook 2022-06-09 05:13:41 -07:00
FreeScout
4d3bddc4d2 Folder meta functions 2022-06-09 05:13:23 -07:00
FreeScout
3cdbeaa768 Subscription hooks 2022-06-09 01:41:30 -07:00
FreeScout
13a9ca2ff3 User mailbox access hooks 2022-06-09 00:00:08 -07:00
Johan Desmyter
53f6c18cbe edit and show the existing phone type information on customer 2022-06-08 15:08:49 +03:00
Johan Desmyter
3211037aba add basic html validation on customer form 2022-06-08 13:59:26 +03:00
FreeScout
f5bf5afe14 Add vendor files to dist 2022-06-06 22:44:49 -07:00
FreeScout
4f65bbc723 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-06-06 22:43:19 -07:00
FreeScout
56e8e3862b v1.8.17 2022-06-06 22:42:54 -07:00
FreeScout
0d53e23151 Allow acess to /.well-known on Apache 2022-06-06 22:25:50 -07:00
FreeScout
dcdc372e45 Fix Passing null to parameter in User - closes #2020 2022-06-05 03:21:31 -07:00
FreeScout
ca847752df Third-party modules section in Modules 2022-06-03 11:04:50 -07:00
FreeScout
6156dc0824 Fix creating phone conversation on PHP 8.1 - closes #2028 2022-06-03 03:06:30 -07:00
FreeScout
04374dccdf Authentication hooks 2022-06-03 01:49:26 -07:00
FreeScout
1e4e67cc37 Hooks for URL in user email notifications 2022-06-03 01:47:47 -07:00
FreeScout
338df4aa44 Add Korean language 2022-06-01 04:51:09 -07:00
FreeScout
fc24702844 Implement IMAP Folder To Save Outgoing Replies for oAuth IMAP connection - closes #2018 2022-06-01 04:36:51 -07:00
FreeScout
fb82f16ef6 Merge pull request #2022 from e1mo/e1mo/app-key-file
Add APP_KEY_FILE environment variable
2022-06-01 12:53:52 +03:00
Moritz 'e1mo' Fromm
d915c646bb Add APP_KEY_FILE environment variable
In lots environments having files with secrets is common (e.g. with
NixOS where static secrets in config files are not secure). This new
environment variable adds support for this. If a APP_KEY is defined it
will take precedence ofer APP_KEY_FILE
Tests have been added to show this beaviour.
2022-05-31 17:33:52 +02:00
FreeScout
400ec40ff1 Added Czech language 2022-05-29 22:52:55 -07:00
FreeScout
d73f64b669 Update Polish language 2022-05-26 03:25:17 -07:00
FreeScout
403a160917 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-05-24 02:43:01 -07:00
FreeScout
3904e6d120 v1.8.16 2022-05-24 02:42:28 -07:00
FreeScout
bcdc2e793e Hooks for mailbox icons and customer default avatar #2012 from RienNeVaPlus/master
Hooks for mailbox icons and customer default avatar
2022-05-24 12:41:30 +03:00
FreeScout
cc68ee3d27 Avoid customer reply duplication loop - closes #2003 2022-05-24 02:38:41 -07:00
RienNeVaPlus
0b0ad5067c Fix argument of filter customer.default_avatar 2022-05-24 11:15:18 +02:00
RienNeVaPlus
b391d4496c Rename action to mailbox.update.before_name 2022-05-24 11:13:57 +02:00
FreeScout
31df77a0b9 Fix fetching issues 2022-05-24 01:30:19 -07:00
RienNeVaPlus
5e053d2954 Add action mailbox.before_name 2022-05-24 09:42:07 +02:00
RienNeVaPlus
3cfa02fde2 Add action mailbox.before_name 2022-05-24 09:40:51 +02:00
RienNeVaPlus
e3a5ac1432 Add action mailbox.before_name 2022-05-24 09:39:57 +02:00
RienNeVaPlus
97abf1da76 Add action mailbox.before_name 2022-05-24 09:39:03 +02:00
RienNeVaPlus
1a28d142f2 Add action mailbox.before_name 2022-05-24 09:38:01 +02:00
RienNeVaPlus
6dcd0dc8cc Allow file uploads in mailbox update form 2022-05-24 09:36:35 +02:00
RienNeVaPlus
23d812cdd4 Add action mailbox.update_before_name 2022-05-24 09:35:59 +02:00
RienNeVaPlus
90270ad31f Add filter customer.default_avatar 2022-05-24 09:34:03 +02:00
FreeScout
5ed8d90c70 Add [Third-Party] suffix to third-party modules 2022-05-16 23:22:06 -07:00
FreeScout
1f2182d014 Add [Third-Party] suffix to third-party modules 2022-05-16 22:11:08 -07:00
FreeScout
c1a2b819ec Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-05-16 21:44:37 -07:00
FreeScout
2b8e3fda63 v1.8.15 2022-05-16 21:44:09 -07:00
FreeScout
8bb0988010 Allow passwords with spaces during installation - closes #1998 2022-05-16 07:20:53 -07:00
FreeScout
5c4eaa17cf Fix Canada/Newfoundland timezone name - closes #1997 2022-05-16 07:01:41 -07:00
FreeScout
4bb44a8d1a Fix Call to a member function sortBy() on array on fetching emails - closes #1984 2022-05-16 06:55:21 -07:00
FreeScout
826418d5e4 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-05-13 00:08:55 -07:00
FreeScout
c2810be1b8 v1.8.14 2022-05-13 00:08:21 -07:00
FreeScout
3243d57b01 editor.vars hook 2022-05-12 22:57:13 -07:00
FreeScout
9bf1637320 User setData() method 2022-05-12 04:22:55 -07:00
FreeScout
ae415033dd Improve modules activation process 2022-05-11 02:19:07 -07:00
FreeScout
41d2743816 Fix Undefined property conversation in SendEmailReplyError job - closes #1974 2022-05-11 02:02:35 -07:00
FreeScout
2e6eb810cd Fix searching Phone conversations - closes #1986 2022-05-11 01:56:22 -07:00
FreeScout
7f99ef2c74 Fix PHP81 issue on deactivating a license 2022-05-10 04:39:23 -07:00
FreeScout
fda79460b8 Fix typo in update.sh #1977 from Pablohn26/patch-1
Fix typo in update.sh
2022-05-06 10:34:44 +03:00
Pablo Hinojosa
b91155c2ce Fix typo 2022-05-05 20:03:58 +02:00
FreeScout
83e2b0af7f Improve error message when user replies from random email address - #1971 2022-05-05 04:23:42 -07:00
FreeScout
d82b3dc896 Set SERVER HTTPS variable if needed in HttpsRedirect middleware 2022-05-03 22:47:27 -07:00
FreeScout
7f732bcfef Fix closed folder sorting - #1965 2022-05-03 03:55:29 -07:00
FreeScout
32a6c46e8a Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-05-01 04:51:25 -07:00
FreeScout
097615ef69 v1.8.13 2022-05-01 04:50:50 -07:00
FreeScout
ebaddd76a7 Allow to change Default Redirect from mailbox dropdown menu - closes #1942 2022-04-30 03:43:35 -07:00
FreeScout
aa4105e0cd Show error in the log when fetching from custom IMAP folder - closes #1961 2022-04-30 01:39:09 -07:00
FreeScout
487e0bf370 Update translations 2022-04-30 01:25:55 -07:00
FreeScout
8121c489d5 Update Gmail instruction link 2022-04-29 23:26:33 -07:00
FreeScout
3362bbeb45 Update general_help_request 2022-04-29 23:25:53 -07:00
FreeScout
08796da55e Make some modifications in RTL style to fix view issues #1945 from rhashnull/master
Make some modifications in RTL style to fix view issues.
2022-04-30 08:03:23 +03:00
Arash Khajelou
b68b17c3df Fix the issue with tranlating :number parameter. 2022-04-25 16:33:44 +04:30
Arash Khajelou
218605334c Make some mofigications in rtl style to fix view issues. 2022-04-25 16:30:35 +04:30
FreeScout
8d9f9bead8 Adjust permission name 2022-04-23 23:26:42 -07:00
FreeScout
86e1c292f2 Update French translation 2022-04-23 23:14:21 -07:00
FreeScout
b4c334ed9c Add customer.company variable 2022-04-23 23:07:37 -07:00
FreeScout
3b3c23451b Add customer.company variable to templating #1931 from e1mo/master
Add customer.company variable to templating
2022-04-24 09:03:22 +03:00
FreeScout
8f71f77b1b Merge branch 'master' of github.com:freescout-helpdesk/freescout 2022-04-23 23:01:12 -07:00
FreeScout
84f53fd682 Do not show separator in the plain text email body for the first thread in the loop - closes #1927 2022-04-23 23:00:08 -07:00
FreeScout
a3a27259fc Update readme 2022-04-22 21:56:53 -07:00
Moritz 'e1mo' Fromm
eff6c8c351 Add customer.company variable to templating
A corresponding Feature-Request exists here:
https://feedback.userreport.com/25a3cb5f-e4bd-4470-b6f3-79fcfaa8e90f
2022-04-21 10:36:44 +02:00
FreeScout
2d21c21256 Merge pull request #1923 from RienNeVaPlus/master
Implement actions & filters for an additional user field
2022-04-18 17:18:48 +03:00
RienNeVaPlus
cf01a524e5 Add filter user.setup_save 2022-04-18 01:40:53 +02:00
RienNeVaPlus
51e2f71afc Add filter user.create_save 2022-04-18 01:39:39 +02:00
RienNeVaPlus
358781905f Add action user.setup.before_job_title 2022-04-18 01:38:30 +02:00
RienNeVaPlus
6b782ede62 Add action user.edit.before_email 2022-04-18 01:37:20 +02:00
RienNeVaPlus
425080093b Add action user.create.before_email 2022-04-18 01:36:01 +02:00
FreeScout
415b20020a Add vendor files to dist 2022-04-14 06:13:12 -07:00
FreeScout
29b36472c9 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-04-14 06:10:41 -07:00
FreeScout
5f6bd2d993 v1.8.12 2022-04-14 06:09:42 -07:00
FreeScout
8fb6d9152f Trigger phone call when clicking on customer phone - closes #1899 2022-04-14 05:05:07 -07:00
FreeScout
1884048f9c Fix getFullName() on null after deleting a customer - closes #1909 2022-04-14 05:00:10 -07:00
FreeScout
1d859d3fb9 Fix resettings a password on PHP 8.1 - closes #1911 2022-04-14 04:42:48 -07:00
FreeScout
b41e78e71e Merge pull request #1912 from Lars-/master
Multiple filters
2022-04-14 14:25:34 +03:00
FreeScout
e8afbd85a9 Fix clearing Fetching logs - closes #1915 2022-04-14 04:21:54 -07:00
Lars Jansen
d9a8fc8e8b Actions before and after thread recipients 2022-04-12 16:07:12 +02:00
Lars Jansen
a15b65f819 Filter outgoing name 2022-04-12 16:05:16 +02:00
FreeScout
7dc35d143a menu.user.name_append hook 2022-04-06 07:08:00 -07:00
FreeScout
0bdb22e934 Take create_thread parameter into account in changeUser() method 2022-04-06 07:07:28 -07:00
FreeScout
09b777181a Hooks to adjust assignee list 2022-04-06 04:51:31 -07:00
FreeScout
18217c58b6 Hooks to allow adding fields to the user profile 2022-04-06 03:23:51 -07:00
FreeScout
147926bcf1 Add vendor files to dist 2022-04-06 00:03:46 -07:00
FreeScout
3c904fc048 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-04-06 00:00:26 -07:00
FreeScout
d1736d9f09 jobs.send_reply_to_customer.headers hook 2022-04-05 23:56:38 -07:00
FreeScout
8ef1fd811b Merge pull request #1882 from DavidAnderson684/patch-3
Add filter allowing customisation of outgoing headers
2022-04-06 09:53:58 +03:00
FreeScout
a64cfa6307 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-04-05 23:52:57 -07:00
FreeScout
3f80845371 v1.8.11 2022-04-05 23:52:25 -07:00
FreeScout
80382199ee Fix PHP 8.1 preg_match issue in Mail.php - closes #1888 2022-04-05 22:21:19 -07:00
FreeScout
292e9ec0ae Add noindex meta tag 2022-04-05 22:19:24 -07:00
FreeScout
59b57e268d Fix Passing null to parameter in Kernel.php - closes #1884 2022-04-05 07:25:26 -07:00
FreeScout
153e68f7ae Make symfony/http-foundation/Request.php PHP 8.1 compatible - closes #1841 2022-04-05 07:19:47 -07:00
FreeScout
0b3d36cee0 Allow to translate a message shows after replying - closes #1862 2022-04-05 07:04:38 -07:00
David Anderson
ce2557dd24 Add filter allowing customisation of outgoing headers 2022-04-05 13:52:49 +00:00
FreeScout
a8f5835a39 Merge pull request #1871 from Lars-/master
Add filters to be able to filter SMTP settings when sending a mail
2022-04-05 16:35:16 +03:00
FreeScout
9a273473d5 Fixed undefined variable error on muting notifications #1857 from Jobs-Foundation/master
Fixed undefined variable error on muting notifications
2022-04-05 16:28:23 +03:00
Lars Jansen
25b0c19db1 Add filters to be able to filter SMTP settings when sending a mail 2022-04-02 09:01:45 +02:00
Zachary Schroeder
bbc3e7f3aa fixed undefined variable error 2022-03-29 13:56:13 -05:00
FreeScout
8053591031 Add vendor files to dist 2022-03-29 06:13:20 -07:00
FreeScout
3d2fcd020d Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-03-29 06:11:56 -07:00
FreeScout
3d7fd68fa8 v1.8.10 2022-03-29 06:11:25 -07:00
FreeScout
1a011d9ea4 Fix Object of class Webklex\PHPIMAP\Header could not be converted to string - closes #1852 2022-03-29 01:18:22 -07:00
FreeScout
57a943edec Fix invalid encoding "default" on PHP 8 - closes #1837 2022-03-29 00:46:37 -07:00
FreeScout
85e9dc53f2 Ensure that there is always a Customer email address when updating a conversation #1847 from edwardhughes/master
Ensure that there is always a Customer email address when updating a conversation
2022-03-29 10:15:16 +03:00
FreeScout
a682d8f7df Add empty string check to hasManageMailboxAccess() #1850 from Jobs-Foundation/master
Add empty string check to hasManageMailboxAccess()
2022-03-29 10:12:47 +03:00
FreeScout
bdc1073a28 Limit user dropdown height in bulk actions - closes #1840 2022-03-29 00:11:32 -07:00
Zachary Schroeder
ac79c57de4 add empty string check 2022-03-28 11:08:36 -05:00
Edward Hughes IV
1b70e45a1c Ensure that there is always a Customer email address when updating a Conversation. 2022-03-25 14:37:54 -04:00
FreeScout
97d0c470fb Fix null to parameter in Swiftmailer - closes #1831 2022-03-20 07:01:08 -07:00
FreeScout
c92589da2a Skip error in setUrl() - closes #1789 2022-03-19 23:32:08 -07:00
FreeScout
3dffd231ae Update FR translation 2022-03-19 23:19:33 -07:00
FreeScout
f429bdef8e Allow PHP 8 in update.sh - closes #1819 2022-03-19 23:14:34 -07:00
FreeScout
11f223ab04 Persian language 2022-03-14 04:18:46 -07:00
FreeScout
2ccf407bd9 RTL adjustments 2022-03-14 00:21:41 -07:00
FreeScout
a172d476f5 Merge pull request #1750 from rhashnull/master
Add RTL and Fa-IR language support for project.
2022-03-14 10:17:22 +03:00
FreeScout
f37303e8a0 Fix recipients names when sending new conversations separately - closes #1802 2022-03-14 00:06:56 -07:00
FreeScout
60016fd532 Show instructions on error 500 page - closes #1808 2022-03-13 06:26:11 -07:00
FreeScout
ab3de1f60b Show Microsoft OAuth connect link only if username is not an email 2022-03-13 01:14:49 -08:00
FreeScout
6014509dd2 Add vendor files to dist 2022-03-13 01:03:57 -08:00
FreeScout
4e856ea783 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-03-13 01:01:34 -08:00
FreeScout
23aeefbcbd v1.8.9 2022-03-13 01:01:03 -08:00
FreeScout
b84afb99a5 IMAP oAuth 2022-03-13 00:55:59 -08:00
FreeScout
2dada15292 Merge branch 'oauth2' 2022-03-13 00:54:03 -08:00
FreeScout
a87e2199f0 IMAP oAuth 2022-03-13 00:53:01 -08:00
Arash Khajelou
0e85c73a77 Fix the key name for locales_rtl config. 2022-03-12 15:19:06 +03:30
Arash Khajelou
d7ef54a37e Reverse the format for app.blade.php file. 2022-03-12 15:15:50 +03:30
Arash Khajelou
ed47a5e3c1 Merge branch 'freescout-22-03-12-master' 2022-03-12 15:13:03 +03:30
Arash Khajelou
27698ebea0 Cleanup and add the needed style for rtl locales. 2022-03-12 15:09:40 +03:30
Arash Khajelou
3bcdcd9284 Add locale-{{ app()->getLocale() }} to the body and load Yekan font only for .locale-fa. 2022-03-12 15:09:40 +03:30
Arash Khajelou
640379e4d7 If some language is RTL add dir="rtl" attribute to <html> tag and "rtl" class to the <body> 2022-03-12 15:09:40 +03:30
Arash Khajelou
47b4dfb4b8 Reverse the format of file Helper.php 2022-03-12 15:09:37 +03:30
Arash Khajelou
da2ea1cd1b Add to /app/Misc/Helper.php the isLocaleRtl() method to check if current language is RTL using the config parameter. 2022-03-12 15:08:24 +03:30
Arash Khajelou
12e80d60b6 Add to app.php a list of RTL languages: 'locales_rtl' => ['fa'], 2022-03-12 15:04:14 +03:30
Arash Khajelou
141fd8320e Remove .envrc file 2022-03-12 15:04:14 +03:30
Arash Khajelou
a9ac930cc0 Add persian language support to the project. 2022-03-12 15:04:14 +03:30
Arash Khajelou
344f06178f Cleanup and add the needed style for rtl locales. 2022-03-12 14:55:31 +03:30
Arash Khajelou
0f03f4fffd Add locale-{{ app()->getLocale() }} to the body and load Yekan font only for .locale-fa. 2022-03-12 14:53:44 +03:30
Arash Khajelou
24ef816c2e If some language is RTL add dir="rtl" attribute to <html> tag and "rtl" class to the <body> 2022-03-12 14:51:22 +03:30
Arash Khajelou
516e02be1d Reverse the format of file Helper.php 2022-03-12 14:47:19 +03:30
Arash Khajelou
3c13551a7a Add to /app/Misc/Helper.php the isLocaleRtl() method to check if current language is RTL using the config parameter. 2022-03-12 14:43:50 +03:30
Arash Khajelou
efcf05ad8b Add to app.php a list of RTL languages: 'locales_rtl' => ['fa'], 2022-03-12 14:37:08 +03:30
Arash Khajelou
26b7e1c5c3 Remove .envrc file 2022-03-12 14:31:10 +03:30
FreeScout
2e71180f7c Fix saving System Emails page with empty password on PHP 8.1 2022-03-10 04:02:12 -08:00
FreeScout
a42e0db899 IMAP oAuth - embedded images 2022-03-10 01:26:24 -08:00
FreeScout
3c7dda7118 IMAP oAuth - help link 2022-03-09 06:13:22 -08:00
FreeScout
740d8aa706 IMAP oAuth 2022-03-09 05:24:22 -08:00
FreeScout
e82a1c2b81 IMAP oAuth - refresh access token 2022-03-09 03:45:15 -08:00
FreeScout
7ea7795913 Fix home ulr on 404 - closes #1762 2022-03-09 02:06:24 -08:00
FreeScout
2589aa1da9 IMAP oAuth 2022-03-09 01:10:16 -08:00
FreeScout
18295e83fb Issue template 2022-03-08 22:14:22 -08:00
FreeScout
768c4561a9 IMAP oAuth 2022-03-08 22:13:25 -08:00
FreeScout
dcb7e7715f Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-03-04 09:22:48 -08:00
FreeScout
767b20f059 v1.8.8 2022-03-04 09:22:23 -08:00
FreeScout
66fba5f20a Take Spread the Word option into accoutn in test mailbox email - #1773 2022-03-04 04:09:13 -08:00
FreeScout
921186e2e0 Fix issue with searching closes #1788 2022-03-04 04:01:15 -08:00
FreeScout
5d41b6908c Issue template 2022-03-04 03:26:47 -08:00
FreeScout
fede8d633a Issue template 2022-03-04 01:19:11 -08:00
FreeScout
182f49f758 IMAP oAuth 2022-03-04 01:13:29 -08:00
FreeScout
91c914fd14 Allow to save fetching settings with empty password on PHP 8.1 2022-02-28 00:57:25 -08:00
FreeScout
c6ea0a85cb Fix PHP 8.1 error when opening fetching settings for a new mailbox 2022-02-28 00:55:18 -08:00
FreeScout
0a6e0a100c Helper currentUrl() 2022-02-26 05:49:39 -08:00
FreeScout
d4b81c4cdb Merge pull request #1768 from Wouter0100/fix/remove-mailbox-cc
Do not add mailbox emails itself to CC on fetching
2022-02-26 08:18:06 +03:00
FreeScout
2b4f67befe Change homepage link on 404 page - closes #1762 2022-02-25 21:11:55 -08:00
Wouter van Os
56597bbd29 fix: do not add mailbox emails itself to CC in new conversation
This fixes a loop that is caused by moving a conversation to
another mailbox. In that case the old mailbox was still visible
as a CC when replying on the moved converstaion. This CC would
result in a new converstation being created.
2022-02-25 17:42:17 +01:00
FreeScout
c0fc874ce4 If custom login page path set do not show login form at /login - closes #1751 2022-02-25 06:25:19 -08:00
FreeScout
d26ab69b18 Fix underscores in encoded subjects 2022-02-22 22:21:01 -08:00
FreeScout
346148ae60 Fix underscores in encoded subjects - closes #1744 2022-02-22 22:10:42 -08:00
FreeScout
8e9bcc6040 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-02-22 08:49:20 -08:00
FreeScout
f4c76467bb v1.8.7 2022-02-22 08:48:57 -08:00
FreeScout
26a8d8511f Merge branch 'master' of github.com:freescout-helpdesk/freescout 2022-02-22 02:34:21 -08:00
FreeScout
1873dc6a20 Update modules check 2022-02-22 00:07:20 -08:00
FreeScout
8fa94d8452 Merge pull request #1748 from Lars-/master
Added hooks in preparation for custom signatures module
2022-02-21 09:21:50 +03:00
FreeScout
d62088fa1f Merge pull request #1754 from psuet/patch-1
Add autocomplete Tags to Auth Views
2022-02-21 09:19:27 +03:00
Lars Jansen
03d8e2e0e8 Update update.blade.php 2022-02-20 09:32:44 +01:00
Lars Jansen
4397e0c756 Update editor_bottom_toolbar.blade.php 2022-02-20 09:32:16 +01:00
Lars Jansen
42b23f6e3a Update main.js 2022-02-20 09:31:38 +01:00
Lars Jansen
501b0719cc Update MailboxesController.php 2022-02-20 09:30:39 +01:00
Lars Jansen
1a7289a395 Update ConversationsController.php 2022-02-20 09:30:09 +01:00
Lars Jansen
6153db71c8 Update Conversation.php 2022-02-20 09:29:06 +01:00
Paul Sütterlin
5a7eae76d7 Add autocomplete Tags to Auth Views 2022-02-20 06:21:16 +01:00
Arash Khajelou
cc8e5da17b Add persian language support to the project. 2022-02-19 17:04:24 +03:30
Lars Jansen
54d44ba87a Update editor_bottom_toolbar.blade.php 2022-02-18 15:58:08 +01:00
Lars Jansen
64c46e3941 Update update.blade.php 2022-02-18 15:44:02 +01:00
Lars Jansen
2458539c6f Added hooks in preparation for custom signatures module 2022-02-18 15:31:38 +01:00
FreeScout
c56b9a0573 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-02-17 21:51:11 -08:00
FreeScout
eeb00763d3 v1.8.6 2022-02-17 21:50:46 -08:00
FreeScout
800a484acc mail_vars.replace hook 2022-02-16 22:40:25 -08:00
FreeScout
b3e444be47 Merge pull request #1742 from Lars-/master
Mail vars hook
2022-02-17 09:37:41 +03:00
FreeScout
ff025b94d2 Adjust command cron schedule 2022-02-16 22:35:09 -08:00
Lars Jansen
96986e8a22 Allow custom mail vars 2022-02-17 07:25:17 +01:00
FreeScout
2d0c72d5bb PULL_REQUEST_TEMPLATE 2022-02-16 22:11:07 -08:00
FreeScout
384ff40d88 Fix parsing dates with underscores - closes #1738 2022-02-16 04:04:52 -08:00
FreeScout
8dca28adeb Fix preg_match issue in Str.php 2022-02-16 04:04:07 -08:00
FreeScout
1f6a95c4a7 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-02-15 05:35:30 -08:00
FreeScout
3493f16035 v1.8.5 2022-02-15 05:34:40 -08:00
FreeScout
97d12be54d Fix command creating modules 2022-02-14 22:11:11 -08:00
FreeScout
19d84bb479 Improve Module::isActive() method performance 2022-02-14 01:11:00 -08:00
FreeScout
2a29e3194e Fix empty parameter passing to strstr() in connection.blade.php 2022-02-13 23:48:54 -08:00
FreeScout
4b8904f335 parseDateToCarbon() helper function 2022-02-13 23:48:35 -08:00
FreeScout
a9d43435e9 Add aria-labels to select2 options 2022-02-13 23:26:12 -08:00
FreeScout
9884a767ff Add aria-labels to summernote buttons 2022-02-13 22:59:48 -08:00
FreeScout
aa1dba6241 Clean summernote folder 2022-02-13 22:38:53 -08:00
FreeScout
ba9293a598 Adjust conversation.can_submit hook 2022-02-08 22:02:47 -08:00
FreeScout
7a15e55da2 Fix null issue in Purifier 2022-02-08 06:34:12 -08:00
FreeScout
77a851cbe0 Do not allow users and mailboxes have the same email addresses 2022-02-08 03:46:23 -08:00
FreeScout
595eaa7a20 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-02-07 02:30:35 -08:00
FreeScout
a66b6d019e v1.8.4 2022-02-07 02:30:03 -08:00
FreeScout
b6257aa21c Merge branch 'master' of github.com:freescout-helpdesk/freescout 2022-02-07 02:29:08 -08:00
FreeScout
1a46c7f001 Failed opening required compat/vlucas/phpdotenv/src/Loader.php - closes #1714 2022-02-07 02:29:01 -08:00
FreeScout
8ddbe18543 Merge pull request #1715 from afbora/fix/1713-php8-compatibility
Fix Translation Manager PHP 8 compatibility
2022-02-07 13:11:50 +03:00
Ahmet Bora
5c8de33eb6 Fix php 8 compatibility #1713 2022-02-07 12:03:32 +03:00
FreeScout
7c663cacf8 Add vendor files to dist 2022-02-04 23:53:37 -08:00
FreeScout
1bedf8394d Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-02-04 23:49:49 -08:00
FreeScout
df0ca8b740 v1.8.3 2022-02-04 23:49:00 -08:00
FreeScout
ced188f286 Flip reply icon - closes #1699 2022-02-04 22:37:31 -08:00
FreeScout
96fa6b5507 Store all vendor overrides in overrides folder 2022-02-04 07:18:23 -08:00
FreeScout
c19600bc7c Store all vendor overrides in overrides folder 2022-02-04 04:18:23 -08:00
FreeScout
cbddefcda2 Add vendor files to dist 2022-02-04 00:26:30 -08:00
FreeScout
a06d2f4c81 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-02-04 00:24:16 -08:00
FreeScout
929fc80e66 v1.8.2 2022-02-04 00:24:02 -08:00
FreeScout
56f2f1053b Preserve in vendor exclude-from-classmap files 2022-02-04 00:22:18 -08:00
FreeScout
3f1ec34dea Add vendor files to dist 2022-02-03 23:40:05 -08:00
FreeScout
ddc4f9d452 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-02-03 23:36:54 -08:00
FreeScout
14ae10536f v1.8.1 2022-02-03 23:36:16 -08:00
FreeScout
a9a3863d05 Fix ?: operator 2022-02-03 23:30:48 -08:00
FreeScout
9e8ebe17ea PHP 8.1 compatibility 2022-02-03 09:43:12 -08:00
FreeScout
9cca472d29 Add vendor files to dist 2022-02-03 04:03:34 -08:00
FreeScout
44390a6280 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-02-03 03:57:32 -08:00
FreeScout
345cdb2702 v1.8.0 2022-02-03 03:57:01 -08:00
FreeScout
a2adf76962 PHP 8.1 compatibility 2022-02-03 03:47:48 -08:00
FreeScout
3d477f6296 Add vendor files to dist 2022-02-02 22:49:30 -08:00
FreeScout
b0469ed5bc Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-02-02 22:48:17 -08:00
FreeScout
63f5b92a3c PHP 8.1 compatibility 2022-02-02 22:48:10 -08:00
FreeScout
a5d03df940 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-02-02 22:26:06 -08:00
FreeScout
2b07bcbca7 PHP 8.1 compatibility - closes #1691 2022-02-02 22:25:12 -08:00
FreeScout
d55bfd6495 Optimize search query 2022-01-31 21:35:39 -08:00
FreeScout
8210891675 Fix redirecting to root in subdirectory 2022-01-31 21:35:18 -08:00
FreeScout
beeeadf34a Merge pull request #1689 from GaneshKandu/patch-2
Fix redirecting to root in subdirectory
2022-02-01 08:30:35 +03:00
Ganesh Kandu
1f8b2098cc Freescout 1.7.30 redirecting to root
#1668 
This can be solution of this issue

if ```APP_DASHBOARD_PATH``` is empty it will redirect to ```/``` only
2022-01-31 14:26:38 +05:30
FreeScout
669ebf79e1 Optimize search query 2022-01-31 00:29:24 -08:00
FreeScout
8a0077c94d Fix search navigation with complex search filters 2022-01-31 00:08:43 -08:00
FreeScout
a2910deed3 Fix customer conversations pagination 2022-01-30 23:37:47 -08:00
FreeScout
039623097d Conversation::search() function 2022-01-30 05:43:18 -08:00
FreeScout
833158b0de Light color for spam conversations - closes #1680 2022-01-29 06:13:58 -08:00
FreeScout
d1598d61ac Add Collision detection to Readme 2022-01-29 05:57:28 -08:00
FreeScout
672149d3f7 Check if mailbox exists in SendReplyToCustomer job - closes #1678 2022-01-29 05:53:59 -08:00
FreeScout
5db4c5ced2 Show merged conversation link in the notice - closes #1686 2022-01-29 05:50:32 -08:00
FreeScout
8f07621946 Remove MS SQL from installation wizard - closes #1685 2022-01-29 02:04:32 -08:00
FreeScout
3235e60f72 Set all required parameters in Conversation::create() to avoid PostgreSQL error 2022-01-25 22:23:02 -08:00
FreeScout
566640d287 Set default options in \Helper::htmlToText() no to split the text 2022-01-24 22:58:33 -08:00
FreeScout
c10686e546 Fix customers search on PostgreSQL - closes #1664 2022-01-24 09:37:50 -08:00
FreeScout
fb1ac61d64 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-01-24 01:14:00 -08:00
FreeScout
813bc84575 v1.7.30 2022-01-24 01:12:58 -08:00
FreeScout
323e9d7a20 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-01-23 22:44:39 -08:00
FreeScout
f134a0288e v1.7.29 2022-01-23 22:43:21 -08:00
FreeScout
7324f15c50 Fix sorting conversations - closes #1662 2022-01-23 22:39:38 -08:00
FreeScout
b8286e7875 Clear note from cache after sending it - closes #1434 2022-01-22 22:09:05 -08:00
FreeScout
1afb66ce0b Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-01-22 01:24:55 -08:00
FreeScout
ec4670757e v1.7.28 2022-01-22 01:24:20 -08:00
FreeScout
92a9e7cb78 Helper isPgSql() function 2022-01-22 01:21:51 -08:00
FreeScout
dd8e8274a1 Improve sorting convesations table 2022-01-21 23:53:12 -08:00
FreeScout
b010996391 Merge pull request #1651 from alinmbb/master
Sorting conversations by columns
2022-01-22 09:07:46 +03:00
FreeScout
5bbe2b9d73 search.conversations_tab_append hook 2022-01-21 08:24:03 -08:00
FreeScout
c2336d9ca8 Fix Call to a member function isDraft() on null in SendNotificationToUsers - closes #1649 2022-01-21 06:48:01 -08:00
FreeScout
1b29571ba9 Add Integromat to Readme 2022-01-20 07:31:58 -08:00
mbb
810d2e3924 Added Conversations sorting by subject, Number, Creation Date in Mailbox view page 2022-01-20 09:23:44 +00:00
FreeScout
55d24994df search.conversations_tab_append hook 2022-01-15 06:31:48 -08:00
FreeScout
f0bba4b77b .fs-trigger-modal class to trigger modals 2022-01-15 06:31:28 -08:00
FreeScout
eefc7fd153 Allow to customize login path 2022-01-14 06:27:45 -08:00
FreeScout
f24b2e5040 Merge pull request #1628 from searsaw/pass-htmltotext-config
Add ability to pass config to Html2Text
2022-01-08 07:55:02 +03:00
Alex Sears
d96c180631 Add ability to pass config to Html2Text 2022-01-07 15:53:09 -05:00
FreeScout
f78933e2a4 Merge pull request #1623 from TylerB260/master
convsersation -> conversation Typo Fix
2022-01-06 08:20:36 +03:00
Tyler R. Bundy
b30b996e9c convsersation -> conversation 2022-01-05 13:14:21 -06:00
FreeScout
a51cb49225 Redirect to /home after login 2022-01-05 04:54:53 -08:00
FreeScout
f4924c3140 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2022-01-05 02:36:04 -08:00
FreeScout
c1b08383ef v1.7.27 2022-01-05 02:35:37 -08:00
FreeScout
59651ee24d guest_mode and no_footer options in the layout template 2022-01-05 00:21:05 -08:00
FreeScout
11b79b2b48 Add dashboard_path and home_controller to config 2022-01-04 23:52:53 -08:00
FreeScout
ac03db6380 menu.mailboxes hook 2022-01-04 01:31:36 -08:00
FreeScout
5d590d598e Always cache mailboxes in menu 2022-01-04 01:17:43 -08:00
FreeScout
17a157eb60 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2021-12-31 04:16:16 -08:00
FreeScout
2446a94d50 v1.7.26 2021-12-31 04:15:47 -08:00
FreeScout
53240b6360 deleteAttachments() function 2021-12-31 04:10:14 -08:00
FreeScout
b4b09213f7 Allow to pass user or user_id to Mailbox userHasAccess() 2021-12-31 04:00:36 -08:00
FreeScout
cdf6fd07cf thread.attachment_append hook 2021-12-31 01:08:12 -08:00
FreeScout
a0c5748c59 Merge pull request #1593 from Wouter0100/feature/add-fetching-email-hooks
Added hooks for custom fetching
2021-12-29 11:03:20 +03:00
Wouter van Os
d599c63816 Implemented @action in connection_incoming instead of foreach 2021-12-28 19:57:23 +01:00
FreeScout
c4fb834cd0 Allow to pass plain list of emails to Customer setData() 2021-12-28 06:59:24 -08:00
FreeScout
f605cf7e85 Allow to pass plain list of emails to Customer setData() 2021-12-28 06:38:38 -08:00
FreeScout
fa06b7148e Expand customer address block if non-empty 2021-12-28 05:51:13 -08:00
FreeScout
f17755aad8 Sanitize country in cusmer setData() 2021-12-28 05:49:05 -08:00
Wouter van Os
e4fdab0a19 Implemented feedback for implementing custom fetch providers
- Defaulting settings to visible
- Only add WHERE clause when not in_protocols is not empty
- Add tested arg to fetch_test
2021-12-27 18:01:45 +01:00
FreeScout
94af31c686 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2021-12-25 22:02:18 -08:00
FreeScout
28902b4c9f v1.7.25 2021-12-25 22:01:32 -08:00
FreeScout
e57ed73f98 Send attachments to all emails when sending to multiple recepients - closes #1599 2021-12-25 06:26:34 -08:00
FreeScout
bf1eb5985a Remove limitation on user Alternate Emails max length - closes #1592 2021-12-25 06:08:54 -08:00
FreeScout
2188ce8a4e Helper::setLocale() function 2021-12-24 23:41:13 -08:00
Wouter van Os
33767a590a Added hooks for custom fetch email providers 2021-12-23 23:19:19 +01:00
FreeScout
352469b662 Fix logs names in Logs 2021-12-15 22:42:54 -08:00
FreeScout
01b8da7cc2 conversations_table.before_mailbox hook 2021-12-13 09:26:42 -08:00
FreeScout
f9b517c849 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2021-12-13 06:41:47 -08:00
FreeScout
962418a9ee v1.7.24 2021-12-13 06:41:28 -08:00
FreeScout
3353d57c84 Merge pull request #1578 from philippkraft/master
Improve accessibility of modal dialogs (#1150)
2021-12-12 13:13:38 +03:00
FreeScout
0ed7db3b0c Fix last reply date for phone conversations 2021-12-09 22:44:26 -08:00
FreeScout
5ad58e5b25 conversations_table.before_mailbox hook 2021-12-09 01:17:12 -08:00
FreeScout
e28f174f20 Adjust default subscription implementation 2021-12-08 22:28:03 -08:00
FreeScout
4a8aae7aff Implement configuration for default subscriptions (fixes #294) 2021-12-09 08:28:59 +03:00
Philipp Kraft
e780a863cf Improve accessibility of modal dialogs (#1150)
Change aria-hidden attribute to aria-modal attribute to make modal dialogs accessible for screen readers
2021-12-08 20:35:55 +01:00
Moritz Marquardt
86c2724ceb Fix formatting (tabs vs. spaces) 2021-12-08 15:16:22 +01:00
Moritz Marquardt
6677cf39a9 Fix accessibility & translation issues and add German translation for default subscriptions 2021-12-08 15:01:44 +01:00
Moritz Marquardt
0bac1ae3c4 Make default subscription settings persistent 2021-12-08 14:51:02 +01:00
Moritz Marquardt
a152d8bf06 Add UI for subscriptions defaults 2021-12-08 14:39:04 +01:00
FreeScout
badba682a0 .dropdown-menu.with-icons style 2021-12-08 01:09:13 -08:00
FreeScout
90ae9e2a7e Conversations::create() should create phone conversations in Pending status 2021-12-06 22:42:34 -08:00
FreeScout
69cbede2f4 Clarify in comment why customer changes when CCed recipient replies 2021-12-05 21:56:07 -08:00
FreeScout
754c3d7e75 Merge pull request #1575 from Wouter0100/patch-2
Fix Thread type being changed in ThreadObserver
2021-12-06 08:34:04 +03:00
Wouter van Os
3814a2d3e9 Fix issue of Thread type being incorrect after save
This caused all kinds of weird behaviours, where one of them was #1574. This fixes it.
2021-12-05 21:16:20 +01:00
FreeScout
97fb5678bc Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2021-12-03 04:58:12 -08:00
FreeScout
964249c1d4 v1.7.23 2021-12-03 04:57:52 -08:00
FreeScout
607ae55db7 Fix issue with chat convesations being converted into regular conversations - closes #1565, #1568 2021-12-03 04:57:11 -08:00
FreeScout
b7dae29b5f Properly process attachments with slashes - closes #1566 2021-12-03 04:44:40 -08:00
FreeScout
87d48a3428 Fix Trying to get property id of non-object in SendNotificationToUsers.php - closes #1557 2021-12-03 04:38:52 -08:00
FreeScout
82a8876209 replaceAll() JS function 2021-12-02 21:57:00 -08:00
FreeScout
325ad1914b .panel-sortable-input style 2021-12-02 21:56:40 -08:00
FreeScout
d1bcef4df7 Update rollback for remove_unique_index_in_folders_table 2021-12-01 06:08:21 -08:00
FreeScout
7c0ef24bfd loadMailboxes() for convesations and checking status in changeStatus() 2021-12-01 04:12:11 -08:00
Moritz Marquardt
038c3610ac Implement configuration file for default subscriptions (fixes #294) 2021-11-30 19:11:02 +01:00
FreeScout
62ea562f26 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2021-11-30 04:12:54 -08:00
FreeScout
3f17e9b811 v1.7.22 2021-11-30 04:12:30 -08:00
FreeScout
c08c077b8f Properly save attachments without a name - closes #1548 2021-11-30 04:02:51 -08:00
FreeScout
b5efc28b0f Set data-customer_id on customer conversation page to fix deleting customer 2021-11-30 03:37:01 -08:00
FreeScout
ab55a5fe19 Allow multiple personal folders for users - closes #843 2021-11-30 00:57:05 -08:00
FreeScout
f5e493e5dc Fix empty customer when creating a phone conversation without saving it as a draft first 2021-11-29 23:03:51 -08:00
FreeScout
e6ab5eba07 Fix (no subject) issue when creating a new conversation and sending it quickly 2021-11-29 22:45:45 -08:00
FreeScout
d0f1b621fe Fix creating a phone conversation without saving it first as draft 2021-11-29 22:42:33 -08:00
FreeScout
79aca06be8 Allow to get via phone conversation last reply via getLastReply() 2021-11-29 22:42:10 -08:00
FreeScout
9e7f2e22dd Add html_tag parameter to initModals() function 2021-11-29 00:38:23 -08:00
FreeScout
d11c9c83f0 glyphicon-spin 2021-11-19 07:06:59 -08:00
FreeScout
9e0622f91f .footer-hide class 2021-11-17 03:33:47 -08:00
FreeScout
ea7e30b9cc Disable authentication for cron URL - closes #1545 2021-11-17 03:33:33 -08:00
FreeScout
9c52ea8eca Add badges partial 2021-11-17 00:24:05 -08:00
FreeScout
79db2920ca Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2021-11-14 23:13:11 -08:00
FreeScout
df9370a37f v1.7.21 2021-11-14 23:12:55 -08:00
FreeScout
416d07fcce Heading styles 2021-11-14 23:12:30 -08:00
FreeScout
5384a32d58 Set last reply date for phone conversations 2021-11-09 09:28:12 -08:00
FreeScout
18e9679553 Adjust .main-heading style 2021-11-09 08:46:29 -08:00
FreeScout
c0cef1b178 Slovak language added 2021-11-08 03:34:06 -08:00
FreeScout
045c1833e3 Update French translations 2021-11-08 03:30:49 -08:00
FreeScout
d4f8134de4 Update Croatian translations 2021-11-08 03:27:27 -08:00
FreeScout
78596eb4c9 Merge pull request #1528 from andrewlimaza/master
Add support to get module image from modules.json
2021-11-01 16:50:49 +03:00
Andrew Lima
380a275248 Add support to get module image from modules.json
Add support for module.json 'img' attribute. Use this to set a path to the image for your custom module icon/logo.
2021-11-01 14:23:53 +02:00
FreeScout
a0b98cabf1 Fix duplicated customers in the Search - closes #1521 2021-10-29 07:17:51 -07:00
FreeScout
2ccc6dd141 Disable sql_require_primary_key=0 for PostgreSQL - closes #1518 2021-10-28 05:51:19 -07:00
FreeScout
a52336dc01 Fix notifications dropdown height on screen with height > 1086px - closes #1517 2021-10-28 05:37:01 -07:00
FreeScout
d70ae69dae Correct photo size in user profile 2021-10-28 05:25:41 -07:00
FreeScout
5eeed269b6 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2021-10-21 05:54:00 -07:00
FreeScout
eedc9e8f94 v1.7.20 2021-10-21 05:53:42 -07:00
FreeScout
950af7b268 Add Croatian language 2021-10-21 05:50:53 -07:00
FreeScout
5f9e02012a Access user in created_by_user_id in Thread::createExtended() 2021-10-21 05:40:57 -07:00
FreeScout
548517b906 Merge branch 'master' of github.com:freescout-helpdesk/freescout 2021-10-21 00:54:21 -07:00
FreeScout
c29f902483 Fix auto refreshing conversation by new user message - closes #1405 2021-10-21 00:53:44 -07:00
FreeScout
46919ba5a3 Merge pull request #1500 from alinmbb/master
Add side layout hook on the New Conversation page
2021-10-20 18:01:48 +03:00
FreeScout
c4a4f33633 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2021-10-20 07:45:16 -07:00
FreeScout
5176f0456f v1.7.19 2021-10-20 07:44:54 -07:00
alinmbb
fb3e2635f5 Update create.blade.php
Updated hook name
2021-10-20 13:38:49 +02:00
alinmbb
380d1269d0 Update create.blade.php
Added side layout hook on the New Conversation page
2021-10-20 13:33:05 +02:00
FreeScout
069e82c097 users.email filter 2021-10-20 04:26:54 -07:00
FreeScout
9a836a01fd Add password reset throttling 2021-10-20 00:27:07 -07:00
FreeScout
eaffa01452 Add community modules link to the readme 2021-10-19 23:43:45 -07:00
FreeScout
169fa21966 Add cloud hosted link to readme 2021-10-19 23:41:13 -07:00
FreeScout
20e0dae7a3 Add cloud hosted link to readme 2021-10-19 23:39:33 -07:00
FreeScout
358db187be If conversation from deleted customer arrives the customer is empty 2021-10-16 00:50:38 -07:00
FreeScout
fe17e3c4bb Merge pull request #1491 from laurentenhoor/feature/minimal-ui-changes-with-maximal-impact
Set min dashboard card header height
2021-10-16 10:38:35 +03:00
FreeScout
60e0d3c40c Check customer existence in thread tempalte 2021-10-15 23:55:15 -07:00
FreeScout
1753a6f654 customer.edit.before_form hook 2021-10-15 23:54:57 -07:00
FreeScout
d41e0e6c8c Fix protocol in search tabs 2021-10-15 23:54:21 -07:00
FreeScout
6f09d91a50 closeAllModals() JS function 2021-10-15 10:59:44 -07:00
FreeScout
75c6008acc Fix error in SendNotificationToUsers.php when deleting customers 2021-10-15 10:59:32 -07:00
Lauren ten Hoor
7c581cc7bb Another fix for additional dashboard items. 2021-10-15 12:05:50 +01:00
FreeScout
0715f008ca Merge pull request #1490 from laurentenhoor/feature/minimal-ui-changes-with-maximal-impact
Minor UI updated for more professional look and feel.
2021-10-15 12:29:09 +03:00
Lauren ten Hoor
61f4c3760b Minor UI updated for more professional look and feel. 2021-10-15 10:18:50 +01:00
FreeScout
ad1a464335 Add customer menu 2021-10-15 01:16:03 -07:00
FreeScout
76ca9a1153 Merge pull request #1486 from laurentenhoor/bugfix/unable-to-run-isDraft()-on-null
Fixing error about: "unable to run isDraft() on null".
2021-10-15 09:42:08 +03:00
Lauren ten Hoor
461a786ebc Fixing error about: "unable to run isDraft() on null". 2021-10-14 13:38:03 +01:00
FreeScout
170c282f3e Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2021-10-14 03:26:29 -07:00
FreeScout
c7480c81b2 v1.7.18 2021-10-14 03:26:11 -07:00
FreeScout
de62a0c11b Add .eml extension to RFC822 attachments - closes #986 2021-10-13 11:04:30 -07:00
FreeScout
dc35f63586 Fix reopening a conversation when customer replies from chat - closes #1461 2021-10-13 07:31:46 -07:00
FreeScout
f5d7808792 Fix reopening a conversation when customer replies from chat - closes #1461 2021-10-13 01:51:49 -07:00
FreeScout
1bf03b619a Undelete deleted conversation when customer replies via chat or API - closes #1468 2021-10-13 00:55:20 -07:00
FreeScout
e109f5782c Add Danish language 2021-10-12 23:24:02 -07:00
FreeScout
3c57cf0f65 Fix users_to_notify must be an array 2021-10-06 11:05:54 -07:00
FreeScout
475917c110 Log error when app can not fetch message from mail server in Show Original window - closes #1446 2021-10-04 23:22:02 -07:00
FreeScout
3b7ece1790 Web cron - closes #1452 2021-10-04 04:54:02 -07:00
FreeScout
2a6c5faa79 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2021-09-23 03:06:15 -07:00
FreeScout
7446cce387 v1.7.17 2021-09-23 03:05:59 -07:00
FreeScout
15bb477e84 thread.menu.append hook 2021-09-22 05:28:33 -07:00
FreeScout
83071f3a1d Allow to skip user time zone in dateFormat() function 2021-09-22 05:27:32 -07:00
FreeScout
eb579823db Add indexes to Conversations table - closes #1425 2021-09-21 03:12:54 -07:00
FreeScout
fe702bcaea Fix Undefined constant STDIN on module activation - closes #1423 2021-09-21 01:15:44 -07:00
FreeScout
0d87a6ea93 Fix Call to a member function getFullName in web_notifications partial - closes #1410 2021-09-21 00:19:26 -07:00
FreeScout
d9135c134b Update Russian translation 2021-09-21 00:13:13 -07:00
FreeScout
eb33b0c3c7 Update Polish translation 2021-09-21 00:12:15 -07:00
FreeScout
93471f4342 Update German translation 2021-09-21 00:11:00 -07:00
FreeScout
0db0bfdfd4 Improve security of Translate page 2021-08-29 01:47:05 -07:00
FreeScout
92e4dbcaa8 Add SESSION_SECURE_COOKIE to web installer 2021-08-29 00:53:24 -07:00
FreeScout
15443d155b Add SESSION_SECURE_COOKIE to .env.example 2021-08-29 00:50:05 -07:00
FreeScout
21283962c4 Add SECURITY.md 2021-08-29 00:08:39 -07:00
FreeScout
1816aa2761 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2021-08-25 05:53:54 -07:00
FreeScout
3d862b03eb v1.7.16 2021-08-25 05:53:36 -07:00
FreeScout
411fca2ab3 Fix page generation error with multiple agents 2021-08-25 05:24:27 -07:00
FreeScout
8190907e39 Merge pull request #1396 from ejhayter/ejhayter-patch-2
Fix page generation error with multiple agents
2021-08-25 15:05:47 +03:00
Emily Hayter
05fbc0b80d Fix page generation error with multiple agents
When multiple agents were viewing an email, the following error was seen:

usort(): Returning bool from comparison function is deprecated, return
    an integer less than, equal to, or greater than zero at
    /www/html/app/Http/Controllers/ConversationsController.php:252

Opted for spaceship operator, I was not able to verify that the code is
operating as intended, only that it resolved the generated error.
2021-08-25 12:33:29 +01:00
FreeScout
347397f4da Help info for Alternate Emails field 2021-08-20 00:41:44 -07:00
FreeScout
1d0e37950d Allow users to reply from Alternate emails - closes #1363 2021-08-19 07:12:47 -07:00
FreeScout
7828df20fc Add vendor files to dist 2021-08-10 22:19:57 -07:00
FreeScout
a72f6b964f Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2021-08-10 22:18:39 -07:00
FreeScout
950dc05843 v1.7.15 2021-08-10 22:18:18 -07:00
FreeScout
6a242635f7 fzaninotto/faker v1.9.2 2021-08-10 22:14:53 -07:00
FreeScout
dff1bd0d40 Merge pull request #1364 from admdly/fix-faker-php74
Update fzaninotto/faker to fix PHP >= 7.4 compatibility
2021-08-11 08:13:31 +03:00
Adam
d678390b17 Bump fzaninotto/faker to fix PHP >= 7.4 compatibility
Update fzaninotto/faker to fix issue due to changes with implode() in PHP >= 7.4 (see fzaninotto/faker#1748).
2021-08-10 06:06:54 +01:00
FreeScout
579b0bc5ef Fix saving multiple recipients in a new conversation draft 2021-08-06 23:31:07 -07:00
FreeScout
23b01416d7 Fix short text in the menu notification 2021-08-06 07:18:05 -07:00
FreeScout
3266b33b05 Make SVG attachments non-viewable to avoid XSS - closes #1359 2021-08-06 03:12:51 -07:00
FreeScout
ad686553e5 Allow modules to show menu notifications 2021-08-05 23:45:57 -07:00
FreeScout
e289f5acbd Fix short text in the menu notification 2021-08-05 07:05:36 -07:00
FreeScout
1573f73a0e Extend thread description text compilation 2021-08-05 06:52:28 -07:00
FreeScout
efcac585ef Fix summernote click on HintPopover suggestions doesnt insert 2021-08-05 03:34:44 -07:00
FreeScout
89757bc7dd Subscription hooks 2021-08-05 03:25:12 -07:00
FreeScout
04e8391ca2 editor.options hook 2021-08-04 23:47:27 -07:00
FreeScout
7f6bda6c76 Add vendor files to dist 2021-08-03 07:47:45 -07:00
FreeScout
b3b5063b50 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2021-08-03 07:45:49 -07:00
FreeScout
a79d9aa094 v1.7.14 2021-08-03 07:45:35 -07:00
FreeScout
b8f3200f8f Fix htmlpurifier INTL_IDNA_VARIANT_2003 issue on PHP7.2 and 7.3 - closes #1322 2021-08-03 07:30:57 -07:00
FreeScout
b702566cb0 Fix mobile notifications link 2021-08-03 06:26:39 -07:00
FreeScout
69b4d0a322 Fix starred folder position 2021-08-01 04:12:27 -07:00
FreeScout
b2d17e3639 Highlight folders counters 2021-07-31 11:02:15 -07:00
FreeScout
29ef585c31 Folders hooks 2021-07-31 11:00:55 -07:00
FreeScout
d3d1c4ccb9 Highlight folders counters 2021-07-31 05:51:00 -07:00
FreeScout
f53a260dff Add arrow showing conversations sorting column - closes #919 2021-07-30 23:16:19 -07:00
FreeScout
e8e86d1bf4 email.auto_reply.subject hook 2021-07-28 22:49:36 -07:00
FreeScout
284d8f07fa Fix in_array expects parameter 2 to be array in BroadcastNotification 2021-07-28 21:00:57 -07:00
FreeScout
aca1d26a76 Properly restore Cc and Bcc from draft 2021-07-18 05:01:29 -07:00
FreeScout
3ef5c28bac Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2021-07-18 01:37:49 -07:00
FreeScout
de5c9f6047 v1.7.13 2021-07-18 01:37:27 -07:00
FreeScout
b4b93af96a Make users unique in the Assigned search filter - closes #1326 2021-07-17 22:30:59 -07:00
FreeScout
9d52e8853e Conversation recipient selector hooks 2021-07-12 00:22:48 -07:00
FreeScout
2369c9b3be v1.7.12 2021-07-10 22:37:56 -07:00
FreeScout
8ee0699b3e v1.7.12 2021-07-10 22:35:58 -07:00
FreeScout
fcb18e56f0 v1.7.12 2021-07-10 22:32:19 -07:00
FreeScout
14bbab0969 Merge pull request #1319 from fulldecent/patch-5
Add supported versions of PHP
2021-07-11 08:30:23 +03:00
William Entriken
d84fe65460 Add supported versions of PHP 2021-07-09 14:51:48 -04:00
FreeScout
047b8d1433 Merge pull request #1308 from henrikhj/patch-1
Fix tracking pixel in PHP 8
2021-07-07 09:39:32 +03:00
FreeScout
6e699a27ab Merge pull request #1310 from fulldecent/patch-4
Add button role for conversation actions
2021-07-07 09:36:38 +03:00
William Entriken
fd45d20876 Extend aria-labels 2021-07-06 16:16:56 -04:00
William Entriken
f977ff30ea More role and aria label 2021-07-06 16:16:56 -04:00
William Entriken
27946aabae aria-label for dropdowns 2021-07-06 16:16:56 -04:00
William Entriken
acdc935f70 Add aria-label 2021-07-06 16:16:53 -04:00
William Entriken
28cd928a1b Add role buttons 2021-07-06 16:16:44 -04:00
Henrik Jensen
405f09ff00 Fixes #1252
Fixes #1252 "Missing format specifier at end of string" in sprintf due to mismatched number of % and format specifiers.
2021-07-05 15:16:04 +02:00
FreeScout
9c1ac14bf1 Fix duplicating attachments when creating a new conversation 2021-07-03 04:28:58 -07:00
FreeScout
920afd6045 Add Telegram to customer social profiles list 2021-07-03 03:00:54 -07:00
FreeScout
60912da17c Add mailbox name to the title - closes #1297 2021-07-02 23:13:40 -07:00
FreeScout
68eff16f64 Merge pull request #1284 from fulldecent/patch-3
Create FUNDING.yml
2021-07-03 08:12:56 +03:00
William Entriken
475eb0565d Add funding option, fixes #1279 2021-06-24 14:27:57 -04:00
FreeScout
cc58b6739b Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2021-06-23 07:32:51 -07:00
FreeScout
3f66a76727 v1.7.11 2021-06-23 07:32:42 -07:00
FreeScout
deaec751b0 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2021-06-23 07:31:32 -07:00
FreeScout
baff0ab7e4 Fix Array to string converstion error when creating a custommer - closes #1282 2021-06-23 07:28:52 -07:00
FreeScout
45a56f6ca3 Empty alt for logo image 2021-06-22 23:30:30 -07:00
FreeScout
8a14756cea Merge pull request #1278 from fulldecent/patch-1
Fix HTML warning by adding alt to the logo image
2021-06-23 09:28:55 +03:00
FreeScout
c39b0f1e02 Return hyphen to the footer 2021-06-22 23:27:46 -07:00
FreeScout
27d8706023 Merge pull request #1280 from fulldecent/patch-2
Update numeric range dash
2021-06-23 09:24:50 +03:00
FreeScout
ef434e5491 Updated Japanese translations 2021-06-22 23:24:16 -07:00
FreeScout
88c960dfad Italian translation 2021-06-22 23:22:48 -07:00
William Entriken
d028dee724 Update app.blade.php 2021-06-22 11:25:27 -04:00
William Entriken
d17a9e2ef1 Fix HTML warning 2021-06-22 10:23:17 -04:00
FreeScout
85399792fb email.reply_to_customer.subject hook 2021-06-17 00:51:44 -07:00
FreeScout
a02ed17233 Replace calendar partial with include_datepicker 2021-06-16 23:00:31 -07:00
FreeScout
3dc5f1c843 Replace calendar partial with include_datepicker 2021-06-16 10:04:36 -07:00
FreeScout
2a077ecf81 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2021-06-16 05:06:41 -07:00
FreeScout
1ae5fe8cc3 v1.7.10 2021-06-16 05:05:06 -07:00
FreeScout
658ac6b759 Disable updated_at field in Job 2021-06-15 05:18:24 -07:00
FreeScout
08973f9b50 email_notification.before_body hook 2021-06-15 01:49:15 -07:00
FreeScout
1abd3e2aa7 Thread unset meta function 2021-06-15 01:48:43 -07:00
FreeScout
49929dd4a0 Delay hooks 2021-06-15 01:48:28 -07:00
FreeScout
15cce31886 Adjust alerts within thread body 2021-06-11 02:00:32 -07:00
FreeScout
0302556635 thread.before_save_from_request hook 2021-06-11 02:00:17 -07:00
FreeScout
1ae8ff4b99 Init modals after adding editor bottom tollbar to HTML 2021-06-10 06:54:33 -07:00
FreeScout
ca75a702ca conversation.append_send_dropdown hook 2021-06-10 06:54:03 -07:00
FreeScout
14096aeaf8 Fix customer sidebar height with JS 2021-06-10 02:09:23 -07:00
FreeScout
b9ebef7817 Fix customer sidebar height with JS 2021-06-10 01:46:33 -07:00
FreeScout
b4f6f8f2d0 Update PL translation 2021-06-09 01:44:32 -07:00
FreeScout
d27b2f9727 Copy customer email on clicking on it 2021-06-05 03:49:15 -07:00
FreeScout
fd0e58e957 Merge pull request #1248 from foliovision/fix-email-customer-link
Open New Conversation by clicking on customer email
2021-06-05 13:34:48 +03:00
Alec Kinnear
eaf08c60d4 Fixing missing Email Customer link in sidebar
Realized same template is used also for the customer view, where $mailbox cannot be used
2021-06-04 15:24:08 +02:00
Alec Kinnear
6e900d111c Fixing missing Email Customer link in sidebar
Found proper function
2021-06-04 13:55:36 +02:00
Alec Kinnear
cf6a192ccc Fixing missing Email Customer link in sidebar 2021-06-04 13:46:18 +02:00
FreeScout
515a8d2b54 Remove office hours notice from mailbox auto reply page 2021-05-21 22:14:59 -07:00
FreeScout
4018604028 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2021-05-21 07:55:19 -07:00
FreeScout
55d70f8454 Fix encrypting mailbox password migration 2021-05-21 07:54:45 -07:00
FreeScout
fd08b5d4ee v1.7.9 2021-05-21 07:42:05 -07:00
FreeScout
f7cd18d4ad Fix mailbox password encyption migration 2021-05-21 07:41:39 -07:00
FreeScout
078e098b28 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2021-05-21 05:55:25 -07:00
FreeScout
b2f761ef76 1.7.8 2021-05-21 05:54:48 -07:00
FreeScout
cdd894b50c Improve encrypting mail password 2021-05-21 05:54:02 -07:00
FreeScout
323524ac1b Merge pull request #1227 from davask/encrypt_mail_password
encrypt mail password
2021-05-21 15:35:29 +03:00
FreeScout
bc04ca3db7 Improve encrypting out password 2021-05-21 05:32:08 -07:00
FreeScout
434d2c76e0 Merge branch 'master' of github.com:freescout-helpdesk/freescout 2021-05-21 05:28:34 -07:00
FreeScout
009aecc72a Merge pull request #1226 from davask/encrypt_out_password
Encrypt out_password
2021-05-21 15:27:47 +03:00
davask
ddd7885038 encrypt mail password 2021-05-21 12:21:54 +02:00
FreeScout
a46a7f5429 autoreply.should_send 2021-05-21 01:51:11 -07:00
davask
fe5e7011cc Encrypt out_password 2021-05-21 10:22:57 +02:00
FreeScout
e12da9e82d Properly update folders when moving conversation - closes #1210 2021-05-20 02:09:12 -07:00
FreeScout
dddf4d12df Add vendor files to dist 2021-05-18 23:27:40 -07:00
FreeScout
96033cbc78 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2021-05-18 23:26:08 -07:00
FreeScout
b821d3fd09 v1.7.7 2021-05-18 23:25:19 -07:00
FreeScout
1b6dcb00e7 mailbox.url hook 2021-05-18 08:03:49 -07:00
FreeScout
938e4e1ad7 folder.url hook 2021-05-18 07:44:31 -07:00
FreeScout
cd99b04507 conversations.ajax_pagination_folder hook 2021-05-18 06:36:55 -07:00
FreeScout
ba49cde9f6 folder.active_count_mine_folder hook 2021-05-18 06:35:41 -07:00
FreeScout
3f82e25074 Cache starred conversations per mailbox 2021-05-18 00:58:19 -07:00
FreeScout
f132f88059 Improve conversations pagination 2021-05-18 00:54:16 -07:00
FreeScout
38ff9e748a Allow to pass params to conversations table template 2021-05-17 23:05:50 -07:00
FreeScout
c299e0c3e2 mailbox.sidebar_folder_url hook 2021-05-17 23:05:26 -07:00
FreeScout
d4127889c3 Filter to disable mailbox sidebar buttons 2021-05-17 06:01:43 -07:00
FreeScout
994ead0e3e Hook after mailbox sidebar - closes #1209 2021-05-17 05:54:15 -07:00
FreeScout
3e5900b1e0 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2021-05-14 11:20:54 -07:00
FreeScout
f579c43b29 v1.7.6 2021-05-14 11:19:56 -07:00
FreeScout
f2fee6779d thread.attachments_list_append hook 2021-05-14 10:42:54 -07:00
FreeScout
4abb7ecc46 Improved createZipArchive() function 2021-05-14 10:37:16 -07:00
FreeScout
7e8fa56bf5 Add link to remind license keys 2021-05-11 07:13:25 -07:00
FreeScout
ce02ba86db Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2021-05-11 02:46:23 -07:00
FreeScout
7fcb585268 v1.7.5 2021-05-11 02:45:24 -07:00
FreeScout
e143280c6c getFileContents() function for Attachment 2021-05-11 02:42:52 -07:00
FreeScout
f82d36afb0 Improve no_activations_left message 2021-05-09 10:33:37 -07:00
FreeScout
e14d899ac4 Fix createExtended Thread method 2021-04-30 10:30:03 -07:00
FreeScout
6a16cbabd2 Fix search in PostgreSQL - closes #1174 2021-04-29 01:39:31 -07:00
FreeScout
e52711d592 Process incoming email even if imap_mime_header_decode fails - closes #351 2021-04-29 00:38:21 -07:00
FreeScout
abc01538f9 Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2021-04-23 00:13:23 -07:00
FreeScout
6eaf4359b5 v1.7.4 2021-04-23 00:13:14 -07:00
FreeScout
728959332b Merge branch 'master' of github.com:freescout-helpdesk/freescout into dist 2021-04-23 00:11:58 -07:00
FreeScout
23cdcffb12 v1.7.3 2021-04-22 22:46:42 -07:00
FreeScout
944363765a Update readme 2021-04-22 00:09:13 -07:00
1010 changed files with 90818 additions and 77874 deletions

View File

@@ -9,13 +9,28 @@
# Application URL
APP_URL=https://example.com
# Use HTTP protocol and redirect to HTTPS
#APP_FORCE_HTTPS=true
# If you are using HTTPS, feel free to uncomment this line to improve security
#SESSION_SECURE_COOKIE=true
# Enter your proxy address here if freescout.net is not available from your server
# (access to freescout.net is required to obtain official modules)
#APP_PROXY=
# Custom headers to add to all outgoing emails.
#APP_CUSTOM_MAIL_HEADERS="IsTransactional:True;X-Custom-Header:value"
# Uncomment if you have many folders and you are experiencing performance issues
#APP_UPDATE_FOLDER_COUNTERS_IN_BACKGROUND=true
# Timezones: https://github.com/freescout-helpdesk/freescout/wiki/PHP-Timezones
# Comment it to use default timezone from php.ini
#APP_TIMEZONE=Europe/London
# Comma separated list of trusted proxies for proper IP detection in FreeScout.
# To trust all proxies that connect to your server use single asterisk: *
# To trust ALL proxies, including those that are in a chain of forwarding use double asterisk: **
#APP_TRUSTED_PROXIES=192.168.1.1,192.168.1.2,192.168.1.3
DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306
@@ -29,4 +44,4 @@ DB_PASSWORD=
APP_KEY=
# Uncomment to see errors in your browser, don't forget to comment it back when debugging finished
#APP_DEBUG=true
#APP_DEBUG=true

View File

@@ -0,0 +1,25 @@
---
name: General Help Request
about: Create a general help request
---
<!--
* If you have some error the first thing you should do is to check "Manage » Logs » App Logs"! If there are any errors there - make sure to provide them in the issue.
* Have you read FAQ? https://github.com/freescout-helpdesk/freescout/wiki/FAQ
* If you have "Whoops something went wrong" error see: https://github.com/freescout-helpdesk/freescout/wiki/Installation-Guide#11-troubleshooting
* Before creating an issue try to disable all non-official modules and remove all custom code from your FreeScout and see if the issue still persists.
* Feature requests for modules should be posted here: https://freescout.net/request-feature/
* Docker questions should go here: https://github.com/tiredofit/docker-freescout/issues
* Create separate issues for different topics - no need to mix all your problems into one issue.
* Questions on issues with payments for modules should be sent by email: https://freescout.net/contact-us/
* Don't forget that you are not the project manager. Try to be humble, polite, friendly and positive. Otherwise your account may be banned (and it can't be undone).
* Mentioning how much you've spent on modules is also the way to be banned.
Still here? Well clean this out and go ahead :)
-->
PHP version:
FreeScout version:
Database: MySQL / PostgreSQL
Are you using CloudFlare: Yes / No
Are you using non-official modules: Yes / No

3
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,3 @@
Keep in mind that pull requests should be sent to the `master` branch! See https://github.com/freescout-helpdesk/freescout/wiki/Development-Guide#github-workflow
Now you can delete this text and type the description of your pull request...

20
.github/workflows/lint-php.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
name: PHP Code Sniffer
on:
workflow_dispatch:
jobs:
build:
name: Lint PHP
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.3
tools: phpcs
- name: Run check
run: phpcs

59
.github/workflows/test-pgsql.yml vendored Normal file
View File

@@ -0,0 +1,59 @@
name: Test App (PostgreSQL)
on:
push:
branches:
- master
workflow_dispatch:
jobs:
test:
name: Test App (PostgreSQL)
runs-on: ubuntu-latest
env:
DB_CONNECTION: testing_pgsql
services:
postgres:
image: postgres:latest
env:
POSTGRES_USER: freescout-test
POSTGRES_PASSWORD: freescout-test
POSTGRES_DB: freescout-test
ports:
- 5432:5432
# Set health checks to wait until postgres has started
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
strategy:
matrix:
php: ['7.3', '7.4', '8.0', '8.1', '8.2']
steps:
- uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: pgsql, mbstring, xml, imap, zip, gd, curl, intl, json
- name: Install composer dependencies
run: composer install --ignore-platform-reqs --no-interaction
- name: Migrate and seed the database
run: |
php${{ matrix.php }} artisan migrate --force -n --database=testing_pgsql
php${{ matrix.php }} artisan db:seed --force -n --database=testing_pgsql
env:
DB_PORT: ${{ job.services.postgres.ports[5432] }}
- name: Run PHP tests
run: php${{ matrix.php }} ./vendor/bin/phpunit
env:
DB_PORT: ${{ job.services.postgres.ports[5432] }}

45
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
name: Test App (MySQL)
on:
push:
branches:
- master
workflow_dispatch:
jobs:
test:
name: Test App (MySQL)
runs-on: ubuntu-latest
strategy:
matrix:
php: ['7.3', '7.4', '8.0', '8.1', '8.2']
steps:
- uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: mysql, mbstring, xml, imap, zip, gd, curl, intl, json
- name: Start MySQL
run: |
sudo systemctl start mysql
- name: Setup database
run: |
mysql -uroot -proot -e 'CREATE DATABASE IF NOT EXISTS `freescout-test`;'
mysql -uroot -proot -e "CREATE USER 'freescout-test'@'localhost' IDENTIFIED WITH mysql_native_password BY 'freescout-test';"
mysql -uroot -proot -e "GRANT ALL ON \`freescout-test\`.* TO 'freescout-test'@'localhost';"
mysql -uroot -proot -e 'FLUSH PRIVILEGES;'
- name: Install composer dependencies
run: composer install --ignore-platform-reqs --no-interaction
- name: Migrate and seed the database
run: |
php${{ matrix.php }} artisan migrate --force -n --database=testing
php${{ matrix.php }} artisan db:seed --force -n --database=testing
- name: Run PHP tests
run: php${{ matrix.php }} ./vendor/bin/phpunit

5
.gitignore vendored
View File

@@ -14,7 +14,7 @@ Homestead.yaml
npm-debug.log
yarn-error.log
.env
app/Console/Commands/Test*
/bootstrap/compiled.php
composer.phar
#composer.lock
@@ -32,4 +32,5 @@ Thumbs.db
/storage/.installed
/tools
.well-known
/resources/lang/module.*
/resources/lang/module.*
.phpunit.result.cache

View File

@@ -5,5 +5,6 @@
# On some hostings it does not work as "public/$1", but works as "/public/$1".
# But we can't write it as "/public/$1" because in this case it does not work
# when application is installed in subdirectory.
RewriteCond %{REQUEST_URI} !/\.well\-known/?.*
RewriteRule (.*) public/$1 [L]
</IfModule>

View File

@@ -3,6 +3,8 @@ language: php
php:
- 7.0
- 7.2
- 7.4
- 8.0
before_script:
- cp .env.travis .env

View File

@@ -522,7 +522,7 @@ contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
any implied license or other defences to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.

View File

@@ -2,35 +2,45 @@
<div align="center">
<img src="https://raw.githubusercontent.com/freescout-helpdesk/freescout/master/public/img/logo-300.png" width="180" height="180" />
<img src="https://raw.githubusercontent.com/freescout-help-desk/freescout/master/public/img/logo-300.png" width="180" height="180" />
<br/><br/>
[![PHP version](https://freescout-helpdesk.github.io/img/badges/PHP-7.1%2B-blue.svg)](https://github.com/freescout-helpdesk/freescout#requirements) [![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Ffreescout-helpdesk%2Ffreescout&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com)
[![PHP version](https://freescout-helpdesk.github.io/img/badges/PHP-7.1%2B-blue.svg)](https://github.com/freescout-help-desk/freescout#requirements) [![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Ffreescout-helpdesk%2Ffreescout&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com)
</div>
**FreeScout** is the super lightweight free open source help desk and shared inbox written in PHP7 (Laravel framework) – self hosted clone of HelpScout. Now you can enjoy free Zendesk & Help Scout without giving up privacy or locking yourself into a service you don't control. FreeScout has been developed from scratch and is not using any copyrighted Help Scout or Zendesk materials.
**FreeScout** is the super lightweight and powerful free open source help desk and shared inbox built with PHP (Laravel framework). Now you can enjoy free Zendesk & Help Scout without giving up privacy or locking yourself into a service you don't control. FreeScout has been developed from scratch and is not using any copyrighted Help Scout or Zendesk materials.
If you want to support the project feel free to **star this repository**. It helps to increase the visibility of the project and let people know that it is valuable. Thanks for your support!
![FreeScout](https://freescout-helpdesk.github.io/img/screenshots/screenshot.png)
## Table of Contents
* [Features](#features)
* [Demo](#demo)
* [Features](#features)
* [Mobile Apps](#mobile-apps)
* [Requirements](#requirements)
* [Installation](#installation)
* [Cloud Hosted](#cloud-hosted)
* [Modules](#modules)
* [Tools & Integrations](#tools--integrations)
* [News & Updates](#news--updates)
* [Contributing](#contributing)
* [Screenshots](#screenshots)
## Demo
**[Live Demo](https://demo.freescout.net)**
## Features
* No limitations on the number of users, tickets, etc.
* No limitations on the number of users, tickets, mailboxes, etc.
* 100% Mobile-friendly.
* Multilingual (English, French, Japanese, Italian, Polish, Portuguese, Russian, Dutch, German, Spanish, Swedish).
* Multilingual: English, Chinese, Croatian, Czech, Danish, Dutch, Finnish, French, German, Italian, Japanese, Kazakh, Korean, Norwegian, Persian, Polish, Portuguese, Russian, Spanish, Slovak, Swedish, Turkish.
* Seamless email integration.
* Supports modern Microsoft Exchange authentication.
* Fully supports screen readers (for visually impaired).
* Built with strong focus on [security](https://freescout.net/security).
* Web installer & updater.
* Starred conversations.
* Forwarding conversations.
@@ -38,22 +48,21 @@
* Moving conversations between mailboxes.
* Phone conversations.
* Sending new conversations to multiple recipients at once.
* Collision detection – notice is shown when two agents open the same conversation.
* Push notifications.
* Following a conversation.
* Auto reply.
* Notes.
* Internal notes.
* Automatic refreshing of the conversations list without the need to reload the page.
* Pasting screenshots from the clipboard into the reply area.
* Configuring notifications on a per user basis.
* Open tracking.
* Editing threads.
* Search.
* And more…
Need anything else? Suggest features [here](https://freescout.net/request-feature/).
## Demo
**[Live Demo](https://demo.freescout.net)**
## Mobile Apps
Mobile apps support the same functionality and modules as the web version of your FreeScout installation. Both support agents and administrators can use mobile apps.
@@ -68,9 +77,11 @@ FreeScout is a pure PHP/MySQL application, so it can be easily deployed even on
* PHP 7.1 - 8.x
* MySQL 5.0+ / MariaDB 5.0+ / PostgreSQL
There are no minimum system requirements (CPU / RAM) – FreeScout will run on any system.
## Installation
[Installation Guide](https://github.com/freescout-helpdesk/freescout/wiki/Installation-Guide)
[Installation Guide](https://github.com/freescout-help-desk/freescout/wiki/Installation-Guide)
Images & one-click installs:
@@ -78,28 +89,36 @@ Images & one-click installs:
* [Softaculous](http://www.softaculous.com/apps/customersupport/FreeScout) (cPanel, Plesk, ISPmanager, H-Sphere, DirectAdmin, InterWorx)
* [Fantastico](http://ff3.netenberg.com/visitors/scripts/freescout/view) (cPanel, DirectAdmin, ISP Manager, ISP Config)
* [Cloudron](https://cloudron.io/store/net.freescout.cloudronapp.html)
* [Ubuntu](https://github.com/freescout-helpdesk/freescout/wiki/Installation-Guide#interactive-installation-bash-script-ubuntu) (bash script)
* [Ubuntu](https://github.com/freescout-help-desk/freescout/wiki/Installation-Guide#interactive-installation-bash-script-ubuntu) (bash script)
## Cloud Hosted
[Cloud Hosted FreeScout](https://freescout.net/cloud-hosted/)
## Modules
[Available modules](https://freescout.net/modules/)
* [Official Modules](https://freescout.net/modules/)
* [Community Modules](https://freescout.net/community-modules/)
## Tools & Integrations
* [API](https://api-docs.freescout.net/)
* [Migrate to FreeScout](http://freescout.net/migrate/) (from any help desk)
* [Zapier Integration](https://freescout.net/zapier/)
* [Zapier](https://freescout.net/zapier/)
* [Make](https://freescout.net/make-integration/) (Integromat)
## News & Updates
If you would like to be notified about important news and updates you can subscribe [here](https://freescout.net/subscribe/)
Don't miss news, updates and new modules!
[Email Newsletter](https://freescout.net/subscribe/) | [Facebook](https://freescout.net/facebook/) | [Twitter](https://freescout.net/twitter/) | [YouTube](https://freescout.net/youtube/) | [Telegram](https://freescout.net/telegram/) | [RSS](https://freescout.net/feed/)
## Contributing
* [Support the project by leaving a feedback](https://github.com/freescout-helpdesk/freescout/issues/288)
* [Development Guide](https://github.com/freescout-helpdesk/freescout/wiki/Development-Guide)
* [Todo list](https://github.com/freescout-helpdesk/freescout/labels/help%20wanted)
* [Translate](https://github.com/freescout-helpdesk/freescout/wiki/Translate)
* [Support the project by leaving a feedback](https://github.com/freescout-help-desk/freescout/issues/288)
* [Development Guide](https://github.com/freescout-help-desk/freescout/wiki/Development-Guide)
* [Todo list](https://github.com/freescout-help-desk/freescout/labels/help%20wanted)
* [Translate](https://github.com/freescout-help-desk/freescout/wiki/Translate)
## Screenshots

5
SECURITY.md Normal file
View File

@@ -0,0 +1,5 @@
# Security Policy
## Reporting a Vulnerability
Email at support@freescout.net

View File

@@ -64,7 +64,7 @@ class ActivityLog extends Activity
case self::DESCRIPTION_EMAILS_SENDING_ERROR_ALERT:
return __('Error sending alert');
case self::DESCRIPTION_EMAILS_SENDING_WRONG_EMAIL:
return __('Error sending email to the user who replied to notiication from wrong email');
return __('Error sending email to the user who replied to notification from wrong email');
case self::DESCRIPTION_EMAILS_FETCHING_ERROR:
return __('Error fetching email');
case self::DESCRIPTION_SYSTEM_ERROR:
@@ -96,7 +96,7 @@ class ActivityLog extends Activity
case self::NAME_APP_LOGS:
return __('App Logs');
default:
return ucfirst($log_name);
return ucwords(str_replace('_', ' ', $log_name));
}
}

View File

@@ -21,6 +21,8 @@ class Attachment extends Model
CONST DISK = 'private';
CONST MIME_TYPE_MAX_LENGTH = 127;
// https://github.com/Webklex/laravel-imap/blob/master/src/IMAP/Attachment.php
public static $types = [
'message' => self::TYPE_MESSAGE,
@@ -57,14 +59,48 @@ class Attachment extends Model
return false;
}
// Sanitize mime type.
// https://github.com/freescout-helpdesk/freescout/issues/3048
$mime_duplicate = strpos($mime_type, "application/vnd.openxmlformats", 1);
if ($mime_duplicate) {
$mime_type = substr($mime_type, $mime_duplicate);
}
$mime_type = substr($mime_type, 0, self::MIME_TYPE_MAX_LENGTH);
$orig_extension = pathinfo($file_name, PATHINFO_EXTENSION);
// Add underscore to the extension if file has restricted extension.
$file_name = \Helper::sanitizeUploadedFileName($file_name);
$file_name = \Helper::sanitizeUploadedFileName($file_name, $uploaded_file, $content);
// Replace some symbols in file name.
// Gmail can not load image if it contains spaces.
$file_name = preg_replace('/[ #]/', '-', $file_name);
$file_name = preg_replace('/[ #\/]/', '_', $file_name);
// Replace soft hyphens.
$file_name = str_replace(html_entity_decode('&#xAD;'), '_', $file_name);
if (!$file_name) {
if (!$orig_extension) {
preg_match("/.*\/([^\/]+)$/", $mime_type, $m);
if (!empty($m[1])) {
$orig_extension = $m[1];
}
}
$file_name = uniqid();
if ($orig_extension) {
$file_name .= '.'.$orig_extension;
}
}
// https://github.com/freescout-helpdesk/freescout/issues/2385
// Fix for webklex/php-imap.
if ($file_name == 'undefined' && $mime_type == 'message/rfc822') {
$file_name = 'RFC822.eml';
}
// https://github.com/freescout-helpdesk/freescout/issues/1412#issuecomment-1658881493
if ($file_name == 'undefined' && $mime_type == 'text/calendar') {
$file_name = 'calendar.ics';
}
if (strlen($file_name) > 255) {
$without_ext = pathinfo($file_name, PATHINFO_FILENAME);
@@ -114,12 +150,18 @@ class Attachment extends Model
$file_dir .= $i.DIRECTORY_SEPARATOR;
if ($uploaded_file) {
$uploaded_file->storeAs(self::DIRECTORY.DIRECTORY_SEPARATOR.$file_dir, $file_name, ['disk' => self::DISK]);
} else {
Storage::disk(self::DISK)->put($file_path, $content);
try {
if ($uploaded_file) {
$uploaded_file->storeAs(self::DIRECTORY.DIRECTORY_SEPARATOR.$file_dir, $file_name, ['disk' => self::DISK]);
} else {
Storage::disk(self::DISK)->put($file_path, $content);
}
} catch (\Exception $e) {
\Helper::logException($e, '[Attachment::saveFileToDisk()]');
}
\Helper::sanitizeUploadedFileData($file_path, \Helper::getPrivateStorage(), $content);
return [
'file_dir' => $file_dir,
'file_path' => $file_path,
@@ -192,7 +234,7 @@ class Attachment extends Model
}
/**
* Conver type name to integer.
* Convert type name to integer.
*/
public static function typeNameToInt($type_name)
{
@@ -210,7 +252,18 @@ class Attachment extends Model
*/
public function url()
{
return Storage::url($this->getStorageFilePath()).'?id='.$this->id.'&token='.$this->getToken();
$file_path = $this->getStorageFilePath();
// URL must contain only forward slashes.
$file_path = str_replace(DIRECTORY_SEPARATOR, '/', $file_path);
$file_url = Storage::url($file_path);
// Fix percents.
// https://github.com/freescout-helpdesk/freescout/issues/3530
$file_url = str_replace('%', '%25', $file_url);
return $file_url.'?id='.$this->id.'&token='.$this->getToken();
}
/**
@@ -233,7 +286,13 @@ class Attachment extends Model
if ($view) {
$headers['Content-Disposition'] = '';
}
return $this->getDisk()->download($this->getStorageFilePath(), $this->file_name, $headers);
$file_name = $this->file_name;
if ($file_name == "RFC822"){
$file_name = $file_name.'.eml';
}
return $this->getDisk()->download($this->getStorageFilePath(), $file_name, $headers);
}
private function getDisk() {
@@ -270,6 +329,14 @@ class Attachment extends Model
}
}
/**
* Check if the attachment file actually exists on the disk.
*/
public function fileExists()
{
return $this->getDisk()->exists(self::DIRECTORY.DIRECTORY_SEPARATOR.$this->file_dir.$this->file_name);
}
public static function formatBytes($size, $precision = 0)
{
$size = (int) $size;
@@ -325,12 +392,42 @@ class Attachment extends Model
self::whereIn('id', $attachments->pluck('id')->toArray())->delete();
}
/**
* Delete attachments and update Thread & Conversation.
*/
public static function deleteAttachments($attachments)
{
if (!$attachments instanceof \Illuminate\Support\Collection) {
$attachments = collect($attachments);
}
foreach ($attachments as $attachment) {
if ($attachment->thread_id && $attachment->thread
&& count($attachment->thread->attachments) <= 1
) {
$attachment->thread->has_attachments = false;
$attachment->thread->save();
// Update conversation.
$conversation = $attachment->thread->conversation;
foreach ($conversation->threads as $thread) {
if ($thread->has_attachments) {
break 2;
}
}
$conversation->has_attachments = false;
$conversation->save();
}
}
Attachment::deleteForever($attachments);
}
/**
* Create a copy of the attachment and it's file.
*/
public function duplicate($thread_id)
public function duplicate($thread_id = null)
{
$new_attachment = $this->replicate();
$new_attachment->thread_id = $thread_id;
$new_attachment->save();
@@ -353,4 +450,9 @@ class Attachment extends Model
return $new_attachment;
}
public function getFileContents()
{
return $this->getDisk()->get($this->getStorageFilePath());
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class CheckRequirements extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'freescout:check-requirements';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Check console version of PHP';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
// PHP extensions.
$php_extensions = \Helper::checkRequiredExtensions();
$this->comment("PHP Version");
$this->line(' '.str_pad(phpversion(), 30, '.'). ' '.(version_compare(phpversion(), config('installer.core.minPhpVersion'), '>=') ? '<fg=green>OK</>' : '<fg=red>NOT FOUND</>'), false);
$this->comment("PHP Extensions");
$this->output($php_extensions);
// Functions.
$functions = \Helper::checkRequiredFunctions();
$this->comment("Functions");
$this->output($functions);
$this->line('');
}
public function output($items)
{
foreach ($items as $item => $status) {
$this->line(' '.str_pad($item, 30, '.'). ' '.($status ? '<fg=green>OK</>' : '<fg=red>NOT FOUND</>'), false);
}
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class CleanNotificationsTable extends Command
{
const PERIOD = '-6 months';
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'freescout:clean-notifications-table';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Delete old read records from notifications table.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
\DB::table('notifications')->where('created_at', '<', \Carbon\Carbon::now()->modify(self::PERIOD))
->whereNotNull('read_at')
->delete();
$this->info('['.date('Y-m-d H:i:s').'] Deleted old read notifications for: '.self::PERIOD);
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
// When processing attachments FreeScout may create files in /tmp folder.
// So it's good to clean this folder periodically.
class CleanTmp extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'freescout:clean-tmp';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Remove from system temp folder FreeScout files older than 1 week to avoid "No space left on device"';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
\Helper::shellExec('find '.\Helper::getTempDir().' -mtime +7 -type f -name '.\Helper::getTempFilePrefix().'* -exec rm -r -f {} \;');
$this->comment("Done");
}
}

View File

@@ -54,5 +54,9 @@ class ClearCache extends Command
if (!$this->option('doNotGenerateVars')) {
$this->call('freescout:generate-vars');
}
// This should not be done during installation.
if (\Helper::isInstalled()) {
\Helper::queueWorkerRestart();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -66,7 +66,7 @@ class GenerateVars extends Command
}
$this->info("Created: ".substr($file_path, strlen(base_path())+1));
} catch (\Exception $e) {
$msg = "Error occured saving /storage/app/public/js/vars.js. ".\Helper::formatException($e);
$msg = "Error occurred saving /storage/app/public/js/vars.js. ".\Helper::formatException($e);
\Log::error($msg);
$this->error($msg);
}

View File

@@ -43,38 +43,60 @@ class ModuleCheckLicenses extends Command
$this->info('Active modules found: '.count($modules));
$params = [
'url' => \App\Module::getAppUrl(),
'data' => [],
];
foreach ($modules as $module) {
$license = $module->getLicense();
if (!$module->isOfficial() || !$license) {
continue;
}
$params = [
'license' => $license,
'module_alias' => $module->getAlias(),
'url' => \App\Module::getAppUrl(),
];
$result = WpApi::checkLicense($params);
if (!empty($result['status']) && $result['status'] != 'valid') {
$msg = 'Module '.$module->getName().' has been deactivated due to invalid license: '.json_encode($result);
$this->error($module->getName().': '.$msg);
$data['license'] = $license;
$data['module_alias'] = $module->getAlias();
// Deactive module
\App\Module::deactiveModule($module->getAlias(), true);
$params['data'][] = $data;
}
// Inform admin
\Log::error($msg);
activity()
->withProperties([
'error' => $msg,
])
->useLog(\App\ActivityLog::NAME_SYSTEM)
->log(\App\ActivityLog::DESCRIPTION_SYSTEM_ERROR);
} else {
$this->info($module->getName().': OK');
$result = WpApi::checkLicenses($params);
if (!empty($result['statuses'])) {
foreach ($modules as $module) {
$module_alias = $module->getAlias();
foreach ($result['statuses'] as $result_module_alias => $status) {
if ($result_module_alias != $module_alias) {
continue;
}
if (!empty($status) && $status != 'valid') {
$msg = 'Module '.$module->getName().' has been deactivated due to invalid license: '.$status;
$this->error($module->getName().': '.$msg);
// Deactive module
\App\Module::deactiveModule($module->getAlias(), true);
// Inform admin
\Log::error($msg);
activity()
->withProperties([
'error' => $msg,
])
->useLog(\App\ActivityLog::NAME_SYSTEM)
->log(\App\ActivityLog::DESCRIPTION_SYSTEM_ERROR);
} else {
$this->info($module->getName().': OK');
}
continue 2;
}
$this->info($module->getName().': Unknown status');
}
}
$this->info('Checking licenses finished');
}
}

View File

@@ -21,7 +21,7 @@ class ModuleInstall extends Command
*
* @var string
*/
protected $description = 'Install module or all modules (if module_alias is empty)';
protected $description = 'Install module or all modules (if module_alias is empty): run migrations and create a symlink';
/**
* Create a new command instance.
@@ -86,19 +86,53 @@ class ModuleInstall extends Command
$this->call('freescout:clear-cache');
}
// There is similar function in \App\Module.
public function createModulePublicSymlink($module)
{
$from = public_path('modules').DIRECTORY_SEPARATOR.$module->alias;
$to = $module->getExtraPath('Public');
if (file_exists($from)) {
return $this->info('Public symlink already exists');
}
// file_exists() may throw "open_basedir restriction in effect".
try {
symlink($to, $from);
// If module's Public is symlink.
if (is_link($to)) {
@unlink($to);
}
// Symlimk may exist but lead to the module folder in a wrong case.
// So we need first try to remove it.
if (!file_exists($from) || !is_link($from)) {
if (is_dir($from)) {
@rename($from, $from.'_'.date('YmdHis'));
} else {
@unlink($from);
}
}
if (file_exists($from)) {
return $this->info('Public symlink already exists');
}
// Check target.
if (!file_exists($to)) {
// Try to create Public folder.
try {
\File::makeDirectory($to, \Helper::DIR_PERMISSIONS);
} catch (\Exception $e) {
// If it's a broken symlink.
if (is_link($to)) {
@unlink($to);
}
}
}
try {
symlink($to, $from);
} catch (\Exception $e) {
$this->error('Error occurred creating ['.$from.' » '.$to.'] symlink: '.$e->getMessage());
}
} catch (\Exception $e) {
$this->error('Error occured creating ['.$from.'] symlink: '.$e->getMessage());
$this->error('Error occurred creating ['.$from.' » '.$to.'] symlink: '.$e->getMessage());
}
$this->info('The ['.$from.'] symlink has been created');

View File

@@ -21,6 +21,12 @@ class ModuleLaroute extends Command
*/
protected $description = 'Generate a laravel routes JS-file for a module or all modules (if module_alias is empty)';
public $routes;
public $config;
public $generator;
/**
* Create a new command instance.
*

View File

@@ -0,0 +1,169 @@
<?php
/**
* php artisan freescout:module-install modulealias.
*/
namespace App\Console\Commands;
use Illuminate\Console\Command;
class ModuleUpdate extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'freescout:module-update {module_alias?}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Update all modules or a single module (if module_alias is set)';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$install_all = false;
$modules = [];
// We have to clear modules cache first to update modules cache
\Artisan::call('cache:clear');
// Create a symlink for the module (or all modules)
$module_alias = $this->argument('module_alias');
$modules_directory = \WpApi::getModules();
if (\WpApi::$lastError) {
$this->error(__('Error occurred').': '.$lastError['message'].' ('.$lastError['code'].')');
return;
}
$installed_modules = \Module::all();
$counter = 0;
$found = false;
foreach ($modules_directory as $dir_module) {
// Update single module.
if ($module_alias && $dir_module['alias'] != $module_alias) {
continue;
}
$found = true;
// Detect if new version is available.
foreach ($installed_modules as $module) {
if ($module->getAlias() != $dir_module['alias'] /*|| !$module->active()*/) {
continue;
}
if (!empty($dir_module['version']) && version_compare($dir_module['version'], $module->get('version'), '>')) {
$update_result = \App\Module::updateModule($dir_module['alias']);
$this->info('['.$update_result['module_name'].' Module'.']');
if ($update_result['status'] == 'success') {
$this->line($update_result['msg_success']);
} else {
$msg = $update_result['msg'];
if ($update_result['download_msg']) {
$msg .= ' ('.$update_result['download_msg'].')';
}
$this->error('ERROR: '.$msg);
}
if (trim($update_result['output'])) {
$this->line(preg_replace("#\n#", "\n> ", '> '.trim($update_result['output'])));
}
$counter++;
}
}
}
// Update custom modules.
// Loop through each installed module.
foreach ($installed_modules as $module) {
// Skip if the module is an official one
if (\App\Module::isOfficial($module->get('authorUrl'))) {
continue;
}
// Get the URL for the latest version of the module
$latest_version_number_url = $module->get('latestVersionUrl');
if (! $latest_version_number_url) {
continue;
}
// Create a new Guzzle HTTP client
$client = new \GuzzleHttp\Client();
try {
// Send a GET request to the latest version URL
$response = $client->request('GET', $latest_version_number_url, \Helper::setGuzzleDefaultOptions());
// Get the latest version number from the response body
$latest_version = trim((string) $response->getBody());
if (empty($latest_version)) {
continue;
} else {
// Get the current version of the module
$current_version = $module->get('version');
}
} catch (\Exception $e) {
// If there's an exception, skip to the next iteration
continue;
}
// If the latest version is greater than the current version
if (version_compare($latest_version, $current_version, '>')) {
// Update the module
$update_result = \App\Module::updateModule($module->getAlias());
// Print the module name and status
$this->info('[' . $update_result['module_name'] . ' Module' . ']');
if ($update_result['status'] == 'success') {
// If the update was successful, print the success message
$this->line($update_result['msg_success']);
} else {
// If the update failed, print the error message
$msg = $update_result['msg'];
if ($update_result['download_msg']) {
$msg .= ' (' . $update_result['download_msg'] . ')';
}
$this->error('ERROR: ' . $msg);
}
// If there's any output from the update, print it
if (trim($update_result['output'])) {
$this->line(preg_replace("#\n#", "\n> ", '> ' . trim($update_result['output'])));
}
// Increment the counter
$counter ++;
}
}
if ($module_alias && !$found) {
$this->error('Module with the following alias not found: '.$module_alias);
} elseif (!$counter) {
$this->line('All modules are up-to-date');
}
\Artisan::call('freescout:clear-cache');
}
}

View File

@@ -0,0 +1,109 @@
<?php
namespace App\Console\Commands;
use App\Mailbox;
use Webklex\PHPIMAP\IMAP;
use Webklex\PHPIMAP\Message;
use Illuminate\Console\Command;
class ParseEml extends Command
{
/**
* The name and signature of the console command.
*
* --mailbox Any mailbox able to connect via IMAP to its mail server.
*
* @var string
*/
protected $signature = 'freescout:parse-eml {--mailbox=2}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Parse EML file';
/**
* Current mailbox.
*
* Used to process emails sent to multiple mailboxes.
*/
public $mailbox;
/**
* Used to process emails sent to multiple mailboxes.
*/
public $mailboxes;
public $extra_import = [];
/**
* Page size when requesting emails from mail server.
*/
const PAGE_SIZE = 300;
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$email = file_get_contents(storage_path('logs/email.eml'));
if (!str_contains($email, "\r\n")){
$email = str_replace("\n", "\r\n", $email);
}
$raw_header = substr($email, 0, strpos($email, "\r\n\r\n"));
$raw_body = substr($email, strlen($raw_header)+8);
$mailbox = Mailbox::find($this->option('mailbox'));
\Config::set('app.new_fetching_library', 'true');
$client = \MailHelper::getMailboxClient($mailbox);
$client->openFolder("INBOX");
$message = Message::make(/*$this->option('uid')*/null, null, $client, $raw_header, $raw_body, [/*0 => "\\Seen"*/], IMAP::ST_UID);
$this->line('Headers: ');
$this->info($message->getHeader()->raw);
$this->line('From: ');
$this->info(json_encode($message->getFrom()[0] ?? [], JSON_UNESCAPED_UNICODE));
$this->line('Reply-To: ');
$this->info(json_encode($message->getReplyTo()[0] ?? [], JSON_UNESCAPED_UNICODE));
$this->line('In-Reply-To: ');
$this->info($message->getInReplyTo());
$this->line('References: ');
$this->info(json_encode(array_values(array_filter(preg_split('/[, <>]/', $message->getReferences() ?? ''))), JSON_UNESCAPED_UNICODE));
$this->line('Date: ');
$this->info($message->getDate());
$this->line('Subject: ');
$this->info($message->getSubject());
$this->line('Text Body: ');
$this->info($message->getTextBody());
$this->line('HTML Body: ');
$this->info($message->getHTMLBody(false));
$attachments = $message->getAttachments();
if (count($attachments)) {
$this->line('Attachments: ');
foreach ($attachments as $attachment) {
$this->info($attachment->getName());
}
}
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace App\Console\Commands;
use App\Job;
use Illuminate\Console\Command;
class SendMonitor extends Command
{
const CHECK_PERIOD = 12 * 3600; // 12 hours
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'freescout:send-monitor';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Check if queue:work is processing emails queue and show an alert in the web interface if needed';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
// Get SendReplyToCustomer jobs.
$pending_jobs = \App\Job::where('queue', 'emails')
->where('payload', 'like', '{"displayName":"App\\\\\\\\Jobs\\\\\\\\SendReplyToCustomer"%')
->where('available_at', '<', time() - self::CHECK_PERIOD)
->exists();
// Check failed_jobs.
// No need - it can be done via Manage > Alerts > Logs Monitoring
// if (!$pending_jobs) {
// $pending_jobs = \App\FailedJob::where('queue', 'emails')
// ->where('payload', 'like', '{"displayName":"App\\\\\\\\Jobs\\\\\\\\SendReplyToCustomer"%')
// ->where('created_at', '<', time() - self::CHECK_PERIOD)
// ->exists();
// }
if ($pending_jobs) {
\Option::set('send_emails_problem', '1');
$this->error('['.date('Y-m-d H:i:s').'] There are problems with emails queue processing');
} else {
\Option::remove('send_emails_problem');
$this->info('['.date('Y-m-d H:i:s').'] Emails queue processing is working');
}
}
}

View File

@@ -55,7 +55,7 @@ class Update extends Command
\Updater::update();
$this->call('freescout:after-app-update');
} catch (\Exception $e) {
$this->error('Error occured: '.$e->getMessage());
$this->error('Error occurred: '.$e->getMessage());
}
} else {
$this->info('You have the latest version installed: '.config('app.version'));

View File

@@ -10,6 +10,8 @@ use App\Option;
class Kernel extends ConsoleKernel
{
const FETCH_MAX_EXECUTION_TIME = 30; // minutes
/**
* The Artisan commands provided by your application.
*
@@ -22,6 +24,7 @@ class Kernel extends ConsoleKernel
/**
* Define the application's command schedule.
* If --no-interaction flag is set the script will not run 'queue:work' daemon.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
*
@@ -29,8 +32,11 @@ class Kernel extends ConsoleKernel
*/
protected function schedule(Schedule $schedule)
{
// Keep in mind that this function is also called on clearing cache.
// https://github.com/freescout-helpdesk/freescout/issues/3970
if (!$this->isScheduleRun() && !\Helper::isRoute('system.cron')) {
return;
}
// Remove failed jobs
$schedule->command('queue:flush')
->weekly();
@@ -43,11 +49,20 @@ class Kernel extends ConsoleKernel
->everyMinute()
->withoutOverlapping();
$schedule->command('freescout:send-monitor')
// Every 10 minutes.
->cron('*/10 * * * *')
->withoutOverlapping();
$schedule->command('freescout:update-folder-counters')
->hourly();
$schedule->command('freescout:module-check-licenses')
->daily();
$app_key = config('app.key');
if ($app_key) {
$crc = crc32($app_key);
$schedule->command('freescout:module-check-licenses')
->cron((int)($crc % 59).' '.(int)($crc % 23).' * * *');
}
// Check if user finished viewing conversation.
$schedule->command('freescout:check-conv-viewers')
@@ -57,6 +72,12 @@ class Kernel extends ConsoleKernel
$schedule->command('freescout:clean-send-log')
->monthly();
$schedule->command('freescout:clean-notifications-table')
->weekly();
$schedule->command('freescout:clean-tmp')
->daily();
// Logs monitoring.
$alert_logs_period = config('app.alert_logs_period');
if (config('app.alert_logs') && $alert_logs_period) {
@@ -82,9 +103,57 @@ class Kernel extends ConsoleKernel
}
}
$fetch_unseen = (int)config('app.fetch_unseen');
$fetch_command_identifier = \Helper::getWorkerIdentifier('freescout:fetch-emails');
$fetch_command_name = 'freescout:fetch-emails'
. ' --identifier='.$fetch_command_identifier
. ' --unseen='.$fetch_unseen;
// Kill fetch commands running for too long.
// In shedule:run this code is executed every time $schedule->command() in this function is executed.
if (function_exists('shell_exec')) {
$fetch_command_pids = \Helper::getRunningProcesses($fetch_command_identifier);
// The name of the command here must be exactly the same as below!
// Otherwise long fetching will be killed and won't run longer than 1 mintue.
// $schedule->command() creates a new mutex, so we need to store mutex name in cache.
// $mutex_name = $schedule->command($fetch_command_name)
// ->everyMinute() - this also need to be set
// ->skip(function () {
// return true;
// })
// ->mutexName();
$mutex_name = \Cache::get('fetch_mutex_name') ?? '';
// If there is no cache mutext but there are running fetch commands
// it means the mutex had expired after self::FETCH_MAX_EXECUTION_TIME
// and the existing command(s) is running longer than self::FETCH_MAX_EXECUTION_TIME.
if (count($fetch_command_pids) > 0 && !\Cache::get($mutex_name)) {
// Kill freescout:fetch-emails commands running for too long
shell_exec('kill '.implode(' | kill ', $fetch_command_pids));
} elseif (count($fetch_command_pids) == 0) {
// Make sure 'ps' command actually works.
$ps_works = \Helper::getRunningProcesses('schedule:run');
if (count($ps_works)) {
// Previous freescout:fetch-emails may have been killed or errored and did not remove the mutex.
// So here we are forcefully removing the mutex. Otherwise mutex will live for 24 hours.
if (\Cache::has($mutex_name)) {
\Cache::forget($mutex_name);
}
}
}
}
// Fetch emails from mailboxes
$fetch_command = $schedule->command('freescout:fetch-emails')
->withoutOverlapping()
$fetch_command = $schedule->command($fetch_command_name)
// withoutOverlapping() option creates a mutex in the cache
// which by default expires in 24 hours.
// So we are passing an 'expiresAt' parameter to withoutOverlapping() to
// prevent fetching from not being executed when fetching command by some reason
// does not remove the mutex from the cache.
->withoutOverlapping($expiresAt = self::FETCH_MAX_EXECUTION_TIME /* minutes */)
->sendOutputTo(storage_path().'/logs/fetch-emails.log');
switch (config('app.fetch_schedule')) {
@@ -107,25 +176,43 @@ class Kernel extends ConsoleKernel
$fetch_command->everyMinute();
break;
}
\Cache::put('fetch_mutex_name', $fetch_command->mutexName(), self::FETCH_MAX_EXECUTION_TIME);
$schedule = \Eventy::filter('schedule', $schedule);
// If --no-daemonize flag is passed - do not run 'queue:work' daemon.
foreach ($_SERVER['argv'] ?? [] as $arg) {
if ($arg == '--no-interaction') {
return;
}
}
// Command runs as subprocess and sets cache mutex. If schedule:run command is killed
// subprocess does not clear the mutex and it stays in the cache until cache:clear is executed.
// By default, the lock will expire after 24 hours.
$queue_work_params = Config('app.queue_work_params');
// Add identifier to avoid conflicts with other FreeScout instances on the same server.
$queue_work_params['--queue'] .= ','.\Helper::getWorkerIdentifier();
// $schedule->command('queue:work') command below has withoutOverlapping() option,
// which works via special mutex stored in the cache preventing several 'queue:work' to work at the same time.
// So when the cache is cleared the mutex indicating that the 'queue:work' is running is removed,
// and the second 'queue:work' command is launched by cron. When `artisan schedule:run` is executed it sees
// that there are two 'queue:work' processes running and kills them.
// After one minute 'queue:work' is executed by cron via `artisan schedule:run` and works in the background.
if (function_exists('shell_exec')) {
$running_commands = $this->getRunningQueueProcesses();
$running_commands = \Helper::getRunningProcesses();
if (count($running_commands) > 1) {
// Stop all queue:work processes.
// queue:work command is stopped by settings a cache key
\Helper::queueWorkRestart();
\Helper::queueWorkerRestart();
// Sometimes processes stuck and just continue running, so we need to kill them.
// Sleep to let processes stop.
sleep(1);
// Check processes again.
$worker_pids = $this->getRunningQueueProcesses();
$worker_pids = \Helper::getRunningProcesses();
if (count($worker_pids) > 1) {
// Current process also has to be killed, as otherwise it "stucks"
@@ -138,13 +225,25 @@ class Kernel extends ConsoleKernel
// }
shell_exec('kill '.implode(' | kill ', $worker_pids));
}
} elseif (count($running_commands) == 0) {
// Make sure 'ps' command actually works.
$ps_works = \Helper::getRunningProcesses('schedule:run');
if (count($ps_works)) {
// Previous queue:work may have been killed or errored and did not remove the mutex.
// So here we are forcefully removing the mutex.
$mutex_name = $schedule->command('queue:work', $queue_work_params)
->skip(function () {
return true;
})
->mutexName();
if (\Cache::get($mutex_name)) {
\Cache::forget($mutex_name);
}
}
}
}
$queue_work_params = Config('app.queue_work_params');
// Add identifier to avoid conflicts with other FreeScout instances on the same server.
$queue_work_params['--queue'] .= ','.\Helper::getWorkerIdentifier();
$schedule->command('queue:work', $queue_work_params)
->everyMinute()
->withoutOverlapping()
@@ -152,26 +251,16 @@ class Kernel extends ConsoleKernel
}
/**
* Get pids of the queue:work processes.
*
* @return [type] [description]
* This function is needed because every time $schedule->command() is executed
* the schedule() is executed also.
*/
protected function getRunningQueueProcesses()
public function isScheduleRun()
{
$pids = [];
try {
$processes = preg_split("/[\r\n]/", shell_exec("ps aux | grep '".\Helper::getWorkerIdentifier()."'"));
foreach ($processes as $process) {
preg_match("/^[\S]+\s+([\d]+)\s+/", $process, $m);
if (!preg_match("/(sh \-c|grep )/", $process) && !empty($m[1])) {
$pids[] = $m[1];
}
}
} catch (\Exception $e) {
// Do nothing
if (\Helper::isConsole()) {
return true;
} else {
return !empty($_SERVER['argv']) && in_array('schedule:run', $_SERVER['argv']);
}
return $pids;
}
/**

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@
namespace App;
use App\Email;
use App\CustomerChannel;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Storage;
@@ -15,7 +16,6 @@ class Customer extends Model
public $rememberCacheDriver = 'array';
const PHOTO_DIRECTORY = 'customers';
const PHOTO_SIZE = 64; // px
const PHOTO_QUALITY = 77;
/**
@@ -50,10 +50,10 @@ class Customer extends Model
public static $phone_types = [
self::PHONE_TYPE_WORK => 'work',
self::PHONE_TYPE_HOME => 'home',
self::PHONE_TYPE_OTHER => 'other',
self::PHONE_TYPE_MOBILE => 'mobile',
self::PHONE_TYPE_FAX => 'fax',
self::PHONE_TYPE_PAGER => 'pager',
self::PHONE_TYPE_OTHER => 'other',
];
/**
@@ -130,6 +130,7 @@ class Customer extends Model
*/
const SOCIAL_TYPE_TWITTER = 1;
const SOCIAL_TYPE_FACEBOOK = 2;
const SOCIAL_TYPE_TELEGRAM = 14;
const SOCIAL_TYPE_LINKEDIN = 3;
const SOCIAL_TYPE_ABOUTME = 4;
const SOCIAL_TYPE_GOOGLE = 5;
@@ -145,6 +146,7 @@ class Customer extends Model
public static $social_types = [
self::SOCIAL_TYPE_TWITTER => 'twitter',
self::SOCIAL_TYPE_FACEBOOK => 'facebook',
self::SOCIAL_TYPE_TELEGRAM => 'telegram',
self::SOCIAL_TYPE_LINKEDIN => 'linkedin',
self::SOCIAL_TYPE_ABOUTME => 'aboutme',
self::SOCIAL_TYPE_GOOGLE => 'google',
@@ -161,6 +163,7 @@ class Customer extends Model
public static $social_type_names = [
self::SOCIAL_TYPE_TWITTER => 'Twitter',
self::SOCIAL_TYPE_FACEBOOK => 'Facebook',
self::SOCIAL_TYPE_TELEGRAM => 'Telegram',
self::SOCIAL_TYPE_LINKEDIN => 'Linkedin',
self::SOCIAL_TYPE_ABOUTME => 'About.me',
self::SOCIAL_TYPE_GOOGLE => 'Google',
@@ -487,7 +490,7 @@ class Customer extends Model
*/
public function getMainEmail()
{
return optional($this->emails_cached()->first())->email;
return optional($this->emails_cached()->first())->email.'';
}
/**
@@ -593,12 +596,19 @@ class Customer extends Model
$this->emails()->saveMany($new_emails);
}
}
// Create customers for deleted emails
foreach ($deleted_emails as $email) {
$customer = new self();
$customer->save();
$email->customer()->associate($customer);
$email->save();
if (Conversation::where('customer_email', $email->email)->exists()) {
// Create customers for deleted emails
// if there is a conversation with 'customer_email'.
$customer = new self();
$customer->save();
$email->customer()->associate($customer);
$email->save();
} else {
// Simply delete an email.
$email->delete();
}
}
}
}
@@ -626,20 +636,31 @@ class Customer extends Model
*/
public function getPhones($dummy_if_empty = false)
{
$phones = json_decode($this->phones, true);
$phones = json_decode($this->phones ?? '', true);
if (is_array($phones) && count($phones)) {
return $phones;
} elseif ($dummy_if_empty) {
return [[
'type' => self::PHONE_TYPE_WORK,
'value' => '',
'type' => self::PHONE_TYPE_WORK,
]];
} else {
return [];
}
}
public function getMainPhoneValue()
{
return $this->getMainPhoneNumber();
}
public function getMainPhoneNumber()
{
$phones = $this->getPhones();
return $phones[0]['value'] ?? '';
}
/**
* Set phones as JSON.
*
@@ -682,12 +703,14 @@ class Customer extends Model
$phones[] = [
'value' => (string) $phone['value'],
'type' => (int) $phone['type'],
'n' => (string)\Helper::phoneToNumeric($phone['value']),
];
}
} else {
$phones[] = [
'value' => (string) $phone,
'type' => self::PHONE_TYPE_WORK
'type' => self::PHONE_TYPE_WORK,
'n' => (string)\Helper::phoneToNumeric($phone),
];
}
}
@@ -718,8 +741,16 @@ class Customer extends Model
*/
public static function findByPhone($phone)
{
$phone = trim($phone);
return Customer::where('phones', 'LIKE', '%"'.$phone.'"%')->first();
return Customer::byPhone($phone)->first();
}
/**
* Get query.
*/
public static function byPhone($phone)
{
$phone_numeric = \Helper::phoneToNumeric($phone);
return Customer::where('phones', 'LIKE', '%"'.$phone_numeric.'"%');
}
/**
@@ -729,7 +760,7 @@ class Customer extends Model
*/
public function getSocialProfiles($dummy_if_empty = false)
{
$social_profiles = json_decode($this->social_profiles, true);
$social_profiles = json_decode($this->social_profiles ?? '', true);
if (is_array($social_profiles) && count($social_profiles)) {
return json_decode($this->social_profiles, true);
@@ -750,7 +781,7 @@ class Customer extends Model
*/
public function getWebsites($dummy_if_empty = false)
{
$websites = json_decode($this->websites, true);
$websites = json_decode($this->websites ?? '', true);
if (is_array($websites) && count($websites)) {
return $websites;
} elseif ($dummy_if_empty) {
@@ -760,6 +791,12 @@ class Customer extends Model
}
}
public function getMainWebsite()
{
$websites = $this->getWebsites();
return $websites[0] ?? '';
}
/**
* Set websites as JSON.
*
@@ -830,12 +867,16 @@ class Customer extends Model
}
$social_profiles[] = [
// Order of elements in array is important as we rely on it
// when searching customers by social profiles using "like".
'value' => (string) $social_profile['value'],
'type' => $type,
];
}
} else {
$social_profiles[] = [
// Order of elements in array is important as we rely on it
// when searching customers by social profiles using "like".
'value' => (string) $social_profile,
'type' => self::SOCIAL_TYPE_OTHER,
];
@@ -914,9 +955,25 @@ class Customer extends Model
$customer->save();
}
if (empty($email_obj->id) || !$email_obj->customer_id) {
$email_obj->customer()->associate($customer);
$email_obj->save();
if (empty($email_obj->id) || !$email_obj->customer_id || $email_obj->customer_id != $customer->id) {
// Email may have been set in setData().
$save_email = true;
if (!empty($data['emails']) && is_array($data['emails'])) {
foreach ($data['emails'] as $data_email) {
if (is_string($data_email) && $data_email == $email) {
$save_email = false;
break;
}
if (is_array($data_email) && !empty($data_email['value']) && $data_email['value'] == $email) {
$save_email = false;
break;
}
}
}
if ($save_email) {
$email_obj->customer()->associate($customer);
$email_obj->save();
}
}
// Todo: check phone uniqueness.
@@ -946,10 +1003,24 @@ class Customer extends Model
if ($replace_data) {
// Replace data.
$this->fill($data);
$data_prepared = $data;
foreach ($data_prepared as $i => $value) {
if (is_array($value)) {
unset($data_prepared[$i]);
}
}
$this->fill($data_prepared);
$result = true;
} else {
// Update empty fields.
// Do not set last name if first name is already set (and vise versa).
if (!empty($this->first_name) && !empty($data['last_name'])) {
unset($data['last_name']);
}
if (!empty($this->last_name) && !empty($data['first_name'])) {
unset($data['first_name']);
}
foreach ($data as $key => $value) {
if (in_array($key, $this->fillable) && empty($this->$key)) {
$this->$key = $value;
@@ -966,20 +1037,6 @@ class Customer extends Model
if (!in_array($key, $this->json_fields) && $key != 'emails') {
continue;
}
if ($key == 'emails') {
foreach ($value as $email_data) {
if (!empty($email_data['value'])) {
if (!$this->id) {
$this->save();
}
$email_created = Email::create($email_data['value'], $this->id, $email_data['type']);
if ($email_created) {
$result = true;
}
}
}
}
if ($key == 'phones') {
if (isset($value['value'])) {
$this->addPhone($value);
@@ -1006,8 +1063,42 @@ class Customer extends Model
$this->setSocialProfiles($value);
$result = true;
}
if ($key == 'country') {
if (array_search($this->country, Customer::$countries)) {
$this->country = array_search($this->country, Customer::$countries);
}
$this->country = strtoupper(mb_substr($this->country, 0, 2));
$result = true;
}
}
// Emails must be processed the last as they need to save object.
foreach ($data as $key => $value) {
if ($key == 'emails') {
foreach ($value as $email_data) {
if (is_string($email_data)) {
if (!$this->id) {
$this->save();
}
$email_created = Email::create($email_data, $this->id, Email::TYPE_WORK);
if ($email_created) {
$result = true;
}
} elseif (!empty($email_data['value'])) {
if (!$this->id) {
$this->save();
}
$email_created = Email::create($email_data['value'], $this->id, $email_data['type']);
if ($email_created) {
$result = true;
}
}
}
break;
}
}
// Maybe Todo: check phone uniqueness.
// Same phone can be written in many ways, so it's almost useless to chek uniqueness.
@@ -1123,14 +1214,19 @@ class Customer extends Model
->get()
->toArray();
foreach ($data as $values) {
foreach ($list as $email) {
// Dummy customer.
$customer = new Customer();
$customer->email = $values['email'];
$customer->first_name = $values['first_name'];
$customer->last_name = $values['last_name'];
$customer->email = $email;
$result[$values['email']] = $customer->getNameAndEmail();
foreach ($data as $values) {
if (strtolower($values['email']) == strtolower($email)) {
$customer->first_name = $values['first_name'];
$customer->last_name = $values['last_name'];
break;
}
}
$result[$email] = $customer->getNameAndEmail();
}
return $result;
@@ -1174,7 +1270,7 @@ class Customer extends Model
return '';
}
} else {
return asset('/img/default-avatar.png');
return \Eventy::filter('customer.default_avatar', asset('/img/default-avatar.png'), $this);
}
}
@@ -1188,7 +1284,8 @@ class Customer extends Model
*/
public function savePhoto($real_path, $mime_type)
{
$resized_image = \App\Misc\Helper::resizeImage($real_path, $mime_type, self::PHOTO_SIZE, self::PHOTO_SIZE);
$photo_size = config('app.customer_photo_size');
$resized_image = \App\Misc\Helper::resizeImage($real_path, $mime_type, $photo_size, $photo_size);
if (!$resized_image) {
return false;
@@ -1199,7 +1296,7 @@ class Customer extends Model
$dest_dir = pathinfo($dest_path, PATHINFO_DIRNAME);
if (!file_exists($dest_dir)) {
\File::makeDirectory($dest_dir, 0755);
\File::makeDirectory($dest_dir, \Helper::DIR_PERMISSIONS);
}
// Remove current photo
@@ -1263,7 +1360,15 @@ class Customer extends Model
$sp['value_url'] = $sp['value'];
if (!preg_match("/^https?:\/\//i", $sp['value_url'])) {
$sp['value_url'] = 'http://'.$sp['value_url'];
switch ($sp['type']) {
case self::SOCIAL_TYPE_TELEGRAM:
$sp['value_url'] = 'https://t.me/'.$sp['value'];
break;
default:
$sp['value_url'] = 'http://'.$sp['value_url'];
break;
}
}
if (empty($sp['value_url'])) {
$sp['value_url'] = '';
@@ -1280,13 +1385,13 @@ class Customer extends Model
return false;
}
$image_data = file_get_contents($url);
$image_data = \Helper::getRemoteFileContents($url);
if (!$image_data) {
return false;
}
$temp_file = tempnam(sys_get_temp_dir(), 'photo');
$temp_file = \Helper::getTempFileName();
\File::put($temp_file, $image_data);
@@ -1326,4 +1431,123 @@ class Customer extends Model
$meta[$key] = $value;
$this->meta = $meta;
}
public static function getPhoneTypeName($code)
{
$phone_types = [
self::PHONE_TYPE_WORK => __('Work'),
self::PHONE_TYPE_HOME => __('Home'),
self::PHONE_TYPE_OTHER => __('Other'),
self::PHONE_TYPE_MOBILE => __('Mobile'),
self::PHONE_TYPE_FAX => __('Fax'),
self::PHONE_TYPE_PAGER => __('Pager'),
];
return $phone_types[$code] ?? '';
}
public static function isDefaultPhoneType($code)
{
return (self::PHONE_TYPE_WORK == $code);
}
// Method does not check if the customer
// has conversations.
public function deleteCustomer()
{
// Delete emails.
Email::where('customer_id', $this->id)->delete();
$this->delete();
}
public function getChannels()
{
if (!$this->channel || !$this->channel_id) {
return collect([]);
}
return CustomerChannel::where('customer_id', $this->id)->get();
}
public function addChannel($channel, $channel_id)
{
// We are doing this to let existing modules not to throw error
// and as a flag that this customer has record(s) in cucstomer_channel table.
if (!$this->channel || !$this->channel_id) {
$this->channel = $channel;
$this->channel_id = $channel_id;
$this->save();
}
return CustomerChannel::create($this->id, $channel, $channel_id);
}
public static function getCustomerByChannel($channel, $channel_id)
{
$customer_channel = CustomerChannel::where('channel', $channel)
->where('channel_id', $channel_id)
->first();
if ($customer_channel) {
return $customer_channel->customer;
} else {
return null;
}
}
public function getChannelId($channel)
{
return CustomerChannel::where('customer_id', $this->id)
->where('channel', $channel)
->value('channel_id');
}
public static function findCustomersBySocialProfile($type, $value, $exclude_channel = null)
{
$value = mb_strtolower($value);
$like = '%'.$value.'","type":'.$type.'}]';
$customers = Customer::where('social_profiles', \Helper::sqlLikeOperator(), $like)->get();
// Now more prcise filtering.
foreach ($customers as $i => $customer) {
$ok = false;
foreach ($customer->getSocialProfiles() as $social_profile) {
if ($social_profile['type'] == $type
// Try to check username written in different ways:
// - username
// - @username
// - https://example.org/username
&& preg_match("#(^|/|@)".preg_quote($value)."$#", trim(mb_strtolower($social_profile['value'])))
) {
$ok = true;
break;
}
}
if (!$ok) {
$customers->forget($i);
}
}
if ($exclude_channel && count($customers)) {
$exclude_customer_ids = CustomerChannel::whereIn('customer_id', $customers->pluck('id'))
->where('channel', $exclude_channel)
->pluck('customer_id');
return $customers->whereNotIn('customer_id', $exclude_customer_ids);
} else {
return $customers;
}
}
/**
* Get dummy customer.
*/
public static function getDummyCustomer()
{
$customer = new self();
$customer->first_name = __('Customer');
$customer->last_name = '';
return $customer;
}
}

58
app/CustomerChannel.php Normal file
View File

@@ -0,0 +1,58 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class CustomerChannel extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'customer_channel';
public $timestamps = false;
/**
* Cached communication channels.
*/
public static $channels = null;
public function customer()
{
return $this->belongsTo('App\Customer');
}
public static function create($customer_id, $channel, $channel_id)
{
try {
$customer_channel = new self();
$customer_channel->customer_id = $customer_id;
$customer_channel->channel = $channel;
$customer_channel->channel_id = $channel_id;
$customer_channel->save();
return $customer_channel;
} catch (\Exception $e) {
// Already exists.
return null;
}
}
public function getChannelName()
{
return \Eventy::filter('channel.name', '', $this->channel);
}
public static function getChannels()
{
if (self::$channels !== null) {
return self::$channels;
} else {
self::$channels = \Eventy::filter('channels.list', []);
return self::$channels;
}
}
}

View File

@@ -52,7 +52,7 @@ class Email extends Model
// Email validation is not recommended:
// http://stackoverflow.com/questions/201323/using-a-regular-expression-to-validate-an-email-address/201378#201378
// So we just check for @
if (!preg_match('/@/', $email)) {
if (!preg_match('/^.+@.+$/', $email ?? '')) {
return false;
}
$email = filter_var($email, FILTER_SANITIZE_EMAIL);

102
app/Events/RealtimeChat.php Normal file
View File

@@ -0,0 +1,102 @@
<?php
/**
* Fefresh chats list when new thread created in mailbox.
*/
namespace App\Events;
use App\Conversation;
use App\Mailbox;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Queue\SerializesModels;
class RealtimeChat implements ShouldBroadcastNow
{
use SerializesModels;
/**
* The notification data.
*
* @var array
*/
public $data = [];
/**
* Create a new event instance.
*
* @return void
*/
public function __construct($data)
{
$this->data = $data;
}
/**
* Get the channels the event should broadcast on.
*
* @return Channel|array
*/
public function broadcastOn()
{
return new \Illuminate\Broadcasting\Channel($this->channelName());
}
/**
* Get the data to broadcast.
*
* @return array
*/
public function broadcastWith()
{
return $this->data;
}
/**
* Get the broadcast channel name for the event.
*
* @return string
*/
protected function channelName()
{
if (!empty($this->data['mailbox_id'])) {
return 'chat.'.$this->data['mailbox_id'];
} else {
return 'chat.0';
}
}
/**
* Helper funciton.
*/
public static function dispatchSelf($mailbox_id)
{
if (!\Helper::isChatModeAvailable()) {
return;
}
$notification_data = [
'mailbox_id' => $mailbox_id
];
event(new \App\Events\RealtimeChat($notification_data));
}
public static function processPayload($payload)
{
$user = auth()->user();
$mailbox = Mailbox::rememberForever()->find($payload->mailbox_id);
// Check if user can listen to this event.
if (!$user || !$mailbox || !$user->can('viewCached', $mailbox)) {
return [];
}
// Chats are retrieved in the template.
$template_data = [
'mailbox' => $mailbox,
];
$payload->chats_html = \View::make('mailboxes/partials/chat_list')->with($template_data)->render();
return $payload;
}
}

View File

@@ -10,4 +10,27 @@ class FailedJob extends Model
* Automatically converted into Carbon dates.
*/
protected $dates = ['failed_at'];
public $payload_decoded = null;
public function getPayloadDecoded()
{
if ($this->payload_decoded !== null) {
return $this->payload_decoded;
}
$this->payload_decoded = json_decode($this->payload, true);
return $this->payload_decoded;
}
public function getCommand()
{
return \App\Job::getPayloadCommand($this->getPayloadDecoded());
}
public static function retry($job_id)
{
\Artisan::call('queue:retry', ['id' => $job_id]);
}
}

View File

@@ -17,18 +17,18 @@ class Folder extends Model
const TYPE_DRAFTS = 30;
const TYPE_ASSIGNED = 40;
const TYPE_CLOSED = 60;
const TYPE_DELETED = 70;
const TYPE_SPAM = 80;
const TYPE_DELETED = 110;
public static $types = [
self::TYPE_UNASSIGNED => 'Unassigned',
self::TYPE_MINE => 'Mine',
self::TYPE_STARRED => 'Starred',
self::TYPE_DRAFTS => 'Drafts',
self::TYPE_ASSIGNED => 'Assigned',
self::TYPE_CLOSED => 'Closed',
self::TYPE_SPAM => 'Spam',
self::TYPE_DELETED => 'Deleted',
self::TYPE_STARRED => 'Starred',
];
/**
@@ -152,7 +152,6 @@ class Folder extends Model
case self::TYPE_STARRED:
$order_by[] = ['status' => 'asc'];
$order_by[] = ['last_reply_at' => 'desc'];
//$order_by = [['conversation_folder.id' => 'desc']];
break;
case self::TYPE_DRAFTS:
@@ -161,6 +160,7 @@ class Folder extends Model
case self::TYPE_CLOSED:
$order_by = [['closed_at' => 'desc']];
break;
case self::TYPE_SPAM:
$order_by = [['last_reply_at' => 'desc']];
@@ -175,6 +175,26 @@ class Folder extends Model
break;
}
// Process columns sorting.
$sorting = Conversation::getConvTableSorting();
if ($sorting['sort_by'] == 'date') {
if ($sorting['order'] != 'desc') {
foreach ($order_by as $block_i => $block) {
foreach ($block as $field => $order) {
if ($field == 'status') {
unset($order_by[$block_i][$field]);
} else {
if ($order == 'desc') {
$order_by[$block_i][$field] = 'asc';
}
}
}
}
}
} else {
$order_by = [[$sorting['sort_by'] => $sorting['order']]];
}
return $order_by;
}
@@ -202,6 +222,15 @@ class Folder extends Model
}
public function updateCounters()
{
if (config('app.update_folder_counters_in_background')) {
\App\Jobs\UpdateFolderCounters::dispatch($this);
} else {
$this->updateCountersNow();
}
}
public function updateCountersNow()
{
if (\Eventy::filter('folder.update_counters', false, $this)) {
return;
@@ -209,8 +238,8 @@ class Folder extends Model
if ($this->type == self::TYPE_MINE && $this->user_id) {
$this->active_count = Conversation::where('user_id', $this->user_id)
->where('mailbox_id', $this->mailbox_id)
->where('status', Conversation::STATUS_ACTIVE)
->where('state', Conversation::STATE_PUBLISHED)
->where('status', Conversation::STATUS_ACTIVE)
->count();
$this->total_count = Conversation::where('user_id', $this->user_id)
->where('mailbox_id', $this->mailbox_id)
@@ -273,10 +302,14 @@ class Folder extends Model
{
$active_count = $this->active_count;
if ($this->type == self::TYPE_ASSIGNED) {
if ($folders) {
$mine_folder = $folders->firstWhere('type', self::TYPE_MINE);
} else {
$mine_folder = $this->mailbox->folders()->where('type', self::TYPE_MINE)->first();
$mine_folder = \Eventy::filter('folder.active_count_mine_folder', null, $this, $folders);
if (!$mine_folder) {
if ($folders) {
$mine_folder = $folders->firstWhere('type', self::TYPE_MINE);
} elseif ($this->mailbox_id) {
$mine_folder = $this->mailbox->folders()->where('type', self::TYPE_MINE)->first();
}
}
if ($mine_folder) {
@@ -350,4 +383,74 @@ class Folder extends Model
return'last_reply_at';
}
}
public function url($mailbox_id)
{
return \Eventy::filter('folder.url', route('mailboxes.view.folder', ['id'=>$mailbox_id, 'folder_id'=>$this->id]), $mailbox_id, $this);
}
public static function create($data, $unique_per_user = true, $save = true)
{
if (!isset($data['mailbox_id']) || !isset($data['type'])) {
return null;
}
$folder = new Folder ();
$folder->mailbox_id = $data['mailbox_id'];
$folder->type = $data['type'];
if (!empty($data['user_id'])) {
if ($unique_per_user) {
$user_folder = Folder::where('mailbox_id', $data['mailbox_id'])
->where('user_id', $data['user_id'])
->where('type', $data['type'])
->first();
// User folder already exists.
if ($user_folder) {
return $user_folder;
}
}
$folder->user_id = $data['user_id'];
}
if ($save) {
$folder->save();
}
return $folder;
}
/**
* Get meta value.
*/
public function getMeta($key, $default = null)
{
$metas = $this->meta;
if (isset($metas[$key])) {
return $metas[$key];
} else {
return $default;
}
}
/**
* Set meta value.
*/
public function setMeta($key, $value)
{
$metas = $this->meta;
$metas[$key] = $value;
$this->meta = $metas;
}
/**
* Unset thread meta value.
*/
public function unsetMeta($key)
{
$metas = $this->meta;
if (isset($metas[$key])) {
unset($metas[$key]);
$this->meta = $metas;
}
}
}

View File

@@ -2,8 +2,10 @@
namespace App\Http\Controllers\Auth;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Validation\ValidationException;
class LoginController extends Controller
{
@@ -25,7 +27,9 @@ class LoginController extends Controller
*
* @var string
*/
protected $redirectTo = '/';
protected $redirectTo = '/home';
public $decayMinutes = 10; // minutes to lockout
/**
* Create a new controller instance.
@@ -36,4 +40,40 @@ class LoginController extends Controller
{
$this->middleware('guest')->except('logout');
}
/**
* Handle a login request to the application.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\Http\JsonResponse
*/
public function login(Request $request)
{
$this->validateLogin($request);
// If the class is using the ThrottlesLogins trait, we can automatically throttle
// the login attempts for this application. We'll key this by the username and
// the IP address of the client making these requests into this application.
if ($this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
$custom_errors = \Eventy::filter('login.custom_check', [], $request);
if ($custom_errors) {
throw ValidationException::withMessages($custom_errors);
}
if ($this->attemptLogin($request)) {
return $this->sendLoginResponse($request);
}
// If the login attempt was unsuccessful we will increment the number of attempts
// to login and redirect the user back to the login form. Of course, when this
// user surpasses their maximum number of attempts they will get locked out.
$this->incrementLoginAttempts($request);
return $this->sendFailedLoginResponse($request);
}
}

View File

@@ -4,6 +4,9 @@ namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Hash;
use Illuminate\Auth\Events\PasswordReset;
class ResetPasswordController extends Controller
{
@@ -36,4 +39,24 @@ class ResetPasswordController extends Controller
{
$this->middleware('guest');
}
/**
* Reset the given user's password.
*
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @param string $password
* @return void
*/
protected function resetPassword($user, $password)
{
$user->password = Hash::make($password);
$user->setRememberToken(Str::random(60));
$user->save();
event(new PasswordReset($user));
//$this->guard()->login($user);
}
}

1218
app/Http/Controllers/ConversationsController.php Normal file → Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -77,12 +77,12 @@ class CustomersController extends Controller
// Photo
$validator->after(function ($validator) use ($customer, $request) {
if ($request->hasFile('photo_url')) {
$path_url = $customer->savePhoto($request->file('photo_url')->getRealPath(), $request->file('photo_url')->getMimeType());
$path_url = $customer->savePhoto($request->file('photo_url')->getRealPath() ?: $request->file('photo_url')->getPathname(), $request->file('photo_url')->getMimeType());
if ($path_url) {
$customer->photo_url = $path_url;
} else {
$validator->errors()->add('photo_url', __('Error occured processing the image. Make sure that PHP GD extension is enabled.'));
$validator->errors()->add('photo_url', __('Error occurred processing the image. Make sure that PHP GD extension is enabled.'));
}
}
});
@@ -131,7 +131,7 @@ class CustomersController extends Controller
'a_end' => '</a></strong>',
]).' ';
$new_emails_change_customer[] = $email->email;
$new_emails_change_customer[] = $email;
}
}
@@ -157,17 +157,22 @@ class CustomersController extends Controller
$customer->syncEmails($request->emails);
// Update customer_id in all conversations to the current customer
// Update customer_id in all conversations added to the current customer.
foreach ($new_emails_change_customer as $new_email) {
$conversations_to_change_customer = Conversation::where('customer_email', $new_email)->get();
if ($new_email->customer_id) {
$conversations_to_change_customer = Conversation::where('customer_id', $new_email->customer_id)->get();
} else {
// This does not work for phone conversations.
$conversations_to_change_customer = Conversation::where('customer_email', $new_email->email)->get();
}
foreach ($conversations_to_change_customer as $conversation) {
// We have to pass user to create line item and let others know that customer has changed.
// Conversation may be even in other mailbox where user does not have an access.
$conversation->changeCustomer($new_email, $customer, auth()->user());
$conversation->changeCustomer($new_email->email, $customer, auth()->user());
}
}
// Update customer in conversations for removed emails
// Update customer in conversations for emails removed from current customer.
foreach ($removed_emails as $removed_email) {
$email = Email::where('email', $removed_email)->first();
if ($email) {
@@ -212,7 +217,7 @@ class CustomersController extends Controller
$user = User::findOrFail($id);
$this->authorize('update', $user);
$user->mailboxes()->sync($request->mailboxes);
$user->mailboxes()->sync($request->mailboxes ?: []);
\Session::flash('flash_success', __('Permissions saved successfully'));
@@ -285,7 +290,11 @@ class CustomersController extends Controller
->orWhere('last_name', 'like', '%'.$q.'%');
}
if ($request->search_by == 'phone') {
$customers_query->where('customers.phones', 'like', '%'.$q.'%');
$phone_numeric = \Helper::phoneToNumeric($q);
if (!$phone_numeric) {
$phone_numeric = $q;
}
$customers_query->where('customers.phones', 'like', '%'.$phone_numeric.'%');
}
$customers = $customers_query->paginate(20);
@@ -306,7 +315,8 @@ class CustomersController extends Controller
// Get phone which matches
$phones = $customer->getPhones();
foreach ($phones as $phone) {
if (strstr($phone['value'], $q)) {
$phone_numeric = \Helper::phoneToNumeric($q);
if (strstr($phone['value'], $q) || strstr($phone['n'] ?? '', $phone_numeric)) {
$text = $phone['value'];
if ($customer->getFullName()) {
$text .= ' — '.$customer->getFullName();
@@ -328,7 +338,8 @@ class CustomersController extends Controller
if (!empty($request->use_id)) {
$id = $customer->id;
} else {
$id = $customer->email;
// https://github.com/freescout-helpdesk/freescout/issues/4057
$id = $customer->email ?? $customer->getMainEmail();
}
}
$response['results'][] = [
@@ -401,7 +412,7 @@ class CustomersController extends Controller
}
if ($response['status'] == 'error' && empty($response['msg'])) {
$response['msg'] = 'Unknown error occured';
$response['msg'] = 'Unknown error occurred';
}
return \Response::json($response);

332
app/Http/Controllers/MailboxesController.php Normal file → Executable file
View File

@@ -63,6 +63,8 @@ class MailboxesController extends Controller
*/
public function createSave(Request $request)
{
$invalid = false;
$this->authorize('create', 'App\Mailbox');
$validator = Validator::make($request->all(), [
@@ -72,7 +74,12 @@ class MailboxesController extends Controller
// //event(new Registered($user = $this->create($request->all())));
if ($validator->fails()) {
if (Mailbox::userEmailExists($request->email)) {
$invalid = true;
$validator->errors()->add('email', __('There is a user with such email. Users and mailboxes can not have the same email addresses.'));
}
if ($invalid || $validator->fails()) {
return redirect()->route('mailboxes.create')
->withErrors($validator)
->withInput();
@@ -80,9 +87,10 @@ class MailboxesController extends Controller
$mailbox = new Mailbox();
$mailbox->fill($request->all());
$mailbox->save();
$mailbox->users()->sync($request->users);
$mailbox->users()->sync($request->users ?: []);
$mailbox->syncPersonalFolders($request->users);
\Session::flash('flash_success_floating', __('Mailbox created successfully'));
@@ -101,7 +109,12 @@ class MailboxesController extends Controller
$accessible_route = '';
$mailbox_settings = $user->mailboxSettings($mailbox->id);
$access_permissions = json_decode($mailbox_settings->access);
if (!is_array($mailbox_settings->access)) {
$access_permissions = json_decode($mailbox_settings->access ?? '');
} else {
$access_permissions = $mailbox_settings->access;
}
if ($access_permissions && is_array($access_permissions)) {
foreach ($access_permissions as $perm) {
@@ -142,6 +155,7 @@ class MailboxesController extends Controller
*/
public function updateSave($id, Request $request)
{
$invalid = false;
$mailbox = Mailbox::findOrFail($id);
$user = auth()->user();
@@ -152,6 +166,11 @@ class MailboxesController extends Controller
if ($user->can('updateSettings', $mailbox)) {
// Checkboxes
$request->merge([
'aliases_reply' => ($request->filled('aliases_reply') ?? false),
]);
// if not admin, the text only fields don't pass so spike them into the request.
if (!auth()->user()->isAdmin()) {
$request->merge([
@@ -163,7 +182,7 @@ class MailboxesController extends Controller
$validator = Validator::make($request->all(), [
'name' => 'required|string|max:40',
'email' => 'required|string|email|max:128|unique:mailboxes,email,'.$id,
'aliases' => 'nullable|string|max:255',
'aliases' => 'nullable|string',
'from_name' => 'required|integer',
'from_name_custom' => 'nullable|string|max:128',
'ticket_status' => 'required|integer',
@@ -172,8 +191,14 @@ class MailboxesController extends Controller
]);
//event(new Registered($user = $this->create($request->all())));
if (Mailbox::userEmailExists($request->email)) {
$invalid = true;
$validator->errors()->add('email', __('There is a user with such email. Users and mailboxes can not have the same email addresses.'));
}
if ($validator->fails()) {
$validator = \Eventy::filter('mailbox.settings_validator', $validator, $mailbox, $request);
if ($invalid || count($validator->errors()) || $validator->fails()) {
return redirect()->route('mailboxes.update', ['id' => $id])
->withErrors($validator)
->withInput();
@@ -192,7 +217,10 @@ class MailboxesController extends Controller
}
}
\Eventy::action('mailbox.settings_before_save', $mailbox, $request);
$mailbox->fill($request->all());
$mailbox->signature = \Helper::stripDangerousTags($mailbox->signature);
$mailbox->save();
@@ -242,7 +270,7 @@ class MailboxesController extends Controller
$user = auth()->user();
$mailbox->users()->sync($request->users);
$mailbox->users()->sync(\Eventy::filter('mailbox.permission_users', $request->users, $id) ?: []);
$mailbox->syncPersonalFolders($request->users);
// Save admins settings.
@@ -264,7 +292,7 @@ class MailboxesController extends Controller
$access = [];
$mailbox_with_settings = $mailbox_user->mailboxesWithSettings()->where('mailbox_id', $id)->first();
foreach (\App\Mailbox::$access_permissions as $perm) {
foreach (Mailbox::$access_permissions as $perm) {
if (!empty($request->managers[$mailbox_user->id]['access'][$perm])) {
$access[] = $request->managers[$mailbox_user->id]['access'][$perm];
}
@@ -320,7 +348,7 @@ class MailboxesController extends Controller
}
// Do not save dummy password.
if (preg_match("/^\*+$/", $request->out_password)) {
if (preg_match("/^\*+$/", $request->out_password ?? '')) {
$params = $request->except(['out_password']);
} else {
$params = $request->all();
@@ -333,7 +361,7 @@ class MailboxesController extends Controller
}
// Sometimes background job continues to use old connection settings.
\Helper::queueWorkRestart();
\Helper::queueWorkerRestart();
\Session::flash('flash_success_floating', __('Connection settings saved!'));
@@ -343,16 +371,16 @@ class MailboxesController extends Controller
/**
* Mailbox incoming settings.
*/
public function connectionIncoming($id)
public function connectionIncoming($id, Request $request)
{
$mailbox = Mailbox::findOrFail($id);
$this->authorize('admin', $mailbox);
$fields = [
'in_server' => $mailbox->in_server,
'in_port' => $mailbox->in_port,
'in_username' => $mailbox->in_username,
'in_password' => $mailbox->in_password,
'in_server' => $mailbox->in_server ?? '',
'in_port' => $mailbox->in_port ?? '',
'in_username' => $mailbox->in_username ?? '',
'in_password' => $mailbox->in_password ?? '',
];
$validator = Validator::make($fields, [
@@ -392,12 +420,14 @@ class MailboxesController extends Controller
]);
// Do not save dummy password.
if (preg_match("/^\*+$/", $request->in_password)) {
if (preg_match("/^\*+$/", $request->in_password ?? '')) {
$params = $request->except(['in_password']);
} else {
$params = $request->all();
}
\Eventy::action('mailbox.incoming_settings_before_save', $mailbox, $request);
$mailbox->fill($params);
// Save IMAP Folders.
@@ -420,15 +450,11 @@ class MailboxesController extends Controller
/**
* View mailbox.
*/
public function view($id, $folder_id = null)
public function view(Request $request, $id, $folder_id = null)
{
$user = auth()->user();
if ($user->isAdmin()) {
$mailbox = Mailbox::findOrFailWithSettings($id, $user->id);
} else {
$mailbox = Mailbox::findOrFail($id);
}
$mailbox = Mailbox::findOrFailWithSettings($id, $user->id);
$this->authorize('viewCached', $mailbox);
$folders = $mailbox->getAssesibleFolders();
@@ -449,7 +475,9 @@ class MailboxesController extends Controller
$this->authorize('view', $folder);
$query_conversations = Conversation::getQueryByFolder($folder, $user->id);
$conversations = $folder->queryAddOrderBy($query_conversations)->paginate(Conversation::DEFAULT_LIST_SIZE);
$conversations = $folder->queryAddOrderBy($query_conversations)->paginate(
Conversation::DEFAULT_LIST_SIZE, ['*'], 'page', $request->get('page')
);
return view('mailboxes/view', [
'mailbox' => $mailbox,
@@ -534,6 +562,7 @@ class MailboxesController extends Controller
}
$mailbox->fill($request->all());
$mailbox->auto_reply_message = \Helper::stripDangerousTags($mailbox->auto_reply_message);
$mailbox->save();
@@ -595,17 +624,22 @@ class MailboxesController extends Controller
}
if (!$response['msg']) {
$test_result = false;
$test_result = [
'status' => 'error'
];
try {
$test_result = \App\Misc\Mail::sendTestMail($request->to, $mailbox);
$test_result = \MailHelper::sendTestMail($request->to, $mailbox);
} catch (\Exception $e) {
$response['msg'] = $e->getMessage();
$test_result['msg'] = $e->getMessage();
}
if (!$test_result && !$response['msg']) {
$response['msg'] = __('Error occurred sending email. Please check your mail server logs for more details.');
if ($test_result['status'] == 'error') {
$response['msg'] = $test_result['msg']
?: __('Error occurred sending email. Please check your mail server logs for more details.');
}
$response['log'] = $test_result['log'] ?? '';
}
if (!$response['msg']) {
@@ -628,29 +662,34 @@ class MailboxesController extends Controller
$response['msg'] = __('Not enough permissions');
}
$response = \Eventy::filter('mailbox.fetch_test', $response, $mailbox);
$tested = (isset($response['tested']) && $response['tested'] === true);
// Check if outgoing port is open.
if (!$response['msg']) {
if (!$response['msg'] && !$tested) {
$test_result = \Helper::checkPort($mailbox->in_server, $mailbox->in_port);
if (!$test_result) {
$response['msg'] = __(':host is not available on :port port. Make sure that :host address is correct and that outgoing port :port on YOUR server is open.', ['host' => '<strong>'.$mailbox->in_server.'</strong>', 'port' => '<strong>'.$mailbox->in_port.'</strong>']);
}
}
if (!$response['msg']) {
if (!$response['msg'] && !$tested) {
$test_result = false;
try {
$test_result = \MailHelper::fetchTest($mailbox);
} catch (\Exception $e) {
$response['msg'] = $e->getMessage();
}
$test_result = \MailHelper::fetchTest($mailbox);
if (!$test_result && !$response['msg']) {
$response['log'] = $test_result['log'] ?? '';
if ($test_result['result'] != 'success' && $test_result['msg']) {
$response['msg'] = $test_result['msg'];
}
if ($test_result['result'] != 'success' && !$response['msg']) {
$response['msg'] = __('Error occurred connecting to the server');
}
}
if (!$response['msg']) {
if (!$response['msg'] && !$tested) {
$response['status'] = 'success';
}
break;
@@ -675,23 +714,33 @@ class MailboxesController extends Controller
$imap_folders = $client->getFolders();
$response['folders'] = [];
if (count($imap_folders)) {
foreach ($imap_folders as $imap_folder) {
if (!empty($imap_folder->name)) {
$response['folders'][] = $imap_folder->name;
}
// Maybe we need a recursion here.
if (!empty($imap_folder->children)) {
foreach ($imap_folder->children as $child_imap_folder) {
if (!empty($child_imap_folder->fullName)) {
$response['folders'][] = $child_imap_folder->fullName;
}
}
}
}
$response = $this->interateFolders($response, $imap_folders);
$response['folders'] = array_values(array_unique($response['folders']));
}
if (count($response['folders'])) {
// https://github.com/freescout-helpdesk/freescout/issues/3933
// Exclude duplicate INBOX.Name and Name folders.
// $folder_excluded = false;
// foreach ($response['folders'] as $i => $folder_name) {
// if (\Str::startsWith($folder_name, 'INBOX.')) {
// foreach ($response['folders'] as $folder_name2) {
// if ($folder_name != $folder_name2 && $folder_name == 'INBOX.'.$folder_name2) {
// unset($response['folders'][$i]);
// $folder_excluded = true;
// continue 2;
// }
// }
// }
// }
// if ($folder_excluded) {
// $response['folders'] = array_values($response['folders']);
// }
$response['msg_success'] = __('IMAP folders retrieved: '.implode(', ', $response['folders']));
} else {
$response['msg_success'] = __('Connected, but no IMAP folders found');
@@ -715,15 +764,20 @@ class MailboxesController extends Controller
$response['msg'] = __('Mailbox not found');
} elseif (!$user->can('admin', $mailbox)) {
$response['msg'] = __('Not enough permissions');
} elseif (!Hash::check($request->password, $user->password)) {
} elseif (!$user->isDummyPassword() && !Hash::check($request->password ?? '', $user->password)) {
$response['msg'] = __('Please double check your password, and try again');
}
if (!$response['msg']) {
// Remove threads and conversations
Thread::whereIn('conversation_id', $mailbox->conversations()->pluck('id')->toArray())
->delete();
// Remove threads and conversations.
$conversation_ids = $mailbox->conversations()->pluck('id')->toArray();
for ($i=0; $i < ceil(count($conversation_ids) / \Helper::IN_LIMIT); $i++) {
$slice_ids = array_slice($conversation_ids, $i*\Helper::IN_LIMIT, \Helper::IN_LIMIT);
Thread::whereIn('conversation_id', $slice_ids)->delete();
}
$mailbox->conversations()->delete();
$mailbox->users()->sync([]);
$mailbox->folders()->delete();
@@ -743,8 +797,6 @@ class MailboxesController extends Controller
if (!$mailbox) {
$response['msg'] = __('Mailbox not found');
} elseif (!$user->isAdmin()) {
$response['msg'] = __('Not enough permissions');
}
if (!$response['msg']) {
@@ -752,7 +804,7 @@ class MailboxesController extends Controller
$mailbox_user = $user->mailboxesWithSettings()->where('mailbox_id', $mailbox->id)->first();
if (!$mailbox_user) {
// User may not be connected to the mailbox yet
$user->mailboxes()->attach($id);
$user->mailboxes()->attach($mailbox->id);
$mailbox_user = $user->mailboxesWithSettings()->where('mailbox_id', $mailbox->id)->first();
}
$mailbox_user->settings->mute = (bool)$request->mute;
@@ -768,10 +820,176 @@ class MailboxesController extends Controller
}
if ($response['status'] == 'error' && empty($response['msg'])) {
$response['msg'] = 'Unknown error occured';
$response['msg'] = 'Unknown error occurred';
}
return \Response::json($response);
}
// Recursively interate over folders.
public function interateFolders($response, $imap_folders, $subfolder = false) {
foreach ($imap_folders as $imap_folder) {
if (!empty($imap_folder->name) && !$subfolder) {
$response['folders'][] = $imap_folder->name;
}
// Check for children and recurse.
if (!empty($imap_folder->children)) {
$response = $this->interateFolders($response, $imap_folder->children, true);
}
// Old library.
if (!empty($imap_folder->fullName)) {
$response['folders'][] = $imap_folder->fullName;
} elseif (!empty($imap_folder->full_name)) {
// New library.
$response['folders'][] = $imap_folder->full_name;
}
}
return $response;
}
public function oauth(Request $request)
{
$mailbox_id = $request->id ?? '';
$provider = $request->provider ?? '';
$in_out = $request->in_out ?? 'in';
$state_data = [];
if (!empty($request->state)) {
$state_data = json_decode($request->state, true);
if (!empty($state_data['mailbox_id'])) {
$mailbox_id = $state_data['mailbox_id'];
}
if (!empty($state_data['provider'])) {
$provider = $state_data['provider'];
}
if (!empty($state_data['in_out'])) {
$in_out = $state_data['in_out'];
}
}
// MS Exchange.
if (!empty($request->error) && $request->error == 'invalid_request' && !empty($request->error_description)) {
return htmlspecialchars($request->error_description);
}
if (empty($provider)) {
return 'Invalid oAuth Provider';
}
$mailbox = Mailbox::findOrFail($mailbox_id);
$this->authorize('admin', $mailbox);
if (empty($mailbox)) {
return __('Mailbox not found').': '.$mailbox_id;
}
if ($in_out == 'in') {
$username = $mailbox->in_username;
$password = $mailbox->in_password;
} else {
$username = $mailbox->out_username;
$password = $mailbox->out_password;
}
if (empty($username)) {
return 'Enter oAuth Client ID as Username and save mailbox settings';
}
if (empty($password)) {
return 'Enter oAuth Client Secret as Password and save mailbox settings';
}
$session_data = [];
if (\Session::get('mailbox_oauth_'.$provider.'_'.$mailbox_id)) {
$session_data = \Session::get('mailbox_oauth_'.$provider.'_'.$mailbox_id);
}
if (empty($request->code)) {
// Start.
$state = [
'provider' => $provider,
'mailbox_id' => $mailbox_id,
'in_out' => $in_out,
'state' => crc32($username.$password),
];
$url = \MailHelper::oauthGetAuthorizationUrl(\MailHelper::OAUTH_PROVIDER_MICROSOFT, [
'state' => json_encode($state),
'client_id' => $username,
]);
if ($url) {
\Session::put('mailbox_oauth_'.$provider.'_'.$mailbox_id, $state);
// [
// 'provider' => $request->provider,
// 'mailbox_id' => $request->mailbox_id,
// 'state' => $provider->getState(),
// ]);
return redirect()->away($url);
} else {
return 'Could not generate authorization URL: check Client ID (Username) and Client Secret (Password)';
}
// Check given state against previously stored one to mitigate CSRF attack
} elseif (empty($request->state) || ($state_data['state'] ?? '') !== ($session_data['state'] ?? '')) {
\Session::forget('mailbox_oauth_'.$provider.'_'.$mailbox_id);
return 'Invalid oAuth state';
} else {
// state is set.
// Try to get an access token (using the authorization code grant)
$token_data = \MailHelper::oauthGetAccessToken(\MailHelper::OAUTH_PROVIDER_MICROSOFT, [
'client_id' => $username,
'client_secret' => $password,
'code' => $request->code,
]);
if (!empty($token_data['a_token'])) {
// Set username and password for the oppozite in_out.
if ($in_out == 'in') {
if (empty($mailbox->out_server)
|| (trim($mailbox->out_server) == \MailHelper::OAUTH_MICROSOFT_SMTP
&& (!$mailbox->out_username || $mailbox->out_username == $username))
) {
$mailbox->out_username = $username;
$mailbox->out_password = $password;
}
//$mailbox->out_method = Mailbox::OUT_METHOD_SMTP;
} else {
$mailbox->in_username = $username;
$mailbox->in_password = $password;
}
$mailbox->setMetaParam('oauth', $token_data, true);
} elseif (!empty($token_data['error'])) {
return __('Error occurred').': '.htmlspecialchars($token_data['error']);
}
if ($in_out == 'in') {
$route = 'mailboxes.connection.incoming';
} else {
$route = 'mailboxes.connection';
}
return redirect()->route($route, ['id' => $mailbox_id]);
}
}
public function oauthDisconnect(Request $request)
{
$mailbox_id = $request->id ?? '';
$provider = $request->provider ?? '';
$in_out = $request->in_out ?? 'in';
$mailbox = Mailbox::findOrFail($mailbox_id);
$this->authorize('admin', $mailbox);
// oAuth Disconnect.
$mailbox->removeMetaParam('oauth', true);
if ($in_out == 'in') {
$route = 'mailboxes.connection.incoming';
} else {
$route = 'mailboxes.connection';
}
return \MailHelper::oauthDisconnect($provider, route($route, ['id' => $mailbox_id]));
}
}

View File

@@ -28,13 +28,18 @@ class ModulesController extends Controller
{
$installed_modules = [];
$modules_directory = [];
$third_party_modules = [];
$all_modules = [];
$flashes = [];
$updates_available = false;
$flash = \Cache::get('modules_flash');
if ($flash) {
$flashes[] = $flash;
if (is_array($flash) && !isset($flash['text'])) {
$flashes = $flash;
} else {
$flashes[] = $flash;
}
\Cache::forget('modules_flash');
}
@@ -54,8 +59,7 @@ class ModulesController extends Controller
\Module::clearCache();
$modules = \Module::all();
foreach ($modules as $module) {
$img = '';
$installed_modules[] = [
$module_data = [
'alias' => $module->getAlias(),
'name' => $module->getName(),
'description' => $module->getDescription(),
@@ -67,14 +71,19 @@ class ModulesController extends Controller
'requiredPhpExtensions' => $module->get('requiredPhpExtensions'),
'requiredPhpExtensionsMissing' => \App\Module::getMissingExtensions($module->get('requiredPhpExtensions')),
'requiredModulesMissing' => \App\Module::getMissingModules($module->get('requiredModules'), $modules),
'img' => $img,
'img' => $module->get('img'),
'active' => $module->active(), //\App\Module::isActive($module->getAlias()),
'installed' => true,
'activated' => \App\Module::isLicenseActivated($module->getAlias(), $module->get('authorUrl')),
'license' => \App\Module::getLicense($module->getAlias()),
// Update configuration for third party modules
'latestVersionNumberUrl' => $module->get('latestVersionUrl'),
'latestVersionZipUrl' => $module->get('latestVersionZipUrl'),
// Determined later
'new_version' => '',
];
$module_data = \App\Module::formatModuleData($module_data);
$installed_modules[] = $module_data;
}
// No need, as we update modules list on each page load
@@ -86,6 +95,9 @@ class ModulesController extends Controller
// Prepare directory modules
if (is_array($modules_directory)) {
foreach ($modules_directory as $i_dir => $dir_module) {
$modules_directory[$i_dir] = \App\Module::formatModuleData($dir_module);
// Remove modules without aliases
if (empty($dir_module['alias'])) {
unset($modules_directory[$i_dir]);
@@ -118,17 +130,76 @@ class ModulesController extends Controller
}
$modules_directory[$i_dir]['active'] = \App\Module::isActive($dir_module['alias']);
$modules_directory[$i_dir]['activated'] = false;
// Do not show third-party modules in Modules Directory.
if (\App\Module::isThirdParty($dir_module)) {
$third_party_modules[] = $modules_directory[$i_dir];
unset($modules_directory[$i_dir]);
}
}
} else {
$modules_directory = [];
}
// Loop through each installed module
foreach ($installed_modules as $i_installed => $module) {
// Check if the module is an official one
if (\App\Module::isOfficial($module['authorUrl'])) {
continue;
}
// Get the URL for the latest version of the module
$latest_version_number_url = $module['latestVersionNumberUrl'] ?? null;
if (! $latest_version_number_url) {
continue;
}
// Create a new Guzzle HTTP client
$client = new \GuzzleHttp\Client();
try {
// Send a GET request to the latest version URL
$response = $client->request('GET', $latest_version_number_url, \Helper::setGuzzleDefaultOptions());
// Get the latest version number from the response body
$latest_version = trim((string) $response->getBody());
if (empty($latest_version)) {
continue;
} else {
// Get the current version of the module
$current_version = $module['version'];
}
} catch (\Exception $e) {
// If there's an exception, skip to the next iteration
continue;
}
// If the latest version is greater than the current version
if (version_compare($latest_version, $current_version, '>')) {
// Update the installed module's version
$installed_modules[ $i_installed ]['new_version'] = $latest_version;
// Set the flag to indicate that updates are available
$updates_available = true;
}
}
// Check modules symlinks. Somestimes instead of symlinks folders with files appear.
$invalid_symlinks = \App\Module::checkSymlinks(
collect($installed_modules)->where('active', true)->pluck('alias')->toArray()
);
// Sort all modules.
asort($all_modules);
return view('modules/modules', [
'installed_modules' => $installed_modules,
'modules_directory' => $modules_directory,
'third_party_modules' => $third_party_modules,
'flashes' => $flashes,
'updates_available' => $updates_available,
'all_modules' => $all_modules,
'invalid_symlinks' => $invalid_symlinks,
]);
}
@@ -221,10 +292,10 @@ class ModulesController extends Controller
\Session::flash('flash_error_floating', $response['msg']);
}
\Session::flash('flash_error_unescaped', __('Error occured downloading the module. Please :%a_being%download:%a_end% module manually and extract into :folder', ['%a_being%' => '<a href="'.$license_details['download_link'].'" target="_blank">', '%a_end%' => '</a>', 'folder' => '<strong>'.\Module::getPath().'</strong>']));
\Session::flash('flash_error_unescaped', __('Error occurred downloading the module. Please :%a_being%download:%a_end% module manually and extract into :folder', ['%a_being%' => '<a href="'.$license_details['download_link'].'" target="_blank">', '%a_end%' => '</a>', 'folder' => '<strong>'.\Module::getPath().'</strong>']));
}
} else {
$response['msg'] = __('Error occured. Please try again later.');
$response['msg'] = __('Error occurred. Please try again later.');
}
} else {
// Just activate license
@@ -234,9 +305,9 @@ class ModulesController extends Controller
$response['status'] = 'success';
}
} elseif (!empty($result['error'])) {
$response['msg'] = $this->getErrorMessage($result['error'], $result);
$response['msg'] = \App\Module::getErrorMessage($result['error'], $result);
} else {
$response['msg'] = __('Error occured. Please try again later.');
$response['msg'] = __('Error occurred. Please try again later.');
}
}
}
@@ -279,9 +350,25 @@ class ModulesController extends Controller
case 'inactive':
$response['msg'] = __('License key has not been activated yet');
case 'site_inactive':
$response['msg'] = __('Your domain is deactivated');
$response['msg'] = __('No activations left for this license key').' ('.__("Use 'Deactivate License' link above to transfer license key from another domain").')';
break;
}
} elseif (!empty($license_result['status']) && $license_result['status'] == 'inactive') {
// Activate the license.
$result = WpApi::activateLicense($params);
if (WpApi::$lastError) {
$response['msg'] = WpApi::$lastError['message'];
} elseif (!empty($result['code']) && !empty($result['message'])) {
$response['msg'] = $result['message'];
} else {
if (!empty($result['status']) && $result['status'] == 'valid') {
// Success.
} elseif (!empty($result['error'])) {
$response['msg'] = \App\Module::getErrorMessage($result['error'], $result);
} else {
// Some unknown error. Do nothing.
}
}
}
}
}
@@ -300,7 +387,7 @@ class ModulesController extends Controller
}
$type = 'danger';
$msg = __('Error occured activating ":name" module', ['name' => $name]);
$msg = __('Error occurred activating ":name" module', ['name' => $name]);
if (session('flashes_floating') && is_array(session('flashes_floating'))) {
// If there was any error, module has been deactivated via modules.register_error filter
$msg = '';
@@ -318,10 +405,10 @@ class ModulesController extends Controller
// Check public folder.
if ($module && file_exists($module->getPath().DIRECTORY_SEPARATOR.'Public')) {
$public_folder = public_path().\Module::getPublicPath($alias);
if (!file_exists($public_folder)) {
$symlink_path = public_path().\Module::getPublicPath($alias);
if (!file_exists($symlink_path)) {
$type = 'danger';
$msg = 'Error occured creating a module symlink ('.$public_folder.'). Please check folder permissions.';
$msg = 'Error occurred creating a module symlink ('.$symlink_path.'). Please check folder permissions.';
\App\Module::setActive($alias, false);
\Artisan::call('freescout:clear-cache');
}
@@ -329,7 +416,7 @@ class ModulesController extends Controller
if ($type == 'success') {
// Migrate again, in case migration did not work in the moment the module was activated.
\Artisan::call('migrate');
\Artisan::call('migrate', ['--force' => true]);
}
// \Session::flash does not work after BufferedOutput
@@ -360,10 +447,10 @@ class ModulesController extends Controller
}
$type = 'danger';
$msg = __('Error occured deactivating :name module', ['name' => $name]);
$msg = __('Error occurred deactivating :name module', ['name' => $name]);
if (strstr($output, 'Configuration cached successfully')) {
$type = 'success';
$msg = __('":name" module successfully DEactivated!', ['name' => $name]);
$msg = __('":name" module successfully Deactivated!', ['name' => $name]);
}
// \Session::flash does not work after BufferedOutput
@@ -399,7 +486,7 @@ class ModulesController extends Controller
} else {
if (!empty($result['status']) && $result['status'] == 'success') {
$db_module = \App\Module::getByAlias($alias);
if ($db_module && trim($db_module->license) == trim($license)) {
if ($db_module && trim($db_module->license ?? '') == trim($license ?? '')) {
// Remove remembered license key and deactivate license in DB
\App\Module::deactivateLicense($alias, '');
@@ -410,7 +497,7 @@ class ModulesController extends Controller
// Flash does not work here.
$flash = [
'text' => '<strong>'.__('License successfully DEactivated!').'</strong>',
'text' => '<strong>'.__('License successfully Deactivated!').'</strong>',
'unescaped' => true,
'type' => 'success',
];
@@ -418,9 +505,9 @@ class ModulesController extends Controller
$response['status'] = 'success';
} elseif (!empty($result['error'])) {
$response['msg'] = $this->getErrorMessage($result['error'], $result);
$response['msg'] = \App\Module::getErrorMessage($result['error'], $result);
} else {
$response['msg'] = __('Error occured. Please try again later.');
$response['msg'] = __('Error occurred. Please try again later.');
}
}
}
@@ -445,110 +532,34 @@ class ModulesController extends Controller
break;
case 'update':
$alias = $request->alias;
$module = \Module::findByAlias($alias);
$update_result = \App\Module::updateModule($request->alias);
if (!$module) {
$response['msg'] = __('Module not found').': '.$alias;
}
if ($update_result['download_error']) {
$response['reload'] = true;
// Download new version
$download_error = false;
if (!$response['msg']) {
$params = [
'license' => \App\Module::getLicense($alias),
'module_alias' => $alias,
'url' => \App\Module::getAppUrl(),
];
$license_details = WpApi::getVersion($params);
if ($update_result['msg']) {
\Session::flash('flash_error_floating', $update_result['msg']);
}
if (WpApi::$lastError) {
$response['msg'] = WpApi::$lastError['message'];
} elseif (!empty($license_details['code']) && !empty($license_details['message'])) {
$response['msg'] = $license_details['message'];
} elseif (!empty($license_details['required_app_version']) && !\Helper::checkAppVersion($license_details['required_app_version'])) {
$response['msg'] = 'Module requires app version:'.' '.$license_details['required_app_version'];
} elseif (!empty($license_details['download_link'])) {
// Download module
$module_archive = \Module::getPath().DIRECTORY_SEPARATOR.$alias.'.zip';
try {
\Helper::downloadRemoteFile($license_details['download_link'], $module_archive);
} catch (\Exception $e) {
$response['msg'] = $e->getMessage();
}
if (!file_exists($module_archive)) {
$download_error = true;
} else {
// Extract
try {
\Helper::unzip($module_archive, \Module::getPath());
} catch (\Exception $e) {
// We will show this as floating message on page reload
$response['msg'] = $e->getMessage();
}
// Check if extracted module exists
\Module::clearCache();
$module = \Module::findByAlias($alias);
if (!$module) {
$download_error = true;
}
}
// Remove archive
if (file_exists($module_archive)) {
\File::delete($module_archive);
}
if ($download_error) {
$response['reload'] = true;
if ($response['msg']) {
\Session::flash('flash_error_floating', $response['msg']);
}
\Session::flash('flash_error_unescaped', __('Error occured downloading the module. Please :%a_being%download:%a_end% module manually and extract into :folder', ['%a_being%' => '<a href="'.$license_details['download_link'].'" target="_blank">', '%a_end%' => '</a>', 'folder' => '<strong>'.\Module::getPath().'</strong>']));
}
} elseif ($license_details['status'] && $response['msg'] = $this->getErrorMessage($license_details['status'])) {
//$response['msg'] = ;
} else {
$response['msg'] = __('Error occured. Please try again later.');
if ($update_result['download_msg']) {
\Session::flash('flash_error_unescaped', $update_result['download_msg']);
}
}
// Install updated module
if (!$response['msg'] && !$download_error) {
$outputLog = new BufferedOutput();
\Artisan::call('freescout:module-install', ['module_alias' => $alias], $outputLog);
$output = $outputLog->fetch();
// Get module name
$name = '?';
if ($module) {
$name = $module->getName();
}
// Install updated module.
if ($update_result['output'] || $update_result['status']) {
$type = 'danger';
$msg = __('Error occured activating ":name" module', ['name' => $name]);
if (session('flashes_floating') && is_array(session('flashes_floating'))) {
// If there was any error, module has been deactivated via modules.register_error filter
$msg = '';
foreach (session('flashes_floating') as $flash) {
$msg .= $flash['text'].' ';
}
} elseif (strstr($output, 'Configuration cached successfully')) {
$msg = $update_result['msg'];
if ($update_result['status'] == 'success') {
$type = 'success';
$msg = __('":name" module successfully updated!', ['name' => $name]);
} else {
// Deactivate module
\App\Module::setActive($alias, false);
\Artisan::call('freescout:clear-cache');
$msg = $update_result['msg_success'];
}
// \Session::flash does not work after BufferedOutput
$flash = [
'text' => '<strong>'.$msg.'</strong><pre class="margin-top">'.$output.'</pre>',
'text' => '<strong>'.$msg.'</strong><pre class="margin-top">'.$update_result['output'].'</pre>',
'unescaped' => true,
'type' => $type,
];
@@ -558,51 +569,50 @@ class ModulesController extends Controller
break;
case 'update_all':
$update_all_flashes = [];
foreach ($request->aliases as $alias) {
$update_result = \App\Module::updateModule($alias);
$type = 'danger';
$msg = $update_result['msg'];
if ($update_result['status'] == 'success') {
$type = 'success';
$msg = $update_result['msg_success'];
} elseif ($update_result['download_msg']) {
$msg .= '<br/>'.$update_result['download_msg'];
}
$text = '<strong>'.$update_result['module_name'].':</strong> '.$msg;
if (trim($update_result['output'])) {
$text .= '<pre class="margin-top">'.$update_result['output'].'</pre>';
}
// \Session::flash does not work after BufferedOutput
$update_all_flashes[] = [
'text' => $text,
'unescaped' => true,
'type' => $type,
];
}
if ($update_all_flashes) {
\Cache::forever('modules_flash', $update_all_flashes);
}
$response['status'] = 'success';
break;
default:
$response['msg'] = 'Unknown action';
break;
}
if ($response['status'] == 'error' && empty($response['msg'])) {
$response['msg'] = 'Unknown error occured';
$response['msg'] = 'Unknown error occurred';
}
return \Response::json($response);
}
public function getErrorMessage($code, $result = null)
{
$msg = '';
switch ($code) {
case 'missing':
$msg = __('License key does not exist');
break;
case 'license_not_activable':
$msg = __("You have to activate each bundle's module separately");
break;
case 'disabled':
$msg = __('License key has been revoked');
break;
case 'no_activations_left':
$msg = __('No activations left for this license key');
break;
case 'expired':
$msg = __('License key has expired');
break;
case 'key_mismatch':
$msg = __('License key belongs to another module');
break;
case 'invalid_item_id':
$msg = __('Module not found in the modules directory');
break;
default:
if ($result && !empty($result['error'])) {
$msg = __('Error code:'.' '.$result['error']);
}
break;
}
return $msg;
}
}

View File

@@ -11,7 +11,7 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Validator;
class PublicController extends Controller
class OpenController extends Controller
{
/**
* Create a new controller instance.
@@ -34,7 +34,11 @@ class PublicController extends Controller
}
$user = User::where('invite_hash', $hash)->first();
return view('public/user_setup', ['user' => $user]);
if ($user && $user->locale) {
\Helper::setLocale($user->locale);
}
return view('open/user_setup', ['user' => $user]);
}
/**
@@ -72,7 +76,7 @@ class PublicController extends Controller
if ($path_url) {
$user->photo_url = $path_url;
} else {
$validator->errors()->add('photo_url', __('Error occured processing the image. Make sure that PHP GD extension is enabled.'));
$validator->errors()->add('photo_url', __('Error occurred processing the image. Make sure that PHP GD extension is enabled.'));
}
}
});
@@ -97,7 +101,8 @@ class PublicController extends Controller
$user->invite_state = User::INVITE_STATE_ACTIVATED;
$user->invite_hash = '';
$user = \Eventy::filter('user.setup_save', $user, $request);
$user->save();
// Login user
@@ -124,7 +129,7 @@ class PublicController extends Controller
}
// Create a 1x1 ttransparent pixel and return it
$pixel = sprintf('%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%', 71, 73, 70, 56, 57, 97, 1, 0, 1, 0, 128, 255, 0, 192, 192, 192, 0, 0, 0, 33, 249, 4, 1, 0, 0, 0, 0, 44, 0, 0, 0, 0, 1, 0, 1, 0, 0, 2, 2, 68, 1, 0, 59);
$pixel = sprintf('%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c', 71, 73, 70, 56, 57, 97, 1, 0, 1, 0, 128, 255, 0, 192, 192, 192, 0, 0, 0, 33, 249, 4, 1, 0, 0, 0, 0, 44, 0, 0, 0, 0, 1, 0, 1, 0, 0, 2, 2, 68, 1, 0, 59);
$response = \Response::make($pixel, 200);
$response->header('Content-type', 'image/gif');
$response->header('Content-Length', 42);
@@ -173,6 +178,20 @@ class PublicController extends Controller
if (in_array($file_ext, config('app.viewable_attachments'))) {
$view_attachment = true;
}
// If HTML file is renamed into .txt for example it will be shown by the browser as HTML.
if ($view_attachment && $attachment->mime_type) {
$allowed_mime_type = false;
foreach (config('app.viewable_mime_types') as $mime_type) {
if (preg_match('#'.$mime_type.'#', $attachment->mime_type)) {
$allowed_mime_type = true;
break;
}
}
if (!$allowed_mime_type) {
$view_attachment = false;
}
}
if (config('app.download_attachments_via') == 'apache') {
// Send using Apache mod_xsendfile.
@@ -198,4 +217,12 @@ class PublicController extends Controller
return $response;
}
/**
* Needed for the mobile app.
*/
// public function mobilePing()
// {
// echo file_get_contents(public_path('installer/css/fontawesome.css'));
// }
}

View File

@@ -36,7 +36,7 @@ class SecureController extends Controller
}
// Sort by name.
$mailboxes = $mailboxes->sortBy('name');
$mailboxes = \Eventy::filter('dashboard.mailboxes', $mailboxes->sortBy('name'));
return view('secure/dashboard', ['mailboxes' => $mailboxes]);
}
@@ -112,12 +112,16 @@ class SecureController extends Controller
'type',
'email',
'status',
'conversation',
'message',
'user',
'customer',
];
$activities = SendLog::orderBy('created_at', 'desc')->paginate($page_size);
$activities_query = SendLog::orderBy('created_at', 'desc');
if ($request->get('thread_id')) {
$activities_query->where('thread_id', $request->get('thread_id'));
}
$activities = $activities_query->paginate($page_size);
foreach ($activities as $record) {
$conversation = '';
@@ -132,13 +136,16 @@ class SecureController extends Controller
$status .= '. Message-ID: '.$record->message_id;
}
}
if ($record->smtp_queue_id) {
$status .= '. SMTP ID: '.$record->smtp_queue_id;
}
$logs[] = [
'date' => $record->created_at,
'type' => $record->getMailTypeName(),
'email' => $record->email,
'status' => $status,
'conversation' => $conversation,
'message' => $conversation,
'user' => $record->user,
'customer' => $record->customer,
];
@@ -170,7 +177,7 @@ class SecureController extends Controller
$name = '';
if (!empty($request->name)) {
$activities = ActivityLog::inLog($request->name)->orderBy('created_at', 'desc')->get();
//$activities = ActivityLog::inLog($request->name)->orderBy('created_at', 'desc')->get();
$name = $request->name;
} elseif (count($names = ActivityLog::select('log_name')->distinct()->get()->pluck('log_name'))) {
$name = ActivityLog::NAME_OUT_EMAILS;
@@ -208,7 +215,7 @@ class SecureController extends Controller
}
if (!$request->hasFile('file') || !$request->file('file')->isValid() || !$request->file) {
$response['msg'] = __('Error occured uploading file');
$response['msg'] = __('Error occurred uploading file');
}
if (!$response['msg']) {
@@ -220,7 +227,7 @@ class SecureController extends Controller
$response['status'] = 'success';
$response['url'] = Helper::uploadedFileUrl($filename);
} else {
$response['msg'] = __('Error occured uploading file');
$response['msg'] = __('Error occurred uploading file');
}
}

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers;
use App\Conversation;
use App\Option;
use App\Subscription;
use App\User;
use Illuminate\Http\Request;
use Validator;
@@ -100,14 +101,24 @@ class SettingsController extends Controller
'env' => 'APP_FETCH_SCHEDULE',
],
'mail_password' => [
'safe_password' => true
]
'safe_password' => true,
'encrypt' => true,
],
// 'use_mail_date_on_fetching' => [
// 'env' => 'APP_USE_MAIL_DATE_ON_FETCHING',
// ],
],
];
break;
case 'general':
$params = [
'settings' => [
'custom_number' => [
'env' => 'APP_CUSTOM_NUMBER',
],
'max_message_size' => [
'env' => 'APP_MAX_MESSAGE_SIZE',
],
'email_conv_history' => [
'env' => 'APP_EMAIL_CONV_HISTORY',
],
@@ -128,9 +139,19 @@ class SettingsController extends Controller
];
break;
case 'alerts':
$subscriptions_defaults = Subscription::getDefaultSubscriptions();
$subscriptions = array();
foreach ($subscriptions_defaults as $medium => $subscriptions_for_medium) {
foreach ($subscriptions_defaults[$medium] as $subscription) {
$subscriptions[] = (object) array("medium" => $medium, "event" => $subscription);
}
}
$params = [
'template_vars' => [
'logs' => \App\ActivityLog::getAvailableLogs(),
'person' => null,
'subscriptions' => $subscriptions,
'mobile_available' => \Eventy::filter('notifications.mobile_available', false),
],
'settings' => [
'alert_logs' => [
@@ -155,6 +176,8 @@ class SettingsController extends Controller
break;
}
$params = \Eventy::filter('settings.alter_section_params', $params, $section);
if ($param) {
if (isset($params[$param])) {
return $params[$param];
@@ -175,10 +198,12 @@ class SettingsController extends Controller
$settings = [
'company_name' => Option::get('company_name', \Config::get('app.name')),
'next_ticket' => (Option::get('next_ticket') >= Conversation::max('number') + 1) ? Option::get('next_ticket') : Conversation::max('number') + 1,
'custom_number' => (int)config('app.custom_number'),
'user_permissions' => User::getGlobalUserPermissions(),
'email_branding' => Option::get('email_branding'),
'open_tracking' => Option::get('open_tracking'),
'email_conv_history' => config('app.email_conv_history'),
'max_message_size' => config('app.max_message_size'),
'email_user_history' => config('app.email_user_history'),
'enrich_customer_data' => Option::get('enrich_customer_data'),
'time_format' => Option::get('time_format', User::TIME_FORMAT_24),
@@ -193,9 +218,10 @@ class SettingsController extends Controller
'mail_host' => Option::get('mail_host', \Config::get('mail.host')),
'mail_port' => Option::get('mail_port', \Config::get('mail.port')),
'mail_username' => Option::get('mail_username', \Config::get('mail.username')),
'mail_password' => Option::get('mail_password', \Config::get('mail.password')),
'mail_password' => \Helper::decrypt(Option::get('mail_password', \Config::get('mail.password'))),
'mail_encryption' => Option::get('mail_encryption', \Config::get('mail.encryption')),
'fetch_schedule' => config('app.fetch_schedule'),
//'use_mail_date_on_fetching' => config('app.use_mail_date_on_fetching'),
];
break;
case 'alerts':
@@ -206,6 +232,7 @@ class SettingsController extends Controller
'alert_logs',
'alert_logs_names',
'alert_logs_period',
'subscription_defaults',
], [
'alert_logs_names' => [],
'alert_logs' => config('app.alert_logs'),
@@ -217,6 +244,8 @@ class SettingsController extends Controller
break;
}
$settings = \Eventy::filter('settings.alter_section_settings', $settings, $section);
return $settings;
}
@@ -284,6 +313,10 @@ class SettingsController extends Controller
$env_value = json_encode($env_value);
}
if (!empty($settings_params[$option_name]['encrypt'])) {
$env_value = encrypt($env_value);
}
if (!empty($settings_params[$option_name]['env_encode'])) {
$env_value = base64_encode($env_value);
}
@@ -296,8 +329,13 @@ class SettingsController extends Controller
}
// By some reason isset() does not work for empty elements.
if (array_key_exists($option_name, $request->settings)) {
if (isset($request->settings) && array_key_exists($option_name, $request->settings)) {
$option_value = $request->settings[$option_name];
if (!empty($settings_params[$option_name]['encrypt'])) {
$option_value = encrypt($option_value);
}
Option::set($option_name, $option_value);
} else {
// If option does not exist, default will be used,
@@ -318,6 +356,8 @@ class SettingsController extends Controller
}
// Clear cache if some options have been saved to .env file.
// Clearing the cache also restarts queue:work as it also
// needs to get new .env parameters.
if ($cc_required) {
\Helper::clearCache(['--doNotGenerateVars' => true]);
}
@@ -354,17 +394,22 @@ class SettingsController extends Controller
}
if (!$response['msg']) {
$test_result = false;
$test_result = [
'status' => 'error'
];
try {
$test_result = \MailHelper::sendTestMail($request->to);
} catch (\Exception $e) {
$response['msg'] = $e->getMessage();
$test_result['msg'] = $e->getMessage();
}
if (!$test_result && !$response['msg']) {
$response['msg'] = __('Error occurred sending email. Please check your mail server logs for more details.');
if ($test_result['status'] == 'error') {
$response['msg'] = $test_result['msg']
?: __('Error occurred sending email. Please check your mail server logs for more details.');
}
$response['log'] = $test_result['log'] ?? '';
}
if (!$response['msg']) {
@@ -383,7 +428,7 @@ class SettingsController extends Controller
}
if ($response['status'] == 'error' && empty($response['msg'])) {
$response['msg'] = 'Unknown error occured';
$response['msg'] = 'Unknown error occurred';
}
return \Response::json($response);

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers;
use App\Option;
use App\User;
use App\FailedJob;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Symfony\Component\Console\Output\BufferedOutput;
@@ -19,7 +20,9 @@ class SystemController extends Controller
*/
public function __construct()
{
$this->middleware('auth');
$this->middleware('auth', ['except' => [
'cron'
]]);
}
/**
@@ -27,28 +30,11 @@ class SystemController extends Controller
*/
public function status(Request $request)
{
// PHP extensions
$php_extensions = [];
foreach (\Config::get('app.required_extensions') as $extension_name) {
$alternatives = explode('/', $extension_name);
if ($alternatives) {
foreach ($alternatives as $alternative) {
$php_extensions[$extension_name] = extension_loaded(trim($alternative));
if ($php_extensions[$extension_name]) {
break;
}
}
} else {
$php_extensions[$extension_name] = extension_loaded($extension_name);
}
}
// PHP extensions.
$php_extensions = \Helper::checkRequiredExtensions();
// Functions.
$functions = [
'shell_exec (PHP)' => function_exists('shell_exec'),
'proc_open (PHP)' => function_exists('proc_open'),
'ps (shell)' => function_exists('shell_exec') ? shell_exec('ps') : false,
];
$functions = \Helper::checkRequiredFunctions();
// Permissions.
$permissions = [];
@@ -67,8 +53,10 @@ class SystemController extends Controller
// Check if cache files are writable.
$non_writable_cache_file = '';
if (function_exists('shell_exec')) {
$non_writable_cache_file = shell_exec('find '.base_path('storage/framework/cache/data/').' -type f | xargs -I {} sh -c \'[ ! -w "{}" ] && echo {}\' 2>&1 | head -n 1');
$non_writable_cache_file = trim($non_writable_cache_file);
$non_writable_cache_file = \Helper::shellExec('find '.base_path('storage/framework/cache/data/').' -type f | xargs -I {} sh -c \'[ ! -w "{}" ] && echo {}\' 2>&1 | head -n 1');
$non_writable_cache_file = trim($non_writable_cache_file ?? '');
// Leave only one line (in case head -n 1 does not work)
$non_writable_cache_file = preg_replace("#[\r\n].+#m", '', $non_writable_cache_file);
if (!strstr($non_writable_cache_file, base_path('storage/framework/cache/data/'))) {
$non_writable_cache_file = '';
}
@@ -92,8 +80,8 @@ class SystemController extends Controller
$env_is_writable = is_writable(base_path('.env'));
// Jobs
$queued_jobs = \App\Job::orderBy('created_at', 'desc')->get();
$failed_jobs = \App\FailedJob::orderBy('failed_at', 'desc')->get();
$queued_jobs = \App\Job::orderBy('created_at', 'desc')->limit(100)->get();
$failed_jobs = \App\FailedJob::orderBy('failed_at', 'desc')->limit(100)->get();
$failed_queues = $failed_jobs->pluck('queue')->unique();
// Commands
@@ -109,10 +97,16 @@ class SystemController extends Controller
$running_commands = 0;
try {
$processes = preg_split("/[\r\n]/", shell_exec("ps aux | grep '{$command_identifier}'"));
$processes = preg_split("/[\r\n]/", \Helper::shellExec("ps aux | grep '{$command_identifier}'"));
$pids = [];
foreach ($processes as $process) {
$process = trim($process);
preg_match("/^[\S]+\s+([\d]+)\s+/", $process, $m);
if (empty($m)) {
// Another format (used in Docker image).
// 1713 nginx 0:00 /usr/bin/php82...
preg_match("/^([\d]+)\s+[\S]+\s+/", $process, $m);
}
if (!preg_match("/(sh \-c|grep )/", $process) && !empty($m[1])) {
$running_commands++;
$pids[] = $m[1];
@@ -131,7 +125,7 @@ class SystemController extends Controller
} elseif ($running_commands > 1) {
// queue:work command is stopped by settings a cache key
if ($command_name == 'queue:work') {
\Helper::queueWorkRestart();
\Helper::queueWorkerRestart();
$commands[] = [
'name' => $command_name,
'status' => 'error',
@@ -206,6 +200,11 @@ class SystemController extends Controller
$latest_version = \Config::get('app.version');
}
// Detect missing migrations.
$migrations_output = \Helper::runCommand('migrate:status');
preg_match_all("#\| N \| ([^\|]+)\|#", $migrations_output, $migrations_m);
$missing_migrations = $migrations_m[1] ?? [];
return view('system/status', [
'commands' => $commands,
'queued_jobs' => $queued_jobs,
@@ -220,6 +219,8 @@ class SystemController extends Controller
'public_symlink_exists' => $public_symlink_exists,
'env_is_writable' => $env_is_writable,
'non_writable_cache_file' => $non_writable_cache_file,
'missing_migrations' => $missing_migrations,
'invalid_symlinks' => \App\Module::checkSymlinks(),
]);
}
@@ -231,6 +232,12 @@ class SystemController extends Controller
\Session::flash('flash_success_floating', __('Done'));
break;
case 'retry_job':
\App\Job::where('id', $request->job_id)->update(['available_at' => time()]);
sleep(1);
\Session::flash('flash_success_floating', __('Done'));
break;
case 'delete_failed_jobs':
\App\FailedJob::where('queue', $request->failed_queue)->delete();
\Session::flash('flash_success_floating', __('Failed jobs deleted'));
@@ -283,6 +290,7 @@ class SystemController extends Controller
$params = [];
$params['--days'] = (int)$request->days;
$params['--unseen'] = (int)$request->unseen;
$params['--debug'] = (int)$request->debug;
\Artisan::call('freescout:fetch-emails', $params, $outputLog);
break;
@@ -324,7 +332,7 @@ class SystemController extends Controller
// Artisan::output()
} catch (\Exception $e) {
$response['msg'] = __('Error occured. Please try again or try another :%a_start%update method:%a_end%', ['%a_start%' => '<a href="'.config('app.freescout_url').'/docs/update/" target="_blank">', '%a_end%' => '</a>']);
$response['msg'] = __('Error occurred. Please try again or try another :%a_start%update method:%a_end%', ['%a_start%' => '<a href="'.config('app.freescout_url').'/docs/update/" target="_blank">', '%a_end%' => '</a>']);
$response['msg'] .= '<br/><br/>'.$e->getMessage();
\Helper::logException($e);
@@ -342,7 +350,7 @@ class SystemController extends Controller
$response['new_version_available'] = \Updater::isNewVersionAvailable(config('app.version'));
$response['status'] = 'success';
} catch (\Exception $e) {
$response['msg'] = __('Error occured').': '.$e->getMessage();
$response['msg'] = __('Error occurred').': '.$e->getMessage();
}
if (!$response['msg'] && !$response['new_version_available']) {
// Adding session flash is useless as cache is cleated
@@ -359,9 +367,51 @@ class SystemController extends Controller
}
if ($response['status'] == 'error' && empty($response['msg'])) {
$response['msg'] = 'Unknown error occured';
$response['msg'] = 'Unknown error occurred';
}
return \Response::json($response);
}
/**
* Web Cron.
*/
public function cron(Request $request)
{
if (empty($request->hash) || $request->hash != \Helper::getWebCronHash()) {
abort(404);
}
$outputLog = new BufferedOutput();
\Artisan::call('schedule:run', [], $outputLog);
$output = $outputLog->fetch();
return response($output, 200)->header('Content-Type', 'text/plain');
}
/**
* Ajax HTML.
*/
public function ajaxHtml(Request $request)
{
switch ($request->action) {
case 'job_details':
$job = \App\FailedJob::find($request->param);
if (!$job) {
abort(404);
}
$html = '';
$payload = json_decode($job->payload, true);
if (!empty($payload['data']['command'])) {
$html .= '<pre>'.print_r(unserialize($payload['data']['command']), 1).'</pre>';
}
$html .= '<pre>'.$job->exception.'</pre>';
return response($html);
}
abort(404);
}
}

View File

@@ -57,6 +57,7 @@ class UsersController extends Controller
*/
public function createSave(Request $request)
{
$invalid = false;
$this->authorize('create', 'App\User');
$auth_user = auth()->user();
@@ -74,7 +75,12 @@ class UsersController extends Controller
}
$validator = Validator::make($request->all(), $rules);
if ($validator->fails()) {
if (User::mailboxEmailExists($request->email)) {
$invalid = true;
$validator->errors()->add('email', __('There is a mailbox with such email. Users and mailboxes can not have the same email addresses.'));
}
if ($invalid || $validator->fails()) {
return redirect()->route('users.create')
->withErrors($validator)
->withInput();
@@ -92,9 +98,12 @@ class UsersController extends Controller
// Set some random password before sending invite
$user->password = Hash::make($user->generateRandomPassword());
}
// Set system timezone.
$user->timezone = config('app.timezone') ?: User::DEFAULT_TIMEZONE;
$user = \Eventy::filter('user.create_save', $user, $request);
$user->save();
$user->mailboxes()->sync($request->mailboxes);
$user->mailboxes()->sync($request->mailboxes ?: []);
$user->syncPersonalFolders($request->mailboxes);
// Send invite
@@ -103,7 +112,7 @@ class UsersController extends Controller
$user->sendInvite(true);
} catch (\Exception $e) {
// Admin is allowed to see exceptions
\Session::flash('flash_error_floating', $e->getMessage());
\Session::flash('flash_error_floating', $e->getMessage().' — '.__('Check mail settings in "Manage » Settings » Mail Settings"'));
}
}
@@ -147,6 +156,8 @@ class UsersController extends Controller
*/
public function profileSave($id, Request $request)
{
$invalid = false;
$user = User::findOrFail($id);
$this->authorize('update', $user);
@@ -155,7 +166,7 @@ class UsersController extends Controller
'first_name' => 'required|string|max:20',
'last_name' => 'required|string|max:30',
'email' => 'required|string|email|max:100|unique:users,email,'.$id,
'emails' => 'max:100',
//'emails' => 'max:100',
'job_title' => 'max:100',
'phone' => 'max:60',
'timezone' => 'required|string|max:255',
@@ -175,7 +186,8 @@ class UsersController extends Controller
if ($path_url) {
$user->photo_url = $path_url;
} else {
$validator->errors()->add('photo_url', __('Error occured processing the image. Make sure that PHP GD extension is enabled.'));
$invalid = true;
$validator->errors()->add('photo_url', __('Error occurred processing the image. Make sure that PHP GD extension is enabled.'));
}
}
@@ -183,12 +195,18 @@ class UsersController extends Controller
if ($user->isAdmin() && isset($request->role) && $request->role != User::ROLE_ADMIN) {
$admins_count = User::where('role', User::ROLE_ADMIN)->count();
if ($admins_count < 2) {
$invalid = true;
$validator->errors()->add('role', __('Role of the only one administrator can not be changed.'));
}
}
});
if ($validator->fails()) {
if (User::mailboxEmailExists($request->email)) {
$invalid = true;
$validator->errors()->add('email', __('There is a mailbox with such email. Users and mailboxes can not have the same email addresses.'));
}
if ($invalid || $validator->fails()) {
return redirect()->route('users.profile', ['id' => $id])
->withErrors($validator)
->withInput();
@@ -214,12 +232,14 @@ class UsersController extends Controller
$request_data['status'] = User::STATUS_ACTIVE;
}
}
$user->fill($request_data);
$user->setData($request_data);
if (empty($request->input('enable_kb_shortcuts'))) {
$user->enable_kb_shortcuts = false;
}
$user = \Eventy::filter('user.save_profile', $user, $request);
$user->save();
\Session::flash('flash_success_floating', __('Profile saved successfully'));
@@ -266,7 +286,7 @@ class UsersController extends Controller
$user = User::findOrFail($id);
$user->mailboxes()->sync($request->mailboxes);
$user->mailboxes()->sync($request->mailboxes ?: []);
$user->syncPersonalFolders($request->mailboxes);
// Save permissions.
@@ -370,8 +390,8 @@ class UsersController extends Controller
$response['status'] = 'success';
} catch (\Exception $e) {
// Admin is allowed to see exceptions
$response['msg'] = $e->getMessage();
// Admin is allowed to see exceptions.
$response['msg'] = $e->getMessage().' — '.__('Check mail settings in "Manage » Settings » Mail Settings"');
}
}
break;
@@ -478,30 +498,42 @@ class UsersController extends Controller
$user->conversations->each(function ($conversation) use ($auth_user, $request) {
// We don't fire ConversationUserChanged event to avoid sending notifications to users
if (!empty($request->assign_user) && !empty($request->assign_user[$conversation->mailbox_id]) && (int) $request->assign_user[$conversation->mailbox_id] != -1) {
if (!empty($request->assign_user)
&& !empty($request->assign_user[$conversation->mailbox_id])
&& (int) $request->assign_user[$conversation->mailbox_id] != -1
) {
// Set assignee.
// In this case conversation stays assigned, just assignee changes.
$conversation->user_id = $request->assign_user[$conversation->mailbox_id];
// In this case conversation stays assigned, just assignee changes.
} else {
// Set assignee.
// Make convesation Unassigned.
// Unset assignee.
// Maybe use changeUser() here.
$conversation->user_id = null;
// Change conversation folder to ANASSIGNED.
$folder_id = null;
if (!empty($mailbox_unassigned_folders[$conversation->mailbox_id])) {
$folder_id = $mailbox_unassigned_folders[$conversation->mailbox_id];
} else {
$folder = $conversation->mailbox->folders()
->where('type', Folder::TYPE_UNASSIGNED)
->first();
if ($conversation->isPublished()
&& ($conversation->isActive() || $conversation->isPending())
) {
// Change conversation folder to UNASSIGNED.
$folder_id = null;
if (!empty($mailbox_unassigned_folders[$conversation->mailbox_id])) {
$folder_id = $mailbox_unassigned_folders[$conversation->mailbox_id];
} else {
$folder = $conversation->mailbox->folders()
->where('type', Folder::TYPE_UNASSIGNED)
->first();
if ($folder) {
$folder_id = $folder->id;
$mailbox_unassigned_folders[$conversation->mailbox_id] = $folder_id;
if ($folder) {
$folder_id = $folder->id;
$mailbox_unassigned_folders[$conversation->mailbox_id] = $folder_id;
}
}
if ($folder_id) {
$conversation->folder_id = $folder_id;
}
}
if ($folder_id) {
$conversation->folder_id = $folder_id;
}
}
@@ -540,7 +572,7 @@ class UsersController extends Controller
$user->status = \App\User::STATUS_DELETED;
// Update email.
$email_suffix = '_deleted'.date('YmdHis');
$email_suffix = User::EMAIL_DELETED_SUFFIX.date('YmdHis');
// We have to truncate email to avoid "Data too long" error.
$user->email = mb_substr($user->email, 0, User::EMAIL_MAX_LENGTH - mb_strlen($email_suffix)).$email_suffix;
@@ -560,7 +592,7 @@ class UsersController extends Controller
}
if ($response['status'] == 'error' && empty($response['msg'])) {
$response['msg'] = 'Unknown error occured';
$response['msg'] = 'Unknown error occurred';
}
return \Response::json($response);

View File

@@ -41,6 +41,7 @@ class Kernel extends HttpKernel
\App\Http\Middleware\HttpsRedirect::class,
\App\Http\Middleware\Localize::class,
\App\Http\Middleware\LogoutIfDeleted::class,
\App\Http\Middleware\FrameGuard::class,
\App\Http\Middleware\CustomHandle::class,
],

View File

@@ -16,6 +16,11 @@ class CustomHandle
*/
public function handle($request, Closure $next)
{
// Enable/disable chat mode
if ($request->exists('chat_mode')) {
\Helper::setChatMode((int)$request->chat_mode);
}
// Hook.
\Eventy::action('middleware.web.custom_handle', $request);

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Http\Middleware;
use Closure;
class FrameGuard
{
/**
* Handle the given request and get the response.
*/
public function handle($request, Closure $next)
{
$response = $next($request);
$x_frame_options = config('app.x_frame_options');
if (false !== $x_frame_options
&& $x_frame_options !== 'false'
&& $x_frame_options !== '0'
) {
$value = 'SAMEORIGIN';
if (is_string($x_frame_options) && preg_match("#(DENY|SAMEORIGIN|ALLOW-FROM)#i", $x_frame_options)) {
$value = $x_frame_options;
}
$response->headers->set('X-Frame-Options', $value, false);
}
return $response;
}
}

View File

@@ -18,26 +18,31 @@ class HttpsRedirect {
*
* @var array
*/
protected $headers = [
Request::HEADER_FORWARDED => 'FORWARDED',
Request::HEADER_X_FORWARDED_FOR => 'X_FORWARDED_FOR',
Request::HEADER_X_FORWARDED_HOST => 'X_FORWARDED_HOST',
Request::HEADER_X_FORWARDED_PORT => 'X_FORWARDED_PORT',
Request::HEADER_X_FORWARDED_PROTO => 'X_FORWARDED_PROTO',
];
// protected $headers = [
// Request::HEADER_FORWARDED => 'FORWARDED',
// Request::HEADER_X_FORWARDED_FOR => 'X_FORWARDED_FOR',
// Request::HEADER_X_FORWARDED_HOST => 'X_FORWARDED_HOST',
// Request::HEADER_X_FORWARDED_PORT => 'X_FORWARDED_PORT',
// Request::HEADER_X_FORWARDED_PROTO => 'X_FORWARDED_PROTO',
// ];
public function handle($request, Closure $next)
{
if (\Config::get('app.force_https') == 'true') {
$request->setTrustedProxies( [ $request->getClientIp() ], array_keys($this->headers));
if (!$request->secure() && strtolower($_SERVER['HTTPS'] ?? '') != 'on'
&& ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') != 'https'
&& ($_SERVER['HTTP_CF_VISITOR'] ?? '') != '{"scheme":"https"}'
) {
if (\Helper::isHttps()) {
//$request->setTrustedProxies( [ $request->getClientIp() ], array_keys($this->headers));
//!$request->secure()
if (!\Helper::isCurrentUrlHttps()) {
return redirect()->secure($request->getRequestUri());
}
}
// Correct protocol in $_SERVER
if (\Helper::isHttps()
//&& !$request->secure()
&& strtolower($_SERVER['HTTPS'] ?? '') != 'on'
) {
$_SERVER['HTTPS'] = 'on';
}
return $next($request);
}
}

View File

@@ -19,12 +19,9 @@ class Localize
// Interface language is set automatically, as locale is stored in .env file.
// Set user language if user logged in.
$user_locale = session('user_locale');
$user_locale = \Eventy::filter('locale', session('user_locale'));
if ($user_locale) {
// app()->setLocale() also dynamically changes config('app.locale'),
// so we have to remember current locale in real_locale parameter.
\Config::set('app.real_locale', config('app.locale'));
app()->setLocale($user_locale);
\Helper::setLocale($user_locale);
}
return $next($request);

View File

@@ -14,7 +14,7 @@ class TerminateHandler
public function terminate($request, $response)
{
// Process events which occured
// Process events which occurred
Subscription::processEvents();
}
}

View File

@@ -17,9 +17,10 @@ class TokenAuth
*/
public function handle($request, Closure $next)
{
// This is needed to restore authentication when app session expires.
if (!$request->user() && !empty($request->auth_token) && $request->cookie('in_app')) {
try {
$user = User::where(\DB::raw('md5(CONCAT(id, "'.config('app.key').'"))') , $request->auth_token)
$user = User::where(\DB::raw('md5(CONCAT(id, created_at, "'.config('app.key').'"))'), $request->auth_token)
->first();
} catch (\Exception $e) {
\Helper::logException($e, '[TokenAuth]');

View File

@@ -2,12 +2,57 @@
namespace App;
use App\Thread;
use Illuminate\Database\Eloquent\Model;
class Job extends Model
{
const UPDATED_AT = null;
public $payload_decoded = null;
/**
* Automatically converted into Carbon dates.
*/
protected $dates = ['created_at', 'available_at', 'reserved_at'];
public function getPayloadDecoded()
{
if ($this->payload_decoded !== null) {
return $this->payload_decoded;
}
$this->payload_decoded = json_decode($this->payload, true);
return $this->payload_decoded;
}
public function getCommand()
{
return self::getPayloadCommand($this->getPayloadDecoded());
}
public function getCommandLastThread()
{
$command = $this->getCommand();
if ($command && !empty($command->threads)) {
return Thread::getLastThread($command->threads);
}
return null;
}
public static function getPayloadCommand($payload)
{
if (empty($payload['data']) || empty($payload['data']['command'])) {
return null;
}
try {
// If some record has been deleted from DB, there will be an error:
// No query results for model [App\Conversation].
return unserialize($payload['data']['command']);
} catch (\Exception $e) {
return null;
}
}
}

View File

@@ -0,0 +1,41 @@
<?php
/**
* Used to stop current queue:work in order to start a new one.
* https://github.com/freescout-helpdesk/freescout/issues/2507#issuecomment-1376006247
*/
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class RestartQueueWorker implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct()
{
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$this->delete();
// register_shutdown_function() is called on exit(),
// so commands mutexes are removed.
exit();
}
}

View File

@@ -20,8 +20,17 @@ class SendAlert implements ShouldQueue
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $text;
public $title;
/**
* The number of seconds the job can run before timing out.
* fwrite() function in /vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/StreamBuffer.php
* in some cases may stuck and continue infinitely. This blocks queue:work and no other jobs are processed.
* So we need to set the timeout. On timeout the whole queue:work process is being killed by Laravel.
*/
public $timeout = 120;
/**
* Create a new job instance.
*
@@ -43,18 +52,17 @@ class SendAlert implements ShouldQueue
// Configure mail driver according to Mailbox settings
\MailHelper::setSystemMailDriver();
$recipients = [];
$super_admin = User::getSuperAdmin();
if ($super_admin) {
$recipients[] = $super_admin->email;
}
$recipients = User::nonDeleted()
->where('role', User::ROLE_ADMIN)
->where('invite_state', User::INVITE_STATE_ACTIVATED)
->pluck('email')
->toArray();
$extra = \MailHelper::sanitizeEmails(\Option::get('alert_recipients'));
if ($extra) {
$recipients = array_unique(array_merge($recipients, $extra));
}
foreach ($recipients as $recipient) {
$exception = null;

View File

@@ -23,6 +23,14 @@ class SendAutoReply implements ShouldQueue
public $customer;
/**
* The number of seconds the job can run before timing out.
* fwrite() function in /vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/StreamBuffer.php
* in some cases may stuck and continue infinitely. This blocks queue:work and no other jobs are processed.
* So we need to set the timeout. On timeout the whole queue:work process is being killed by Laravel.
*/
public $timeout = 120;
/**
* Create a new job instance.
*
@@ -43,15 +51,20 @@ class SendAutoReply implements ShouldQueue
*/
public function handle()
{
// Auto reply disabled.
if (!empty($this->conversation->meta['ar_off'])) {
return;
}
// Configure mail driver according to Mailbox settings
\App\Misc\Mail::setMailDriver($this->mailbox);
\App\Misc\Mail::setMailDriver($this->mailbox, null, $this->conversation);
// Auto reply appears as reply in customer's mailbox
$headers['In-Reply-To'] = '<'.$this->thread->message_id.'>';
$headers['References'] = '<'.$this->thread->message_id.'>';
// Create Message-ID for the auto reply
$message_id = \App\Misc\Mail::MESSAGE_ID_PREFIX_AUTO_REPLY.'-'.$this->thread->id.'-'.md5($this->thread->id).'@'.$this->mailbox->getEmailDomain();
$message_id = \App\Misc\Mail::MESSAGE_ID_PREFIX_AUTO_REPLY.'-'.$this->thread->id.'-'.\MailHelper::getMessageIdHash($this->thread->id).'@'.$this->mailbox->getEmailDomain();
$headers['Message-ID'] = $message_id;
$customer_email = $this->conversation->customer_email;

View File

@@ -24,6 +24,14 @@ class SendEmailReplyError implements ShouldQueue
public $mailbox;
/**
* The number of seconds the job can run before timing out.
* fwrite() function in /vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/StreamBuffer.php
* in some cases may stuck and continue infinitely. This blocks queue:work and no other jobs are processed.
* So we need to set the timeout. On timeout the whole queue:work process is being killed by Laravel.
*/
public $timeout = 120;
/**
* Create a new job instance.
*

View File

@@ -16,15 +16,23 @@ class SendNotificationToUsers implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
// Max retries + 1
public $tries = 168; // One per hour
public $users;
public $conversation;
public $threads;
// Max retries + 1
public $tries = 168; // One per hour
/**
* The number of seconds the job can run before timing out.
* fwrite() function in /vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/StreamBuffer.php
* in some cases may stuck and continue infinitely. This blocks queue:work and no other jobs are processed.
* So we need to set the timeout. On timeout the whole queue:work process is being killed by Laravel.
*/
public $timeout = 120;
/**
* Create a new job instance.
*
@@ -47,16 +55,18 @@ class SendNotificationToUsers implements ShouldQueue
$mailbox = $this->conversation->mailbox;
// Configure mail driver according to Mailbox settings
\App\Misc\Mail::setMailDriver($mailbox);
\App\Misc\Mail::setMailDriver($mailbox, null, $this->conversation);
// Threads has to be sorted here, if sorted before, they come here in wrong order
$this->threads = $this->threads->sortByDesc(function ($item, $key) {
return $item->id;
});
$this->threads = Thread::sortThreads($this->threads);
$headers = [];
$last_thread = $this->threads->first();
if (!$last_thread) {
return;
}
// If thread is draft, it means it has been undone
if ($last_thread->isDraft()) {
return;
@@ -75,12 +85,23 @@ class SendNotificationToUsers implements ShouldQueue
$prev_message_id = \App\Misc\Mail::MESSAGE_ID_PREFIX_NOTIFICATION_IN_REPLY.'-'.$this->conversation->id.'-'.md5($this->conversation->id).'@'.$mailbox->getEmailDomain();
$headers['In-Reply-To'] = '<'.$prev_message_id.'>';
$headers['References'] = '<'.$prev_message_id.'>';
// https://github.com/freescout-helpdesk/freescout/issues/2488
$headers['X-Auto-Response-Suppress'] = 'All';
// We throw an exception if any of the send attempts throws an exception (connection error, etc)
$global_exception = null;
foreach ($this->users as $user) {
// User can ne deleted from DB.
if (!isset($user->id)) {
continue;
}
if ($user->isDeleted()) {
continue;
}
// If for one user sending fails the job is marked as failed and retried after some time.
// So we have to check if notification email has already been successfully sent to this user.
if ($this->attempts() > 1) {
@@ -101,7 +122,10 @@ class SendNotificationToUsers implements ShouldQueue
// If this is notification on message from customer, set customer as sender name
$from_name = '';
if ($last_thread->type == Thread::TYPE_CUSTOMER) {
$from_name = $last_thread->customer->getFullName(true, true);
$from_name = '';
if ($last_thread->customer) {
$from_name = $last_thread->customer->getFullName(true, true);
}
if ($from_name) {
$from_name = $from_name.' '.__('via').' '.$mailbox->name;
}
@@ -115,6 +139,7 @@ class SendNotificationToUsers implements ShouldQueue
app()->setLocale($user->getLocale());
$headers['X-FreeScout-Mail-Type'] = 'user.notification';
$headers = \Eventy::filter('jobs.send_reply_to_customer.headers', $headers, $user, $mailbox, $this->conversation, $this->threads, $from);
$exception = null;
@@ -156,7 +181,8 @@ class SendNotificationToUsers implements ShouldQueue
if ($global_exception) {
// Retry job with delay.
// https://stackoverflow.com/questions/35258175/how-can-i-create-delays-between-failed-queued-job-attempts-in-laravel
if ($this->attempts() < $this->tries) {
// We do not try to resend Bounce messages: https://github.com/freescout-helpdesk/freescout/issues/3156
if ($this->attempts() < $this->tries && !$last_thread->isBounce()) {
if ($this->attempts() == 1) {
// Second attempt after 5 min.
$this->release(300);

View File

@@ -3,8 +3,10 @@
namespace App\Jobs;
use App\Mail\ReplyToCustomer;
use App\Customer;
use App\SendLog;
use App\Thread;
use App\Misc\SwiftGetSmtpQueueId;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
@@ -17,9 +19,6 @@ class SendReplyToCustomer implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
// Number of retries + 1
public $tries = 168; // one per hour
public $conversation;
public $threads;
@@ -32,6 +31,17 @@ class SendReplyToCustomer implements ShouldQueue
private $message_id = '';
private $customer_email = '';
// Number of retries + 1
public $tries = 168; // one per hour
/**
* The number of seconds the job can run before timing out.
* fwrite() function in /vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/StreamBuffer.php
* in some cases may stuck and continue infinitely. This blocks queue:work and no other jobs are processed.
* So we need to set the timeout. On timeout the whole queue:work process is being killed by Laravel.
*/
public $timeout = 120;
/**
* Create a new job instance.
*
@@ -61,6 +71,11 @@ class SendReplyToCustomer implements ShouldQueue
$mailbox = $this->conversation->mailbox;
// Mailbox may be deleted.
if (!$mailbox) {
return;
}
// Add forwarded conversation replies.
if ($this->conversation->threads_count == 1 && count($this->threads) == 1) {
$forward_child_thread = $this->threads[0];
@@ -68,10 +83,8 @@ class SendReplyToCustomer implements ShouldQueue
// Add replies from original conversation.
$forwarded_replies = $forward_child_thread->getForwardParentConversation()->getReplies();
$forwarded_replies = $forwarded_replies->sortByDesc(function ($item, $key) {
return $item->created_at;
});
$forward_parent_thread = Thread::find($forward_child_thread->getMeta('forward_parent_thread_id'));
$forwarded_replies = Thread::sortThreads($forwarded_replies);
$forward_parent_thread = Thread::find($forward_child_thread->getMetaFw(Thread::META_FORWARD_PARENT_THREAD_ID));
if ($forward_parent_thread) {
// Remove threads created after forwarding.
@@ -87,13 +100,16 @@ class SendReplyToCustomer implements ShouldQueue
}
// Threads has to be sorted here, if sorted before, they come here in wrong order
$this->threads = $this->threads->sortByDesc(function ($item, $key) {
return $item->id;
});
$this->threads = Thread::sortThreads($this->threads);
$new = false;
$headers = [];
$this->last_thread = $this->threads->first();
if ($this->last_thread === null) {
return;
}
$last_customer_thread = null;
// If thread is draft, it means it has been undone
@@ -101,6 +117,17 @@ class SendReplyToCustomer implements ShouldQueue
return;
}
// After sending an email we are saving it into "IMAP Folder To Save Outgoing Replies".
// This process may stuck or make SendReplyToCustomer job die with
// "Allowed memory size of NNN bytes exhausted" error.
// https://github.com/freescout-helpdesk/freescout/issues/3632
if ($this->attempts() >= 1 &&
($this->last_thread->send_status == SendLog::STATUS_ACCEPTED
|| $this->last_thread->isSendStatusSuccess())
) {
return;
}
if (count($this->threads) == 1) {
$new = true;
}
@@ -115,9 +142,37 @@ class SendReplyToCustomer implements ShouldQueue
}
}
// In-Reply-To and References headers.
$references = '';
if (!$new && !empty($last_customer_thread) && $last_customer_thread->message_id) {
$headers['In-Reply-To'] = '<'.$last_customer_thread->message_id.'>';
//$headers['References'] = '<'.$last_customer_thread->message_id.'>';
// https://github.com/freescout-helpdesk/freescout/issues/3175
$i = 0;
$references_array = [];
foreach ($this->threads as $thread) {
if ($i > 0) {
$reference = $thread->getMessageId();
if ($reference) {
$references_array[] = $reference;
}
}
$i++;
}
if ($references_array) {
$references = '<'.implode('> <', array_reverse($references_array)).'>';
}
if ($references) {
$headers['References'] = $references;
}
}
// Conversation history.
$email_conv_history = config('app.email_conv_history');
$threads_count = count($this->threads);
$meta_conv_history = $this->last_thread->getMeta(Thread::META_CONVERSATION_HISTORY);
if (!empty($meta_conv_history)) {
$email_conv_history = $meta_conv_history;
@@ -154,19 +209,34 @@ class SendReplyToCustomer implements ShouldQueue
}
// Configure mail driver according to Mailbox settings
\App\Misc\Mail::setMailDriver($mailbox, $this->last_thread->created_by_user);
\MailHelper::setMailDriver($mailbox, $this->last_thread->created_by_user, $this->conversation);
// Get penultimate email Message-Id if reply
if (!$new && !empty($last_customer_thread) && $last_customer_thread->message_id) {
$headers['In-Reply-To'] = '<'.$last_customer_thread->message_id.'>';
$headers['References'] = '<'.$last_customer_thread->message_id.'>';
// https://github.com/freescout-helpdesk/freescout/issues/3330
if (!\MailHelper::$smtp_queue_id_plugin_registered) {
\Mail::getSwiftMailer()->registerPlugin(new SwiftGetSmtpQueueId());
\MailHelper::$smtp_queue_id_plugin_registered = true;
}
$this->message_id = \App\Misc\Mail::MESSAGE_ID_PREFIX_REPLY_TO_CUSTOMER.'-'.$this->last_thread->id.'-'.md5($this->last_thread->id).'@'.$mailbox->getEmailDomain();
$this->message_id = $this->last_thread->getMessageId($mailbox);
$headers['Message-ID'] = $this->message_id;
$this->customer_email = $this->conversation->customer_email;
// For phone conversations we may need to get customer email.
// https://github.com/freescout-helpdesk/freescout/issues/3270
if (!$this->customer_email && $this->conversation->isPhone()) {
$this->customer_email = $this->conversation->customer->getMainEmail();
if (!$this->customer_email) {
return;
}
}
// Try to get customer by email
if (!$this->customer) {
$this->customer = Customer::getByEmail($this->customer_email);
return;
}
$to_array = $mailbox->removeMailboxEmailsFromList($this->last_thread->getToArray());
$cc_array = $mailbox->removeMailboxEmailsFromList($this->last_thread->getCcArray());
$bcc_array = $mailbox->removeMailboxEmailsFromList($this->last_thread->getBccArray());
@@ -203,15 +273,29 @@ class SendReplyToCustomer implements ShouldQueue
// $bcc_array = [];
// }
$subject = $this->conversation->subject;
if (!$new && !$is_forward) {
$subject = 'Re: '.$subject;
}
$subject = \Eventy::filter('email.reply_to_customer.subject', $subject, $this->conversation, $this->last_thread);
$this->threads = \Eventy::filter('email.reply_to_customer.threads', $this->threads, $this->conversation, $mailbox);
$headers['X-FreeScout-Mail-Type'] = 'customer.message';
$reply_mail = new ReplyToCustomer($this->conversation, $this->threads, $headers, $mailbox);
$reply_mail = new ReplyToCustomer($this->conversation, $this->threads, $headers, $mailbox, $subject, $threads_count);
$smtp_queue_id = null;
try {
Mail::to($to)
->cc($cc_array)
->bcc($bcc_array)
->send($reply_mail);
$this->last_thread->send_status = SendLog::STATUS_ACCEPTED;
$this->last_thread->save();
$smtp_queue_id = SwiftGetSmtpQueueId::$last_smtp_queue_id;
} catch (\Exception $e) {
// We come here in case SMTP server unavailable for example
if ($this->attempts() == 1) {
@@ -235,6 +319,14 @@ class SendReplyToCustomer implements ShouldQueue
$error_message = $e->getMessage();
// Remove stack trace from error message.
$error_message = preg_replace('#[\r\n]*" in /.*#s', '"', $error_message);
// SMTP response code is stored in the exception message:
// Expected response code 235 but got code "535", with message...
preg_match('#but got code "(\d+)",#', $error_message, $response_m);
$response_code = (int)($response_m[1] ?? 0);
// Retry job with delay.
// https://stackoverflow.com/questions/35258175/how-can-i-create-delays-between-failed-queued-job-attempts-in-laravel
if ($this->attempts() < $this->tries && !preg_match("/".config("app.no_retry_mail_errors")."/i", $error_message)) {
@@ -246,6 +338,13 @@ class SendReplyToCustomer implements ShouldQueue
$this->release(3600);
}
// If an email has not been sent after 1 hour - show an error message to support agent.
if ($this->attempts() >= 3 || $response_code >= 500) {
$this->last_thread->send_status = SendLog::STATUS_SEND_INTERMEDIATE_ERROR;
$this->last_thread->updateSendStatusData(['msg' => $error_message]);
$this->last_thread->save();
}
throw $e;
} else {
$this->last_thread->send_status = SendLog::STATUS_SEND_ERROR;
@@ -259,40 +358,143 @@ class SendReplyToCustomer implements ShouldQueue
}
}
SwiftGetSmtpQueueId::$last_smtp_queue_id = null;
// Clean error message if email finally has been sent.
if ($this->last_thread->send_status == SendLog::STATUS_SEND_ERROR) {
$this->last_thread->send_status = null;
$this->last_thread->updateSendStatusData(['msg' => '']);
$this->last_thread->save();
}
$imap_sent_folder = $mailbox->imap_sent_folder;
if ($imap_sent_folder) {
try {
$client = \MailHelper::getMailboxClient($mailbox);
$client->connect();
$envelope['from'] = $mailbox->getMailFrom()['address'];
$envelope['to'] = $this->customer_email;
$envelope['subject'] = 'Re: ' . $this->conversation->subject;
$mail_from = $mailbox->getMailFrom($this->last_thread->created_by_user ?? null, $this->conversation);
if (!empty($mail_from['name'])) {
$envelope['from'] = '"'.$mail_from['name'].'" <'.$mail_from['address'].'>';
} else {
$envelope['from'] = $mail_from['address'];
}
if (is_array($to) && !empty($to[0]) && !empty($to[0]['name']) && !empty($to[0]['email'])) {
$envelope['to'] = '"'.$to[0]['name'].'" <'.$to[0]['email'].'>';
} else {
$envelope['to'] = $this->customer_email;
}
$envelope['subject'] = $subject;
$envelope['date'] = now()->toRfc2822String();
$envelope['message_id'] = $this->message_id;
// CC.
if (count($cc_array)) {
$envelope['cc'] = implode(',', $cc_array);
}
// Get penultimate email Message-Id if reply
if (!$new && !empty($last_customer_thread) && $last_customer_thread->message_id) {
$envelope['custom_headers'] = [
'In-Reply-To: <'.$last_customer_thread->message_id.'>',
'References: <'.$last_customer_thread->message_id.'>'
'References: '.$references,
];
}
// Remove new lines to avoid "imap_mail_compose(): header injection attempt in subject".
foreach ($envelope as $i => $envelope_value) {
$envelope[$i] = preg_replace("/[\r\n]/", '', $envelope_value);
}
$part1['type'] = TYPETEXT;
$part1['subtype'] = 'html';
$part1['contents.data'] = $reply_mail->render();
$parts = [];
// Multipart flag.
if ($this->last_thread->has_attachments) {
$multipart = [];
$multipart["type"] = TYPEMULTIPART;
$multipart["subtype"] = "mixed";
// https://github.com/freescout-helpdesk/freescout/issues/3934
//$multipart["subtype"] = "alternative";
$parts[] = $multipart;
}
// Body.
$part_body['type'] = TYPETEXT;
$part_body['subtype'] = 'html';
$part_body['contents.data'] = $reply_mail->render();
$part_body['charset'] = 'utf-8';
$parts[] = $part_body;
// Add attachments.
if ($this->last_thread->has_attachments) {
foreach ($this->last_thread->attachments as $attachment) {
if ($attachment->embedded) {
continue;
}
if ($attachment->fileExists()) {
$part = [];
$part["type"] = 'APPLICATION';
$part["encoding"] = ENCBASE64;
$part["subtype"] = "octet-stream";
$part["description"] = $attachment->file_name;
$part['disposition.type'] = 'attachment';
$part['disposition'] = array('filename' => $attachment->file_name);
$part['type.parameters'] = array('name' => $attachment->file_name);
$part["description"] = '';
$part["contents.data"] = base64_encode($attachment->getFileContents());
$parts[] = $part;
} else {
\Log::error('[IMAP Folder To Save Outgoing Replies] Thread: '.$this->last_thread->id.'. Attachment file not find on disk: '.$attachment->getLocalFilePath());
}
}
}
try {
$folder = $client->getFolder($imap_sent_folder);
$folder->appendMessage(imap_mail_compose($envelope, [$part1]), '\Seen', now()->format('d-M-Y H:i:s O'));
// https://github.com/freescout-helpdesk/freescout/issues/3502
$imap_sent_folder = mb_convert_encoding($imap_sent_folder, "UTF7-IMAP","UTF-8");
// https://github.com/Webklex/php-imap/issues/380
if (method_exists($client, 'getFolderByPath')) {
$folder = $client->getFolderByPath($imap_sent_folder);
} else {
$folder = $client->getFolder($imap_sent_folder);
}
// Get folder method does not work if sent folder has spaces.
if ($folder) {
try {
$save_result = $this->saveEmailToFolder($client, $folder, $envelope, $parts, $bcc_array);
// Sometimes emails with attachments by some reason are not saved.
// https://github.com/freescout-helpdesk/freescout/issues/2749
if (!$save_result) {
// Save without attachments.
$save_result = $this->saveEmailToFolder($client, $folder, $envelope, [$part_body], $bcc_array);
if (!$save_result) {
\Log::error($this->getImapSaveErrorPrefix($mailbox).'Could not save outgoing reply to the IMAP folder (check folder name and make sure IMAP folder does not have spaces - folders with spaces do not work): '.$imap_sent_folder);
}
}
} catch (\Exception $e) {
// Just log error and continue.
\Helper::logException($e, $this->getImapSaveErrorPrefix($mailbox).'Could not save outgoing reply to the IMAP folder: ');
}
} else {
\Log::error($this->getImapSaveErrorPrefix($mailbox).'Could not save outgoing reply to the IMAP folder (check folder name and make sure IMAP folder does not have spaces - folders with spaces do not work): '.$imap_sent_folder);
}
} catch (\Exception $e) {
// Just log error and continue.
\Helper::logException($e, 'Could not save outgoing reply to the IMAP folder, IMAP folder not found: '.$imap_sent_folder.' - ');
\Helper::logException($e, $this->getImapSaveErrorPrefix($mailbox).'Could not save outgoing reply to the IMAP folder, IMAP folder not found: '.$imap_sent_folder.' - ');
//$this->saveToSendLog('['.date('Y-m-d H:i:s').'] Could not save outgoing reply to the IMAP folder: '.$imap_sent_folder);
}
} catch (\Exception $e) {
// Just log error and continue.
//$this->saveToSendLog('['.date('Y-m-d H:i:s').'] Could not get mailbox IMAP folder: '.$imap_sent_folder);
\Helper::logException($e, 'Could not save outgoing reply to the IMAP folder: '.$imap_sent_folder.' - ');
\Helper::logException($e, $this->getImapSaveErrorPrefix($mailbox).'Could not save outgoing reply to the IMAP folder: '.$imap_sent_folder.' - ');
}
}
@@ -305,7 +507,35 @@ class SendReplyToCustomer implements ShouldQueue
$this->failures = Mail::failures();
// Save to send log
$this->saveToSendLog();
$this->saveToSendLog('', $smtp_queue_id);
}
public function getImapSaveErrorPrefix($mailbox)
{
return '['.$mailbox->name.' » Connection Settings » Fetching Emails » IMAP Folder To Save Outgoing Replies] ';
}
// Save an email to IMAP folder.
public function saveEmailToFolder($client, $folder, $envelope, $parts, $bcc = [])
{
$envelope_str = imap_mail_compose($envelope, $parts);
// Add BCC.
// https://stackoverflow.com/questions/47353938/php-imap-append-with-bcc
if (!empty($bcc)) {
// There will be a "To:" parameter for sure.
$to_pos = strpos($envelope_str , "To:");
if ($to_pos !== false) {
$bcc_str = "Bcc: " . implode(',', $bcc) . "\r\n";
$envelope_str = substr_replace($envelope_str , $bcc_str, $to_pos, 0);
}
}
if (get_class($client) == 'Webklex\PHPIMAP\Client') {
return $folder->appendMessage($envelope_str, ['\Seen'], now()->format('d-M-Y H:i:s O'));
} else {
return $folder->appendMessage($envelope_str, '\Seen', now()->format('d-M-Y H:i:s O'));
}
}
/**
@@ -334,7 +564,7 @@ class SendReplyToCustomer implements ShouldQueue
/**
* Save emails to send log.
*/
public function saveToSendLog($error_message = '')
public function saveToSendLog($error_message = '', $smtp_queue_id = '')
{
foreach ($this->recipients as $recipient) {
if (in_array($recipient, $this->failures)) {
@@ -349,7 +579,7 @@ class SendReplyToCustomer implements ShouldQueue
} else {
$customer_id = null;
}
SendLog::log($this->last_thread->id, $this->message_id, $recipient, SendLog::MAIL_TYPE_EMAIL_TO_CUSTOMER, $status, $customer_id, null, $status_message);
SendLog::log($this->last_thread->id, $this->message_id, $recipient, SendLog::MAIL_TYPE_EMAIL_TO_CUSTOMER, $status, $customer_id, null, $status_message, $smtp_queue_id);
}
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Jobs;
use App\Conversation;
use App\ConversationFolder;
use App\Folder;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
class UpdateFolderCounters implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* @var Folder
*/
private $folder;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Folder $folder)
{
$this->folder = $folder;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$this->folder->updateCountersNow();
}
}

View File

@@ -26,8 +26,8 @@ class LogLockout
public function handle(Lockout $event)
{
activity()
->causedBy($event->user)
->withProperties(['ip' => app('request')->ip()])
//->causedBy($event->user)
->withProperties(['ip' => app('request')->ip(), 'email' => $event->request->email])
->useLog(\App\ActivityLog::NAME_USER)
->log(\App\ActivityLog::DESCRIPTION_USER_LOCKED);
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Listeners;
use App\Conversation;
class RefreshConversations
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param $event
*
* @return void
*/
public function handle($event)
{
Conversation::refreshConversations($event->conversation, $event->thread ?? $event->last_thread);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Listeners;
class RestartSwiftMailer
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*/
public function handle($event)
{
// Destroy Swift mailer to make it clean temp files.
// https://github.com/freescout-helpdesk/freescout/issues/2949
// https://github.com/swiftmailer/swiftmailer/issues/1287#issuecomment-833505298
\App::forgetInstance('mailer');
\App::forgetInstance('swift.mailer');
\App::forgetInstance('swift.transport');
}
}

View File

@@ -27,7 +27,11 @@ class SendAutoReply
{
$conversation = $event->conversation;
if (!$conversation->imported && $conversation->mailbox->auto_reply_enabled) {
// no_autoreply meta value is checked in the SendAutoReply job.
if (!$conversation->imported
&& $conversation->mailbox->auto_reply_enabled
) {
$thread = $conversation->threads()->first();
// Do not send auto reply to auto responders.
@@ -44,6 +48,10 @@ class SendAutoReply
return;
}
if (!\Eventy::filter('autoreply.should_send', true, $conversation)) {
return;
}
// We can not send auto reply to incoming bounce messages, as it will lead to the infinite loop:
// application will be sending auto replies and mail server will be sending bounce messages to auto replies.
// Bounce detection can not be 100% reliable.

View File

@@ -23,9 +23,11 @@ class SendReplyToCustomer
{
$conversation = $event->conversation;
// Do not send email if this is a Phone conversation.
// Do not send email if this is a Phone conversation and customer has no email.
if ($conversation->isPhone()) {
return;
if (!$conversation->customer->getMainEmail()) {
return;
}
}
$replies = $conversation->getReplies();
@@ -35,6 +37,18 @@ class SendReplyToCustomer
return;
}
// Remove threads added after this event had fired.
$thread = $event->last_thread ?? $event->thread ?? null;
if ($thread) {
foreach ($replies as $i => $reply) {
if ($reply->id == $thread->id) {
break;
} else {
$replies->forget($i);
}
}
}
// Chat conversation.
if ($conversation->isChat()) {
\Helper::backgroundAction('chat_conversation.send_reply', [$conversation, $replies, $conversation->customer], now()->addSeconds(Conversation::UNDO_TIMOUT));
@@ -44,8 +58,10 @@ class SendReplyToCustomer
// We can not check imported here, as after conversation has been imported via API
// notifications has to be sent.
//if (!$conversation->imported) {
$delay = \Eventy::filter('conversation.send_reply_to_customer_delay', now()->addSeconds(Conversation::UNDO_TIMOUT), $conversation, $replies);
\App\Jobs\SendReplyToCustomer::dispatch($conversation, $replies, $conversation->customer)
->delay(now()->addSeconds(Conversation::UNDO_TIMOUT))
->delay($delay)
->onQueue('emails');
}
}

View File

@@ -32,6 +32,8 @@ class Alert extends Mailable
*/
public function build()
{
\MailHelper::prepareMailable($this);
$subject = '['.\Config::get('app.name').'] ';
if (!empty($this->title)) {
$subject .= $this->title;

View File

@@ -44,6 +44,8 @@ class AutoReply extends Mailable
*/
public function build()
{
\MailHelper::prepareMailable($this);
$view_params = [];
// Set headers
@@ -56,8 +58,10 @@ class AutoReply extends Mailable
];
// Set variables
$subject = \App\Misc\Mail::replaceMailVars($this->mailbox->auto_reply_subject, $data);
$view_params['auto_reply_message'] = \App\Misc\Mail::replaceMailVars($this->mailbox->auto_reply_message, $data);
$subject = \MailHelper::replaceMailVars($this->mailbox->auto_reply_subject, $data);
$view_params['auto_reply_message'] = \MailHelper::replaceMailVars($this->mailbox->auto_reply_message, $data);
$subject = \Eventy::filter('email.auto_reply.subject', $subject, $this->conversation);
$message = $this->subject($subject)
->view('emails/customer/auto_reply', $view_params)

View File

@@ -26,6 +26,8 @@ class PasswordChanged extends Mailable
*/
public function build()
{
\MailHelper::prepareMailable($this);
$message = $this->subject(__('Password Changed'))
->view('emails/user/password_changed')
->text('emails/user/password_changed_text');

View File

@@ -38,17 +38,29 @@ class ReplyToCustomer extends Mailable
*/
public $mailbox;
/**
* Subject.
*/
public $subject;
/**
* Number of threads.
*/
public $threads_count;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct($conversation, $threads, $headers, $mailbox)
public function __construct($conversation, $threads, $headers, $mailbox, $subject, $threads_count = 1)
{
$this->conversation = $conversation;
$this->threads = $threads;
$this->headers = $headers;
$this->mailbox = $mailbox;
$this->subject = $subject;
$this->threads_count = $threads_count;
}
/**
@@ -58,18 +70,58 @@ class ReplyToCustomer extends Mailable
*/
public function build()
{
\MailHelper::prepareMailable($this);
$thread = $this->threads->first();
$from_alias = trim($thread->from ?? '');
// Set Message-ID
// Settings via $this->addCustomHeaders does not work
$new_headers = $this->headers;
if (!empty($new_headers)) {
$this->withSwiftMessage(function ($swiftmessage) use ($new_headers) {
if (!empty($new_headers['Message-ID'])) {
$swiftmessage->setId($new_headers['Message-ID']);
if (!empty($new_headers) || $from_alias) {
$mailbox = $this->mailbox;
$this->withSwiftMessage(function ($swiftmessage) use ($new_headers, $from_alias, $mailbox, $thread) {
if (!empty($new_headers)) {
if (!empty($new_headers['Message-ID'])) {
$swiftmessage->setId($new_headers['Message-ID']);
}
$headers = $swiftmessage->getHeaders();
foreach ($new_headers as $header => $value) {
if ($header != 'Message-ID') {
$headers->addTextHeader($header, $value);
}
}
}
$headers = $swiftmessage->getHeaders();
foreach ($new_headers as $header => $value) {
if ($header != 'Message-ID') {
$headers->addTextHeader($header, $value);
if (!empty($from_alias)) {
$aliases = $mailbox->getAliases();
// Make sure that the From contains a mailbox alias,
// as user thread may have From specified when a user
// replies to an email notification.
if (array_key_exists($from_alias, $aliases)) {
$from_alias_name = $aliases[$from_alias] ?? '';
// Take into account mailbox From Name setting.
$mailbox_mail_from = $mailbox->getMailFrom($thread->created_by_user, $thread->conversation);
if ($mailbox_mail_from['name'] == $mailbox->name && $from_alias_name) {
// Use name from alias.
} else {
// User name or custom.
$from_alias_name = $mailbox_mail_from['name'];
}
$swift_from = $headers->get('From');
if ($from_alias_name) {
$swift_from->setNameAddresses([
$from_alias => $from_alias_name
]);
} else {
$swift_from->setAddresses([
$from_alias
]);
}
}
}
@@ -77,20 +129,19 @@ class ReplyToCustomer extends Mailable
});
}
$subject = $this->conversation->subject;
if (count($this->threads) > 1) {
$subject = 'Re: '.$subject;
}
// from($this->from) Sets only email, name stays empty.
// So we set from in Mail::setMailDriver
$message = $this->subject($subject)
$message = $this->subject($this->subject)
->view('emails/customer/reply_fancy')
->text('emails/customer/reply_fancy_text');
if ($this->threads->first()->has_attachments) {
foreach ($this->threads->first()->attachments as $attachment) {
$message->attach($attachment->getLocalFilePath());
if ($thread->has_attachments) {
foreach ($thread->attachments as $attachment) {
if ($attachment->fileExists()) {
$message->attach($attachment->getLocalFilePath());
} else {
\Log::error('[ReplyToCustomer] Thread: '.$thread->id.'. Attachment file not find on disk: '.$attachment->getLocalFilePath());
}
}
}

View File

@@ -23,6 +23,8 @@ class Test extends Mailable
*/
public function build()
{
\MailHelper::prepareMailable($this);
$this->withSwiftMessage(function ($swiftmessage) {
$headers = $swiftmessage->getHeaders();
$headers->addTextHeader('X-FreeScout-Mail-Type', 'test.mailbox');

View File

@@ -25,6 +25,8 @@ class UserEmailReplyError extends Mailable
*/
public function build()
{
\MailHelper::prepareMailable($this);
return $this->subject(__('Unable to process your update'))
->view('emails/user/email_reply_error');
}

View File

@@ -27,6 +27,8 @@ class UserInvite extends Mailable
*/
public function build()
{
\MailHelper::prepareMailable($this);
$message = $this->subject(__('Welcome to :company_name!', ['company_name' => Option::getCompanyName()]))
->view('emails/user/user_invite')
->text('emails/user/user_invite_text');

View File

@@ -70,6 +70,8 @@ class UserNotification extends Mailable
*/
public function build()
{
\MailHelper::prepareMailable($this);
// Set Message-ID
// Settings via $this->addCustomHeaders does not work
$new_headers = $this->headers;

View File

@@ -2,6 +2,8 @@
namespace App;
use App\Email;
use App\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Hash;
use Watson\Rememberable\Rememberable;
@@ -25,6 +27,7 @@ class Mailbox extends Model
const TICKET_STATUS_ACTIVE = 1;
const TICKET_STATUS_PENDING = 2;
const TICKET_STATUS_CLOSED = 3;
const TICKET_STATUS_KEEP_CURRENT = 0;
/**
* Default Assignee.
@@ -32,6 +35,7 @@ class Mailbox extends Model
const TICKET_ASSIGNEE_ANYONE = 1;
const TICKET_ASSIGNEE_REPLYING_UNASSIGNED = 2;
const TICKET_ASSIGNEE_REPLYING = 3;
const TICKET_ASSIGNEE_KEEP_CURRENT = 0;
/**
* Email Template.
@@ -54,6 +58,9 @@ class Mailbox extends Model
const OUT_ENCRYPTION_NONE = 1;
const OUT_ENCRYPTION_SSL = 2;
const OUT_ENCRYPTION_TLS = 3;
// StartTLS is already included in TLS:
// allow_self_signed = true
// verify_peer = false
public static $out_encryptions = [
self::OUT_ENCRYPTION_NONE => '',
@@ -78,11 +85,16 @@ class Mailbox extends Model
const IN_ENCRYPTION_NONE = 1;
const IN_ENCRYPTION_SSL = 2;
const IN_ENCRYPTION_TLS = 3;
// For Webklex/laravel-imap this option is the same as 'none'.
// For Webklex/php-imap it works:
// https://github.com/Webklex/php-imap/pull/180
const IN_ENCRYPTION_STARTTLS = 4;
public static $in_encryptions = [
self::IN_ENCRYPTION_NONE => '',
self::IN_ENCRYPTION_SSL => 'ssl',
self::IN_ENCRYPTION_TLS => 'tls',
self::IN_ENCRYPTION_STARTTLS => 'starttls',
];
/**
@@ -98,6 +110,7 @@ class Mailbox extends Model
const ACCESS_PERM_PERMISSIONS = 'perm';
const ACCESS_PERM_AUTO_REPLIES = 'auto';
const ACCESS_PERM_SIGNATURE = 'sig';
const ACCESS_PERM_ASSIGNED = 'asg';
public static $access_permissions = [
self::ACCESS_PERM_EDIT,
@@ -135,7 +148,7 @@ class Mailbox extends Model
*
* @var [type]
*/
protected $fillable = ['name', 'email', 'aliases', 'auto_bcc', 'from_name', 'from_name_custom', 'ticket_status', 'ticket_assignee', 'template', 'before_reply', 'signature', 'out_method', 'out_server', 'out_username', 'out_password', 'out_port', 'out_encryption', 'in_server', 'in_port', 'in_username', 'in_password', 'in_protocol', 'in_encryption', 'in_validate_cert', 'auto_reply_enabled', 'auto_reply_subject', 'auto_reply_message', 'office_hours_enabled', 'ratings', 'ratings_placement', 'ratings_text', 'imap_sent_folder'];
protected $fillable = ['name', 'email', 'aliases', 'aliases_reply', 'auto_bcc', 'from_name', 'from_name_custom', 'ticket_status', 'ticket_assignee', 'template', 'before_reply', 'signature', 'out_method', 'out_server', 'out_username', 'out_password', 'out_port', 'out_encryption', 'in_server', 'in_port', 'in_username', 'in_password', 'in_protocol', 'in_encryption', 'in_validate_cert', 'auto_reply_enabled', 'auto_reply_subject', 'auto_reply_message', 'office_hours_enabled', 'ratings', 'ratings_placement', 'ratings_text', 'imap_sent_folder'];
protected static function boot()
{
@@ -171,7 +184,36 @@ class Mailbox extends Model
return decrypt($value);
} catch (\Exception $e) {
// do nothing if decrypt wasn't succefull
return false;
return '';
}
}
/**
* Automatically encrypt password on save.
*/
public function setOutPasswordAttribute($value)
{
if ($value != '') {
$this->attributes['out_password'] = encrypt($value);
} else {
$this->attributes['out_password'] = '';
}
}
/**
* Automatically decrypt password on read.
*/
public function getOutPasswordAttribute($value)
{
if (!$value) {
return '';
}
try {
return decrypt($value);
} catch (\Exception $e) {
// do nothing if decrypt wasn't succefull
return '';
}
}
@@ -253,11 +295,11 @@ class Mailbox extends Model
continue;
}
foreach ($folder_types as $type) {
$folder = new Folder();
$folder->mailbox_id = $mailbox_id;
$folder->user_id = $user_id;
$folder->type = $type;
$folder->save();
Folder::create([
'mailbox_id' => $mailbox_id,
'user_id' => $user_id,
'type' => $type,
]);
}
}
}
@@ -294,6 +336,11 @@ class Mailbox extends Model
*/
public function getMainFolders()
{
$main_folders = \Eventy::filter('mailbox.main_folders', [], $this);
if ($main_folders) {
return $main_folders;
}
return $this->folders()
->where(function ($query) {
$query->whereIn('type', [Folder::TYPE_UNASSIGNED, Folder::TYPE_ASSIGNED, Folder::TYPE_DRAFTS])
@@ -336,7 +383,7 @@ class Mailbox extends Model
->orderBy('type')
->get();
return \Eventy::filter('mailbox.folders', $folders);
return \Eventy::filter('mailbox.folders', $folders, $this);
}
/**
@@ -372,6 +419,12 @@ class Mailbox extends Model
*/
public function isInActive()
{
$in_active = \Eventy::filter('mailbox.in_active', null, $this);
if (is_bool($in_active)) {
return $in_active;
}
if ($this->in_protocol && $this->in_server && $this->in_port && $this->in_username && $this->in_password) {
return true;
} else {
@@ -416,10 +469,12 @@ class Mailbox extends Model
$users = User::sortUsers($users);
}
$users = \Eventy::filter('mailbox.users_having_access', $users, $this, $cache, $fields, $sort);
return $users;
}
public function usersAssignable($cache = true)
public function usersAssignable($cache = true, $exclude_hidden = true)
{
// Exclude hidden admins.
$mailbox_id = $this->id;
@@ -432,11 +487,17 @@ class Mailbox extends Model
->remember(\Helper::cacheTime($cache))
->get();
$users = $this->users()->select('users.*')->remember(\Helper::cacheTime($cache))->get()->merge($admins)->unique();
$users = $this->users()->select(['users.*', 'mailbox_user.hide'])
->remember(\Helper::cacheTime($cache))
->get()
->merge($admins)
->unique();
foreach ($users as $i => $user) {
if (!empty($user->hide)) {
$users->forget($i);
if ($exclude_hidden) {
foreach ($users as $i => $user) {
if (!empty($user->hide)) {
$users->forget($i);
}
}
}
@@ -450,6 +511,8 @@ class Mailbox extends Model
// Sort by full name
$users = User::sortUsers($users);
$users = \Eventy::filter('mailbox.users_assignable', $users, $this, $cache);
return $users;
}
@@ -474,9 +537,16 @@ class Mailbox extends Model
public function userHasAccess($user_id, $user = null)
{
if (!$user) {
$user = User::find($user_id);
if ($user_id instanceof \App\User) {
$user = $user_id;
} else {
$user = User::find($user_id);
}
}
if ($user && $user->isAdmin()) {
$filter = \Eventy::filter('mailbox.user_has_access', -1, $this, $user);
if ($filter != -1) {
return (bool)$filter;
} elseif ($user && $user->isAdmin()) {
return true;
} else {
return (bool) $this->users()->where('users.id', $user_id)->count();
@@ -487,21 +557,31 @@ class Mailbox extends Model
* Get From array for the Mail function.
*
* @param App\User $from_user
* @param App\Conversation $conversation
*
* @return array
*/
public function getMailFrom($from_user = null)
public function getMailFrom($from_user = null, $conversation = null)
{
// Mailbox name by default
$name = $this->name;
if ($this->from_name == self::FROM_NAME_CUSTOM && $this->from_name_custom) {
$name = $this->from_name_custom;
$data = [
'mailbox' => $this,
'mailbox_from_name' => '', // To avoid recursion.
'conversation' => $conversation,
'user' => $from_user ?: auth()->user(),
];
$name = \MailHelper::replaceMailVars($this->from_name_custom, $data, false, true);
} elseif ($this->from_name == self::FROM_NAME_USER && $from_user) {
$name = $from_user->getFullName();
}
return ['address' => $this->email, 'name' => $name];
return [
'address' => \Eventy::filter('mailbox.get_mail_from_address', $this->email, $from_user, $conversation),
'name' => \Eventy::filter('mailbox.get_mail_from_name', $name, $from_user, $conversation)
];
}
/**
@@ -561,7 +641,32 @@ class Mailbox extends Model
*/
public function getInProtocolName()
{
return self::$in_protocols[$this->in_protocol] ?? '';
return $this->getInProtocols()[$this->in_protocol] ?? '';
}
/**
* Get incoming protocol display name for the UI.
*
* @return array
*/
public static function getInProtocolDisplayNames()
{
$display_names = self::$in_protocols;
$display_names[self::IN_PROTOCOL_IMAP] = 'IMAP';
$display_names[self::IN_PROTOCOL_POP3] = 'POP3';
return \Eventy::filter('mailbox.in_protocols.display_names', $display_names);
}
/**
* Get available incoming protocols.
*
* @return array
*/
public static function getInProtocols()
{
return \Eventy::filter('mailbox.in_protocols', self::$in_protocols);
}
/**
@@ -615,6 +720,9 @@ class Mailbox extends Model
if ($this->aliases) {
$aliases = explode(',', $this->aliases);
foreach ($aliases as $alias) {
$alias = trim($alias);
$alias = preg_replace("#\(.*#", '', $alias);
$alias = Email::sanitizeEmail($alias);
if ($alias) {
$emails[] = $alias;
@@ -625,6 +733,42 @@ class Mailbox extends Model
return $emails;
}
/**
* Get mailbox aliases as an associative array.
*/
public function getAliases($include_mailbox_email = true, $check_aliases_reply = false)
{
if ($check_aliases_reply && !$this->aliases_reply) {
return [];
}
if ($include_mailbox_email) {
$emails = [$this->email => $this->name];
} else {
$emails = [];
}
if ($this->aliases) {
$aliases = explode(',', $this->aliases);
foreach ($aliases as $alias) {
$name = '';
$alias = trim($alias);
preg_match("#[^\(]+\((.*)\)#", $alias, $m);
if (!empty($m[1])) {
$name = $m[1];
$alias = preg_replace("#\(.*#", '', $alias);
}
$alias = Email::sanitizeEmail($alias);
if ($alias) {
$emails[$alias] = $name;
}
}
}
return $emails;
}
/**
* Remove mailbox email and aliases from the list of emails.
*
@@ -675,7 +819,7 @@ class Mailbox extends Model
*/
public function url()
{
return route('mailboxes.view', ['id' => $this->id]);
return \Eventy::filter('mailbox.url', route('mailboxes.view', ['id' => $this->id]), $this);
}
/**
@@ -768,10 +912,88 @@ class Mailbox extends Model
return $route;
}
public function setMetaParam($param, $value)
public function getMeta($key, $default = null)
{
if (isset($this->meta[$key])) {
return $this->meta[$key];
} else {
return $default;
}
}
public function setMeta($key, $value)
{
$meta = $this->meta;
$meta[$key] = $value;
$this->meta = $meta;
}
public function setMetaParam($param, $value, $save = false)
{
$meta = $this->meta;
$meta[$param] = $value;
$this->meta = $meta;
if ($save) {
$this->save();
}
}
public function removeMetaParam($param, $save = false)
{
$meta = $this->meta;
if (isset($meta[$param])) {
unset($meta[$param]);
}
$this->meta = $meta;
if ($save) {
$this->save();
}
}
/**
* Check if there is a user with specified email.
*/
public static function userEmailExists($email)
{
$email = Email::sanitizeEmail($email);
$user = User::where('email', $email)->first();
if ($user) {
return true;
} else {
return false;
}
}
public function oauthEnabled()
{
return !empty($this->meta['oauth']['provider']);
}
public function oauthGetParam($param)
{
return $this->meta['oauth'][$param] ?? '';
}
public function inOauthEnabled()
{
return $this->oauthEnabled()
&& $this->in_username !== null && !strstr($this->in_username, '@');
}
public function outOauthEnabled()
{
return $this->oauthEnabled()
&& $this->out_username !== null && !strstr($this->out_username, '@')
&& $this->out_server !== null && trim($this->out_server) == \MailHelper::OAUTH_MICROSOFT_SMTP;
}
public function setEmailAttribute($value)
{
if ($value) {
$this->attributes['email'] = Email::sanitizeEmail($value);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,7 @@ class Mail
*/
const REPLY_SEPARATOR_HTML = 'fsReplyAbove';
const REPLY_SEPARATOR_TEXT = '-- Please reply above this line --';
const REPLY_SEPARATOR_NOTIFICATION = 'fsNotifReplyAbove';
/**
* Message-ID prefixes for outgoing emails.
@@ -39,12 +40,17 @@ class Mail
const MAIL_ENCRYPTION_TLS = 'tls';
const FETCH_SCHEDULE_EVERY_MINUTE = 1;
const FETCH_SCHEDULE_EVERY_TWO_MINUTES = 2;
const FETCH_SCHEDULE_EVERY_THREE_MINUTES = 3;
const FETCH_SCHEDULE_EVERY_FIVE_MINUTES = 5;
const FETCH_SCHEDULE_EVERY_TEN_MINUTES = 10;
const FETCH_SCHEDULE_EVERY_FIFTEEN_MINUTES = 15;
const FETCH_SCHEDULE_EVERY_THIRTY_MINUTES = 30;
const FETCH_SCHEDULE_HOURLY = 60;
const OAUTH_PROVIDER_MICROSOFT = 'ms';
const OAUTH_MICROSOFT_SMTP = 'smtp.office365.com';
/**
* If reply is not extracted properly from the incoming email, add here a new separator.
* Order is not important.
@@ -56,7 +62,9 @@ class Mail
self::REPLY_SEPARATOR_TEXT, // Our plain text separator
// Email service providers specific separators.
// <div class="gmail_quote" style="font-family:sans-serif;">
'<div class="gmail_quote">', // Gmail
'<div class="gmail_quote" ', // Gmail
'<div id="appendonsend"></div>', // Outlook / Live / Hotmail / Microsoft
'<div name="quote" ',
'yahoo_quoted_', // Yahoo, full: <div id=3D"ydp6h4f5c59yahoo_quoted_2937493705"
@@ -70,6 +78,7 @@ class Mail
'<!-- originalMessage -->',
'‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐',
'--------------- Original Message ---------------',
'-------- Αρχικό μήνυμα --------', // Greek
];
/**
@@ -77,28 +86,67 @@ class Mail
*/
public static $last_mail_config_hash = '';
/**
* Used to get SMTP queue id when sending emails to customers.
*/
public static $smtp_queue_id_plugin_registered = false;
/**
* Configure mail sending parameters.
*
* @param App\Mailbox $mailbox
* @param App\User $user_from
* @param App\Conversation $conversation
*/
public static function setMailDriver($mailbox = null, $user_from = null)
public static function setMailDriver($mailbox = null, $user_from = null, $conversation = null)
{
if ($mailbox) {
// Configure mail driver according to Mailbox settings
\Config::set('mail.driver', $mailbox->getMailDriverName());
\Config::set('mail.from', $mailbox->getMailFrom($user_from));
// Configure mail driver according to Mailbox settings.
$oauth = $mailbox->outOauthEnabled();
// SMTP
// Refresh Access Token.
if ($oauth) {
if ((strtotime($mailbox->oauthGetParam('issued_on')) + (int)$mailbox->oauthGetParam('expires_in')) < time()) {
// Try to get an access token (using the authorization code grant)
$token_data = \MailHelper::oauthGetAccessToken(\MailHelper::OAUTH_PROVIDER_MICROSOFT, [
'client_id' => $mailbox->out_username,
'client_secret' => $mailbox->out_password,
'refresh_token' => $mailbox->oauthGetParam('r_token'),
]);
if (!empty($token_data['a_token'])) {
$mailbox->setMetaParam('oauth', $token_data, true);
} elseif (!empty($token_data['error'])) {
$error_message = 'Error occurred refreshing oAuth Access Token: '.$token_data['error'];
\Helper::log(\App\ActivityLog::NAME_EMAILS_SENDING,
\App\ActivityLog::DESCRIPTION_EMAILS_SENDING_ERROR_TO_CUSTOMER, [
'error' => $error_message,
'mailbox' => $mailbox->name,
]);
//throw new \Exception($error_message, 1);
}
}
}
\Config::set('mail.driver', $mailbox->getMailDriverName());
\Config::set('mail.from', $mailbox->getMailFrom($user_from, $conversation));
// SMTP.
if ($mailbox->out_method == Mailbox::OUT_METHOD_SMTP) {
\Config::set('mail.host', $mailbox->out_server);
\Config::set('mail.port', $mailbox->out_port);
if (!$mailbox->out_username) {
\Config::set('mail.username', null);
\Config::set('mail.password', null);
if ($oauth) {
\Config::set('mail.auth_mode', 'XOAUTH2');
\Config::set('mail.username', $mailbox->email);
\Config::set('mail.password', $mailbox->oauthGetParam('a_token'));
} else {
\Config::set('mail.username', $mailbox->out_username);
\Config::set('mail.password', $mailbox->out_password);
if (!$mailbox->out_username) {
\Config::set('mail.username', null);
\Config::set('mail.password', null);
} else {
\Config::set('mail.username', $mailbox->out_username);
\Config::set('mail.password', $mailbox->out_password);
}
}
\Config::set('mail.encryption', $mailbox->getOutEncryptionName());
}
@@ -135,6 +183,8 @@ class Mail
// We have to update Mailer facade manually, as it does not happen automatically
// and previous instance of app('mailer') is used.
\Mail::swap(app('mailer'));
\Eventy::action('mail.reapply_mail_config');
}
/**
@@ -159,7 +209,7 @@ class Mail
\Config::set('mail.password', null);
} else {
\Config::set('mail.username', Option::get('mail_username'));
\Config::set('mail.password', Option::get('mail_password'));
\Config::set('mail.password', \Helper::decrypt(Option::get('mail_password')));
}
\Config::set('mail.encryption', Option::get('mail_encryption'));
}
@@ -170,7 +220,7 @@ class Mail
/**
* Replace mail vars in the text.
*/
public static function replaceMailVars($text, $data = [], $escape = false)
public static function replaceMailVars($text, $data = [], $escape = false, $remove_non_replaced = false)
{
// Available variables to insert into email in UI.
$vars = [];
@@ -183,12 +233,18 @@ class Mail
if (!empty($data['mailbox'])) {
$vars['{%mailbox.email%}'] = $data['mailbox']->email;
$vars['{%mailbox.name%}'] = $data['mailbox']->name;
$vars['{%mailbox.fromName%}'] = $data['mailbox']->getMailFrom(!empty($data['user']) ? $data['user'] : null)['name'];
// To avoid recursion.
if (isset($data['mailbox_from_name'])) {
$vars['{%mailbox.fromName%}'] = $data['mailbox_from_name'];
} else {
$vars['{%mailbox.fromName%}'] = $data['mailbox']->getMailFrom(!empty($data['user']) ? $data['user'] : null)['name'];
}
}
if (!empty($data['customer'])) {
$vars['{%customer.fullName%}'] = $data['customer']->getFullName(true);
$vars['{%customer.firstName%}'] = $data['customer']->getFirstName(true);
$vars['{%customer.lastName%}'] = $data['customer']->last_name;
$vars['{%customer.company%}'] = $data['customer']->company;
}
if (!empty($data['user'])) {
$vars['{%user.fullName%}'] = $data['user']->getFullName();
@@ -200,13 +256,28 @@ class Mail
$vars['{%user.photoUrl%}'] = $data['user']->getPhotoUrl();
}
$vars = \Eventy::filter('mail_vars.replace', $vars, $data);
if ($escape) {
foreach ($vars as $i => $var) {
$vars[$i] = htmlspecialchars($var);
$vars[$i] = htmlspecialchars($var ?? '');
$vars[$i] = nl2br($vars[$i]);
}
} else {
foreach ($vars as $i => $var) {
$vars[$i] = nl2br($var ?? '');
}
}
return strtr($text, $vars);
$result = strtr($text, $vars);
// Remove non-replaced placeholders.
if ($remove_non_replaced) {
$result = preg_replace('#\{%[^\.%\}]+\.[^%\}]+%\}#', '', $result ?? '');
$result = trim($result);
}
return $result;
}
/**
@@ -214,7 +285,7 @@ class Mail
*/
public static function hasVars($text)
{
return preg_match('/({%|%})/', $text);
return preg_match('/({%|%})/', $text ?? '');
}
/**
@@ -246,14 +317,29 @@ class Mail
return Option::get('mail_driver', 'mail');
}
public static function registerSmtpLogger()
{
$logger = new \Swift_Plugins_Loggers_ArrayLogger();
\Mail::getSwiftMailer()->registerPlugin(new \Swift_Plugins_LoggerPlugin($logger));
return $logger;
}
/**
* Send test email from mailbox.
*/
public static function sendTestMail($to, $mailbox = null)
{
$result = [
'status' => 'success',
'msg' => '',
'log' => '',
];
if ($mailbox) {
// Configure mail driver according to Mailbox settings
\MailHelper::setMailDriver($mailbox);
$smtp_logger = self::registerSmtpLogger();
$status_message = '';
@@ -266,6 +352,7 @@ class Mail
} else {
// System email
\MailHelper::setSystemMailDriver();
$smtp_logger = self::registerSmtpLogger();
$status_message = '';
@@ -281,15 +368,17 @@ class Mail
if (\Mail::failures() || $status_message) {
SendLog::log(null, null, $to, SendLog::MAIL_TYPE_TEST, SendLog::STATUS_SEND_ERROR, null, null, $status_message);
if ($status_message) {
throw new \Exception($status_message, 1);
} else {
return false;
$result['msg'] = $status_message;
}
$result['status'] = 'error';
$result['log'] = $smtp_logger->dump();
} else {
SendLog::log(null, null, $to, SendLog::MAIL_TYPE_TEST, SendLog::STATUS_ACCEPTED);
return true;
$result['status'] = 'success';
}
return $result;
}
/**
@@ -297,45 +386,65 @@ class Mail
*/
public static function fetchTest($mailbox)
{
$client = new Client([
'host' => $mailbox->in_server,
'port' => $mailbox->in_port,
'encryption' => $mailbox->getInEncryptionName(),
'validate_cert' => $mailbox->in_validate_cert,
'username' => $mailbox->in_username,
'password' => $mailbox->in_password,
'protocol' => $mailbox->getInProtocolName(),
]);
$result = [
'result' => 'success',
'msg' => '',
'log' => '',
];
// Connect to the Server
$client->connect();
$client = null;
// Get folder
$folder = $client->getFolder('INBOX');
try {
\Config::set('imap.options.debug', true);
\Webklex\PHPIMAP\Connection\Protocols\ImapProtocol::$output_debug_log = false;
if (!$folder) {
throw new \Exception('Could not get mailbox folder: INBOX', 1);
}
// Get unseen messages for a period
$messages = $folder->query()->unseen()->since(now()->subDays(1))->leaveUnread()->get();
$client = \MailHelper::getMailboxClient($mailbox);
$last_error = $client->getLastError();
if ($last_error && stristr($last_error, 'The specified charset is not supported')) {
// Solution for MS mailboxes.
// https://github.com/freescout-helpdesk/freescout/issues/176
$messages = $folder->query()->unseen()->since(now()->subDays(1))->leaveUnread()->setCharset(null)->get();
if (count($client->getErrors()) > 1) {
$last_error = $client->getLastError();
} else {
$last_error = null;
// Connect to the Server
$client->connect();
// Get folder
$folder = $client->getFolder('INBOX');
if (!$folder) {
$result['result'] = 'error';
$result['msg'] = 'Could not get mailbox folder: INBOX';
//throw new \Exception('Could not get mailbox folder: INBOX', 1);
}
// Get unseen messages for a period
$messages = $folder->query()->unseen()->since(now()->subDays(1))->leaveUnread()->get();
$last_error = '';
if (method_exists($client, 'getLastError')) {
$last_error = $client->getLastError();
}
if ($last_error && stristr($last_error, 'The specified charset is not supported')) {
// Solution for MS mailboxes.
// https://github.com/freescout-helpdesk/freescout/issues/176
$messages = $folder->query()->unseen()->since(now()->subDays(1))->leaveUnread()->setCharset(null)->get();
if (count($client->getErrors()) > 1) {
$last_error = $client->getLastError();
} else {
$last_error = null;
}
}
if ($last_error) {
//throw new \Exception($last_error, 1);
$result['result'] = 'error';
$result['msg'] = $last_error;
}
} catch (\Exception $e) {
$result['result'] = 'error';
$result['msg'] = $e->getMessage();
}
if ($last_error) {
throw new \Exception($last_error, 1);
} else {
return true;
if ($result['result'] == 'error') {
$result['log'] = \Webklex\PHPIMAP\Connection\Protocols\ImapProtocol::getDebugLog();
}
return $result;
}
/**
@@ -350,7 +459,7 @@ class Mail
if (is_array($emails)) {
$emails_array = $emails;
} else {
$emails_array = explode(',', $emails);
$emails_array = explode(',', $emails ?? '');
}
foreach ($emails_array as $i => $email) {
@@ -444,7 +553,7 @@ class Mail
*/
public static function fetchMessageMarkerValue($body)
{
preg_match('/{#FS:([^#]+)#}/', $body, $matches);
preg_match('/{#FS:([^#]+)#}/', $body ?? '', $matches);
if (!empty($matches[1]) && base64_decode($matches[1])) {
// Return first found marker.
return base64_decode($matches[1]);
@@ -453,6 +562,11 @@ class Mail
return '';
}
public static function getMessageIdHash($thread_id)
{
return substr(md5($thread_id.config('app.key')), 0, 16);
}
/**
* Detect autoresponder by headers.
* https://github.com/jpmckinney/multi_mail/wiki/Detecting-autoresponders
@@ -465,11 +579,11 @@ class Mail
$autoresponder_headers = [
'x-autoreply' => '',
'x-autorespond' => '',
'auto-submitted' => 'auto-replied',
'auto-submitted' => '', // this can be auto-replied, auto-generated, etc.
'precedence' => ['auto_reply', 'bulk', 'junk'],
'x-precedence' => ['auto_reply', 'bulk', 'junk'],
];
$headers = explode("\n", $headers_str);
$headers = explode("\n", $headers_str ?? '');
foreach ($autoresponder_headers as $auto_header => $auto_header_value) {
foreach ($headers as $header) {
@@ -561,15 +675,85 @@ class Mail
*/
public static function getMailboxClient($mailbox)
{
return new \Webklex\IMAP\Client([
'host' => $mailbox->in_server,
'port' => $mailbox->in_port,
'encryption' => $mailbox->getInEncryptionName(),
'validate_cert' => $mailbox->in_validate_cert,
'username' => $mailbox->in_username,
'password' => $mailbox->in_password,
'protocol' => $mailbox->getInProtocolName(),
]);
$oauth = $mailbox->oauthEnabled();
$new_library = config('app.new_fetching_library');
if (!$new_library) {
// Old.
return new \Webklex\IMAP\Client([
'host' => $mailbox->in_server,
'port' => $mailbox->in_port,
'encryption' => $mailbox->getInEncryptionName(),
'validate_cert' => $mailbox->in_validate_cert,
'username' => $mailbox->in_username,
'password' => $mailbox->in_password,
'protocol' => $mailbox->getInProtocolName(),
]);
} else {
// New
if ($oauth) {
\Config::set('imap.accounts.default', [
'host' => $mailbox->in_server,
'port' => $mailbox->in_port,
'encryption' => $mailbox->getInEncryptionName(),
'validate_cert' => $mailbox->in_validate_cert,
'username' => $mailbox->email,
'password' => $mailbox->oauthGetParam('a_token'),
'protocol' => $mailbox->getInProtocolName(),
'authentication' => 'oauth',
]);
} else {
\Config::set('imap.accounts.default', [
'host' => $mailbox->in_server,
'port' => $mailbox->in_port,
'encryption' => $mailbox->getInEncryptionName(),
'validate_cert' => $mailbox->in_validate_cert,
// 'username' => $mailbox->email,
// 'password' => $mailbox->oauthGetParam('a_token'),
// 'protocol' => $mailbox->getInProtocolName(),
// 'authentication' => 'oauth',
'username' => $mailbox->in_username,
'password' => $mailbox->in_password,
'protocol' => $mailbox->getInProtocolName(),
]);
}
// To enable debug: /vendor/webklex/php-imap/src/Connection/Protocols
// Debug in console
if (app()->runningInConsole()) {
\Config::set('imap.options.debug', config('app.debug'));
}
$cm = new \Webklex\PHPIMAP\ClientManager(config('imap'));
// Refresh Access Token.
if ($oauth) {
if ((strtotime($mailbox->oauthGetParam('issued_on')) + (int)$mailbox->oauthGetParam('expires_in')) < time()) {
// Try to get an access token (using the authorization code grant)
$token_data = \MailHelper::oauthGetAccessToken(\MailHelper::OAUTH_PROVIDER_MICROSOFT, [
'client_id' => $mailbox->in_username,
'client_secret' => $mailbox->in_password,
'refresh_token' => $mailbox->oauthGetParam('r_token'),
]);
if (!empty($token_data['a_token'])) {
$mailbox->setMetaParam('oauth', $token_data, true);
} elseif (!empty($token_data['error'])) {
$error_message = 'Error occurred refreshing oAuth Access Token: '.$token_data['error'];
\Helper::log(\App\ActivityLog::NAME_EMAILS_FETCHING,
\App\ActivityLog::DESCRIPTION_EMAILS_FETCHING_ERROR, [
'error' => $error_message,
'mailbox' => $mailbox->name,
]);
throw new \Exception($error_message, 1);
}
}
}
// This makes it authenticate two times.
//$cm->setTimeout(60);
return $cm->account('default');
}
}
/**
@@ -588,7 +772,7 @@ class Mail
/**
* Fetch IMAP message by Message-ID.
*/
public static function fetchMessage($mailbox, $message_id)
public static function fetchMessage($mailbox, $message_id, $message_date = null)
{
$no_charset = false;
@@ -600,16 +784,32 @@ class Mail
$client = \MailHelper::getMailboxClient($mailbox);
$client->connect();
} catch (\Exception $e) {
\Helper::logException($e, '('.$mailbox->name.') Could not fetch specific message by Message-ID:');
return null;
}
$imap_folders = $mailbox->getInImapFolders();
$imap_folders = \Eventy::filter('mail.fetch_message.imap_folders', $mailbox->getInImapFolders(), $mailbox);
foreach ($imap_folders as $folder_name) {
try {
$folder = $client->getFolder($folder_name);
$folder = self::getImapFolder($client, $folder_name);
if (!$folder) {
\Log::error('('.$mailbox->name.') Show Original - folder not found: '.$folder_name);
continue;
}
// Message-ID: <123@123.com>
$query = $folder->query()->text('<'.$message_id.'>')->leaveUnread()->limit(1);
$query = $folder->query()
->text('<'.$message_id.'>')
->leaveUnread()
->limit(1);
// Limit using date to speed up the search.
if ($message_date) {
$query->since($message_date->subDays(7));
// Here we should add 14 days, as previous line subtracts 7 days.
$query->before($message_date->addDays(14));
}
if ($no_charset) {
$query->setCharset(null);
@@ -617,12 +817,20 @@ class Mail
$messages = $query->get();
$last_error = $client->getLastError();
$last_error = '';
if (method_exists($client, 'getLastError')) {
$last_error = $client->getLastError();
}
if ($last_error && stristr($last_error, 'The specified charset is not supported')) {
// Solution for MS mailboxes.
// https://github.com/freescout-helpdesk/freescout/issues/176
$messages = $folder->query()->text('<'.$message_id.'>')->leaveUnread()->limit(1)->setCharset(null)->get();
$query = $folder->query()->text('<'.$message_id.'>')->leaveUnread()->limit(1)->setCharset(null);
if ($message_date) {
$query->since($message_date->subDays(7));
$query->before($message_date->addDays(14));
}
$messages = $query->get();
$no_charset = true;
}
@@ -631,10 +839,323 @@ class Mail
}
} catch (\Exception $e) {
// Do nothing.
\Helper::logException($e, '('.$mailbox->name.') Could not fetch specific message by Message-ID via IMAP:');
}
}
return null;
}
public static function oauthGetAuthorizationUrl($provider_code, $params)
{
$args = [];
switch ($provider_code) {
case self::OAUTH_PROVIDER_MICROSOFT:
// https://docs.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth
$args = [
'scope' => 'offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send',
'response_type' => 'code',
'approval_prompt' => 'auto',
'redirect_uri' => route('mailboxes.oauth_callback'),
];
$args = array_merge($args, $params);
$url = 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize?'.http_build_query($args);
break;
}
return $url;
}
public static function oauthGetAccessToken($provider_code, $params)
{
$token_data = [];
$post_params = [];
switch ($provider_code) {
case self::OAUTH_PROVIDER_MICROSOFT:
$post_params = [
'scope' => 'offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send',
"grant_type" => "authorization_code",
'redirect_uri' => route('mailboxes.oauth_callback'),
];
$post_params = array_merge($post_params, $params);
// Refreshing Access Token.
if (!empty($post_params['refresh_token'])) {
$post_params['grant_type'] = 'refresh_token';
}
// $postUrl = "/common/oauth2/token";
// $hostname = "login.microsoftonline.com";
$full_url = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
// $headers = array(
// // "POST " . $postUrl . " HTTP/1.1",
// // "Host: login.windows.net",
// "Content-type: application/x-www-form-urlencoded",
// );
$curl = curl_init($full_url);
curl_setopt($curl, CURLOPT_POST, true);
//curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_POSTFIELDS, $post_params);
curl_setopt($curl, CURLOPT_HTTPHEADER, array("application/x-www-form-urlencoded"));
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
\Helper::setCurlDefaultOptions($curl);
curl_setopt($curl, CURLOPT_TIMEOUT, 180);
$response = curl_exec($curl);
if ($response) {
$result = json_decode($response, true);
// [token_type] => Bearer
// [scope] => IMAP.AccessAsUser.All offline_access SMTP.Send User.Read
// [expires_in] => 4514
// [ext_expires_in] => 4514
// [expires_on] => 1646122657
// [not_before] => 1646117842
// [resource] => 00000002-0000-0000-c000-000000000000
// [access_token] => dd
// [refresh_token] => dd
// [id_token] => dd
if (!empty($result['access_token'])) {
$token_data['provider'] = self::OAUTH_PROVIDER_MICROSOFT;
$token_data['a_token'] = $result['access_token'];
$token_data['r_token'] = $result['refresh_token'];
//$token_data['id_token'] = $result['id_token'];
$token_data['issued_on'] = now()->toDateTimeString();
$token_data['expires_in'] = $result['expires_in'];
} elseif ($response) {
$token_data['error'] = $response;
} else {
$token_data['error'] = 'Response code: '.curl_getinfo($curl, CURLINFO_HTTP_CODE);
}
}
curl_close($curl);
break;
}
return $token_data;
}
public static function oauthDisconnect($provider_code, $redirect_uri)
{
switch ($provider_code) {
case self::OAUTH_PROVIDER_MICROSOFT:
return redirect()->away('https://login.microsoftonline.com/common/oauth2/v2.0/logout?post_logout_redirect_uri='.urlencode($redirect_uri));
break;
}
}
public static function prepareMailable($mailable)
{
$custom_headers_str = config('app.custom_mail_headers');
if (empty($custom_headers_str)) {
return;
}
$custom_headers = explode(';', $custom_headers_str);
$mailable->withSwiftMessage(function ($swiftmessage) use ($custom_headers) {
$headers = $swiftmessage->getHeaders();
foreach ($custom_headers as $custom_header) {
$header_parts = explode(':', $custom_header);
$header_name = trim($header_parts[0] ?? '');
$header_value = trim($header_parts[1] ?? '');
if ($header_name && $header_value) {
$headers->addTextHeader($header_name, $header_value);
}
}
return $swiftmessage;
});
}
public static function getImapFolder($client, $folder_name)
{
// https://github.com/freescout-helpdesk/freescout/issues/3502
$folder_name = mb_convert_encoding($folder_name, "UTF7-IMAP","UTF-8");
if (method_exists($client, 'getFolderByPath')) {
return $client->getFolderByPath($folder_name);
} else {
return $client->getFolder($folder_name);
}
}
/**
* This function is used to decode email subjects and attachment names in Webklex libraries.
*/
public static function decodeSubject($subject)
{
// Remove new lines as iconv_mime_decode() may loose a part separated by new line:
// =?utf-8?Q?Gesch=C3=A4ftskonto?= erstellen =?utf-8?Q?f=C3=BCr?=
// 249143
$subject = preg_replace("/[\r\n]/", '', $subject);
// https://github.com/freescout-helpdesk/freescout/issues/3185
$subject = str_ireplace('=?iso-2022-jp?', '=?iso-2022-jp-ms?', $subject);
// Sometimes imap_utf8() can't decode the subject, for example:
// =?iso-2022-jp?B?GyRCIXlCaBsoQjEzMhskQjlmISEhViUsITwlRyVzGyhCJhskQiUoJS8lOSVGJWolIiFXQGxMZ0U5JE4kPyRhJE4jURsoQiYbJEIjQSU1JW0lcyEhIVo3bjQpJSglLyU5JUYlaiUiISYlbyE8JS8hWxsoQg==?=
// and sometimes iconv_mime_decode() can't decode the subject.
// So we are using both.
//
// We are trying iconv_mime_decode() first because imap_utf8()
// decodes umlauts into two symbols:
// https://github.com/freescout-helpdesk/freescout/issues/2965
// Sometimes subject is split into parts and each part is base63 encoded.
// And sometimes it's first encoded and after that split.
// https://github.com/freescout-helpdesk/freescout/issues/3066
// Step 1. Abnormal way - text is encoded and split into parts.
// Only one type of encoding should be used.
preg_match_all("/(=\?[^\?]+\?[BQ]\?)([^\?]+)(\?=)/i", $subject, $m);
$encodings = $m[1] ?? [];
array_walk($encodings, function($value) {
$value = strtolower($value);
});
$one_encoding = count(array_unique($encodings)) == 1;
if ($one_encoding) {
// First try to join all lines and parts.
// Keep in mind that there can be non-encoded parts also:
// =?utf-8?Q?Gesch=C3=A4ftskonto?= erstellen =?utf-8?Q?f=C3=BCr?=
preg_match_all("/(=\?[^\?]+\?[BQ]\?)([^\?]+)(\?=)[\r\n\t ]*/i", $subject, $m);
$joined_parts = '';
if (count($m[1]) > 1 && !empty($m[2]) && !preg_match("/[\r\n\t ]+[^=]/i", $subject)) {
// Example: GyRCQGlNVTtZRTkhIT4uTlMbKEI=
$joined_parts = $m[1][0].implode('', $m[2]).$m[3][0];
// Base64 and URL encoded string can't contain "=" in the middle
// https://stackoverflow.com/questions/6916805/why-does-a-base64-encoded-string-have-an-sign-at-the-end
$has_equal_in_the_middle = preg_match("#=+([^$\? =])#", $joined_parts);
if (!$has_equal_in_the_middle) {
$subject_decoded = iconv_mime_decode($joined_parts, ICONV_MIME_DECODE_CONTINUE_ON_ERROR, "UTF-8");
if ($subject_decoded
&& trim($subject_decoded) != trim($joined_parts)
&& trim($subject_decoded) != trim(rtrim($joined_parts, '='))
&& !self::isNotYetFullyDecoded($subject_decoded)
) {
return $subject_decoded;
}
// Try imap_utf8().
// =?iso-2022-jp?B?IBskQiFaSEcyPDpuQ?= =?iso-2022-jp?B?C4wTU1qIVs3Mkp2JSIlLyU3JSItahsoQg==?=
$subject_decoded = \imap_utf8($joined_parts);
if ($subject_decoded
&& trim($subject_decoded) != trim($joined_parts)
&& trim($subject_decoded) != trim(rtrim($joined_parts, '='))
&& !self::isNotYetFullyDecoded($subject_decoded)
) {
return $subject_decoded;
}
}
}
}
// Step 2. Standard way - each part is encoded separately.
// iconv_mime_decode() can't decode:
// =?iso-2022-jp?B?IBskQiFaSEcyPDpuQC4wTU1qIVs3Mkp2JSIlLyU3JSItahsoQg==?=
$subject_decoded = iconv_mime_decode($subject, ICONV_MIME_DECODE_CONTINUE_ON_ERROR, "UTF-8");
// Sometimes iconv_mime_decode() can't decode some parts of the subject:
// =?iso-2022-jp?B?IBskQiFaSEcyPDpuQC4wTU1qIVs3Mkp2JSIlLyU3JSItahsoQg==?=
// =?iso-2022-jp?B?GyRCQGlNVTtZRTkhIT4uTlMbKEI=?=
if (self::isNotYetFullyDecoded($subject_decoded)) {
$subject_decoded = \imap_utf8($subject);
}
// All previous functions could not decode text.
// mb_decode_mimeheader() properly decodes umlauts into one unice symbol.
// But we use mb_decode_mimeheader() as a last resort as it may garble some symbols.
// Example: =?ISO-8859-1?Q?Vorgang 538336029: M=F6chten Sie Ihre E-Mail-Adresse =E4ndern??=
if (self::isNotYetFullyDecoded($subject_decoded)) {
$subject_decoded = mb_decode_mimeheader($subject);
}
if (!$subject_decoded) {
$subject_decoded = $subject;
}
return $subject_decoded;
}
public static function isNotYetFullyDecoded($subject_decoded) {
// https://stackoverflow.com/questions/15276191/why-does-a-diamond-with-a-questionmark-in-it-appear-in-my-html
$invalid_utf_symbols = ['<27>'];
return preg_match_all("/=\?[^\?]+\?[BQ]\?/i", $subject_decoded)
|| !mb_check_encoding($subject_decoded, 'UTF-8')
|| \Str::contains($subject_decoded, $invalid_utf_symbols);
}
public static function getHashedReplySeparator($message_id) {
$separator = \MailHelper::REPLY_SEPARATOR_HTML;
if ($message_id) {
$separator .= substr(md5($message_id.config('app.key')), 0, 8);
}
return $separator;
}
// Sanitize status message - remove SMTP username and password.
public static function sanitizeSmtpStatusMessage($status_message) {
$status_message = preg_replace('#(username ")[^"]+(")#', '$1***$2', $status_message ?? '');
$status_message = preg_replace("#(Swift_Transport_Esmtp_Auth_LoginAuthenticator\->authenticate\(Object\(Swift_SmtpTransport\), ')[^\']+(', ')[^\']+('\))#", '$1***$2***$3', $status_message ?? '');
return $status_message;
}
public static function parseEml($content, $mailbox) {
if (!str_contains($content, "\r\n")){
$content = str_replace("\n", "\r\n", $content);
}
$raw_header = substr($content, 0, strpos($content, "\r\n\r\n"));
$raw_body = substr($content, strlen($raw_header)+8);
\Config::set('app.new_fetching_library', 'true');
$client = \MailHelper::getMailboxClient($mailbox);
$client->openFolder("INBOX");
return \Webklex\PHPIMAP\Message::make(null, null, $client, $raw_header, $raw_body, [], \Webklex\PHPIMAP\IMAP::ST_UID);
}
// public static function oauthGetProvider($provider_code, $params)
// {
// $provider = null;
// switch ($provider_code) {
// case self::OAUTH_PROVIDER_MICROSOFT:
// $provider = new \Stevenmaguire\OAuth2\Client\Provider\Microsoft([
// // Required
// 'clientId' => $params['client_id'],
// 'clientSecret' => $params['client_secret'],
// 'redirectUri' => route('mailboxes.oauth_callback'),
// //https://login.microsoftonline.com/common/oauth2/authorize';
// 'urlAuthorize' => 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
// 'urlAccessToken' => 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
// 'urlResourceOwnerDetails' => 'https://outlook.office.com/api/v1.0/me'
// ]);
// break;
// }
// return $provider;
// }
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Misc;
class SwiftGetSmtpQueueId implements \Swift_Events_ResponseListener
{
public static $last_smtp_queue_id = null;
public function responseReceived(\Swift_Events_ResponseEvent $evt)
{
$response_text = $evt->getResponse();
if (strpos($response_text, 'queued') !== false) {
preg_match("#queued as ([^\$\r\n ]+)[$\r\n]#", $response_text, $m);
if (!empty($m[1]) && trim($m[1])) {
self::$last_smtp_queue_id = trim($m[1]);
}
}
}
}

View File

@@ -2,16 +2,15 @@
namespace App\Misc;
use Zttp\Zttp;
class WpApi
{
const ENDPOINT_MODULES = 'freescout/v1/modules';
const METHOD_GET = 'get';
const METHOD_POST = 'post';
const METHOD_GET = 'GET';
const METHOD_POST = 'POST';
const ACTION_CHECK_LICENSE = 'check_license';
const ACTION_CHECK_LICENSES = 'check_licenses';
const ACTION_ACTIVATE_LICENSE = 'activate_license';
const ACTION_DEACTIVATE_LICENSE = 'deactivate_license';
const ACTION_GET_VERSION = 'get_version';
@@ -24,7 +23,30 @@ class WpApi
return \Config::get('app.freescout_alt_api').$path;
} else {
return \Config::get('app.freescout_api').$path;
}
}
public static function httpRequest($method, $url, $params)
{
$client = new \GuzzleHttp\Client();
if ($method == self::METHOD_POST) {
if (strstr($url, '?')) {
$url .= '&';
} else {
$url .= '?';
}
$url .= 'v='.config('app.version');
return $client->request('POST', $url, \Helper::setGuzzleDefaultOptions([
'connect_timeout' => 10,
'form_params' => $params,
]));
} else {
$params['v'] = config('app.version');
return $client->request('GET', $url, \Helper::setGuzzleDefaultOptions([
'connect_timeout' => 10,
'query' => $params,
]));
}
}
@@ -35,14 +57,13 @@ class WpApi
{
self::$lastError = null;
$options = ['connect_timeout' => 7]; // seconds
try {
$response = Zttp::withOptions($options)->$method(self::url($endpoint, $alternative_api), $params);
$response = self::httpRequest($method, self::url($endpoint, $alternative_api), $params);
} catch (\Exception $e) {
if (!$alternative_api) {
return self::request($method, $endpoint, $params, true);
}
\Helper::logException($e, 'WpApi');
self::$lastError = [
'code' => $e->getCode(),
'message' => $e->getMessage(),
@@ -52,8 +73,9 @@ class WpApi
}
// https://guzzle3.readthedocs.io/http-client/response.html
if ($response->status() < 500) {
$json = $response->json();
if ($response->getStatusCode() < 500) {
$json = \Helper::jsonToArray($response->getBody());
if (!empty($json['code']) && !empty($json['message']) &&
!empty($json['data']) && !empty($json['data']['status']) && $json['data']['status'] != 200
) {
@@ -92,6 +114,18 @@ class WpApi
return self::request(self::METHOD_POST, $endpoint, $params);
}
/**
* Check module license.
*/
public static function checkLicenses($params)
{
$params['action'] = self::ACTION_CHECK_LICENSES;
$endpoint = self::ENDPOINT_MODULES;
return self::request(self::METHOD_POST, $endpoint, $params);
}
/**
* Activate module license.
*/

View File

@@ -6,7 +6,9 @@
namespace App;
use App\Misc\WpApi;
use Illuminate\Database\Eloquent\Model;
use Symfony\Component\Console\Output\BufferedOutput;
class Module extends Model
{
@@ -74,7 +76,7 @@ class Module extends Model
public static function isOfficial($author_url)
{
return parse_url($author_url, PHP_URL_HOST) == parse_url(\Config::get('app.freescout_url'), PHP_URL_HOST);
return parse_url($author_url ?? '', PHP_URL_HOST) == parse_url(\Config::get('app.freescout_url') ?? '', PHP_URL_HOST);
}
/**
@@ -181,7 +183,7 @@ class Module extends Model
{
$missing = [];
$list = explode(',', $required_extensions);
$list = explode(',', $required_extensions ?? '');
if (!is_array($list) || !count($list)) {
return [];
}
@@ -231,6 +233,347 @@ class Module extends Model
public static function formatName($name)
{
return preg_replace("/ Module$/", '', $name);
return preg_replace("/ Module($|.*\[.*\]$)/", '$1', $name);
}
public static function formatModuleData($module_data)
{
// Add (Third-Party).
if (\App\Module::isOfficial($module_data['authorUrl'])
&& $module_data['author'] != 'FreeScout'
&& mb_substr(trim($module_data['name']), -1) != ']'
) {
$module_data['name'] = $module_data['name'].' ['.__('Third-Party').']';
}
return $module_data;
}
public static function isThirdParty($module_data)
{
if (\App\Module::isOfficial($module_data['authorUrl'])
&& $module_data['author'] != 'FreeScout'
) {
return true;
} else {
return false;
}
}
public static function getSymlinkPath($alias)
{
return public_path().\Module::getPublicPath($alias);
}
// Check and try to fix invalid or missing symlinks.
public static function checkSymlinks($module_aliases = null)
{
$invalid_symlinks = [];
if ($module_aliases === null) {
// Get all active modules.
foreach (\Module::all() as $module) {
if ($module->active()) {
$module_aliases[] = $module->getAlias();
}
}
}
if ($module_aliases && count($module_aliases)) {
foreach ($module_aliases as $module_alias) {
$from = self::getSymlinkPath($module_alias);
$create = false;
// file_exists() also checks if symlink target exists.
// file_exists() and is_dir() may throw "open_basedir restriction in effect".
try {
if (!file_exists($from) || !is_link($from)) {
if (is_dir($from)) {
@rename($from, $from.'_'.date('YmdHis'));
} else {
@unlink($from);
}
$create = true;
}
} catch (\Exception $e) {
$create = true;
}
// Skip this check.
// elseif (is_link($from) && readlink($symlink_path) != '') {
// // Symlink leads to the wrong place.
// $create = true;
// }
// Try to create the symlink.
if ($create) {
$to = self::createModuleSymlink($module_alias);
if ($to && (!is_link($from) || is_link($to) || !file_exists($from))) {
$invalid_symlinks[$from] = $to;
}
}
}
}
return $invalid_symlinks;
}
// There is similar function in ModuleInstall.php
public static function createModuleSymlink($alias)
{
$from = self::getSymlinkPath($alias);
$module = \Module::findByAlias($alias);
if (!$module) {
return false;
}
$to = $module->getExtraPath('Public');
// file_exists() may throw "open_basedir restriction in effect".
try {
// If module's Public is symlink.
if (is_link($to)) {
@unlink($to);
}
// Symlimk may exist but lead to the module folder in a wrong case.
// So we need first try to remove it.
if (!file_exists($from)) {
@unlink($from);
}
if (file_exists($from)) {
return $to;
}
if (!file_exists($to)) {
// Try to create Public folder.
try {
\File::makeDirectory($to, \Helper::DIR_PERMISSIONS);
} catch (\Exception $e) {
// If it's a broken symlink.
if (is_link($to)) {
@unlink($to);
}
}
}
try {
symlink($to, $from);
} catch (\Exception $e) {
\Log::error('Error occurred creating ['.$from.' » '.$to.'] symlink: '.$e->getMessage());
//return false;
}
} catch (\Exception $e) {
return false;
}
return $to;
}
public static function updateModule($alias)
{
$result = [
'status' => 'error',
// Error message.
'msg' => '',
'msg_success' => '',
'download_error' => false,
// Error message with the link for downloading the module.
'download_msg' => '',
'output' => '',
'module_name' => '',
];
$module = \Module::findByAlias($alias);
if (!$module) {
$result['msg'] = __('Module not found').': '.$alias;
}
// Get module name.
$name = '?';
if ($module) {
$name = $module->getName();
$result['module_name'] = $name;
}
// Download new version.
if (!$result['msg']) {
// Check if the module's author is officially recognized
if (self::isOfficial($module->authorUrl)) {
$params = [
'license' => self::getLicense($alias),
'module_alias' => $alias,
'url' => self::getAppUrl(),
];
$license_details = WpApi::getVersion($params);
if (WpApi::$lastError) {
$result['msg'] = WpApi::$lastError['message'];
} elseif (!empty($license_details['code']) && !empty($license_details['message'])) {
$result['msg'] = $license_details['message'];
} elseif (!empty($license_details['required_app_version']) && !\Helper::checkAppVersion($license_details['required_app_version'])) {
$result['msg'] = 'Module requires app version:'.' '.$license_details['required_app_version'];
} elseif (!empty($license_details['download_link'])) {
// If a download link is available, proceed to update the module from the URL
$result = self::updateFromUrl($module, $license_details['download_link'], $result);
} elseif ($license_details['status'] && $result['msg'] = self::getErrorMessage($license_details['status'])) {
//$result['msg'] = ;
} else {
$result['msg'] = __('Error occurred').': '.json_encode($license_details);
}
} else {
// If the module's author is not officially recognized, check for a direct download link
$latest_version_zip_url = $module->latestVersionZipUrl ?? null;
if (!empty($latest_version_zip_url)) {
// Update the module from the provided ZIP URL
$result = self::updateFromUrl($module, $module->latestVersionZipUrl, $result);
} else {
// If no download link is available, set an error message indicating the module cannot be downloaded
$result['msg'] = __('Error occurred') . ': module not available for download';
}
}
}
// Run post-update instructions.
if (!$result['msg'] && !$result['download_error']) {
$output_log = new BufferedOutput();
\Artisan::call('freescout:module-install', ['module_alias' => $alias], $output_log);
$result['output'] = $output_log->fetch() ?: ' ';
$result['msg'] = __('Error occurred activating ":name" module', ['name' => $name]);
if (session('flashes_floating') && is_array(session('flashes_floating'))) {
// Error.
// If there was any error, module has been deactivated via modules.register_error filter
$result['msg'] = '';
foreach (session('flashes_floating') as $flash) {
$result['msg'] .= $flash['text'].' ';
}
} elseif (strstr($result['output'], 'Configuration cached successfully')) {
// Success.
$result['status'] = 'success';
$result['msg'] = '';
$result['msg_success'] = __('":name" module successfully updated!', ['name' => $name]);
} else {
// Error.
// Deactivate module.
\App\Module::setActive($alias, false);
\Artisan::call('freescout:clear-cache');
}
}
return $result;
}
/**
* Updates a module from a given URL.
*
* @param Module $module The module to be updated.
* @param string $url The URL where the new version of the module can be downloaded.
* @param array $result An associative array to store the result of the update operation.
*
* @return void
*/
private static function updateFromUrl($module, $url, $result)
{
$alias = $module->alias;
// Download module.
$module_archive = \Module::getPath() . DIRECTORY_SEPARATOR . $alias . '.zip';
try {
\Helper::downloadRemoteFile($url, $module_archive);
} catch (\Exception $e) {
$result['msg'] = $e->getMessage();
}
if (!file_exists($module_archive)) {
$result['download_error'] = true;
} else {
// Extract.
try {
// Sometimes by some reason Public folder becomes a symlink leading to itself.
// It causes an error during updating process.
// https://github.com/freescout-helpdesk/freescout/issues/2709
$public_folder = $module->getPath() . DIRECTORY_SEPARATOR . 'Public';
try {
if (is_link($public_folder)) {
unlink($public_folder);
}
} catch (\Exception $e) {
// Do nothing.
}
\Helper::unzip($module_archive, \Module::getPath());
} catch (\Exception $e) {
$result['msg'] = $e->getMessage();
}
// Check if extracted module exists.
\Module::clearCache();
$module = \Module::findByAlias($alias);
if (!$module) {
$result['download_error'] = true;
}
}
// Remove archive.
if (file_exists($module_archive)) {
\File::delete($module_archive);
}
if ($result['download_error']) {
$result['download_msg'] = __('Error occurred downloading the module. Please :%a_being%download:%a_end% module manually and extract into :folder', [
'%a_being%' => '<a href="' . $url . '" target="_blank">',
'%a_end%' => '</a>',
'folder' => '<strong>' . \Module::getPath() . '</strong>',
]);
}
return $result;
}
public static function getErrorMessage($code, $result = null)
{
$msg = '';
switch ($code) {
case 'missing':
$msg = __('License key does not exist');
break;
case 'license_not_activable':
$msg = __("You have to activate each bundle's module separately");
break;
case 'disabled':
$msg = __('License key has been revoked');
break;
case 'no_activations_left':
$msg = __('No activations left for this license key').' ('.__("Use 'Deactivate License' link above to transfer license key from another domain").')';
break;
case 'expired':
$msg = __('License key has expired');
break;
case 'key_mismatch':
$msg = __('License key belongs to another module');
break;
// This also happens when entering a valid license key for wrong module.
case 'invalid_item_id':
$msg = __('Invalid license key');
//$msg = __('Module not found in the modules directory');
break;
case 'site_inactive':
$msg = __('License key is activated on another domain.').' '.__("Use 'Deactivate License' link above to transfer license key from another domain");
//$msg = __('Module not found in the modules directory');
break;
default:
if ($result && !empty($result['error'])) {
$msg = __('Error code:'.' '.$result['error']);
}
break;
}
return $msg;
}
}

View File

@@ -72,6 +72,9 @@ class BroadcastNotification extends Notification implements ShouldQueue
return $data;
}
// Try to convert to array.
$mediums = (array)$payload->mediums;
$thread = Thread::find($payload->thread_id);
if (empty($thread)) {
@@ -82,7 +85,7 @@ class BroadcastNotification extends Notification implements ShouldQueue
$db_notification = new \Illuminate\Notifications\DatabaseNotification();
// HTML for the menu notification (uses same medium as for email)
if (in_array(Subscription::MEDIUM_EMAIL, $payload->mediums)) {
if (in_array(Subscription::MEDIUM_EMAIL, $mediums)) {
$web_notifications_info = [];
// Get last reply or note of the conversation to display it's text
@@ -114,7 +117,7 @@ class BroadcastNotification extends Notification implements ShouldQueue
}
// Text and url for the browser push notification
if (in_array(Subscription::MEDIUM_BROWSER, $payload->mediums)) {
if (in_array(Subscription::MEDIUM_BROWSER, $mediums)) {
$data['browser']['text'] = strip_tags($thread->getActionDescription($thread->conversation->number));
$data['browser']['url'] = $thread->conversation->url(null, $thread->id, ['mark_as_read' => $db_notification->id]);
}

View File

@@ -86,18 +86,18 @@ class WebsiteNotification extends Notification implements ShouldQueue
}
// Get last reply or note of the conversation to display it's text
if ($threads) {
$last_threads = Thread::whereIn('conversation_id', $threads->pluck('conversation_id')->unique()->toArray())
// Select must contain all fields from orderBy() to avoid:
// General error: 3065 Expression #1 of ORDER BY clause is not in SELECT
->select(['id', 'conversation_id', 'body', 'created_at'])
->whereIn('type', [Thread::TYPE_CUSTOMER, Thread::TYPE_MESSAGE, Thread::TYPE_NOTE])
->distinct('conversation_id')
->orderBy('created_at')
// We can not use groupBy because of "isn't in GROUP BY"
//->groupBy('conversation_id')
->get();
}
// if ($threads) {
// $last_threads = Thread::whereIn('conversation_id', $threads->pluck('conversation_id')->unique()->toArray())
// // Select must contain all fields from orderBy() to avoid:
// // General error: 3065 Expression #1 of ORDER BY clause is not in SELECT
// ->select(['id', 'conversation_id', 'body', 'created_at'])
// ->whereIn('type', [Thread::TYPE_CUSTOMER, Thread::TYPE_MESSAGE, Thread::TYPE_NOTE])
// ->distinct('conversation_id')
// ->orderBy('created_at', 'desc')
// // We can not use groupBy because of "isn't in GROUP BY"
// //->groupBy('conversation_id')
// ->get();
// }
// Populate all collected data into array
foreach ($notifications as $notification) {
@@ -129,14 +129,14 @@ class WebsiteNotification extends Notification implements ShouldQueue
continue;
}
$last_thread_body = '';
$conversation = null;
// $last_thread_body = '';
// $conversation = null;
$last_thread = $last_threads->firstWhere('conversation_id', $thread->conversation_id);
if ($last_thread) {
$last_thread_body = $last_thread->body;
$conversation = $last_thread->conversation;
}
// $last_thread = $last_threads->firstWhere('conversation_id', $thread->conversation_id);
// if ($last_thread) {
$last_thread_body = $thread->body;
$conversation = $thread->conversation;
//}
if (empty($conversation)) {
continue;
}

View File

@@ -0,0 +1,18 @@
<?php
namespace App\Observers;
use App\Attachment;
class AttachmentObserver
{
public function created(Attachment $attachment)
{
\Eventy::action('attachment.created', $attachment);
}
public function deleted(Attachment $attachment)
{
\Eventy::action('attachment.deleted', $attachment);
}
}

View File

@@ -37,5 +37,13 @@ class ConversationObserver
public function deleting(Conversation $conversation)
{
$conversation->threads()->delete();
$conversation->followers()->delete();
\Eventy::action('conversation.deleting', $conversation);
}
public function updated(Conversation $conversation)
{
\Eventy::action('conversation.updated', $conversation);
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Observers;
use App\Customer;
use App\CustomerChannel;
class CustomerObserver
{
public function updating(Customer $customer)
{
$phones = $customer->getPhones();
// Set numeric phones.
$customer->setPhones($phones);
}
public function deleting(Customer $customer)
{
\Eventy::action('customer.deleting', $customer);
}
public function created(Customer $customer)
{
if ($customer->channel && $customer->channel_id) {
CustomerChannel::create($customer->id, $customer->channel, $customer->channel_id);
}
}
public function deleted(Customer $customer)
{
CustomerChannel::where('customer_id', $customer->id)->delete();
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace App\Observers;
use App\Follower;
class FollowerObserver
{
public function created(Follower $follower)
{
\Eventy::action('follower.created', $follower);
}
public function deleted(Follower $follower)
{
\Eventy::action('follower.deleted', $follower);
}
}

View File

@@ -17,14 +17,30 @@ class ThreadObserver
// Update data in conversation
$conversation = $thread->conversation;
$now = date('Y-m-d H:i:s');
if (!$conversation) {
return;
}
// Fetch date & time setting.
$use_mail_date_on_fetching = config('app.use_mail_date_on_fetching');
if ($use_mail_date_on_fetching) {
$now = $thread->created_at;
}else{
$now = date('Y-m-d H:i:s');
}
if (!in_array($thread->type, [Thread::TYPE_LINEITEM, Thread::TYPE_NOTE]) && $thread->state == Thread::STATE_PUBLISHED) {
$conversation->threads_count++;
}
if (!in_array($thread->type, [Thread::TYPE_CUSTOMER])) {
$conversation->user_updated_at = $now;
}
if (in_array($thread->type, [Thread::TYPE_CUSTOMER, Thread::TYPE_MESSAGE]) && $thread->state == Thread::STATE_PUBLISHED) {
if ((in_array($thread->type, [Thread::TYPE_CUSTOMER, Thread::TYPE_MESSAGE])
|| ($conversation->isPhone() && in_array($thread->type, [Thread::TYPE_NOTE])))
&& $thread->state == Thread::STATE_PUBLISHED
) {
// $conversation->cc = $thread->cc;
// $conversation->bcc = $thread->bcc;
$conversation->last_reply_at = $now;
@@ -38,7 +54,9 @@ class ThreadObserver
if (in_array($thread->type, [Thread::TYPE_CUSTOMER, Thread::TYPE_MESSAGE, Thread::TYPE_NOTE])
&& $thread->state == Thread::STATE_PUBLISHED
&& !$thread->isForward()
&& ($conversation->threads_count > 1 || $thread->type == Thread::TYPE_NOTE)
// Otherwise preview is not set when conversation is created
// outside of the web interface.
//&& ($conversation->threads_count > 1 || $thread->type == Thread::TYPE_NOTE)
) {
$conversation->setPreview($thread->body);
}
@@ -58,7 +76,23 @@ class ThreadObserver
// \Eventy::action('thread.created', $thread);
// }
\App\Events\RealtimeConvNewThread::dispatchSelf($thread);
\App\Events\RealtimeMailboxNewThread::dispatchSelf($conversation->mailbox_id);
// Real time for user notifications is sent using events.
if ($thread->type == Thread::TYPE_CUSTOMER
|| ($thread->type == Thread::TYPE_MESSAGE && $thread->state == Thread::STATE_DRAFT)
) {
Conversation::refreshConversations($conversation, $thread);
}
\Eventy::action('thread.created', $thread);
}
public function deleting(Thread $thread)
{
\Eventy::action('thread.deleting', $thread);
}
public function updated(Thread $thread)
{
\Eventy::action('thread.updated', $thread);
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Observers;
use App\Follower;
use App\Mailbox;
use App\Subscription;
use App\User;
@@ -23,6 +24,15 @@ class UserObserver
Subscription::addDefaultSubscriptions($user->id);
}
public function creating(User $user)
{
// This is a hack for backward compatibility.
if ($user->type == 0 && preg_match("#^fs.*@example\.org$#", $user->email)
) {
$user->type = User::TYPE_ROBOT;
}
}
/**
* On user delete.
*
@@ -31,5 +41,6 @@ class UserObserver
public function deleting(User $user)
{
$user->folders()->delete();
Follower::whereIn('user_id', $user->id)->delete();
}
}

View File

@@ -71,14 +71,14 @@ class Option extends Model
*
* @return string
*/
public static function get($name, $default = false, $decode = true)
public static function get($name, $default = false, $decode = true, $use_cache = true)
{
// If not passed, get default value from config
if (func_num_args() == 1) {
$default = self::getDefault($name, $default);
}
if (isset(self::$cache[$name])) {
if ($use_cache && isset(self::$cache[$name])) {
if (self::$cache[$name] == self::CACHE_DEFAULT_VALUE) {
return $default;
} else {

View File

@@ -25,7 +25,15 @@ class ConversationPolicy
return true;
} else {
if ($conversation->mailbox->users->contains($user)) {
return true;
// Maybe user can see only assigned conversations.
if (!\Eventy::filter('conversation.is_user_assignee', $conversation->user_id == $user->id, $conversation, $user->id)
&& $conversation->created_by_user_id != $user->id
&& $user->canSeeOnlyAssignedConversations()
) {
return false;
} else {
return true;
}
} else {
return false;
}
@@ -45,7 +53,15 @@ class ConversationPolicy
return true;
} else {
if ($conversation->mailbox->users_cached->contains($user)) {
return true;
// Maybe user can see only assigned conversations.
if (!\Eventy::filter('conversation.is_user_assignee', $conversation->user_id == $user->id, $conversation, $user->id)
&& $conversation->created_by_user_id != $user->id
&& $user->canSeeOnlyAssignedConversations()
) {
return false;
} else {
return true;
}
} else {
return false;
}

View File

@@ -107,7 +107,7 @@ class MailboxPolicy
*/
public function updateAutoReply(User $user, Mailbox $mailbox)
{
if ($user->isAdmin() || $user->hasManageMailboxPermission($mailbox->id, 'auto')) {
if ($user->isAdmin() || $user->hasManageMailboxPermission($mailbox->id, Mailbox::ACCESS_PERM_AUTO_REPLIES)) {
return true;
} else {
return false;
@@ -124,7 +124,7 @@ class MailboxPolicy
*/
public function updatePermissions(User $user, Mailbox $mailbox)
{
if ($user->isAdmin() || $user->hasManageMailboxPermission($mailbox->id, 'perm')) {
if ($user->isAdmin() || $user->hasManageMailboxPermission($mailbox->id, Mailbox::ACCESS_PERM_PERMISSIONS)) {
return true;
} else {
return false;
@@ -141,7 +141,7 @@ class MailboxPolicy
*/
public function updateSettings(User $user, Mailbox $mailbox)
{
if ($user->isAdmin() || $user->hasManageMailboxPermission($mailbox->id, 'edit')) {
if ($user->isAdmin() || $user->hasManageMailboxPermission($mailbox->id, Mailbox::ACCESS_PERM_EDIT)) {
return true;
} else {
return false;
@@ -158,7 +158,7 @@ class MailboxPolicy
*/
public function updateEmailSignature(User $user, Mailbox $mailbox)
{
if ($user->isAdmin() || $user->hasManageMailboxPermission($mailbox->id, 'sig')) {
if ($user->isAdmin() || $user->hasManageMailboxPermission($mailbox->id, Mailbox::ACCESS_PERM_SIGNATURE)) {
return true;
} else {
return false;

View File

@@ -30,4 +30,13 @@ class ThreadPolicy
return false;
}
}
public function delete(User $user, Thread $thread)
{
if ($thread->created_by_user_id == $user->id) {
return true;
} else {
return false;
}
}
}

View File

@@ -105,15 +105,17 @@ class UserPolicy
*/
public function viewMailboxMenu(User $user)
{
if ($user->isAdmin() || \Eventy::filter('user.can_view_mailbox_menu', false, $user)) {
return true;
// hasManageMailboxAccess creates an extra query on each page,
// to avoid this we don't show Manage menu to users,
// user can manage mailboxes from dashboard.
} else if ($user->hasManageMailboxAccess()) {
return true;
} else {
return false;
}
return true;
// if ($user->isAdmin() || \Eventy::filter('user.can_view_mailbox_menu', false, $user)) {
// return true;
// // hasManageMailboxAccess creates an extra query on each page,
// // to avoid this we don't show Manage menu to users,
// // user can manage mailboxes from dashboard.
// } else if ($user->hasManageMailboxAccess()) {
// return true;
// } else {
// return false;
// }
}
}

View File

@@ -25,7 +25,10 @@ class AppServiceProvider extends ServiceProvider
\App\Email::observe(\App\Observers\EmailObserver::class);
\App\User::observe(\App\Observers\UserObserver::class);
\App\Conversation::observe(\App\Observers\ConversationObserver::class);
\App\Customer::observe(\App\Observers\CustomerObserver::class);
\App\Thread::observe(\App\Observers\ThreadObserver::class);
\App\Attachment::observe(\App\Observers\AttachmentObserver::class);
\App\Follower::observe(\App\Observers\FollowerObserver::class);
\Illuminate\Notifications\DatabaseNotification::observe(\App\Observers\DatabaseNotificationObserver::class);
}
@@ -38,7 +41,7 @@ class AppServiceProvider extends ServiceProvider
{
// Forse HTTPS if using CloudFlare "Flexible SSL"
// https://support.cloudflare.com/hc/en-us/articles/200170416-What-do-the-SSL-options-mean-
if (\Config::get('app.force_https') == 'true') {
if (\Helper::isHttps()) {
// $_SERVER['HTTPS'] = 'on';
// $_SERVER['SERVER_PORT'] = '443';
$this->app['url']->forceScheme('https');

View File

@@ -17,6 +17,10 @@ class EventServiceProvider extends ServiceProvider
'App\Listeners\ProcessSwiftMessage',
],
'Illuminate\Mail\Events\MessageSent' => [
'App\Listeners\RestartSwiftMailer',
],
'Illuminate\Auth\Events\Registered' => [
'App\Listeners\LogRegisteredUser',
],
@@ -68,6 +72,7 @@ class EventServiceProvider extends ServiceProvider
'App\Events\UserReplied' => [
'App\Listeners\SendReplyToCustomer',
'App\Listeners\SendNotificationToUsers',
'App\Listeners\RefreshConversations',
],
'App\Events\CustomerReplied' => [
@@ -77,6 +82,7 @@ class EventServiceProvider extends ServiceProvider
'App\Events\UserCreatedConversation' => [
'App\Listeners\SendReplyToCustomer',
'App\Listeners\SendNotificationToUsers',
'App\Listeners\RefreshConversations',
],
'App\Events\CustomerCreatedConversation' => [
@@ -86,6 +92,7 @@ class EventServiceProvider extends ServiceProvider
'App\Events\UserAddedNote' => [
'App\Listeners\SendNotificationToUsers',
'App\Listeners\RefreshConversations',
],
];

View File

@@ -14,6 +14,7 @@ class SendLog extends Model
* https://documentation.mailgun.com/en/latest/api-events.html#event-types.
*/
const STATUS_ACCEPTED = 1; // accepted (for delivery)
const STATUS_SEND_INTERMEDIATE_ERROR = 10;
const STATUS_SEND_ERROR = 2;
const STATUS_DELIVERY_SUCCESS = 4;
const STATUS_DELIVERY_ERROR = 5; // rejected, failed
@@ -42,6 +43,7 @@ class SendLog extends Model
* @var [type]
*/
public static $status_errors = [
self::STATUS_SEND_INTERMEDIATE_ERROR,
self::STATUS_SEND_ERROR,
self::STATUS_DELIVERY_ERROR,
];
@@ -92,8 +94,11 @@ class SendLog extends Model
/**
* Save log record.
*/
public static function log($thread_id, $message_id, $email, $mail_type, $status, $customer_id = null, $user_id = null, $status_message = null)
public static function log($thread_id, $message_id, $email, $mail_type, $status, $customer_id = null, $user_id = null, $status_message = null, $smtp_queue_id = null)
{
// Sanitize status message - remove SMTP username and password.
$status_message = \MailHelper::sanitizeSmtpStatusMessage($status_message);
$send_log = new self();
$send_log->thread_id = $thread_id;
$send_log->message_id = $message_id;
@@ -103,7 +108,15 @@ class SendLog extends Model
$send_log->customer_id = $customer_id;
$send_log->user_id = $user_id;
$send_log->status_message = $status_message;
$send_log->save();
if ($smtp_queue_id) {
$send_log->smtp_queue_id = $smtp_queue_id;
}
try {
$send_log->save();
} catch (\Exception $e) {
\Helper::logException($e, 'Error occurred saving a record to `send_logs` table. ');
return false;
}
return true;
}

View File

@@ -5,6 +5,7 @@
namespace App;
use App\Mailbox;
use App\Notifications\BroadcastNotification;
use App\Notifications\WebsiteNotification;
use Illuminate\Database\Eloquent\Model;
@@ -26,8 +27,8 @@ class Subscription extends Model
const EVENT_CONVERSATION_ASSIGNED_TO_ME = 2;
const EVENT_CONVERSATION_ASSIGNED = 6;
const EVENT_FOLLOWED_CONVERSATION_UPDATED = 13;
const EVENT_I_AM_MENTIONED = 14;
const EVENT_MY_TEAM_MENTIONED = 15;
//const EVENT_MY_TEAM_MENTIONED = 15;
// Notify me when a customer replies…
const EVENT_CUSTOMER_REPLIED_TO_UNASSIGNED = 4;
const EVENT_CUSTOMER_REPLIED_TO_MY = 3;
@@ -41,6 +42,7 @@ class Subscription extends Model
const MEDIUM_EMAIL = 1; // This is also website notifications
const MEDIUM_BROWSER = 2; // Browser push notification
const MEDIUM_MOBILE = 3;
const MEDIUM_MENU = 10; // Notifications menu
public static $mediums = [
self::MEDIUM_EMAIL,
@@ -52,25 +54,23 @@ class Subscription extends Model
self::MEDIUM_EMAIL => [
self::EVENT_CONVERSATION_ASSIGNED_TO_ME,
self::EVENT_FOLLOWED_CONVERSATION_UPDATED,
self::EVENT_I_AM_MENTIONED,
self::EVENT_MY_TEAM_MENTIONED,
//self::EVENT_MY_TEAM_MENTIONED,
self::EVENT_CUSTOMER_REPLIED_TO_MY,
self::EVENT_USER_REPLIED_TO_MY,
],
self::MEDIUM_BROWSER => [
self::EVENT_CONVERSATION_ASSIGNED_TO_ME,
self::EVENT_FOLLOWED_CONVERSATION_UPDATED,
self::EVENT_I_AM_MENTIONED,
self::EVENT_MY_TEAM_MENTIONED,
//self::EVENT_MY_TEAM_MENTIONED,
self::EVENT_CUSTOMER_REPLIED_TO_MY,
self::EVENT_USER_REPLIED_TO_MY,
],
];
/**
* List of events that occured.
* List of events that occurred.
*/
public static $occured_events = [];
public static $occurred_events = [];
public $timestamps = false;
@@ -96,7 +96,12 @@ class Subscription extends Model
*/
public static function addDefaultSubscriptions($user_id)
{
self::saveFromArray(self::$default_subscriptions, $user_id);
self::saveFromArray(self::getDefaultSubscriptions(), $user_id);
}
public static function getDefaultSubscriptions()
{
return Option::get('subscription_defaults', self::$default_subscriptions);
}
/**
@@ -154,7 +159,18 @@ class Subscription extends Model
*/
public static function usersToNotify($event_type, $conversation, $threads, $mailbox_user_ids = null)
{
$thread = $threads[0];
$users_to_notify = [];
$thread = null;
if (isset($threads[0])) {
$thread = $threads[0];
} elseif (count($threads)) {
$thread = array_shift(array_values($threads));
}
if (!$thread) {
return $users_to_notify;
}
// Ignore imported threads.
if ($thread->imported) {
@@ -199,8 +215,10 @@ class Subscription extends Model
$events[] = self::EVENT_USER_REPLIED_TO_UNASSIGNED;
}
break;
// todo: EVENT_I_AM_MENTIONED, EVENT_MY_TEAM_MENTIONED
}
$events = \Eventy::filter('subscription.events_by_type', $events, $event_type, $thread);
// Check if assigned user changed
$user_changed = false;
if ($event_type != self::EVENT_TYPE_ASSIGNED && $event_type != self::EVENT_TYPE_NEW) {
@@ -233,11 +251,13 @@ class Subscription extends Model
->whereIn('event', $events)
->get();
$subscriptions = \Eventy::filter('subscription.subscriptions', $subscriptions, $conversation, $events, $thread);
// Filter subscribers
$users_to_notify = [];
foreach ($subscriptions as $i => $subscription) {
// Actions on conversation where user is assignee
if (in_array($subscription->event, [self::EVENT_CONVERSATION_ASSIGNED_TO_ME, self::EVENT_CUSTOMER_REPLIED_TO_MY, self::EVENT_USER_REPLIED_TO_MY]) && $conversation->user_id != $subscription->user_id
if (in_array($subscription->event, [self::EVENT_CONVERSATION_ASSIGNED_TO_ME, self::EVENT_CUSTOMER_REPLIED_TO_MY, self::EVENT_USER_REPLIED_TO_MY])
&& ($conversation->user_id != $subscription->user_id && !\Eventy::filter('subscription.is_user_assignee', false, $subscription, $conversation))
) {
continue;
}
@@ -250,28 +270,43 @@ class Subscription extends Model
}
// Skip if user muted notifications for this mailbox
if ($subscription->user->isAdmin()) {
//if ($subscription->user->isAdmin()) {
// Mute notifications for events not related directly to the user.
if (!in_array($subscription->event, [self::EVENT_CONVERSATION_ASSIGNED_TO_ME, self::EVENT_FOLLOWED_CONVERSATION_UPDATED, self::EVENT_I_AM_MENTIONED, self::EVENT_CUSTOMER_REPLIED_TO_MY, self::EVENT_USER_REPLIED_TO_MY])
) {
$mailbox_settings = $conversation->mailbox->getUserSettings($subscription->user_id);
// Mute notifications for events not related directly to the user.
if (!in_array($subscription->event, [self::EVENT_CONVERSATION_ASSIGNED_TO_ME, self::EVENT_FOLLOWED_CONVERSATION_UPDATED, self::EVENT_CUSTOMER_REPLIED_TO_MY, self::EVENT_USER_REPLIED_TO_MY])
&& !\Eventy::filter('subscription.is_related_to_user', false, $subscription, $thread)
) {
$mailbox_settings = $conversation->mailbox->getUserSettings($subscription->user_id);
if (!empty($mailbox_settings->mute)) {
continue;
}
if (!empty($mailbox_settings->mute)) {
continue;
}
}
//}
// https://github.com/freescout-helpdesk/freescout/issues/3843#issuecomment-1974811457
if ($conversation->user_id != $subscription->user_id
&& $subscription->user->canSeeOnlyAssignedConversations()
) {
continue;
}
if (\Eventy::filter('subscription.filter_out', false, $subscription, $thread)) {
continue;
}
$users_to_notify[$subscription->medium][] = $subscription->user;
$users_to_notify[$subscription->medium] = array_unique($users_to_notify[$subscription->medium]);
}
// Add menu notifications, for example.
$users_to_notify = \Eventy::filter('subscription.users_to_notify', $users_to_notify, $event_type, $events, $thread);
return $users_to_notify;
}
/**
* Process events which occured.
* Process events which occurred.
*/
public static function processEvents()
{
@@ -280,7 +315,7 @@ class Subscription extends Model
$delay = now()->addSeconds(Conversation::UNDO_TIMOUT);
// Collect into notify array information about all users who need to be notified
foreach (self::$occured_events as $event) {
foreach (self::$occurred_events as $event) {
// Get mailbox users ids
$mailbox_user_ids = [];
foreach (self::$mediums as $medium) {
@@ -302,14 +337,14 @@ class Subscription extends Model
$threads = $event['conversation']->getThreads();
break;
} else {
$users = $notify[$medium][$event['conversation']->id]['users'];
$users[$medium] = $notify[$medium][$event['conversation']->id]['users'];
$threads = $notify[$medium][$event['conversation']->id]['threads'];
}
}
$users_to_notify = self::usersToNotify($event['event_type'], $event['conversation'], $threads, $mailbox_user_ids);
if (!$users_to_notify) {
if (!$users_to_notify || !is_array($users_to_notify)) {
continue;
}
@@ -324,8 +359,9 @@ class Subscription extends Model
if (count($medium_users_to_notify)) {
$notify[$medium][$event['conversation']->id] = [
// Users subarray contains all users who need to receive notification for all events
'users' => array_unique(array_merge($users, $medium_users_to_notify)),
// Users subarray contains all users who need to receive notification
// for all events for the media.
'users' => array_unique(array_merge($users[$medium] ?? [], $medium_users_to_notify)),
'conversation' => $event['conversation'],
'threads' => $threads,
'mailbox_user_ids' => $mailbox_user_ids,
@@ -334,17 +370,27 @@ class Subscription extends Model
}
}
// - Notify by email
// - Real-time menu notification (uses same medium as for email)
// - Email notification (better to create them first)
if (!empty($notify[self::MEDIUM_EMAIL])) {
// Email notification (better to create them first)
foreach ($notify[self::MEDIUM_EMAIL] as $conversation_id => $notify_info) {
\App\Jobs\SendNotificationToUsers::dispatch($notify_info['users'], $notify_info['conversation'], $notify_info['threads'])
->delay($delay)
->onQueue('emails');
}
// - Menu notification (uses same medium as for email)
foreach ($notify[self::MEDIUM_EMAIL] as $notify_info) {
}
// - Menu notification (uses same medium as for Email, if email notifications are disabled - use Browser notificaitons)
if (!empty($notify[self::MEDIUM_EMAIL])
|| !empty($notify[self::MEDIUM_BROWSER])
|| !empty($notify[self::MEDIUM_MENU])
) {
if (!empty($notify[self::MEDIUM_EMAIL])) {
$notify_menu = $notify[self::MEDIUM_EMAIL] ?? [];
} else {
$notify_menu = $notify[self::MEDIUM_BROWSER] ?? [];
}
$notify_menu = $notify_menu + ($notify[self::MEDIUM_MENU] ?? []);
foreach ($notify_menu as $notify_info) {
$website_notification = new WebsiteNotification($notify_info['conversation'], self::chooseThread($notify_info['threads']));
$website_notification->delay($delay);
\Notification::send($notify_info['users'], $website_notification);
@@ -362,11 +408,12 @@ class Subscription extends Model
$thread_id = self::chooseThread($notify_info['threads'])->id;
foreach ($notify_info['users'] as $user) {
$broadcast_id = $thread_id.'_'.$user->id;
$mediums = [$medium];
if (!empty($broadcasts[$thread_id]['mediums'])) {
$mediums = array_unique(array_merge($mediums, $broadcasts[$thread_id]['mediums']));
if (!empty($broadcasts[$broadcast_id]['mediums'])) {
$mediums = array_unique(array_merge($mediums, $broadcasts[$broadcast_id]['mediums']));
}
$broadcasts[$thread_id] = [
$broadcasts[$broadcast_id] = [
'user' => $user,
'conversation' => $notify_info['conversation'],
'threads' => $notify_info['threads'],
@@ -376,7 +423,7 @@ class Subscription extends Model
}
}
// \Notification::sendNow($notify_info['users'], new BroadcastNotification($notify_info['conversation'], $notify_info['threads'][0]));
foreach ($broadcasts as $thread_id => $to_broadcast) {
foreach ($broadcasts as $broadcast_id => $to_broadcast) {
$broadcast_notification = new BroadcastNotification($to_broadcast['conversation'], self::chooseThread($to_broadcast['threads']), $to_broadcast['mediums']);
$broadcast_notification->delay($delay);
$to_broadcast['user']->notify($broadcast_notification);
@@ -385,7 +432,7 @@ class Subscription extends Model
// - Mobile
\Eventy::action('subscription.process_events', $notify);
self::$occured_events = [];
self::$occurred_events = [];
}
/**
@@ -412,7 +459,7 @@ class Subscription extends Model
*/
public static function registerEvent($event_type, $conversation, $caused_by_user_id, $process_now = false)
{
self::$occured_events[] = [
self::$occurred_events[] = [
'event_type' => $event_type,
'conversation' => $conversation,
'caused_by_user_id' => $caused_by_user_id,
@@ -420,7 +467,7 @@ class Subscription extends Model
// Automatically add EVENT_TYPE_UPDATED
if (!in_array($event_type, [self::EVENT_TYPE_UPDATED, self::EVENT_TYPE_NEW])) {
self::$occured_events[] = [
self::$occurred_events[] = [
'event_type' => self::EVENT_TYPE_UPDATED,
'conversation' => $conversation,
'caused_by_user_id' => $caused_by_user_id,

View File

@@ -2,6 +2,8 @@
namespace App;
use App\Mailbox;
use App\SendLog;
use App\Events\ConversationStatusChanged;
use App\Events\ConversationUserChanged;
use App\Events\UserAddedNote;
@@ -56,6 +58,7 @@ class Thread extends Model
//self::TYPE_FORWARDPARENT => 'forwardparent',
// forwardchild is the type set on the first thread of the new forwarded conversation.
//self::TYPE_FORWARDCHILD => 'forwardchild',
// Not used.
self::TYPE_CHAT => 'chat',
];
@@ -158,6 +161,20 @@ class Thread extends Model
const META_PREV_CONVERSATION = 'pc';
const META_MERGED_WITH_CONV = 'mwc';
const META_MERGED_INTO_CONV = 'mic';
const META_FORWARD_PARENT_CONVERSATION_NUMBER = 'fw_pcn';
const META_FORWARD_PARENT_CONVERSATION_ID = 'fw_pci';
const META_FORWARD_PARENT_THREAD_ID = 'fw_pti';
const META_FORWARD_CHILD_CONVERSATION_NUMBER = 'fw_ccn';
const META_FORWARD_CHILD_CONVERSATION_ID = 'fw_cci';
// At some stage metas have been renamed.
public static $meta_fw_backward_compat = [
self::META_FORWARD_PARENT_CONVERSATION_NUMBER => 'forward_parent_conversation_number',
self::META_FORWARD_PARENT_CONVERSATION_ID => 'forward_parent_conversation_id',
self::META_FORWARD_PARENT_THREAD_ID => 'forward_parent_thread_id',
self::META_FORWARD_CHILD_CONVERSATION_NUMBER => 'forward_child_conversation_number',
self::META_FORWARD_CHILD_CONVERSATION_ID => 'forward_child_conversation_id',
];
protected $dates = [
'opened_at',
@@ -287,15 +304,35 @@ class Thread extends Model
$body = $this->body;
}
if ($body === null) {
$body = '';
}
// Change "background:" to "background-color:".
// https://github.com/freescout-helpdesk/freescout/issues/2560
// Keep in mind that with large texts preg_replace() may return null.
$body = preg_replace("/(<[^<>]+style=[\"'][^\"']*)background: *([^;() ]+[;\"'])/", '$1background-color:$2', $body) ?: $body;
// Cut out "collapse" class as it hides elements.
$body = preg_replace("/(<[^<>\r\n]+class=([\"'][^\"']* |[\"']))(collapse|hidden)([\"' ])/", '$1$4', $body) ?: $body;
// Remove only the <!--[if !mso]><!--> and <!--<![endif]--> around the elements.
// https://github.com/freescout-helpdesk/freescout/pull/3865#issuecomment-1990758149
$body = preg_replace('/<!\-\-\[if [^>]+\]><!\-\->(.*?)<![ ]+\-\-<!\[endif\]\-\->/s', '$1', $body);
// https://github.com/freescout-helpdesk/freescout/issues/3894
// Remove <!--[if !mso]><!--> and <!--<![endif]--> comments, preserving the data inside
//$body = preg_replace('/(<!\-\-\[if [^>]+\]>|<!\[endif\]\-\->)/', '', $body);
return \Helper::purifyHtml($body);
}
/**
* Convert body to plain text.
*/
public function getBodyAsText()
public function getBodyAsText($options = ['width' => 0])
{
return \Helper::htmlToText($this->body, true);
return \Helper::htmlToText($this->body, true, $options);
}
public function getBodyWithFormatedLinks(string $body = '') :string
@@ -304,7 +341,31 @@ class Thread extends Model
$body = $this->body;
}
return \Helper::linkify($this->getCleanBody($body));
$body = \Helper::linkify($this->getCleanBody($body));
// Add target="_blank" to links.
$pattern = '/<a(.*?)?href=[\'"]?[\'"]?(.*?)?>/i';
$body = preg_replace_callback($pattern, function($m){
$tpl = array_shift($m);
$href = isset($m[1]) ? $m[1] : null;
if (preg_match('/target=[\'"]?(.*?)[\'"]?/i', $tpl)) {
return $tpl;
}
if (trim($href) && 0 === strpos($href, '#')) {
// Anchor links.
return $tpl;
}
return preg_replace_callback('/href=/i', function($m2){
return sprintf('target="_blank" %s', array_shift($m2));
}, $tpl);
}, $body) ?: $body;
return $body;
}
/**
@@ -512,7 +573,12 @@ class Thread extends Model
return \App\User::getDeletedUser();
}
} else {
return $this->created_by_customer;
// In some cases the created_by_customer can be empty.
if ($this->created_by_customer) {
return $this->created_by_customer;
} else {
return \App\Customer::getDummyCustomer();
}
}
}
@@ -544,7 +610,9 @@ class Thread extends Model
$person = '';
if ($this->type == self::TYPE_CUSTOMER) {
$person = $this->customer_cached->getFullName(true);
if ($this->customer_cached) {
$person = $this->customer_cached->getFullName(true);
}
} elseif ($this->state == self::STATE_DRAFT && !empty($this->edited_by_user_id)) {
// Draft
if (auth()->user() && $this->edited_by_user_id == auth()->user()->id) {
@@ -568,9 +636,10 @@ class Thread extends Model
/**
* Get action text.
* $by_user - user who performed the action.
* $person must be already escaped.
*/
public function getActionText($conversation_number = '', $escape = false, $strip_tags = false, $by_user = null, $person = '')
public function getActionText($conversation_number = '', $escape = false, $strip_tags = false, $by_user = null, $person = '', $viewed_by_user = null)
{
$did_this = '';
@@ -589,7 +658,7 @@ class Thread extends Model
$assignee = htmlspecialchars($assignee);
}
if ($conversation_number) {
$did_this = __(':person assigned :assignee convsersation #:conversation_number', ['assignee' => $assignee, 'conversation_number' => $conversation_number]);
$did_this = __(':person assigned :assignee conversation #:conversation_number', ['assignee' => $assignee, 'conversation_number' => $conversation_number]);
} else {
$did_this = __(":person assigned to :assignee", ['assignee' => $assignee]);
}
@@ -597,11 +666,14 @@ class Thread extends Model
if ($conversation_number) {
$did_this = __(':person changed the customer to :customer in conversation #:conversation_number', ['customer' => $this->customer->getFullName(true), 'conversation_number' => $conversation_number]);
} else {
$customer_name = $this->customer_cached->getFullName(true);
$customer_name = '';
if ($this->customer_cached) {
$customer_name = $this->customer_cached->getFullName(true);
}
if ($escape) {
$customer_name = htmlspecialchars($customer_name);
}
$did_this = __(":person changed the customer to :customer", ['customer' => '<a href="'.$this->customer_cached->url().'" title="'.$this->action_data.'" class="link-black">'.$customer_name.'</a>']);
$did_this = __(":person changed the customer to :customer", ['customer' => '<a href="'.($this->customer_cached ? $this->customer_cached->url() : '').'" title="'.$this->action_data.'" class="link-black">'.$customer_name.'</a>']);
}
} elseif ($this->action_type == self::ACTION_TYPE_DELETED_TICKET) {
$did_this = __(":person deleted");
@@ -618,7 +690,11 @@ class Thread extends Model
if ($merge_conversation) {
$merge_conversation_number = $merge_conversation->number;
}
$did_this = __(":person merged into conversation #:conversation_number", ['conversation_number' => $merge_conversation_number]);
if ($merge_conversation) {
$did_this = __(":person merged into conversation #:conversation_number", ['conversation_number' => '<a href="'.$merge_conversation->url().'" class="link-black">'.$merge_conversation_number.'</a>']);
} else {
$did_this = __(":person merged into conversation #:conversation_number", ['conversation_number' => $merge_conversation_number]);
}
}
}
} elseif ($this->state == self::STATE_DRAFT) {
@@ -629,7 +705,7 @@ class Thread extends Model
}
} else {
if ($this->isForwarded()) {
$did_this = __(':person forwarded a conversation #:forward_parent_conversation_number', ['forward_parent_conversation_number' => $this->getMeta('forward_parent_conversation_number')]);
$did_this = __(':person forwarded a conversation #:forward_parent_conversation_number', ['forward_parent_conversation_number' => $this->getMetaFw(self::META_FORWARD_PARENT_CONVERSATION_NUMBER)]);
} elseif ($this->first) {
$did_this = __(':person started a new conversation #:conversation_number', ['conversation_number' => $conversation_number]);
} elseif ($this->type == self::TYPE_NOTE) {
@@ -639,7 +715,7 @@ class Thread extends Model
}
}
$did_this = \Eventy::filter('thread.action_text', $did_this, $this, $conversation_number, $escape);
$did_this = \Eventy::filter('thread.action_text', $did_this, $this, $conversation_number, $escape, $viewed_by_user);
if ($strip_tags) {
$did_this = strip_tags($did_this);
@@ -659,11 +735,11 @@ class Thread extends Model
/**
* Description of what happened.
*/
public function getActionDescription($conversation_number, $escape = true)
public function getActionDescription($conversation_number, $escape = true, $viewed_by_user = null)
{
// Person
$person = $this->getActionPerson($conversation_number);
$did_this = $this->getActionText($conversation_number);
$did_this = $this->getActionText($conversation_number, false, false, null, '', $viewed_by_user);
if ($escape) {
$person = htmlspecialchars($person);
@@ -688,6 +764,10 @@ class Thread extends Model
{
$this->deteleAttachments();
$this->delete();
if ($this->isNote()) {
Conversation::updatePreview($this->conversation_id);
}
}
/**
@@ -726,7 +806,11 @@ class Thread extends Model
{
// Created by customer
if ($this->source_via == self::PERSON_CUSTOMER) {
return $this->getCreatedBy()->getFirstName(true);
if ($this->getCreatedBy()) {
return $this->getCreatedBy()->getFirstName(true);
} else {
return '';
}
}
// Created by user
@@ -737,7 +821,14 @@ class Thread extends Model
$name = $mailbox->name;
if ($mailbox->from_name == Mailbox::FROM_NAME_CUSTOM && $mailbox->from_name_custom) {
$name = $mailbox->from_name_custom;
$data = [
'mailbox' => $mailbox,
'mailbox_from_name' => '', // To avoid recursion.
'conversation' => $this->conversation,
// If we reach here it means the thread has been created by user.
'user' => $this->getCreatedBy(),
];
$name = \MailHelper::replaceMailVars($mailbox->from_name_custom, $data, false, true);
} elseif ($mailbox->from_name == Mailbox::FROM_NAME_USER && $this->getCreatedBy()) {
$name = $this->getCreatedBy()->getFirstName(true);
}
@@ -803,6 +894,9 @@ class Thread extends Model
} else {
$send_status_data = $new_data;
}
if (!empty($send_status_data['msg'])) {
$send_status_data['msg'] = \MailHelper::sanitizeSmtpStatusMessage($send_status_data['msg']);
}
$this->send_status_data = \Helper::jsonEncodeUtf8($send_status_data);
} else {
$this->send_status_data = null;
@@ -900,7 +994,9 @@ class Thread extends Model
$customer = $conversation->customer;
}
$user_id = $data['user_id'] ?? null;
// User which creatd the thread should be passed in created_by_user_id.
// user_id is check for backward compatibility.
$user_id = $data['created_by_user_id'] ?? $data['user_id'] ?? null;
// Check type.
if ($data['type'] == Thread::TYPE_CUSTOMER && empty($customer)) {
@@ -951,6 +1047,11 @@ class Thread extends Model
$thread->first = true;
}
// Assignee.
if (empty($data['user_id'])) {
$thread->user_id = $conversation->user_id;
}
// Process attachments.
if (!empty($data['attachments'])) {
$has_attachments = false;
@@ -959,7 +1060,7 @@ class Thread extends Model
$content = null;
$uploaded_file = null;
if (get_class($attachment) == 'Illuminate\Http\UploadedFile') {
if (is_object($attachment) && get_class($attachment) == 'Illuminate\Http\UploadedFile') {
$uploaded_file = $attachment;
$attachment = [];
@@ -987,12 +1088,19 @@ class Thread extends Model
}
} else {
// URL.
$uploaded_file = \Helper::downloadRemoteFileAsTmpFile($attachment['file_url']);
if (!$uploaded_file) {
$file_path = \Helper::downloadRemoteFileAsTmp($attachment['file_url']);
if (!$file_path) {
continue;
}
$uploaded_file = new \Illuminate\Http\UploadedFile(
$file_path, basename($file_path),
null, null, true
);
if (empty($attachment['mime_type'])) {
$attachment['mime_type'] = $uploaded_file->getClientMimeType();
$attachment['mime_type'] = mime_content_type($file_path);
if (empty($attachment['mime_type'])) {
$attachment['mime_type'] = $uploaded_file->getMimeType();
}
}
}
}
@@ -1004,7 +1112,7 @@ class Thread extends Model
$attachment['mime_type'],
null,
$content,
$uploaded_file = $uploaded_file,
$uploaded_file,
$embedded = false,
$thread->id,
$user_id ?? null
@@ -1018,6 +1126,10 @@ class Thread extends Model
$thread->has_attachments = true;
$conversation->has_attachments = true;
}
} else {
$has_attachments = $data['has_attachments'] ?? false;
$thread->has_attachments = $has_attachments;
$conversation->has_attachments = $has_attachments;
}
$thread->save();
@@ -1039,30 +1151,61 @@ class Thread extends Model
$conversation->setBcc($bcc);
}
$update_folder = false;
if ($thread->isReply()) {
$conversation->last_reply_at = $now;
if ($is_customer) {
$conversation->last_reply_from = Conversation::PERSON_CUSTOMER;
// Reply from customer makes conversation active
$conversation->status = Conversation::STATUS_ACTIVE;
// Set specific status
if (!empty($data['status'])) {
if ((int)$conversation->status != (int)$data['status']) {
$update_folder = true;
}
$conversation->status = $data['status'];
} else {
if ((int)$conversation->status != Conversation::STATUS_ACTIVE) {
$update_folder = true;
}
// Reply from customer makes conversation active
$conversation->status = Conversation::STATUS_ACTIVE;
}
} else {
$conversation->last_reply_from = Conversation::PERSON_USER;
$conversation->user_updated_at = $now;
$conversation->status = Conversation::STATUS_PENDING;
if (!empty($data['status'])) {
if ((int)$conversation->status != (int)$data['status']) {
$update_folder = true;
}
$conversation->status = $data['status'];
} else {
if ((int)$conversation->status != Conversation::STATUS_PENDING) {
$update_folder = true;
}
// Reply from customer makes conversation active
$conversation->status = Conversation::STATUS_PENDING;
}
}
}
// Set specific status
if (!empty($data['status'])) {
$conversation->status = $data['status'];
}
// Reply from customer to deleted conversation should undelete it.
if ($data['type'] == Thread::TYPE_CUSTOMER && $conversation->state == Conversation::STATE_DELETED) {
$conversation->state = Conversation::STATE_PUBLISHED;
$update_folder = true;
}
if ($update_conv) {
$conversation->customer_id = $customer->id;
if ($is_customer) {
$conversation->customer_email = $customer->getMainEmail();
}
if ($update_folder) {
$conversation->updateFolder();
}
}
// Update conversation here if needed.
@@ -1182,6 +1325,27 @@ class Thread extends Model
$this->setMetas($metas);
}
public function getMetaFw($key, $default = null)
{
$meta = $this->getMeta($key, $default);
if (!$meta) {
$meta = $this->getMeta(self::$meta_fw_backward_compat[$key], $default);
}
return $meta;
}
/**
* Unset thread meta value.
*/
public function unsetMeta($key)
{
$metas = $this->getMetas();
if (isset($metas[$key])) {
unset($metas[$key]);
$this->setMetas($metas);
}
}
/**
* Get full name of the user who forwarded conversation.
*/
@@ -1212,7 +1376,7 @@ class Thread extends Model
*/
public function isForwarded()
{
if ($this->getMeta('forward_parent_conversation_id')) {
if ($this->getMetaFw(self::META_FORWARD_PARENT_CONVERSATION_ID)) {
return true;
} else {
return false;
@@ -1232,7 +1396,7 @@ class Thread extends Model
*/
public function getForwardParentConversation()
{
return Conversation::where('id', $this->getMeta('forward_parent_conversation_id'))
return Conversation::where('id', $this->getMetaFw(self::META_FORWARD_PARENT_CONVERSATION_ID))
->rememberForever()
->first();
}
@@ -1242,7 +1406,7 @@ class Thread extends Model
*/
public function getForwardChildConversation()
{
return Conversation::where('id', $this->getMeta('forward_child_conversation_id'))
return Conversation::where('id', $this->getMetaFw(self::META_FORWARD_CHILD_CONVERSATION_ID))
->first();
}
@@ -1251,7 +1415,23 @@ class Thread extends Model
*/
public function fetchBody()
{
$message = \MailHelper::fetchMessage($this->conversation->mailbox, $this->message_id);
$mailbox = null;
// The conversation may has been moved from another mailbox.
if (!empty($this->conversation->meta['orig_mailbox_id'])) {
$mailbox = Mailbox::find($this->conversation->meta['orig_mailbox_id']);
}
if (!$mailbox) {
$mailbox = $this->conversation->mailbox;
}
$message = \MailHelper::fetchMessage($mailbox, $this->message_id, $this->getMailDate());
// Try without limiting by date.
// https://github.com/freescout-helpdesk/freescout/issues/3658
if (!$message) {
$message = \MailHelper::fetchMessage($mailbox, $this->message_id);
}
if (!$message) {
return '';
@@ -1266,6 +1446,22 @@ class Thread extends Model
return $body;
}
public function parseHeaders()
{
return \MailHelper::parseHeaders($this->headers);
}
public function getMailDate()
{
$data = $this->parseHeaders();
if (empty($data->date)) {
return null;
}
return \Helper::parseDateToCarbon($data->date);
}
public function getActionTypeName()
{
if (!$this->action_type) {
@@ -1276,4 +1472,124 @@ class Thread extends Model
return self::$action_types[$this->action_type] ?? '';
}
public function isCustomerMessage()
{
return $this->type == self::TYPE_CUSTOMER;
}
public function isUserMessage()
{
return $this->type == self::TYPE_MESSAGE;
}
public static function replaceBase64ImagesWithAttachments($body, $user_id = null)
{
\Helper::setPcreBacktrackLimit();
$body = preg_replace_callback("#(<img[^<>]+src=[\"'])data:image/([^;]+);base64,([^\"']+)([\"'])#",
function ($match) {
$attachment = null;
$data = base64_decode($match[3]);
if ($data) {
$attachment = Attachment::create(
$file_name = number_format(microtime(true), 4, '', '').'.'.$match[2],
$mime_type = 'image/'.$match[2],
$type = Attachment::TYPE_IMAGE,
$data,
$uploaded_file = null,
$embedded = true,
$thread_id = null,
$user_id = \Auth::id()
);
}
if ($attachment) {
return $match[1].$attachment->url().$match[4];
} else {
return $match[0];
}
}, $body
);
return $body;
}
public function getMessageId($mailbox = null)
{
if ($this->isCustomerMessage() && $this->message_id) {
return $this->message_id;
}
if ($this->isUserMessage()) {
if (!$mailbox) {
$mailbox = $this->conversation->mailbox;
}
return \MailHelper::MESSAGE_ID_PREFIX_REPLY_TO_CUSTOMER.'-'.$this->id.'-'.\MailHelper::getMessageIdHash($this->id).'@'.$mailbox->getEmailDomain();
}
return '';
}
// Sorts threads in desc order by created_at and ID.
//
// Threads has to be sorted by created_at and not by id.
// https://github.com/freescout-helpdesk/freescout/issues/2938
// Sometimes thread.created_at may be the same,
// in such cases we also need to sort by thread ID.
public static function sortThreads($threads)
{
return $threads->sort(function ($a, $b) {
$a_ts = $a->created_at->getTimestamp();
$b_ts = $b->created_at->getTimestamp();
if ($a_ts == $b_ts) {
if ($a->id < $b->id) {
return 1;
} else {
return -1;
}
} else {
return ($a_ts < $b_ts) ? 1 : -1;
}
});
}
public static function getLastThread($threads)
{
$threads = self::sortThreads($threads);
return $threads->first();
}
public function canRetrySend()
{
if ($this->isSendStatusSuccess()) {
return false;
}
// Check if failed_job still exists.
if (!$this->getFailedJobId()) {
return false;
}
return true;
}
public function isSendStatusSuccess()
{
// We have not tried to send the email yet.
if ((int)$this->send_status == 0) {
return false;
}
if (!in_array($this->send_status, [SendLog::STATUS_SEND_ERROR, SendLog::STATUS_DELIVERY_ERROR, SendLog::STATUS_SEND_INTERMEDIATE_ERROR])) {
return true;
}
return false;
}
public function getFailedJobId()
{
return \App\FailedJob::where('queue', 'emails')
->where('payload', 'like', '{"displayName":"App\\\\\\\\Jobs\\\\\\\\SendReplyToCustomer"%{i:0;i:'.$this->id.';%')
->value('id');
}
}

View File

@@ -6,6 +6,8 @@
namespace App;
use App\Email;
use App\Follower;
use App\Mail\PasswordChanged;
use App\Mail\UserInvite;
use App\Notifications\WebsiteNotification;
@@ -26,11 +28,14 @@ class User extends Authenticatable
public $rememberCacheDriver = 'array';
const PHOTO_DIRECTORY = 'users';
const PHOTO_SIZE = 50; // px
const PHOTO_QUALITY = 77;
const EMAIL_MAX_LENGTH = 100;
const EMAIL_DELETED_SUFFIX = '_deleted';
const DEFAULT_TIMEZONE = 'UTC';
/**
* Roles.
*/
@@ -46,7 +51,7 @@ class User extends Authenticatable
* Types.
*/
const TYPE_USER = 1;
const TYPE_TEAM = 2;
const TYPE_ROBOT = 2; // Workflows, teams, etc.
/**
* Statuses.
@@ -111,12 +116,20 @@ class User extends Authenticatable
*
* @var [type]
*/
protected $fillable = ['role', 'status', 'first_name', 'last_name', 'email', 'password', 'role', 'timezone', 'photo_url', 'type', 'emails', 'job_title', 'phone', 'time_format', 'enable_kb_shortcuts', 'locale'];
protected $fillable = ['role', 'status', 'first_name', 'last_name', 'email', 'password', 'timezone', 'photo_url', 'type', 'emails', 'job_title', 'phone', 'time_format', 'enable_kb_shortcuts', 'locale'];
protected $casts = [
'permissions' => 'array',
];
public function __construct(array $attributes = array())
{
$this->setRawAttributes(array_merge($this->attributes, array(
'timezone' => config('app.timezone') ?: User::DEFAULT_TIMEZONE
)), true);
parent::__construct($attributes);
}
/**
* For array_unique function.
*
@@ -208,7 +221,7 @@ class User extends Authenticatable
*/
public function getFullName()
{
return $this->first_name.' '.$this->last_name;
return \Eventy::filter('user.full_name', $this->first_name.' '.$this->last_name, $this);
}
/**
@@ -293,6 +306,7 @@ class User extends Authenticatable
return $settings;
}
/**
* Get IDs of mailboxes to which user has access.
*/
@@ -321,7 +335,7 @@ class User extends Authenticatable
//$mailboxes = $this->mailboxesCanViewWithSettings(true);
$mailboxes = $this->mailboxesSettings();
foreach ($mailboxes as $mailbox) {
if (!empty(json_decode($mailbox->access))) {
if (!empty($mailbox->access) && !empty(json_decode($mailbox->access))) {
return true;
}
};
@@ -338,13 +352,32 @@ class User extends Authenticatable
} else {
//$mailbox = $this->mailboxesCanViewWithSettings(true)->where('id', $mailbox_id)->first();
$mailbox = $this->mailboxesSettings()->where('mailbox_id', $mailbox_id)->first();
if ($mailbox && !empty(json_decode($mailbox->access))) {
if ($mailbox && !empty(json_decode($mailbox->access ?? ''))) {
return true;
}
}
}
/**
* Main function to check if user has some exta access permission
* for a given mailbox.
*/
public function hasManageMailboxPermission($mailbox_id, $perm) {
// Experimental feature.
// This option does not affect admin users.
if ($perm == Mailbox::ACCESS_PERM_ASSIGNED) {
if ($this->isAdmin()) {
return false;
} else {
$show_only_assigned_conversations = config('app.show_only_assigned_conversations') ?? '';
if (in_array($this->id, explode(',', $show_only_assigned_conversations))) {
return true;
} else {
return false;
}
}
}
if ($this->isAdmin()) {
return true;
} else {
@@ -352,6 +385,8 @@ class User extends Authenticatable
$mailbox = $this->mailboxesSettings()->where('mailbox_id', $mailbox_id)->first();
if ($mailbox && !empty($mailbox->access) && in_array($perm, json_decode($mailbox->access))) {
return true;
} else {
return false;
}
}
}
@@ -370,6 +405,21 @@ class User extends Authenticatable
return str_random($length);
}
/**
* This password indicates that the user has not set the password by himself.
*/
public static function getDummyPassword()
{
return encrypt('dummy_'.str_random(8));
}
public function isDummyPassword()
{
$decrypted_password = \Helper::decrypt($this->password);
return preg_match("#^dummy_#", $decrypted_password);
//return Hash::check($this->getDummyPassword(), $this->password);
}
/**
* Get URL for editing user.
*
@@ -421,11 +471,11 @@ class User extends Authenticatable
continue;
}
foreach (Folder::$personal_types as $type) {
$folder = new Folder();
$folder->mailbox_id = $mailbox_id;
$folder->user_id = $this->id;
$folder->type = $type;
$folder->save();
Folder::create([
'mailbox_id' => $mailbox_id,
'user_id' => $this->id,
'type' => $type,
]);
}
}
}
@@ -438,7 +488,7 @@ class User extends Authenticatable
*
* @return string
*/
public static function dateFormat($date, $format = 'M j, Y H:i', $user = null, $modify_format = true)
public static function dateFormat($date, $format = 'M j, Y H:i', $user = null, $modify_format = true, $use_user_timezone = true)
{
if (!$user) {
$user = auth()->user();
@@ -456,6 +506,10 @@ class User extends Authenticatable
return '';
}
if (!$format) {
$format = 'M j, Y H:i';
}
if ($user && $user !== false) {
if ($modify_format) {
if ($user->time_format == self::TIME_FORMAT_12) {
@@ -463,6 +517,7 @@ class User extends Authenticatable
'H' => 'h',
'G' => 'g',
':i' => ':ia',
':i:s' => ':i:sa',
':ia:s' => ':i:sa',
]);
} else {
@@ -476,7 +531,40 @@ class User extends Authenticatable
}
// todo: formatLocalized has to be used here and below,
// but it returns $format value instead of formatted date
return $date->setTimezone($user->timezone)->format($format);
if ($use_user_timezone) {
$date->setTimezone($user->timezone);
}
}
if (class_exists('IntlDateFormatter')) {
// Convert `strftime` format to `IntlDateFormatter` pattern.
// https://unicode-org.github.io/icu/userguide/format_parse/datetime/
$format = strtr($format, [
'M' => 'MMM',
'm' => 'MM',
'j' => 'd',
'd' => 'dd',
'H' => 'HH',
'h' => 'hh',
'i' => 'mm',
's' => 'ss',
'l' => 'cccc',
'O' => 'xx',
]);
// Remove dot from month name.
$formatted = $date->formatLocalized($format);
if (!strstr($format, '.')) {
$formatted = str_replace('.', '', $formatted);
}
// AM/PM to am/pm.
$formatted = preg_replace_callback('#\d+(AM|PM)$#', function ($m) {
return strtolower($m[0] ?? '');
}, $formatted);
return \Helper::mbUcfirst($formatted);
} else {
return $date->format($format);
}
@@ -547,7 +635,7 @@ class User extends Authenticatable
public static function getUserPermissionName($user_permission)
{
$user_permission_names = [
self::PERM_DELETE_CONVERSATIONS => __('Users are allowed to delete notes/conversations'),
self::PERM_DELETE_CONVERSATIONS => __('Users are allowed to delete conversations'),
self::PERM_EDIT_CONVERSATIONS => __('Users are allowed to edit notes/replies'),
self::PERM_EDIT_SAVED_REPLIES => __('Users are allowed to edit/delete saved replies'),
self::PERM_EDIT_TAGS => __('Users are allowed to manage tags'),
@@ -624,7 +712,7 @@ class User extends Authenticatable
saveToSendLog($this, SendLog::STATUS_SEND_ERROR);
if ($throw_exceptions) {
throw new \Exception(__('Error occured sending email to :email. Please check logs for more details.', ['email' => $this->email]), 1);
throw new \Exception(__('Error occurred sending email to :email. Please check logs for more details.', ['email' => $this->email]), 1);
} else {
return false;
}
@@ -643,9 +731,12 @@ class User extends Authenticatable
/**
* Generate and set password.
*/
public function setPassword()
public function setPassword($password = null)
{
$this->password = Hash::make($this->generateRandomPassword());
if ($password === null) {
$password = $this->generateRandomPassword();
}
$this->password = Hash::make($password);
}
/**
@@ -776,10 +867,14 @@ class User extends Authenticatable
{
$real_path = $uploaded_file;
if (!is_string($uploaded_file)) {
$real_path = $uploaded_file->getRealPath();
// Fallback to getPathname() for Windows.
// https://github.com/freescout-help-desk/freescout/issues/4105
$real_path = $uploaded_file->getRealPath() ?: $uploaded_file->getPathname();
$mime_type = $uploaded_file->getMimeType();
}
$resized_image = \App\Misc\Helper::resizeImage($real_path, $mime_type, self::PHOTO_SIZE, self::PHOTO_SIZE);
$photo_size = config('app.user_photo_size');
$resized_image = \App\Misc\Helper::resizeImage($real_path, $mime_type, $photo_size, $photo_size);
if (!$resized_image) {
return false;
@@ -865,7 +960,7 @@ class User extends Authenticatable
*/
public static function getSuperAdmin()
{
return self::where('role', self::ROLE_ADMIN)->first();
return self::nonDeleted()->where('role', self::ROLE_ADMIN)->first();
}
/**
@@ -879,10 +974,7 @@ class User extends Authenticatable
return null;
}
$user->fill($data);
$user->password = \Hash::make($data['password']);
$user->email = Email::sanitizeEmail($data['email']);
$user->setData($data);
try {
$user->save();
@@ -893,6 +985,36 @@ class User extends Authenticatable
return $user;
}
/**
* Set fields.
*/
public function setData($data, $replace_data = true, $save = false)
{
if (isset($data['email'])) {
$data['email'] = Email::sanitizeEmail($data['email']);
}
if (isset($data['password']) && empty($data['no_password_hashing'])) {
$data['password'] = \Hash::make($data['password']);
}
if ($replace_data) {
$this->fill($data);
} else {
// Update empty fields.
foreach ($data as $key => $value) {
if (in_array($key, $this->fillable) && empty($this->$key)) {
$this->$key = $value;
}
}
}
\Eventy::action('user.set_data', $this, $data, $replace_data);
if ($save) {
$this->save();
}
}
/**
* Check if current user's role is higher than passed.
*/
@@ -935,12 +1057,15 @@ class User extends Authenticatable
/**
* Get query to fetch non-deleted users.
* Some modules may extend this condition, to allow this user $extended parameter.
*
* @return [type] [description]
*/
public static function nonDeleted()
public static function nonDeleted($extended = false)
{
return self::where('status', '!=', self::STATUS_DELETED);
$condition = self::where('status', '!=', self::STATUS_DELETED);
return \Eventy::filter('user.non_deleted_condition', $condition, $extended);
}
public function isActive()
@@ -979,6 +1104,7 @@ class User extends Authenticatable
$join->on('mailbox_user.user_id', '=', 'users.id');
})
->whereIn('mailbox_user.mailbox_id', $mailbox_ids)
->groupBy('users.id')
->get();
}
@@ -1003,12 +1129,12 @@ class User extends Authenticatable
public function getAuthToken()
{
return md5($this->id.config('app.key'));
return md5($this->id.''.$this->created_at.config('app.key'));
}
public static function findNonDeleted($id)
public static function findNonDeleted($id, $extended = false)
{
return User::nonDeleted()->where('id', $id)->first();
return User::nonDeleted($extended)->where('id', $id)->first();
}
/**
@@ -1028,4 +1154,84 @@ class User extends Authenticatable
{
return \Eventy::filter('user_permissions.list', self::$user_permissions);
}
/**
* Check user main and alternate emails.
*/
public function hasEmail($email)
{
$email = Email::sanitizeEmail($email);
if ($this->email == $email) {
return true;
}
$alt_emails = explode(',', $this->emails ?? '');
foreach ($alt_emails as $alt_email) {
if (Email::sanitizeEmail($alt_email) == $email) {
return true;
}
}
return false;
}
/**
* Check if there is a mailbox with specified email.
*/
public static function mailboxEmailExists($email)
{
$email = Email::sanitizeEmail($email);
$mailbox = Mailbox::where('email', $email)->first();
if ($mailbox) {
return true;
} else {
return false;
}
}
public function followConversation($conversation_id)
{
try {
$follower = new Follower();
$follower->conversation_id = $conversation_id;
$follower->user_id = $this->id;
$follower->save();
} catch (\Exception $e) {
// Already exists
}
}
// If there will be some issues, extra "robot" field
// may need to be added to Users table.
public static function getRobotsCondition()
{
return User::where('type', User::TYPE_ROBOT);
}
// Truncate fields to their max lengths to avoid PostgreSQL error:
// SQLSTATE[22001]: String data, right truncated: 7 ERROR: value too long for type character varying(100).
// https://github.com/freescout-helpdesk/freescout/issues/3489
public function setFirstNameAttribute($first_name)
{
$this->attributes['first_name'] = mb_substr($first_name ?? '', 0, 20);
}
public function setLastNameAttribute($last_name)
{
$this->attributes['last_name'] = mb_substr($last_name ?? '', 0, 30);
}
public function setEmailAttribute($email)
{
$this->attributes['email'] = mb_substr($email ?? '', 0, 100);
}
public function setJobTitleAttribute($job_title)
{
$this->attributes['job_title'] = mb_substr($job_title ?? '', 0, 100);
}
public function canSeeOnlyAssignedConversations()
{
return $this->hasManageMailboxPermission(0, Mailbox::ACCESS_PERM_ASSIGNED);
}
}

19
artisan
View File

@@ -9,6 +9,25 @@ if (!version_compare(phpversion(), '7.0.0', '>=')) {
define('LARAVEL_START', microtime(true));
// PHP 8.1 fix.
if (! function_exists('e')) {
/**
* Escape HTML special characters in a string.
*
* @param \Illuminate\Contracts\Support\Htmlable|string $value
* @param bool $doubleEncode
* @return string
*/
function e($value, $doubleEncode = false)
{
if ($value instanceof \Illuminate\Contracts\Support\Htmlable) {
return $value->toHtml();
}
return htmlspecialchars($value ?? '', ENT_QUOTES, 'UTF-8', $doubleEncode);
}
}
/*
|--------------------------------------------------------------------------
| Register The Auto Loader

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