Compare commits

...

43 Commits

Author SHA1 Message Date
Martin Edenhofer
d288b48b0a Updated to 5.0.0 2021-10-05 04:02:08 +02:00
Mantas Masalskis
94011846d6 Fixes #3773 - Inconstant alignment in the listing of attachments/submit button in new article area
Fixes #3774 - Broken dialog whiling uploading oversized attachment
2021-10-04 21:06:21 +02:00
Rolf Schmidt
09fc94cf72 Fixes #3779 - Core Workflow: Add organization condition attributes for object User. 2021-10-04 15:47:54 +02:00
Mantas Masalskis
c34452f65e Fixes #3783 - Improve contrasts in answer search for articles 2021-10-04 15:43:18 +02:00
Rolf Schmidt
2b77711150 Maintenance: Add another reload to setup new js for core workflow. 2021-10-04 15:26:07 +02:00
Rolf Schmidt
57945692cc Follow up 5f2181d8a3 - Fixes #3757 - escaped 'Set fixed' workflows don't refresh set values on active ticket sessions. 2021-10-04 15:26:06 +02:00
Martin Gruner
dfb2abe380 Maintenance: Specify a certain PostgreSQL version for the build process that still works with older platforms. 2021-10-04 14:57:48 +02:00
Rolf Schmidt
07c6bdacfb Fixes #3781 - ObjectManager Attribute without screen attribute causes CoreWorkflows migration to fail 2021-10-01 16:54:05 +02:00
benrubson
ff03e664db Fixes #2674, closes #3775 - Zammad preflight check warning output causes Syntax-Error in postinstall.sh and failing installation. 2021-10-01 16:54:05 +02:00
Romit Choudhary
c72006216e Fixes #2780 - Shared Organisation issue create your first ticket 2021-10-01 16:54:04 +02:00
Rolf Schmidt
6efafd37ae Fixes #3776 - Force users to reload after system migration. 2021-10-01 16:54:03 +02:00
Dominik Klein
35f45f690f Maintenance: Port customer ticket create fields test to capybara. 2021-10-01 16:54:03 +02:00
Romit Choudhary
23c8db7e58 Fixes #2351 - Unable to cancel attachment upload 2021-10-01 16:54:02 +02:00
Martin Gruner
8382a71813 Maintenance: Port admin_calendar_sla_test.rb to Capybara. 2021-10-01 16:54:01 +02:00
Thorsten Eckel
9bb1cf6d00 Fixes #3777 - misspelled KnowledgeBase constant breaks update. 2021-10-01 16:54:01 +02:00
Mantas
2bba743dc3 Fixes #2619 - KB header and footer link-color not changeable 2021-10-01 16:54:00 +02:00
Mantas
d7b0c639c4 Fixes #3028 - Syntax errors break scheduler job for good 2021-10-01 16:53:49 +02:00
Martin Edenhofer
13668dfc8b Fixes #3365 - No script content (e. g. JavaScript) in emails 2021-09-29 10:14:34 +02:00
Mantas Masalskis
1e70665d1a Fixes #3772 - Existing tickets: New article modal with padding-left: 0; padding-right: 0; 2021-09-29 10:09:47 +02:00
Rolf Schmidt
4b0d4f798f Fixes #3757 - escaped 'Set fixed' workflows don't refresh set values on active ticket sessions. 2021-09-29 10:03:34 +02:00
Thorsten Eckel
9ad7c43cd5 Maintenance: Pluralize admin navigation entry name for Core Workflows. 2021-09-29 09:59:56 +02:00
Martin Gruner
ab9fa3d23d Maintenance: Bump nokogiri to v1.12.5. 2021-09-28 08:52:18 +02:00
Dominik Klein
5222f4ba6d Maintenance: Port agent ticket update tests to capybara. 2021-09-27 12:50:50 +02:00
Martin Gruner
9689d3d97e Maintenance: Stabilize System > Objects test (another try). 2021-09-27 11:59:17 +02:00
Martin Gruner
6a10308f29 Maintenance: Stabilize System > Objects test. 2021-09-25 06:29:14 +02:00
Martin Gruner
ab3cede8ef Maintenance: Port System > Translations test to capybara. 2021-09-24 16:20:00 +02:00
Martin Gruner
fa3cb1925d Fixes: #3765 - Possible race condition causing OTRS import to fail 2021-09-24 09:28:28 +02:00
Mantas
a7def1d62d Fixes #3746 - Incorrect alignment in the listing of attachments when creating a ticket 2021-09-23 16:35:28 +02:00
Rolf Schmidt
a3df6b8581 Fixes #3758 - Saved conditions break on selections without reloading. 2021-09-23 15:51:18 +02:00
Romit Choudhary
dd201db791 Fixes #3075 - Misleading view of user icons which are on vacation and disabled 2021-09-23 15:48:24 +02:00
Martin Gruner
60c0b08d21 Maintenance: Work around timeouts caused by the sanitizer in slow CI environments. 2021-09-23 15:15:24 +02:00
Martin Gruner
95571b70c9 Maintenance: Improve template rendering. 2021-09-23 12:02:18 +00:00
Dominik Klein
678161be2e Maintenance: Refactoring of Avatar storage logic. 2021-09-23 12:02:18 +00:00
Rolf Schmidt
008053f730 Maintenance: Add assets level to have different data sets based on permissions 2021-09-23 12:02:18 +00:00
Thorsten Eckel
ed1782fafc Maintenance: Enhance attachment preview capabilities 2021-09-23 12:02:18 +00:00
Rolf Schmidt
72f39d0d9d Maintenance: Remove while loop user check login. 2021-09-23 12:02:18 +00:00
Dominik Klein
7a156c5d48 Maintenance: Improve package installation. 2021-09-23 12:02:18 +00:00
Thorsten Eckel
f31aeec8db Maintenance: Improve application boot time by reducing initial asset payload 2021-09-23 12:02:18 +00:00
Dominik Klein
f67cf3ab1e Maintenance: Increase performance of ticket creation via form. 2021-09-23 12:02:18 +00:00
Thorsten Eckel
b125b3603e Maintenance: Improve clipboard handling of website chat 2021-09-23 12:02:18 +00:00
Martin Gruner
b1e8b3afae Maintenance: Improved updating of user records in the front end. 2021-09-23 12:02:18 +00:00
Rolf Schmidt
573c975bad Maintenance: Enhanced GitHub and GitLab GraphQL endpoint check 2021-09-23 12:02:18 +00:00
Martin Gruner
25155eb6a3 Maintenance: Improved updating of user records. 2021-09-23 12:02:18 +00:00
132 changed files with 2981 additions and 2312 deletions

View File

@@ -116,6 +116,6 @@ env:
- ZAMMAD_RAILS_PORT=3000
- ZAMMAD_WEBSOCKET_PORT=6042
services:
- postgres
- postgres:13
before_install: contrib/packager.io/preinstall.sh
after_install: contrib/packager.io/postinstall.sh

View File

@@ -1,15 +1,186 @@
# Change Log
## [5.0.0](https://github.com/zammad/zammad/tree/5.0.0) (2021-xx-xx)
## [5.0.0](https://github.com/zammad/zammad/tree/5.0.0) (2021-10-05)
[Full Changelog](https://github.com/zammad/zammad/compare/4.1.0...5.0.0)
**Implemented enhancements:**
- Core Workflow: Add organization condition attributes for object User [3779](https://github.com/zammad/zammad/issues/3779) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- No script content (e. g. javascript) in emails [3365](https://github.com/zammad/zammad/issues/3365) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[mail processing](https://github.com/zammad/zammad/labels/mail processing)]
- Read-only custom objects [2102](https://github.com/zammad/zammad/issues/2102) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[object manager attribute](https://github.com/zammad/zammad/labels/object manager attribute)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)]
- Granular admin permission for google channel is missing [3194](https://github.com/zammad/zammad/issues/3194) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
- Use country codes (e.g. `DE` or `ES`) for knowledgebase answer selection [3574](https://github.com/zammad/zammad/issues/3574) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[knowledge base](https://github.com/zammad/zammad/labels/knowledge base)]
- New email account expert view cannot be opened without filling in all fields [3137](https://github.com/zammad/zammad/issues/3137) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)]
- Don't provide option to create API-Token if authentication via API token is disabled [3168](https://github.com/zammad/zammad/issues/3168) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)]
- Define default "stay on tab" / "close tab" behavior [257](https://github.com/zammad/zammad/issues/257) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- Space after ticket hook in ticket/zoom [3265](https://github.com/zammad/zammad/issues/3265) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[ticket](https://github.com/zammad/zammad/labels/ticket)]
- Enhance LDAP login to not affect "failed logins" [2389](https://github.com/zammad/zammad/issues/2389) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[LDAP](https://github.com/zammad/zammad/labels/LDAP)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- Detect Jira follow-ups [3695](https://github.com/zammad/zammad/issues/3695) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[mail processing](https://github.com/zammad/zammad/labels/mail processing)]
- csv import example file download (organizations) is empty [3514](https://github.com/zammad/zammad/issues/3514) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
- Add bulk option to extended search [445](https://github.com/zammad/zammad/issues/445) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- MessageBird integration [3672](https://github.com/zammad/zammad/issues/3672) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[channel](https://github.com/zammad/zammad/labels/channel)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- Add attachment via Trigger and scheduler if wanted by admin [2485](https://github.com/zammad/zammad/issues/2485) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[admin area](https://github.com/zammad/zammad/labels/admin area)] [[trigger](https://github.com/zammad/zammad/labels/trigger)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- Tag KB Answers [2612](https://github.com/zammad/zammad/issues/2612) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[knowledge base](https://github.com/zammad/zammad/labels/knowledge base)]
- Backtrace lines pollute log [3556](https://github.com/zammad/zammad/issues/3556) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[authentication](https://github.com/zammad/zammad/labels/authentication)]
- Visualise locked users in UI and make them unlock-able for admin [2565](https://github.com/zammad/zammad/issues/2565) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- Representation of inactive customers and orgnizations [3302](https://github.com/zammad/zammad/issues/3302) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- No possibility to enforce auto response if one of the blocking auto response mail header exists [3667](https://github.com/zammad/zammad/issues/3667) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[mail processing](https://github.com/zammad/zammad/labels/mail processing)]
- REST doc of Online Notification controler is outdated/wrong and expand param is missing. [3635](https://github.com/zammad/zammad/issues/3635) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
- Scroll background instead of foreground [978](https://github.com/zammad/zammad/issues/978) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[frontend / JS app](https://github.com/zammad/zammad/labels/frontend / JS app)]
- Log if a active user (in UI) has been logged out due to SessionTimeout [3614](https://github.com/zammad/zammad/issues/3614) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
- The rake task `zammad:package:migrate` does not execute migrations for linked packages. [3606](https://github.com/zammad/zammad/issues/3606) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[developer experience](https://github.com/zammad/zammad/labels/developer experience)]
- Accept 2xx as response for requests in UserAgent [3573](https://github.com/zammad/zammad/pull/3573) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
- Don't fail job is `deletable_id` is no longer available [3536](https://github.com/zammad/zammad/issues/3536) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[data privacy](https://github.com/zammad/zammad/labels/data privacy)]
- Add console output for searchindex rebuild status [3562](https://github.com/zammad/zammad/issues/3562) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)]
- Display minutes for session timeouts instead of seconds [3575](https://github.com/zammad/zammad/issues/3575) [[enhancement](https://github.com/zammad/zammad/labels/enhancement)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[admin area](https://github.com/zammad/zammad/labels/admin area)]
**Fixed bugs:**
- Inconstant alignment in the listing of attachments/submit button in new article area [3773](https://github.com/zammad/zammad/issues/3773) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Improve contrasts in answer search for articles [3783](https://github.com/zammad/zammad/issues/3783) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[knowledge base](https://github.com/zammad/zammad/labels/knowledge base)]
- escaped 'Set fixed' workflows don't refresh set values on active ticket sessions [3757](https://github.com/zammad/zammad/issues/3757) [[bug](https://github.com/zammad/zammad/labels/bug)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)]
- ObjectManager Attribute without screen attribute causes CoreWorkflows migration to fail [3781](https://github.com/zammad/zammad/issues/3781) [[bug](https://github.com/zammad/zammad/labels/bug)] [[migration / update](https://github.com/zammad/zammad/labels/migration / update)]
- Zammad preflight check warning output causes Syntax-Error in postinstall.sh and failing installation [2674](https://github.com/zammad/zammad/issues/2674) [[bug](https://github.com/zammad/zammad/labels/bug)] [[migration / update](https://github.com/zammad/zammad/labels/migration / update)]
- Shared organization issue (Create your first ticket) [2780](https://github.com/zammad/zammad/issues/2780) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[ticket](https://github.com/zammad/zammad/labels/ticket)]
- Force users to reload after system migration [3776](https://github.com/zammad/zammad/issues/3776) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- Unable to cancel attachment upload [2351](https://github.com/zammad/zammad/issues/2351) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)]
- [BUG] miss spell in db/migrate/20210923172256_issue_2619_kb_header_link_color.rb [3777](https://github.com/zammad/zammad/issues/3777) [[bug](https://github.com/zammad/zammad/labels/bug)] [[blocker](https://github.com/zammad/zammad/labels/blocker)] [[migration / update](https://github.com/zammad/zammad/labels/migration / update)]
- KB header and footer link-color not changeable [2619](https://github.com/zammad/zammad/issues/2619) [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[knowledge base](https://github.com/zammad/zammad/labels/knowledge base)] [[specification required](https://github.com/zammad/zammad/labels/specification required)] [[theming / skinning](https://github.com/zammad/zammad/labels/theming / skinning)]
- Syntax errors break scheduler job for good [3028](https://github.com/zammad/zammad/issues/3028) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Existing tickets: New article modal with padding-left: 0; padding-right: 0; [3772](https://github.com/zammad/zammad/issues/3772) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[regression](https://github.com/zammad/zammad/labels/regression)]
- Possible race condition causing OTRS import to fail [3765](https://github.com/zammad/zammad/issues/3765) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)]
- Incorrect alignment in the listing of attachments when creating a ticket [3746](https://github.com/zammad/zammad/issues/3746) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Saved conditions break on selections without reloading [3758](https://github.com/zammad/zammad/issues/3758) [[bug](https://github.com/zammad/zammad/labels/bug)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)]
- Misleading view of user icons which are on vacation and disabled [3075](https://github.com/zammad/zammad/issues/3075) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[ticket](https://github.com/zammad/zammad/labels/ticket)]
- User with user_id 1 is show in admin interface (which should not) [3755](https://github.com/zammad/zammad/issues/3755) [[bug](https://github.com/zammad/zammad/labels/bug)] [[blocker](https://github.com/zammad/zammad/labels/blocker)]
- Unable to close tickets in certran cases if core workflow is used [3710](https://github.com/zammad/zammad/issues/3710) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)]
- Login failed after upgrade to zammad 5.0 [3759](https://github.com/zammad/zammad/issues/3759) [[bug](https://github.com/zammad/zammad/labels/bug)] [[blocker](https://github.com/zammad/zammad/labels/blocker)] [[migration / update](https://github.com/zammad/zammad/labels/migration / update)]
- Unable to create a ticket in web app if default for additional boolean value is FALSE [3762](https://github.com/zammad/zammad/issues/3762) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- User search / selection does not always work if you want to correct your search [3696](https://github.com/zammad/zammad/issues/3696) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- Unable to create new organization [3751](https://github.com/zammad/zammad/issues/3751) [[bug](https://github.com/zammad/zammad/labels/bug)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)]
- Core Workflow "is not" operator is working unexpected [3752](https://github.com/zammad/zammad/issues/3752) [[bug](https://github.com/zammad/zammad/labels/bug)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)]
- scrollPageHeader disappears when answering via email [3736](https://github.com/zammad/zammad/issues/3736) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)]
- Scheduler ignores "disable notifications == no" [3684](https://github.com/zammad/zammad/issues/3684) [[bug](https://github.com/zammad/zammad/labels/bug)] [[notification](https://github.com/zammad/zammad/labels/notification)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- Notes on existing ticks are discarded by editing profile settings [3088](https://github.com/zammad/zammad/issues/3088) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)]
- Owner selection empty or pending reminder data picker not shown if customer field has content but no customer is selected (in ticket create screen) [3743](https://github.com/zammad/zammad/issues/3743) [[bug](https://github.com/zammad/zammad/labels/bug)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)]
- Removal of Microsoft365-Channel not possible via UI when having no email address relation [3741](https://github.com/zammad/zammad/issues/3741) [[bug](https://github.com/zammad/zammad/labels/bug)] [[channel](https://github.com/zammad/zammad/labels/channel)]
- Removal of Google-Channel not possible via UI when having a normal email channel of same account [3203](https://github.com/zammad/zammad/issues/3203) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[channel](https://github.com/zammad/zammad/labels/channel)]
- Core-Workflow: Option manipulation of boolean attributes not possible [3740](https://github.com/zammad/zammad/issues/3740) [[bug](https://github.com/zammad/zammad/labels/bug)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)]
- Core Workflow: Show hidden attributes on group selection (ticket edit) [3739](https://github.com/zammad/zammad/issues/3739) [[bug](https://github.com/zammad/zammad/labels/bug)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)]
- OTRS migration doesn't import article creation time [3235](https://github.com/zammad/zammad/issues/3235) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)]
- Spaces stay URL-encoded when going back to search results on KB for agents [3378](https://github.com/zammad/zammad/issues/3378) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[knowledge base](https://github.com/zammad/zammad/labels/knowledge base)]
- Not working unpack of mail body if decrypted body is in signed-data attachment [3705](https://github.com/zammad/zammad/issues/3705) [[bug](https://github.com/zammad/zammad/labels/bug)] [[mail processing](https://github.com/zammad/zammad/labels/mail processing)]
- It should be possible to show attributes which are configured `shown` false [3726](https://github.com/zammad/zammad/issues/3726) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)]
- admin.core_workflow permission is missing [3732](https://github.com/zammad/zammad/issues/3732) [[bug](https://github.com/zammad/zammad/labels/bug)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)]
- AttributeManager doesn't display boolean default values within the UI [3271](https://github.com/zammad/zammad/issues/3271) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[object manager attribute](https://github.com/zammad/zammad/labels/object manager attribute)]
- Spaces in front of mentions can cause partly content removal [3717](https://github.com/zammad/zammad/issues/3717) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- Drop files here not disappearing after dropping a file [3725](https://github.com/zammad/zammad/issues/3725) [[bug](https://github.com/zammad/zammad/labels/bug)] [[regression](https://github.com/zammad/zammad/labels/regression)]
- ⚙ -> Manage -> Calendars -> Edit: "Subscribe to public holidays in" sorted wrong [2528](https://github.com/zammad/zammad/issues/2528) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)]
- Pending reminder notifications are not sent if ticket is opened in tab [2931](https://github.com/zammad/zammad/issues/2931) [[bug](https://github.com/zammad/zammad/labels/bug)] [[ticket](https://github.com/zammad/zammad/labels/ticket)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[frontend / JS app](https://github.com/zammad/zammad/labels/frontend / JS app)]
- Fields are falsey displayed as mandatory if they contain historic screen values [3721](https://github.com/zammad/zammad/issues/3721) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)]
- `ticket.customer_id` is `current_user` breaks email fetching [3503](https://github.com/zammad/zammad/issues/3503) [[bug](https://github.com/zammad/zammad/labels/bug)] [[channel](https://github.com/zammad/zammad/labels/channel)] [[ticket](https://github.com/zammad/zammad/labels/ticket)]
- Scheduler jobs don't ensure current ticket information if they're running a long time [3329](https://github.com/zammad/zammad/issues/3329) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- groups selection for customer panel is not working anymore (with core workflows) [3713](https://github.com/zammad/zammad/issues/3713) [[bug](https://github.com/zammad/zammad/labels/bug)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)]
- Scheduler Time offset [457](https://github.com/zammad/zammad/issues/457) [[bug](https://github.com/zammad/zammad/labels/bug)] [[trigger](https://github.com/zammad/zammad/labels/trigger)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- "Warten auf Erinnerung" - Inconsistent timestamps [2366](https://github.com/zammad/zammad/issues/2366) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Ical escaltion update the calendar entry to 2:00 am [2157](https://github.com/zammad/zammad/issues/2157) [[bug](https://github.com/zammad/zammad/labels/bug)] [[notification](https://github.com/zammad/zammad/labels/notification)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- Inheritance of out of office settings [3694](https://github.com/zammad/zammad/issues/3694) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- Core worflow perform action "select" should not make multiple values selectable [3712](https://github.com/zammad/zammad/issues/3712) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)]
- Setting for CheckMK not saved in zammad [3707](https://github.com/zammad/zammad/issues/3707) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Any user can be set as a replacement for out of office [2293](https://github.com/zammad/zammad/issues/2293) [[bug](https://github.com/zammad/zammad/labels/bug)] [[personal settings/menu](https://github.com/zammad/zammad/labels/personal settings/menu)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- Core Workflow Implementation [3709](https://github.com/zammad/zammad/issues/3709) [[feature backlog](https://github.com/zammad/zammad/labels/feature backlog)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[core](https://github.com/zammad/zammad/labels/core)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)]
- Can't remove auto assignment timeout [2544](https://github.com/zammad/zammad/issues/2544) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- Assign Follow ups has no affect [2455](https://github.com/zammad/zammad/issues/2455) [[bug](https://github.com/zammad/zammad/labels/bug)] [[ticket](https://github.com/zammad/zammad/labels/ticket)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- The calendar shows Chilean holidays one day in advance [3706](https://github.com/zammad/zammad/issues/3706) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[regression](https://github.com/zammad/zammad/labels/regression)]
- Organization removal does not clear organization_id from Tickets [3703](https://github.com/zammad/zammad/issues/3703) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[regression](https://github.com/zammad/zammad/labels/regression)]
- Merge not possible with not set, required attributes [2634](https://github.com/zammad/zammad/issues/2634) [[bug](https://github.com/zammad/zammad/labels/bug)] [[ticket](https://github.com/zammad/zammad/labels/ticket)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[object manager attribute](https://github.com/zammad/zammad/labels/object manager attribute)]
- Merging tickets doesn’t trigger notification for target ticket [3105](https://github.com/zammad/zammad/issues/3105) [[bug](https://github.com/zammad/zammad/labels/bug)] [[notification](https://github.com/zammad/zammad/labels/notification)] [[ticket](https://github.com/zammad/zammad/labels/ticket)]
- One of DB migrations timestamp breaks migrations queue [3702](https://github.com/zammad/zammad/issues/3702) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Tags in ticket create screen displayed wrong [3701](https://github.com/zammad/zammad/issues/3701) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[regression](https://github.com/zammad/zammad/labels/regression)] [[knowledge base](https://github.com/zammad/zammad/labels/knowledge base)]
- Custom date attributes provide inconsistent sorting within grouping [3663](https://github.com/zammad/zammad/issues/3663) [[bug](https://github.com/zammad/zammad/labels/bug)] [[overviews](https://github.com/zammad/zammad/labels/overviews)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- Mix of binary encoded ISO-8859-1 data in header fields (e.g. to) fails mail processing [3697](https://github.com/zammad/zammad/issues/3697) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[mail processing](https://github.com/zammad/zammad/labels/mail processing)]
- Zammad sends mails even though there is no email address visible in TO or CC fields (any more) [3554](https://github.com/zammad/zammad/issues/3554) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)]
- Error 500 / zammad {"error":" 123456 is out of range for ActiveModel::Type::Integer with limit 4 bytes"} [3647](https://github.com/zammad/zammad/issues/3647) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Default value not set for attributes of type input, select, tree_select, richtext, textarea, checkbox [1653](https://github.com/zammad/zammad/issues/1653) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[object manager attribute](https://github.com/zammad/zammad/labels/object manager attribute)]
- When replying, quote article content only [3539](https://github.com/zammad/zammad/issues/3539) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- Trigger ignores changes on some Ticket attributes [3428](https://github.com/zammad/zammad/issues/3428) [[bug](https://github.com/zammad/zammad/labels/bug)] [[trigger](https://github.com/zammad/zammad/labels/trigger)]
- KB search does not allow pagination on endpoint [3068](https://github.com/zammad/zammad/issues/3068) [[bug](https://github.com/zammad/zammad/labels/bug)] [[API](https://github.com/zammad/zammad/labels/API)] [[search](https://github.com/zammad/zammad/labels/search)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[knowledge base](https://github.com/zammad/zammad/labels/knowledge base)]
- “You have not created a ticket yet” shown but I already have closed tickets [3053](https://github.com/zammad/zammad/issues/3053) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[overviews](https://github.com/zammad/zammad/labels/overviews)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[🚧 refactoring 2.0 🚧](https://github.com/zammad/zammad/labels/🚧 refactoring 2.0 🚧)]
- Support HTML5 'required' attribute in forms [1890](https://github.com/zammad/zammad/pull/1890)
- Freshdesk import works not with more then 30_000 tickets [3681](https://github.com/zammad/zammad/issues/3681) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)]
- Pagination for knowledge base answer sorting breaks sorting [3660](https://github.com/zammad/zammad/issues/3660) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[admin area](https://github.com/zammad/zammad/labels/admin area)] [[knowledge base](https://github.com/zammad/zammad/labels/knowledge base)]
- Zendesk-Import `Connection reset by peer` cancels import [3583](https://github.com/zammad/zammad/issues/3583) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)] [[waiting for feedback](https://github.com/zammad/zammad/labels/waiting for feedback)]
- Prefer HTTP standard header "From" over custom "X-On-Behalf-Of" for impersonation [3113](https://github.com/zammad/zammad/issues/3113) [[API](https://github.com/zammad/zammad/labels/API)] [[help appreciated](https://github.com/zammad/zammad/labels/help appreciated)] [[authentication](https://github.com/zammad/zammad/labels/authentication)] [[hacktoberfest](https://github.com/zammad/zammad/labels/hacktoberfest)] [[:construction: refactoring :construction:](https://github.com/zammad/zammad/labels/:construction: refactoring :construction:)] [[specification required](https://github.com/zammad/zammad/labels/specification required)] [[good first issue](https://github.com/zammad/zammad/labels/good first issue)] [[deprecation](https://github.com/zammad/zammad/labels/deprecation)]
- Do not send `domain` and migrate `domain` out of Google and Microsoft 365 channels [3669](https://github.com/zammad/zammad/issues/3669) [[bug](https://github.com/zammad/zammad/labels/bug)] [[channel](https://github.com/zammad/zammad/labels/channel)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- high organization user count relates to bad performance [3648](https://github.com/zammad/zammad/issues/3648) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[performance](https://github.com/zammad/zammad/labels/performance)]
- Removing organizations removes user and ticket as well [3688](https://github.com/zammad/zammad/issues/3688) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- Wrong user is used when "X-On-Behalf-Of” header value is an email that starts with digits [2851](https://github.com/zammad/zammad/issues/2851) [[bug](https://github.com/zammad/zammad/labels/bug)] [[API](https://github.com/zammad/zammad/labels/API)]
- Allow out of office for one day without setting two days [3590](https://github.com/zammad/zammad/issues/3590) [[bug](https://github.com/zammad/zammad/labels/bug)] [[personal settings/menu](https://github.com/zammad/zammad/labels/personal settings/menu)]
- FreshDesk Import doesn't pull in auto-assign domain(s) for organizations [3687](https://github.com/zammad/zammad/issues/3687) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)]
- FreshDesk Import brings in all users as inactive [3689](https://github.com/zammad/zammad/issues/3689) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)]
- KB Public UI icons are misspaced [3680](https://github.com/zammad/zammad/issues/3680) [[bug](https://github.com/zammad/zammad/labels/bug)] [[knowledge base](https://github.com/zammad/zammad/labels/knowledge base)]
- FreshDesk Import Error - undefined method `body' for 10:Integer [3661](https://github.com/zammad/zammad/issues/3661) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)]
- Cannot select multiple tickets in ticket overview with shift+click in Firefox [3449](https://github.com/zammad/zammad/issues/3449) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[overviews](https://github.com/zammad/zammad/labels/overviews)]
- "Drop files here" drag area not always hiding [3460](https://github.com/zammad/zammad/issues/3460) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[help appreciated](https://github.com/zammad/zammad/labels/help appreciated)]
- CTI Callerlog signaling hides "+" for new ticket if more than one user is found [2930](https://github.com/zammad/zammad/issues/2930) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[frontend / JS app](https://github.com/zammad/zammad/labels/frontend / JS app)]
- Zendesk URL check fails with Net::HTTPForbidden 403 error [3679](https://github.com/zammad/zammad/issues/3679) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)]
- Mac Mail inline PDF destroys E-Mail-Body [2407](https://github.com/zammad/zammad/issues/2407) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[mail processing](https://github.com/zammad/zammad/labels/mail processing)]
- Outdated urls on security page (third-party logins) [3627](https://github.com/zammad/zammad/issues/3627) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Duplicated article after bulk action was executed for newly created tickets without a browser refresh in between [3568](https://github.com/zammad/zammad/issues/3568) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Text Editor deletes text unrecoverably if quicktext was canceled with Ctrl+Backspace [2886](https://github.com/zammad/zammad/issues/2886) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[ticket](https://github.com/zammad/zammad/labels/ticket)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[frontend / JS app](https://github.com/zammad/zammad/labels/frontend / JS app)]
- The defined ignored attributes in the popover context are not working [3668](https://github.com/zammad/zammad/issues/3668) [[bug](https://github.com/zammad/zammad/labels/bug)] [[frontend / JS app](https://github.com/zammad/zammad/labels/frontend / JS app)]
- Ensure Upload Cache files are removed after grace period [3579](https://github.com/zammad/zammad/issues/3579) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- Package installation fails because of too long file names [3666](https://github.com/zammad/zammad/issues/3666) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Package migrations fail on package installation or update due to codebase self-modification [3580](https://github.com/zammad/zammad/issues/3580) [[bug](https://github.com/zammad/zammad/labels/bug)] [[core](https://github.com/zammad/zammad/labels/core)]
- saving Boolean field with empty values [2973](https://github.com/zammad/zammad/issues/2973) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)]
- CTI-API should handle doubled requests better [3247](https://github.com/zammad/zammad/issues/3247) [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[CTI](https://github.com/zammad/zammad/labels/CTI)] [[specification required](https://github.com/zammad/zammad/labels/specification required)]
- In some situations the chat button is visible without a active websocket connection [2258](https://github.com/zammad/zammad/issues/2258) [[bug](https://github.com/zammad/zammad/labels/bug)] [[chat](https://github.com/zammad/zammad/labels/chat)]
- Fixes mistypes in article_view.coffee [3658](https://github.com/zammad/zammad/pull/3658)
- Creating and editing users via office 365 failes with Image source is invalid [3617](https://github.com/zammad/zammad/issues/3617) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- FreshDesk Import fails with timeout error [3653](https://github.com/zammad/zammad/issues/3653) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)]
- Chat widget cannot load CSS on (sub)domains that contain "ws" characters [3654](https://github.com/zammad/zammad/issues/3654) [[bug](https://github.com/zammad/zammad/labels/bug)] [[chat](https://github.com/zammad/zammad/labels/chat)]
- timezone issue with elasticsearch [2085](https://github.com/zammad/zammad/issues/2085) [[duplicate](https://github.com/zammad/zammad/labels/duplicate)]
- Invalid html signature detection for exchange warning boxes [3571](https://github.com/zammad/zammad/issues/3571) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- Searching for integer fields does not work as expected [3599](https://github.com/zammad/zammad/issues/3599) [[bug](https://github.com/zammad/zammad/labels/bug)] [[search](https://github.com/zammad/zammad/labels/search)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- FreshDesk import fails on certain custom field types [3628](https://github.com/zammad/zammad/issues/3628) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)]
- Freshdesk import subdomain selection fails [3631](https://github.com/zammad/zammad/issues/3631) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)]
- Search results missing for users with two names within lastname [3598](https://github.com/zammad/zammad/issues/3598) [[bug](https://github.com/zammad/zammad/labels/bug)] [[search](https://github.com/zammad/zammad/labels/search)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- New location notification with X-On-Behalf [3611](https://github.com/zammad/zammad/issues/3611) [[bug](https://github.com/zammad/zammad/labels/bug)] [[API](https://github.com/zammad/zammad/labels/API)] [[notification](https://github.com/zammad/zammad/labels/notification)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- State list of tickets ignore locale for their sorting [3335](https://github.com/zammad/zammad/issues/3335) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[ticket](https://github.com/zammad/zammad/labels/ticket)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- Newly created users are only shown in the admin interface after reload [3050](https://github.com/zammad/zammad/issues/3050) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[frontend / JS app](https://github.com/zammad/zammad/labels/frontend / JS app)]
- Migration 3.6.1 to 4.1 : No configured business hours found! [3641](https://github.com/zammad/zammad/issues/3641) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Verification tokens questionable behavior in developer mode [3116](https://github.com/zammad/zammad/issues/3116) [[developer experience](https://github.com/zammad/zammad/labels/developer experience)]
- Replacing a mention with a new leads to mentioning both agents [3636](https://github.com/zammad/zammad/issues/3636) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Attachment order is wrong [3584](https://github.com/zammad/zammad/issues/3584) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)]
- When disconnecting a Chat, customer can still continue posting messages [2559](https://github.com/zammad/zammad/issues/2559) [[bug](https://github.com/zammad/zammad/labels/bug)] [[chat](https://github.com/zammad/zammad/labels/chat)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- some macros are hidden in the drag & drop- overview with a small screen size [3359](https://github.com/zammad/zammad/issues/3359) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- Add redis as an (optional) session store backend [3450](https://github.com/zammad/zammad/pull/3450)
- Text Module widget is broken/hidden after pressing backspace (once) [3637](https://github.com/zammad/zammad/issues/3637) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Mention widget show same users several times (if rest requests will return in different order) [3639](https://github.com/zammad/zammad/issues/3639) [[bug](https://github.com/zammad/zammad/labels/bug)]
- The rebuild of the index is failing [3633](https://github.com/zammad/zammad/issues/3633) [[bug](https://github.com/zammad/zammad/labels/bug)] [[chat](https://github.com/zammad/zammad/labels/chat)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- Monitoring endpoint complains about `Failed to run SessionTimeoutJob.perform_now after 10 tries #<NoMethodError: undefined method `id' for nil:NilClass` [3632](https://github.com/zammad/zammad/issues/3632) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Updating package-based installations can break the front end. [3629](https://github.com/zammad/zammad/issues/3629) [[bug](https://github.com/zammad/zammad/labels/bug)]
- backup: stop using su for pg_dump [3352](https://github.com/zammad/zammad/pull/3352) [[waiting for feedback](https://github.com/zammad/zammad/labels/waiting for feedback)]
- Reply-to header ignored if from contains a system address [2551](https://github.com/zammad/zammad/issues/2551) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Maintenance: Fix handling of test that require secrets not to run for GitHub PRs. [3626](https://github.com/zammad/zammad/pull/3626)
- replacing find -mtime with find -mmin [3570](https://github.com/zammad/zammad/pull/3570)
- Ensure to migrate old calendar URLs for google calendar sources [3618](https://github.com/zammad/zammad/issues/3618) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[regression](https://github.com/zammad/zammad/labels/regression)]
- Mentions/Subscribe with read permissions [3615](https://github.com/zammad/zammad/issues/3615) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- Chat-Message references not cleared with data privacy task [3610](https://github.com/zammad/zammad/issues/3610) [[bug](https://github.com/zammad/zammad/labels/bug)] [[data privacy](https://github.com/zammad/zammad/labels/data privacy)]
- Overview names are hidden in mobile view for RTL languages [3500](https://github.com/zammad/zammad/issues/3500) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[mobile](https://github.com/zammad/zammad/labels/mobile)]
- SMTP-Attachments missing when using the API under high load [2991](https://github.com/zammad/zammad/issues/2991) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- Inline image conversion from jpeg to png causes huge overhead [3538](https://github.com/zammad/zammad/issues/3538) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- session timeout does regulary logout active users before timeout because of dead old sessions [3605](https://github.com/zammad/zammad/issues/3605) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- Zendesk import creates new ticket numbers rather than using the Zendesk ticket IDs also as ticket numbers in Zammad [3593](https://github.com/zammad/zammad/issues/3593) [[bug](https://github.com/zammad/zammad/labels/bug)] [[import](https://github.com/zammad/zammad/labels/import)] [[ticket](https://github.com/zammad/zammad/labels/ticket)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- wrong behaviour for the disabled option in session timeout [3600](https://github.com/zammad/zammad/issues/3600) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- Header links are not responsive [3133](https://github.com/zammad/zammad/issues/3133) [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[knowledge base](https://github.com/zammad/zammad/labels/knowledge base)]
- Update description of the session timeout setting [3597](https://github.com/zammad/zammad/issues/3597) [[bug](https://github.com/zammad/zammad/labels/bug)] [[admin area](https://github.com/zammad/zammad/labels/admin area)]
- Escalation calculation breaks data privacy ticket deletion [3588](https://github.com/zammad/zammad/issues/3588) [[bug](https://github.com/zammad/zammad/labels/bug)] [[data privacy](https://github.com/zammad/zammad/labels/data privacy)]
- `SessionTimeoutJob.perform_now` fails if user no longer exists [3586](https://github.com/zammad/zammad/issues/3586) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Robots.txt should allow indexing Knowledge Base on custom URLs [3164](https://github.com/zammad/zammad/issues/3164) [[bug](https://github.com/zammad/zammad/labels/bug)] [[knowledge base](https://github.com/zammad/zammad/labels/knowledge base)]
- DataPrivacy may fail to clear user relations [3585](https://github.com/zammad/zammad/issues/3585) [[bug](https://github.com/zammad/zammad/labels/bug)] [[data privacy](https://github.com/zammad/zammad/labels/data privacy)]
- SessionTimeoutJob.perform_now scheduler job fails on update if scheduler process wasn't restarted [3581](https://github.com/zammad/zammad/issues/3581) [[bug](https://github.com/zammad/zammad/labels/bug)]
- Freshdesk import attribute ID mapping lookup constants fail application boot in systems with changed default names [3582](https://github.com/zammad/zammad/issues/3582) [[bug](https://github.com/zammad/zammad/labels/bug)]
- mentioned KB entries with the same title in different categories can not distinguished [3559](https://github.com/zammad/zammad/issues/3559) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)] [[knowledge base](https://github.com/zammad/zammad/labels/knowledge base)]
- Invalid auto assignment conditions may break ticket view for unassigned tickets [3567](https://github.com/zammad/zammad/issues/3567) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
- Add Greek translation for chat [3569](https://github.com/zammad/zammad/pull/3569) [[chat](https://github.com/zammad/zammad/labels/chat)] [[translation](https://github.com/zammad/zammad/labels/translation)]
- Freshdesk [865](https://github.com/zammad/zammad/issues/865) [[feature backlog](https://github.com/zammad/zammad/labels/feature backlog)] [[import](https://github.com/zammad/zammad/labels/import)]
- Old JQuery version delivered [3431](https://github.com/zammad/zammad/issues/3431) [[bug](https://github.com/zammad/zammad/labels/bug)] [[chat](https://github.com/zammad/zammad/labels/chat)]

View File

@@ -227,10 +227,6 @@ group :development, :test do
# Slack helper for testing
gem 'slack-ruby-client', require: false
# Can be used to detect for example the current
# operating system in tests, to handle things differently.
gem 'os'
end
# Want to extend Zammad with additional gems?

View File

@@ -335,7 +335,7 @@ GEM
net-ldap (0.17.0)
netrc (0.11.0)
nio4r (2.5.8)
nokogiri (1.12.4)
nokogiri (1.12.5)
mini_portile2 (~> 2.6.1)
racc (~> 1.4)
nori (2.6.0)
@@ -392,7 +392,6 @@ GEM
omniauth (~> 1.5)
omniauth-oauth2 (>= 1.4.0)
openssl (2.2.0)
os (1.1.1)
overcommit (0.58.0)
childprocess (>= 0.6.3, < 5)
iniparse (~> 1.4)
@@ -711,7 +710,6 @@ DEPENDENCIES
omniauth-twitter
omniauth-weibo-oauth2
openssl
os
overcommit
pg (= 0.21.0)
pry-rails

View File

@@ -23,7 +23,7 @@ class App.UiElement.core_workflow_condition extends App.UiElement.ApplicationSel
organization:
name: 'Organization'
model: 'Organization'
model_show: ['Organization']
model_show: ['User', 'Organization']
'customer.organization':
name: 'Organization'
model: 'Organization'

View File

@@ -128,9 +128,10 @@ class App.UiElement.core_workflow_perform extends App.UiElement.ApplicationSelec
@buildValueConfigMultiple: (config, meta) ->
if _.contains(['add_option', 'remove_option', 'set_fixed_to'], meta.operator)
config.multiple = true
config.nulloption = true
else
config.multiple = false
config.nulloption = false
config.nulloption = false
return config
@HasPreCondition: ->

View File

@@ -6,7 +6,7 @@ class App.UiElement.richtext
attribute.value = attribute.value.text
item = $( App.view('generic/richtext')(attribute: attribute, toolButtons: @toolButtons) )
@contenteditable = item.find('[contenteditable]').ce(
item.find('[contenteditable]').ce(
mode: attribute.type
maxlength: attribute.maxlength
buttons: attribute.buttons
@@ -21,12 +21,12 @@ class App.UiElement.richtext
new App[plugin.controller](params)
if attribute.upload
@attachments = []
attachments = []
item.append( $( App.view('generic/attachment')(attribute: attribute) ) )
renderFile = (file) =>
renderFile = (file) ->
item.find('.attachments').append(App.view('generic/attachment_item')(file))
@attachments.push file
attachments.push file
if params && params.attachments
for file in params.attachments
@@ -46,10 +46,10 @@ class App.UiElement.richtext
, form.form_id)
# remove items
item.find('.attachments').on('click', '.js-delete', (e) =>
item.find('.attachments').on('click', '.js-delete', (e) ->
id = $(e.currentTarget).data('id')
@attachments = _.filter(
@attachments,
attachments = _.filter(
attachments,
(item) ->
return if item.id.toString() is id.toString()
item
@@ -71,67 +71,35 @@ class App.UiElement.richtext
element.empty()
)
@progressBar = item.find('.attachmentUpload-progressBar')
@progressText = item.find('.js-percentage')
@attachmentPlaceholder = item.find('.attachmentPlaceholder')
@attachmentUpload = item.find('.attachmentUpload')
@attachmentsHolder = item.find('.attachments')
@cancelContainer = item.find('.js-cancel')
App.Delay.set( ->
uploader = new App.Html5Upload(
uploadUrl: "#{App.Config.get('api_path')}/attachments"
dropContainer: item.closest('form')
cancelContainer: item.find('.js-cancel')
inputField: item.find('input')
data:
form_id: item.closest('form').find('[name=form_id]').val()
u = => html5Upload.initialize(
uploadUrl: "#{App.Config.get('api_path')}/attachments"
dropContainer: item.closest('form').get(0)
cancelContainer: @cancelContainer
inputField: item.find('input').get(0)
maxSimultaneousUploads: 1,
key: 'File'
data:
form_id: item.closest('form').find('[name=form_id]').val()
onFileAdded: (file) =>
onFileStartCallback: ->
item.find('[contenteditable]').trigger('fileUploadStart')
file.on(
onStart: =>
@attachmentPlaceholder.addClass('hide')
@attachmentUpload.removeClass('hide')
@cancelContainer.removeClass('hide')
item.find('[contenteditable]').trigger('fileUploadStart')
App.Log.debug 'UiElement.richtext', 'upload start'
onFileCompletedCallback: (response) ->
renderFile(response.data)
item.find('input').val('')
item.find('[contenteditable]').trigger('fileUploadStop', ['completed'])
onAborted: =>
@attachmentPlaceholder.removeClass('hide')
@attachmentUpload.addClass('hide')
item.find('input').val('')
item.find('[contenteditable]').trigger('fileUploadStop', ['aborted'])
onFileAbortedCallback: ->
item.find('input').val('')
item.find('[contenteditable]').trigger('fileUploadStop', ['aborted'])
# Called after received response from the server
onCompleted: (response) =>
response = JSON.parse(response)
attachmentPlaceholder: item.find('.attachmentPlaceholder')
attachmentUpload: item.find('.attachmentUpload')
progressBar: item.find('.attachmentUpload-progressBar')
progressText: item.find('.js-percentage')
)
@attachmentPlaceholder.removeClass('hide')
@attachmentUpload.addClass('hide')
# reset progress bar
@progressBar.width(parseInt(0) + '%')
@progressText.text('')
renderFile(response.data)
item.find('input').val('')
item.find('[contenteditable]').trigger('fileUploadStop', ['completed'])
App.Log.debug 'UiElement.richtext', 'upload complete', response.data
# Called during upload progress, first parameter
# is decimal value from 0 to 100.
onProgress: (progress, fileSize, uploadedBytes) =>
@progressBar.width(parseInt(progress) + '%')
@progressText.text(parseInt(progress))
# hide cancel on 90%
if parseInt(progress) >= 90
@cancelContainer.addClass('hide')
App.Log.debug 'UiElement.richtext', 'uploadProgress ', parseInt(progress)
)
)
App.Delay.set(u, 100, undefined, 'form_upload')
uploader.render()
, 100, undefined, 'form_upload')
item

View File

@@ -8,7 +8,7 @@ class App.TicketCreate extends App.Controller
events:
'click .type-tabs .tab': 'changeFormType'
'submit form': 'submit'
'click .js-cancel': 'cancel'
'click .form-controls .js-cancel': 'cancel'
'click .js-active-toggle': 'toggleButton'
types: {
@@ -184,8 +184,11 @@ class App.TicketCreate extends App.Controller
@controllerUnbind('ticket_create_rerender', (template) => @renderQueue(template))
changed: =>
return true if @hasAttachments()
formCurrent = @formParam( @$('.ticket-create') )
diff = difference(@formDefault, formCurrent)
return false if !diff || _.isEmpty(diff)
return true
@@ -461,6 +464,9 @@ class App.TicketCreate extends App.Controller
params: =>
params = @formParam(@$('.main form'))
hasAttachments: =>
@$('.richtext .attachments .attachment').length > 0
submit: (e) =>
e.preventDefault()
@@ -563,7 +569,7 @@ class App.TicketCreate extends App.Controller
# save ticket, create article
# check attachment
if article['body']
if @$('.richtext .attachments .attachment').length < 1
if !@hasAttachments()
matchingWord = App.Utils.checkAttachmentReference(article['body'])
if matchingWord
if !confirm(App.i18n.translateContent('You use %s in text but no attachment is attached. Do you want to continue?', matchingWord))

View File

@@ -1,6 +1,6 @@
class CoreWorkflow extends App.ControllerSubContent
requiredPermission: 'admin.core_workflow'
header: 'Core Workflow'
header: 'Core Workflows'
constructor: ->
super
@@ -54,4 +54,4 @@ class CoreWorkflow extends App.ControllerSubContent
}
return mapping[screen] || screen
App.Config.set('CoreWorkflowObject', { prio: 1750, parent: '#system', name: 'Core Workflow', target: '#system/core_workflow', controller: CoreWorkflow, permission: ['admin.core_workflow'] }, 'NavBarAdmin')
App.Config.set('CoreWorkflowObject', { prio: 1750, parent: '#system', name: 'Core Workflows', target: '#system/core_workflow', controller: CoreWorkflow, permission: ['admin.core_workflow'] }, 'NavBarAdmin')

View File

@@ -27,11 +27,13 @@ class App.KnowledgeBasePublicMenuManager extends App.Controller
{
headline: 'Header menu',
identifier: 'header',
color: kb.color_header
color: kb.color_header,
color_link: kb.color_header_link
},
{
headline: 'Footer menu',
identifier: 'footer'
identifier: 'footer',
color_link: 'hsl(207,12%,50%)'
}
]

View File

@@ -1314,7 +1314,7 @@ class Table extends App.Controller
return if ticketListShow[0] || @permissionCheck('ticket.agent')
tickets_count = user.lifetimeCustomerTicketsCount()
@html App.view('customer_not_ticket_exists')(has_any_tickets: tickets_count > 0)
@html App.view('customer_not_ticket_exists')(has_any_tickets: tickets_count > 0, is_allowed_to_create_ticket: @Config.get('customer_ticket_create'))
if tickets_count == 0
@listenTo user, 'refresh', =>

View File

@@ -200,10 +200,10 @@ class App.TicketZoom extends App.Controller
formMeta = data.form_meta
# on the following states we want to rerender the ticket:
# - if the object attribute configuration has changed (attribute values, restrictions, filters)
# - if the object attribute configuration has changed (attribute values, dependecies, filters)
# - if the user view has changed (agent/customer)
# - if the ticket permission has changed (read/write/full)
if @view && ( !_.isEqual(@formMeta, formMeta) || @view isnt view || @readable isnt readable || @changeable isnt changeable || @fullable isnt fullable )
if @view && ( !_.isEqual(@formMeta.configure_attributes, formMeta.configure_attributes) || !_.isEqual(@formMeta.dependencies, formMeta.dependencies) || !_.isEqual(@formMeta.filter, formMeta.filter) || @view isnt view || @readable isnt readable || @changeable isnt changeable || @fullable isnt fullable )
@renderDone = false
@view = view
@@ -214,6 +214,7 @@ class App.TicketZoom extends App.Controller
# render page
@render(local)
App.Event.trigger('ui::ticket::load', data)
meta: =>

View File

@@ -117,7 +117,7 @@ class App.TicketZoomArticleNew extends App.Controller
@tokanice(@type)
if @defaults.body or @isIE10()
if @defaults.body or @attachments or @isIE10()
@openTextarea(null, true)
tokanice: (type = 'email') ->
@@ -191,82 +191,30 @@ class App.TicketZoomArticleNew extends App.Controller
maxlength: 150000
})
html5Upload.initialize(
uploadUrl: "#{App.Config.get('api_path')}/upload_caches/#{@form_id}"
dropContainer: @$('.article-add').get(0)
cancelContainer: @cancelContainer
inputField: @$('.article-attachment input').get(0)
key: 'File'
maxSimultaneousUploads: 1
onFileAdded: (file) =>
new App.Html5Upload(
uploadUrl: "#{App.Config.get('api_path')}/upload_caches/#{@form_id}"
dropContainer: @$('.article-add')
cancelContainer: @cancelContainer
inputField: @$('.article-attachment input')
file.on(
onFileStartCallback: =>
@callbackFileUploadStart?()
onStart: =>
@attachmentPlaceholder.addClass('hide')
@attachmentUpload.removeClass('hide')
@cancelContainer.removeClass('hide')
onFileCompletedCallback: (response) =>
@attachments.push response.data
@renderAttachment(response.data)
@$('.article-attachment input').val('')
if @callbackFileUploadStart
@callbackFileUploadStart()
@callbackFileUploadStop?()
onAborted: =>
@attachmentPlaceholder.removeClass('hide')
@attachmentUpload.addClass('hide')
@$('.article-attachment input').val('')
onFileAbortedCallback: =>
@callbackFileUploadStop?()
if @callbackFileUploadStop
@callbackFileUploadStop()
# Called after received response from the server
onCompleted: (response) =>
response = JSON.parse(response)
@attachments.push response.data
@attachmentPlaceholder.removeClass('hide')
@attachmentUpload.addClass('hide')
# reset progress bar
@progressBar.width(parseInt(0) + '%')
@progressText.text('')
@renderAttachment(response.data)
@$('.article-attachment input').val('')
if @callbackFileUploadStop
@callbackFileUploadStop()
# Called during upload progress, first parameter
# is decimal value from 0 to 100.
onProgress: (progress, fileSize, uploadedBytes) =>
@progressBar.width(parseInt(progress) + '%')
@progressText.text(parseInt(progress))
# hide cancel on 90%
if parseInt(progress) >= 90
@cancelContainer.addClass('hide')
# Called when upload failed
onError: (message) =>
@attachmentPlaceholder.removeClass('hide')
@attachmentUpload.addClass('hide')
@$('.article-attachment input').val('')
if @callbackFileUploadStop
@callbackFileUploadStop()
new App.ControllerModal(
head: 'Upload Failed'
buttonCancel: 'Cancel'
buttonCancelClass: 'btn--danger'
buttonSubmit: false
message: message
shown: true
small: true
container: @el.closest('.content')
)
)
)
attachmentPlaceholder: @attachmentPlaceholder
attachmentUpload: @attachmentUpload
progressBar: @progressBar
progressText: @progressText
).render()
@bindAttachmentDelete()

View File

@@ -119,7 +119,9 @@ class App.FormHandlerCoreWorkflow
valueFound = false
for value in values
if value && paramValue
# false values are valid values e.g. for boolean fields (be careful)
if value isnt undefined && paramValue isnt undefined
if value.toString() == paramValue.toString()
valueFound = true
break

View File

@@ -1,12 +1,21 @@
class Edit extends App.ControllerObserver
model: 'Ticket'
observeNot:
created_at: true
updated_at: true
globalRerender: false
# No usage of a ControllerObserver here because we want to use
# the data of the ticket zoom ajax request which is using the all=true parameter
# and contain the core workflow information as well. Without observer we also
# dont have double rendering because of the zoom (all=true) and observer (full=true) render callback
class Edit extends App.Controller
constructor: (params) ->
super
@controllerBind('ui::ticket::load', (data) =>
return if data.ticket_id.toString() isnt @ticket.id.toString()
render: (ticket, diff) =>
defaults = ticket.attributes()
@ticket = App.Ticket.find(@ticket.id)
@formMeta = data.form_meta
@render()
)
@render()
render: =>
defaults = @ticket.attributes()
delete defaults.article # ignore article infos
followUpPossible = App.Group.find(defaults.group_id).follow_up_possible
ticketState = App.TicketState.find(defaults.state_id).name
@@ -16,10 +25,13 @@ class Edit extends App.ControllerObserver
if !_.isEmpty(taskState)
defaults = _.extend(defaults, taskState)
# remove core workflow data because it should trigger a request to get data
# for the new ticket + eventually changed task state
@formMeta.core_workflow = undefined
if followUpPossible == 'new_ticket' && ticketState != 'closed' ||
followUpPossible != 'new_ticket' ||
@permissionCheck('admin') || ticket.currentView() is 'agent'
@permissionCheck('admin') || @ticket.currentView() is 'agent'
@controllerFormSidebarTicket = new App.ControllerForm(
elReplace: @el
model: { className: 'Ticket', configure_attributes: @formMeta.configure_attributes || App.Ticket.configure_attributes }
@@ -28,7 +40,7 @@ class Edit extends App.ControllerObserver
filter: @formMeta.filter
formMeta: @formMeta
params: defaults
isDisabled: !ticket.editable()
isDisabled: !@ticket.editable()
taskKey: @taskKey
core_workflow: {
callbacks: [@markForm]
@@ -44,7 +56,7 @@ class Edit extends App.ControllerObserver
filter: @formMeta.filter
formMeta: @formMeta
params: defaults
isDisabled: ticket.editable()
isDisabled: @ticket.editable()
taskKey: @taskKey
core_workflow: {
callbacks: [@markForm]
@@ -57,8 +69,8 @@ class Edit extends App.ControllerObserver
return if @resetBind
@resetBind = true
@controllerBind('ui::ticket::taskReset', (data) =>
return if data.ticket_id.toString() isnt ticket.id.toString()
@render(ticket)
return if data.ticket_id.toString() isnt @ticket.id.toString()
@render()
)
class SidebarTicket extends App.Controller
@@ -128,6 +140,7 @@ class SidebarTicket extends App.Controller
@edit = new Edit(
object_id: @ticket.id
ticket: @ticket
el: localEl.find('.edit')
taskGet: @taskGet
formMeta: @formMeta

View File

@@ -0,0 +1,98 @@
class App.Html5Upload extends App.Controller
uploadUrl: null
maxSimultaneousUploads: 1
key: 'File'
data: null
onFileStartCallback: null
onFileCompletedCallback: null
onFileAbortedCallback: null
dropContainer: null
cancelContainer: null
inputField: null
attachmentPlaceholder: null
attachmentUpload: null
progressBar: null
progressText: null
render: =>
html5Upload.initialize(
uploadUrl: @uploadUrl
dropContainer: @dropContainer.get(0)
cancelContainer: @cancelContainer
inputField: @inputField.get(0)
maxSimultaneousUploads: @maxSimultaneousUploads
key: @key
data: @data
onFileAdded: @onFileAdded
)
onFileAdded: (file) =>
file.on(
onStart: @onFileStart
onAborted: @onFileAborted
onCompleted: @onFileCompleted
onProgress: @onFileProgress
onError: @onFileError
)
onFileStart: =>
@attachmentPlaceholder.addClass('hide')
@attachmentUpload.removeClass('hide')
@cancelContainer.removeClass('hide')
App.Log.debug 'Html5Upload', 'upload start'
@onFileStartCallback?()
onFileProgress: (progress, fileSize, uploadedBytes) =>
progress = parseInt(progress)
@progressBar.width(progress + '%')
@progressText.text(progress)
# hide cancel on 90%
if progress >= 90
@cancelContainer.addClass('hide')
App.Log.debug 'Html5Upload', 'uploadProgress ', progress
onFileCompleted: (response) =>
response = JSON.parse(response)
@hideFileUploading()
@onFileCompletedCallback?(response)
App.Log.debug 'Html5Upload', 'upload complete', response.data
onFileAborted: =>
@hideFileUploading()
@onFileAbortedCallback?()
App.Log.debug 'Html5Upload', 'upload aborted'
onFileError: (message) =>
@hideFileUploading()
@inputField.val('')
@callbackFileUploadStop?()
new App.ControllerModal(
head: 'Upload Failed'
buttonCancel: 'Cancel'
buttonCancelClass: 'btn--danger'
buttonSubmit: false
message: message || 'Cannot upload file'
shown: true
small: true
container: @inputField.closest('.content')
)
App.Log.debug 'Html5Upload', 'upload error'
hideFileUploading: =>
@attachmentPlaceholder.removeClass('hide')
@attachmentUpload.addClass('hide')
@progressBar.width('0%')
@progressText.text('0')

View File

@@ -255,7 +255,7 @@
manager.ajaxUpload(manager.uploadsQueue.shift());
}
};
xhr.abort = function (event) {
xhr.onabort = function (event) {
console.log('Upload abort');
// Reduce number of active uploads:
@@ -269,6 +269,7 @@
// Triggered when upload fails:
xhr.onerror = function () {
console.log('Upload failed: ', upload.fileName);
upload.events.onError('Upload failed: ' + upload.fileName);
};
// Append additional data if provided:

View File

@@ -1,5 +1,5 @@
class App.KnowledgeBase extends App.Model
@configure 'KnowledgeBase', 'iconset', 'color_highlight', 'color_header', 'translation_ids', 'locale_ids', 'homepage_layout', 'category_layout', 'custom_address'
@configure 'KnowledgeBase', 'iconset', 'color_highlight', 'color_header', 'color_header_link', 'translation_ids', 'locale_ids', 'homepage_layout', 'category_layout', 'custom_address'
@extend Spine.Model.Ajax
@extend App.KnowledgeBaseActions
@url: @apiPath + '/knowledge_bases'
@@ -148,6 +148,17 @@ class App.KnowledgeBase extends App.Model
display: false
horizontal: true
shown: true
}, {
name: 'color_header_link'
display: 'Header Link Color'
tag: 'color'
style: 'block'
null: false
screen:
admin_style_color_header_link:
display: false
horizontal: true
shown: true
# Layout picker is disabled in V1
#}, {
# name: 'homepage_layout'

View File

@@ -344,9 +344,12 @@ class App.User extends App.Model
@sameOrganization?(requester)
isChangeableBy: (requester) ->
# full access for admins
return true if requester.permission('admin.user')
# allow agents to change customers
# forbid non-agents to change users
return false if !requester.permission('ticket.agent')
# allow agents to change customers only
return false if @permission(['admin.user', 'ticket.agent'])
@permission('ticket.customer')
isDeleteableBy: (requester) ->

View File

@@ -6,11 +6,15 @@
<% if @has_any_tickets: %>
<p><%- @T('You have no tickets to display in this overview.') %></p>
<% else: %>
<p><%- @T('You have not created a ticket yet.') %></p>
<p><%- @T('The way to communicate with us is this thing called "ticket".') %></p>
<p><%- @T('Please click the button below to create your first one.') %></p>
<% if @is_allowed_to_create_ticket: %>
<p><%- @T('You have not created a ticket yet.') %></p>
<p><%- @T('The way to communicate with us is this thing called "ticket".') %></p>
<p><%- @T('Please click the button below to create your first one.') %></p>
<p><a class="btn btn--primary" href="#customer_ticket_new"><%- @T('Create your first ticket') %></a></p>
<p><a class="btn btn--primary" href="#customer_ticket_new"><%- @T('Create your first ticket') %></a></p>
<% else: %>
<p><%- @T('You currently don\'t have any tickets.') %></p>
<% end %>
<% end %>
</div>
</div>

View File

@@ -17,7 +17,7 @@
<%- @T('Uploading') %> (<span class="js-percentage">0</span>%) ...
</div>
<div class="attachmentUpload-cancel js-cancel">
<%- @Icon('diagonal-cross') %></div><%- @T('Cancel Upload') %>
<%- @Icon('diagonal-cross') %><%- @T('Cancel Upload') %>
</div>
</div>
<div class="attachmentUpload-progressBar" style="width: 0%"></div>

View File

@@ -26,11 +26,12 @@ class App.KnowledgeBaseNewModal extends App.ControllerModal
App.UiElement[attribute.tag].prepareParams?(attribute, dom, params)
applyDefaults: (params) ->
params['iconset'] = 'FontAwesome'
params['color_highlight'] = '#38ae6a'
params['color_header'] = '#f9fafb'
params['homepage_layout'] = 'grid'
params['category_layout'] = 'grid'
params['iconset'] = 'FontAwesome'
params['color_highlight'] = '#38ae6a'
params['color_header'] = '#f9fafb'
params['color_header_link'] = 'hsl(206,8%,50%)'
params['homepage_layout'] = 'grid'
params['category_layout'] = 'grid'
onSubmit: (e) ->
params = @formParams(@el)

View File

@@ -14,7 +14,7 @@
<div class="kb-menu-preview">
<div class="label"><%= kb_locale.systemLocale().name %></div>
<div class="kb-menu-preview-container kb-menu-preview-container--<%= location.identifier %>" style="background-color: <%= location.color %>">
<div class="kb-menu-preview-container kb-menu-preview-container--<%= location.identifier %>" style="background-color: <%= location.color %>; color: <%= location.color_link %>;">
<% menu_items = App.KnowledgeBaseMenuItem.using_kb_locale_location(kb_locale, location.identifier) %>
<% if menu_items.length == 0: %>

View File

@@ -3,6 +3,11 @@
</div>
<div class="page-content">
<p>
<%- @T('The installation of packages comes with security implications, because arbitrary code will be executed in the context of the Zammad application.') %>
<br>
<%- @T('Only packages from known, trusted and verfied sources should be installed.') %>
</p>
<p>
<%- @T('After installing, updating or uninstalling packages the following commands need to be executed on the server:') %>
<ul>
@@ -48,4 +53,4 @@
<% end %>
</tbody>
</table>
</div>
</div>

View File

@@ -2661,7 +2661,7 @@ input.has-error {
}
a {
color: hsl(206,8%,50%);
color: inherit;
}
.label {
@@ -4997,6 +4997,11 @@ footer {
}
}
&--vacation {
filter: grayscale(70%);
opacity: 1;
}
&--idle {
filter: grayscale(100%);
opacity: 0.5;
@@ -5007,11 +5012,6 @@ footer {
opacity: 0.2;
}
&--vacation {
filter: grayscale(70%);
opacity: 1;
}
&--unique {
background-image: image_url("/assets/images/avatar-bg.png");
background-size: 300px 226px;
@@ -7051,7 +7051,7 @@ footer {
padding: 10px 0;
color: #b3b3b3;
overflow: hidden;
@extend .u-unclickable, .u-textTruncate;
@extend .u-textTruncate;
}
.attachments:not(:empty) {
@@ -7060,6 +7060,16 @@ footer {
margin: 6px -12px 30px;
}
.ticket-create .attachments:not(:empty) {
margin-left: 0;
margin-right: 0;
margin-bottom: 56px;
}
.ticket-create .attachment--row {
line-height: 1.45;
}
.attachment.attachment--row {
font-size: 13px;
padding: 1px 10px 1px 7px;
@@ -8442,6 +8452,10 @@ footer {
.dropdown li.with-category, .dropdown.dropdown--actions li.with-category {
line-height: 19.5px;
small {
color: #fff !important;
}
}
.dropdown.dropdown--actions li.with-category {

View File

@@ -10,8 +10,8 @@ class ApplicationController < ActionController::Base
include ApplicationController::RendersModels
include ApplicationController::HasUser
include ApplicationController::HasResponseExtentions
include ApplicationController::HasDownload
include ApplicationController::PreventsCsrf
include ApplicationController::HasSecureContentSecurityPolicyForDownloads
include ApplicationController::LogsHttpAccess
include ApplicationController::Authorizes
end

View File

@@ -0,0 +1,44 @@
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
module ApplicationController::HasDownload
extend ActiveSupport::Concern
included do
around_action do |_controller, block|
subscriber = proc do
policy = ActionDispatch::ContentSecurityPolicy.new
policy.default_src :none
# The 'plugin_types' rule is deprecated and should be changed in the future.
policy.plugin_types 'application/pdf'
request.content_security_policy = policy
end
ActiveSupport::Notifications.subscribed(subscriber, 'send_file.action_controller') do
ActiveSupport::Notifications.subscribed(subscriber, 'send_data.action_controller') do
block.call
end
end
end
end
private
def file_id
@file_id ||= params[:id]
end
def download_file
@download_file ||= ::ApplicationController::HasDownload::DownloadFile.new(file_id, disposition: sanitized_disposition)
end
def sanitized_disposition
disposition = params.fetch(:disposition, 'inline')
valid_disposition = %w[inline attachment]
return disposition if valid_disposition.include?(disposition)
raise Exceptions::Forbidden, "Invalid disposition #{disposition} requested. Only #{valid_disposition.join(', ')} are valid."
end
end

View File

@@ -0,0 +1,54 @@
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
class ApplicationController::HasDownload::DownloadFile < SimpleDelegator
attr_reader :requested_disposition
def initialize(id, disposition: 'inline')
@requested_disposition = disposition
super(Store.find(id))
end
def disposition
return 'attachment' if forcibly_download_as_binary? || !allowed_inline?
requested_disposition
end
def content_type
return ActiveStorage.binary_content_type if forcibly_download_as_binary?
file_content_type
end
def content(view_type)
return __getobj__.content if view_type.blank? || !preferences[:resizable]
return content_inline if content_inline? && view_type == 'inline'
return content_preview if content_preview? && view_type == 'preview'
__getobj__.content
end
private
def allowed_inline?
ActiveStorage.content_types_allowed_inline.include?(content_type)
end
def forcibly_download_as_binary?
ActiveStorage.content_types_to_serve_as_binary.include?(file_content_type)
end
def file_content_type
@file_content_type ||= preferences['Content-Type'] || preferences['Mime-Type'] || ActiveStorage.binary_content_type
end
def content_inline?
preferences[:content_inline] == true
end
def content_preview?
preferences[:content_preview] == true
end
end

View File

@@ -1,25 +0,0 @@
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
module ApplicationController::HasSecureContentSecurityPolicyForDownloads
extend ActiveSupport::Concern
included do
around_action do |_controller, block|
subscriber = proc do
policy = ActionDispatch::ContentSecurityPolicy.new
policy.default_src :none
policy.plugin_types 'application/pdf'
request.content_security_policy = policy
end
ActiveSupport::Notifications.subscribed(subscriber, 'send_file.action_controller') do
ActiveSupport::Notifications.subscribed(subscriber, 'send_data.action_controller') do
block.call
end
end
end
end
end

View File

@@ -6,14 +6,13 @@ class AttachmentsController < ApplicationController
prepend_before_action :authentication_check_only, only: %i[show destroy]
def show
content = @file.content_preview if params[:preview] && @file.preferences[:content_preview]
content ||= @file.content
view_type = params[:preview] ? 'preview' : nil
send_data(
content,
filename: @file.filename,
type: @file.preferences['Content-Type'] || @file.preferences['Mime-Type'] || 'application/octet-stream',
disposition: sanitized_disposition
download_file.content(view_type),
filename: download_file.filename,
type: download_file.content_type,
disposition: download_file.disposition
)
end
@@ -52,7 +51,7 @@ class AttachmentsController < ApplicationController
end
def destroy
Store.remove_item(@file.id)
Store.remove_item(download_file.id)
render json: {
success: true,
@@ -72,18 +71,8 @@ class AttachmentsController < ApplicationController
private
def sanitized_disposition
disposition = params.fetch(:disposition, 'inline')
valid_disposition = %w[inline attachment]
return disposition if valid_disposition.include?(disposition)
raise Exceptions::Forbidden, "Invalid disposition #{disposition} requested. Only #{valid_disposition.join(', ')} are valid."
end
def authorize!
@file = Store.find(params[:id])
record = @file&.store_object&.name&.safe_constantize&.find(@file.o_id)
record = download_file&.store_object&.name&.safe_constantize&.find(download_file.o_id)
authorize(record) if record
rescue Pundit::NotAuthorizedError
raise ActiveRecord::RecordNotFound

View File

@@ -156,7 +156,7 @@ class FormController < ApplicationController
end
def token_gen(fingerprint)
crypt = ActiveSupport::MessageEncryptor.new(Setting.get('application_secret')[0, 32])
crypt = ActiveSupport::MessageEncryptor.new(Setting.get('application_secret')[0, 32], serializer: JSON)
fingerprint = "#{Base64.strict_encode64(Setting.get('fqdn'))}:#{Time.zone.now.to_i}:#{Base64.strict_encode64(fingerprint)}"
Base64.strict_encode64(crypt.encrypt_and_sign(fingerprint))
end
@@ -167,7 +167,7 @@ class FormController < ApplicationController
raise Exceptions::Forbidden
end
begin
crypt = ActiveSupport::MessageEncryptor.new(Setting.get('application_secret')[0, 32])
crypt = ActiveSupport::MessageEncryptor.new(Setting.get('application_secret')[0, 32], serializer: JSON)
result = crypt.decrypt_and_verify(Base64.decode64(token))
rescue
Rails.logger.info 'Invalid token for form!'

View File

@@ -175,29 +175,11 @@ class TicketArticlesController < ApplicationController
end
raise Exceptions::Forbidden, 'Requested file id is not linked with article_id.' if !access
# find file
file = Store.find(params[:id])
disposition = sanitized_disposition
content = nil
if params[:view].present? && file.preferences[:resizable] == true
if file.preferences[:content_inline] == true && params[:view] == 'inline'
content = file.content_inline
elsif file.preferences[:content_preview] == true && params[:view] == 'preview'
content = file.content_preview
end
end
if content.blank?
content = file.content
end
send_data(
content,
filename: file.filename,
type: file.preferences['Content-Type'] || file.preferences['Mime-Type'] || 'application/octet-stream',
disposition: disposition
download_file.content(params[:view]),
filename: download_file.filename,
type: download_file.content_type,
disposition: download_file.disposition
)
end
@@ -278,14 +260,4 @@ class TicketArticlesController < ApplicationController
render json: result
end
private
def sanitized_disposition
disposition = params.fetch(:disposition, 'inline')
valid_disposition = %w[inline attachment]
return disposition if valid_disposition.include?(disposition)
raise Exceptions::Forbidden, "Invalid disposition #{disposition} requested. Only #{valid_disposition.join(', ')} are valid."
end
end

View File

@@ -722,31 +722,28 @@ curl http://localhost/api/v1/users/image/8d6cca1c6bdc226cf2ba131e264ca2c7 -v -u
=end
def image
# cache image
response.headers['Expires'] = 1.year.from_now.httpdate
response.headers['Cache-Control'] = 'cache, store, max-age=31536000, must-revalidate'
response.headers['Pragma'] = 'cache'
file = Avatar.get_by_hash(params[:hash])
if file
file_content_type = file.preferences['Content-Type'] || file.preferences['Mime-Type']
return serve_default_image if ActiveStorage.content_types_allowed_inline.exclude?(file_content_type)
send_data(
file.content,
filename: file.filename,
type: file.preferences['Content-Type'] || file.preferences['Mime-Type'],
type: file_content_type,
disposition: 'inline'
)
return
end
# serve default image
image = 'R0lGODdhMAAwAOMAAMzMzJaWlr6+vqqqqqOjo8XFxbe3t7GxsZycnAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAMAAwAAAEcxDISau9OOvNu/9gKI5kaZ5oqq5s675wLM90bd94ru98TwuAA+KQAQqJK8EAgBAgMEqmkzUgBIeSwWGZtR5XhSqAULACCoGCJGwlm1MGQrq9RqgB8fm4ZTUgDBIEcRR9fz6HiImKi4yNjo+QkZKTlJWWkBEAOw=='
send_data(
Base64.decode64(image),
filename: 'image.gif',
type: 'image/gif',
disposition: 'inline'
)
serve_default_image
end
=begin
@@ -778,6 +775,11 @@ curl http://localhost/api/v1/users/avatar -v -u #{login}:#{password} -H "Content
return
end
if ActiveStorage::Variant::WEB_IMAGE_CONTENT_TYPES.exclude?(file_full[:mime_type])
render json: { error: 'Mime type is invalid' }, status: :unprocessable_entity
return
end
begin
file_resize = StaticAssets.data_url_attributes(params[:avatar_resize])
rescue
@@ -1061,4 +1063,15 @@ curl http://localhost/api/v1/users/avatar -v -u #{login}:#{password} -H "Content
render json: { message: 'ok' }, status: :created
end
def serve_default_image
image = 'R0lGODdhMAAwAOMAAMzMzJaWlr6+vqqqqqOjo8XFxbe3t7GxsZycnAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAMAAwAAAEcxDISau9OOvNu/9gKI5kaZ5oqq5s675wLM90bd94ru98TwuAA+KQAQqJK8EAgBAgMEqmkzUgBIeSwWGZtR5XhSqAULACCoGCJGwlm1MGQrq9RqgB8fm4ZTUgDBIEcRR9fz6HiImKi4yNjo+QkZKTlJWWkBEAOw=='
send_data(
Base64.decode64(image),
filename: 'image.gif',
type: 'image/gif',
disposition: 'inline'
)
end
end

View File

@@ -121,7 +121,7 @@ returns
key = "#{self.class}::aws::#{id}"
cache = Cache.read(key)
return cache if cache
return filter_unauthorized_attributes(cache) if cache
attributes = self.attributes
relevant = %i[has_and_belongs_to_many has_many]
@@ -160,7 +160,7 @@ returns
filter_attributes(attributes)
Cache.write(key, attributes)
attributes
filter_unauthorized_attributes(attributes)
end
=begin
@@ -234,8 +234,7 @@ returns
end
filter_attributes(attributes)
attributes
filter_unauthorized_attributes(attributes)
end
def filter_attributes(attributes)
@@ -243,6 +242,10 @@ returns
attributes.except!('password', 'token', 'tokens', 'token_ids')
end
def filter_unauthorized_attributes(attributes)
attributes
end
=begin
reference if association id check

View File

@@ -72,7 +72,6 @@ add avatar by url
=end
def self.add(data)
# lookups
if data[:object]
object_id = ObjectLookup.by_name(data[:object])

View File

@@ -30,10 +30,26 @@ class CoreWorkflow::Attributes
end
end
def selectable_field?(key)
return if key == 'id'
return if !@payload['params'].key?(key)
# some objects have no attributes like "CoreWorkflow"-object as well.
# attributes only exists in the frontend so we skip this check
return true if object_elements.blank?
object_elements_hash.key?(key)
end
def overwrite_selected(result)
selected_attributes = selected_only.attributes
selected_attributes.each_key do |key|
next if selected_attributes[key].nil?
next if !selectable_field?(key)
# special behaviour for owner id
if key == 'owner_id' && selected_attributes[key].nil?
selected_attributes[key] = 1
end
result[key.to_sym] = selected_attributes[key]
end
@@ -55,6 +71,10 @@ class CoreWorkflow::Attributes
# dont use lookup here because the cache will not
# know about new attributes and make crashes
@saved_only ||= payload_class.find_by(id: @payload['params']['id'])
# we use marshal here because clone still uses references and dup can't
# detect changes for the rails object
Marshal.load(Marshal.dump(@saved_only))
end
def saved
@@ -67,6 +87,10 @@ class CoreWorkflow::Attributes
end
end
def object_elements_hash
@object_elements_hash ||= object_elements.index_by { |x| x[:name] }
end
def screen_value(attribute, type)
attribute[:screens].dig(@payload['screen'], type)
end

View File

@@ -18,4 +18,26 @@ class CoreWorkflow::Result::Backend
def result(backend, field, value = nil)
@result_object.run_backend_value(backend, field, value)
end
def saved_value
# make sure we have a saved object
return if @result_object.attributes.saved_only.blank?
# we only want to have the saved value in the restrictions
# if no changes happend to the form. If the users does changes
# to the form then also the saved value should get removed
return if @result_object.attributes.selected.changed?
# attribute can be blank e.g. in custom development
# or if attribute is only available in the frontend but not
# in the backend
return if attribute.blank?
@result_object.attributes.saved_attribute_value(attribute).to_s
end
def attribute
@attribute ||= @result_object.attributes.object_elements_hash[field]
end
end

View File

@@ -3,8 +3,14 @@
class CoreWorkflow::Result::RemoveOption < CoreWorkflow::Result::BaseOption
def run
@result_object.result[:restrict_values][field] ||= Array(@result_object.payload['params'][field])
@result_object.result[:restrict_values][field] -= Array(@perform_config['remove_option'])
@result_object.result[:restrict_values][field] -= Array(config_value)
remove_excluded_param_values
true
end
def config_value
result = Array(@perform_config['remove_option'])
result -= Array(saved_value)
result
end
end

View File

@@ -5,21 +5,23 @@ class CoreWorkflow::Result::SetFixedTo < CoreWorkflow::Result::BaseOption
@result_object.result[:restrict_values][field] = if restriction_set?
restrict_values
else
replace_values
config_value
end
remove_excluded_param_values
true
end
def config_value
result = Array(@perform_config['set_fixed_to'])
result |= Array(saved_value)
result
end
def restriction_set?
@result_object.result[:restrict_values][field]
end
def restrict_values
@result_object.result[:restrict_values][field].reject { |v| Array(@perform_config['set_fixed_to']).exclude?(v) }
end
def replace_values
Array(@perform_config['set_fixed_to'])
@result_object.result[:restrict_values][field].reject { |v| config_value.exclude?(v) }
end
end

View File

@@ -12,6 +12,8 @@ class Group < ApplicationModel
include HasTicketCreateScreenImpact
include HasSearchIndexBackend
include Group::Assets
belongs_to :email_address, optional: true
belongs_to :signature, optional: true

View File

@@ -0,0 +1,14 @@
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
class Group
module Assets
extend ActiveSupport::Concern
def filter_unauthorized_attributes(attributes)
return super if UserInfo.assets.blank? || UserInfo.assets.agent?
attributes = super
attributes.slice('id', 'name', 'active')
end
end
end

View File

@@ -27,8 +27,9 @@ class KnowledgeBase < ApplicationModel
validates :category_layout, inclusion: { in: KnowledgeBase::LAYOUTS }
validates :homepage_layout, inclusion: { in: KnowledgeBase::LAYOUTS }
validates :color_highlight, presence: true, color: true
validates :color_header, presence: true, color: true
validates :color_highlight, presence: true, color: true
validates :color_header, presence: true, color: true
validates :color_header_link, presence: true, color: true
validates :iconset, inclusion: { in: KnowledgeBase::ICONSETS }

View File

@@ -43,7 +43,7 @@ class ObjectManager::Element::Backend
end
def screens
attribute.screens.transform_values do |permission_options|
@screens ||= attribute.screens.transform_values do |permission_options|
screen_value(permission_options)
end
end

View File

@@ -70,5 +70,12 @@ returns
end
data
end
def filter_unauthorized_attributes(attributes)
return super if UserInfo.assets.blank? || UserInfo.assets.agent?
attributes = super
attributes.slice('id', 'name', 'active')
end
end
end

View File

@@ -263,32 +263,37 @@ subsequently in a separate step.
)
end
# store package
if !data[:reinstall]
package_db = Package.create(meta)
Store.add(
object: 'Package',
o_id: package_db.id,
data: package.to_json,
filename: "#{meta[:name]}-#{meta[:version]}.zpm",
preferences: {},
created_by_id: UserInfo.current_user_id || 1,
)
end
Transaction.execute do
# store package
if !data[:reinstall]
package_db = Package.create(meta)
Store.add(
object: 'Package',
o_id: package_db.id,
data: package.to_json,
filename: "#{meta[:name]}-#{meta[:version]}.zpm",
preferences: {},
created_by_id: UserInfo.current_user_id || 1,
)
end
# write files
package['files'].each do |file|
permission = file['permission'] || '644'
content = Base64.decode64(file['content'])
_write_file(file['location'], permission, content)
end
# write files
package['files'].each do |file|
if !allowed_file_path?(file['location'])
raise "Can't create file, because of not allowed file location: #{file['location']}!"
end
# update package state
package_db.state = 'installed'
package_db.save
permission = file['permission'] || '644'
content = Base64.decode64(file['content'])
_write_file(file['location'], permission, content)
end
# update package state
package_db.state = 'installed'
package_db.save
end
# prebuild assets
package_db
end
@@ -483,4 +488,9 @@ execute all pending package migrations at once
true
end
def self.allowed_file_path?(file)
file.exclude?('..') && file.exclude?('%2e%2e')
end
private_class_method :allowed_file_path?
end

View File

@@ -60,5 +60,13 @@ returns
end
data
end
def filter_unauthorized_attributes(attributes)
return super if UserInfo.assets.blank? || UserInfo.assets.agent?
attributes = super
attributes['name'] = "Role_#{id}"
attributes.slice('id', 'name', 'group_ids', 'permission_ids', 'active')
end
end
end

View File

@@ -926,17 +926,16 @@ try to find correct name
end
# check if login already exists
self.login = login.downcase.strip
check = true
while check
base_login = login.downcase.strip
alternatives = [nil] + Array(1..20) + [ SecureRandom.uuid ]
alternatives.each do |suffix|
self.login = "#{base_login}#{suffix}"
exists = User.find_by(login: login)
if exists && exists.id != id
self.login = "#{login}#{rand(999)}" # rubocop:disable Zammad/ForbidRand
else
check = false
end
return true if !exists || exists.id == id
end
true
raise Exceptions::UnprocessableEntity, "Invalid user login generation for login #{login}!"
end
def check_mail_delivery_failed

View File

@@ -110,5 +110,20 @@ returns
end
data
end
def filter_unauthorized_attributes(attributes)
return super if UserInfo.assets.blank? || UserInfo.assets.agent?
# customer assets for the user session
if UserInfo.current_user_id == id
attributes = super
attributes.except!('web', 'phone', 'mobile', 'fax', 'department', 'street', 'zip', 'city', 'country', 'address', 'note')
return attributes
end
# customer assets for other user
attributes = super
attributes.slice('id', 'firstname', 'lastname', 'image', 'image_source', 'active')
end
end
end

View File

@@ -13,6 +13,7 @@ class SettingPolicy < ApplicationPolicy
private
def permitted?
return false if record.preferences[:protected]
return true if !record.preferences[:permission]
user.permissions?(record.preferences[:permission])

View File

@@ -13,11 +13,14 @@ class UserPolicy < ApplicationPolicy
end
def update?
# full access for admins
return true if user.permissions?('admin.user')
# forbid non-agents to change users
return false if !user.permissions?('ticket.agent')
# allow agents to change customers
# allow agents to change customers only
return false if record.permissions?(['admin.user', 'ticket.agent'])
record.permissions?('ticket.customer')
end

View File

@@ -28,4 +28,8 @@
.header {
background-color: <%= knowledge_base.color_header %>;
}
.header .menu-item {
color: <%= knowledge_base.color_header_link %>;
}
</style>

View File

@@ -5,11 +5,11 @@ Rails.application.config.html_sanitizer_tags_remove_content = %w[
style
comment
meta
script
]
# content of this tags will will be inserted html quoted
Rails.application.config.html_sanitizer_tags_quote_content = %w[
script
]
# only this tags are allowed

View File

@@ -228,7 +228,7 @@ function create_webserver_config () {
function setup_elasticsearch () {
echo "# Configuring Elasticsearch..."
ES_CONNECTION="$(zammad run rails r "puts Setting.get('es_url')"| tail -n 1 2>> /dev/null)"
ES_CONNECTION="$(zammad run rails r "puts '',Setting.get('es_url')"| tail -n 1 2>> /dev/null)"
if [ -z "${ES_CONNECTION}" ]; then
echo "-- Nevermind, no es_url is set, leaving Elasticsearch untouched ...!"

View File

@@ -8,8 +8,9 @@ class InitializeKnowledgeBase < ActiveRecord::Migration[5.0]
create_table :knowledge_bases do |t|
t.string :iconset, limit: 30, null: false
t.string :color_highlight, limit: 25, null: false
t.string :color_header, limit: 25, null: false
t.string :color_highlight, limit: 25, null: false
t.string :color_header, limit: 25, null: false
t.string :color_header_link, limit: 25, null: false
t.string :homepage_layout, null: false
t.string :category_layout, null: false

View File

@@ -75,6 +75,8 @@ class InitCoreWorkflow < ActiveRecord::Migration[5.2]
def fix_pending_time
pending_time = ObjectManager::Attribute.find_by(name: 'pending_time', object_lookup: ObjectLookup.find_by(name: 'Ticket'))
return if pending_time.blank?
pending_time.data_option.delete('required_if')
pending_time.data_option.delete('shown_if')
pending_time.save
@@ -83,6 +85,8 @@ class InitCoreWorkflow < ActiveRecord::Migration[5.2]
def fix_organization_screens
%w[domain note].each do |name|
field = ObjectManager::Attribute.find_by(name: name, object_lookup: ObjectLookup.find_by(name: 'Organization'))
next if field.blank?
field.screens['create'] ||= {}
field.screens['create']['-all-'] ||= {}
field.screens['create']['-all-']['null'] = true
@@ -93,6 +97,8 @@ class InitCoreWorkflow < ActiveRecord::Migration[5.2]
def fix_user_screens
%w[email web phone mobile organization_id fax department street zip city country address password vip note role_ids].each do |name|
field = ObjectManager::Attribute.find_by(name: name, object_lookup: ObjectLookup.find_by(name: 'User'))
next if field.blank?
field.screens['create'] ||= {}
field.screens['create']['-all-'] ||= {}
field.screens['create']['-all-']['null'] = true

View File

@@ -0,0 +1,15 @@
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
class MaintenanceImproveSettingPreferences < ActiveRecord::Migration[6.0]
def change
return if !Setting.exists?(name: 'system_init_done')
protected_settings = %w[application_secret]
protected_settings.each do |name|
setting = Setting.find_by(name: name)
setting.preferences[:protected] = true
setting.save!
end
end
end

View File

@@ -11,6 +11,8 @@ class Issue3751MissingWorkflowScreens < ActiveRecord::Migration[6.0]
def fix_organization_screens_create
%w[name shared domain_assignment active].each do |name|
field = ObjectManager::Attribute.find_by(name: name, object_lookup: ObjectLookup.find_by(name: 'Organization'))
next if field.blank?
field.screens['create'] ||= {}
field.screens['create']['-all-'] ||= {}
field.screens['create']['-all-']['null'] = false
@@ -21,6 +23,8 @@ class Issue3751MissingWorkflowScreens < ActiveRecord::Migration[6.0]
def fix_user_screens_create
%w[firstname lastname active].each do |name|
field = ObjectManager::Attribute.find_by(name: name, object_lookup: ObjectLookup.find_by(name: 'User'))
next if field.blank?
field.screens['create'] ||= {}
field.screens['create']['-all-'] ||= {}
field.screens['create']['-all-']['null'] = false

View File

@@ -0,0 +1,10 @@
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
class RemoveOtrsDiffWorkerSchedulerEntry < ActiveRecord::Migration[6.0]
def change
# return if it's a new setup
return if !Setting.exists?(name: 'system_init_done')
Scheduler.find_by(method: 'Import::OTRS.diff_worker')&.destroy
end
end

View File

@@ -0,0 +1,11 @@
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
class Issue2619KbHeaderLinkColor < ActiveRecord::Migration[6.0]
def up
return if !Setting.exists?(name: 'system_init_done')
add_column :knowledge_bases, :color_header_link, :string, limit: 25, null: false, default: 'hsl(206,8%,50%)' # rubocop:disable Zammad/ExistsResetColumnInformation
change_column_default :knowledge_bases, :color_header_link, nil
KnowledgeBase.reset_column_information
end
end

View File

@@ -0,0 +1,11 @@
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
class ReloadAfterCoreWorkflow < ActiveRecord::Migration[4.2]
def up
# return if it's a new setup
return if !Setting.exists?(name: 'system_init_done')
AppVersion.set(true, 'app_version')
end
end

View File

@@ -0,0 +1,11 @@
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
class ReloadAfterCoreWorkflowAgain < ActiveRecord::Migration[6.0]
def up
# return if it's a new setup
return if !Setting.exists?(name: 'system_init_done')
AppVersion.set(true, 'app_version')
end
end

View File

@@ -21,15 +21,6 @@ Scheduler.create_if_not_exists(
prio: 1,
active: true,
)
Scheduler.create_if_not_exists(
name: 'Import OTRS diff load',
method: 'Import::OTRS.diff_worker',
period: 3.minutes,
prio: 1,
active: true,
updated_by_id: 1,
created_by_id: 1,
)
Scheduler.create_if_not_exists(
name: 'Check Channels',
method: 'Channel.fetch',

View File

@@ -9,6 +9,7 @@ Setting.create_if_not_exists(
state: SecureRandom.hex(128),
preferences: {
permission: ['admin'],
protected: true,
},
frontend: false
)

View File

@@ -1,12 +1,14 @@
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
require 'uri'
class GitHub
class HttpClient
attr_reader :api_token, :endpoint
def initialize(endpoint, api_token)
raise 'api_token required' if api_token.blank?
raise 'endpoint required' if endpoint.blank?
raise 'endpoint required' if endpoint.blank? || endpoint.exclude?('/graphql') || endpoint.scan(URI::DEFAULT_PARSER.make_regexp).blank?
@api_token = api_token
@endpoint = endpoint
@@ -30,7 +32,7 @@ class GitHub
if !response.success?
Rails.logger.error response.error
raise "Error while requesting GitHub GraphQL API: #{response.error}"
raise 'GitHub request failed! Please have a look at the log file for details'
end
response.data

View File

@@ -1,12 +1,14 @@
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
require 'uri'
class GitLab
class HttpClient
attr_reader :api_token, :endpoint
def initialize(endpoint, api_token)
raise 'api_token required' if api_token.blank?
raise 'endpoint required' if endpoint.blank?
raise 'endpoint required' if endpoint.blank? || endpoint.exclude?('/graphql') || endpoint.scan(URI::DEFAULT_PARSER.make_regexp).blank?
@api_token = api_token
@endpoint = endpoint
@@ -30,7 +32,7 @@ class GitLab
if !response.success?
Rails.logger.error response.error
raise "Error while requesting GitLab GraphQL API: #{response.error}"
raise 'GitLab request failed! Please have a look at the log file for details'
end
response.data

View File

@@ -324,7 +324,8 @@ returns
locale: data[:locale],
timezone: data[:timezone],
template: template[:subject],
escape: false
escape: false,
trusted: true,
).render
# strip off the extra newline at the end of the subject to avoid =0A suffixes (see #2726)
@@ -334,7 +335,8 @@ returns
objects: data[:objects],
locale: data[:locale],
timezone: data[:timezone],
template: template[:body]
template: template[:body],
trusted: true,
).render
if !data[:raw]
@@ -348,7 +350,8 @@ returns
objects: data[:objects],
locale: data[:locale],
timezone: data[:timezone],
template: application_template
template: application_template,
trusted: true,
).render
end
{

View File

@@ -13,7 +13,8 @@ examples how to use
locale: 'de-de',
timezone: 'America/Port-au-Prince',
template: 'some template <b>#{ticket.title}</b> {config.fqdn}',
escape: false
escape: false,
trusted: false, # Allow ERB tags in the template?
).render
message_body = NotificationFactory::Renderer.new(
@@ -27,16 +28,20 @@ examples how to use
=end
def initialize(objects:, template:, locale: nil, timezone: nil, escape: true)
def initialize(objects:, template:, locale: nil, timezone: nil, escape: true, trusted: false) # rubocop:disable Metrics/ParameterLists
@objects = objects
@locale = locale || Locale.default
@timezone = timezone || Setting.get('timezone_default')
@template = NotificationFactory::Template.new(template, escape)
@template = NotificationFactory::Template.new(template, escape, trusted)
@escape = escape
end
def render
ERB.new(@template.to_s).result(binding)
rescue Exception => e # rubocop:disable Lint/RescueException
raise StandardError, e.message if e.is_a? SyntaxError
raise
end
# d - data of object

View File

@@ -46,14 +46,16 @@ returns
locale: data[:locale],
timezone: data[:timezone],
template: template[:subject],
escape: false
escape: false,
trusted: true
).render
message_body = NotificationFactory::Renderer.new(
objects: data[:objects],
locale: data[:locale],
timezone: data[:timezone],
template: template[:body],
escape: false
escape: false,
trusted: true
).render
if !data[:raw]
@@ -68,7 +70,8 @@ returns
locale: data[:locale],
timezone: data[:timezone],
template: application_template,
escape: false
escape: false,
trusted: true
).render
end
{

View File

@@ -9,17 +9,21 @@ examples how to use
cleaned_template = NotificationFactory::Template.new(
'some template <b>#{ticket.title}</b> #{config.fqdn}',
true,
false, # Allow ERB tags in the template?
).to_s
=end
def initialize(template, escape)
def initialize(template, escape, trusted)
@template = template
@escape = escape
@escape = escape
@trusted = trusted
end
def to_s
@template.gsub(%r{\#{\s*(.*?)\s*}}m) do
result = @template
result.gsub!(%r{<%(?!%)}, '<%%') if !@trusted
result.gsub(%r{\#{\s*(.*?)\s*}}m) do
# some browsers start adding HTML tags
# fixes https://github.com/zammad/zammad/issues/385
input_template = $1.gsub(%r{\A<.+?>\s*|\s*<.+?>\z}, '')

View File

@@ -4,7 +4,7 @@ module SessionHelper
def self.json_hash(user)
collections, assets = default_collections(user)
{
session: user.filter_attributes(user.attributes),
session: user.filter_unauthorized_attributes(user.filter_attributes(user.attributes)),
models: models(user),
collections: collections,
assets: assets,

View File

@@ -7,6 +7,11 @@ module UserInfo
def self.current_user_id=(user_id)
Thread.current[:user_id] = user_id
Thread.current[:assets] = UserInfo::Assets.new(user_id)
end
def self.assets
Thread.current[:assets]
end
def self.ensure_current_user_id

52
lib/user_info/assets.rb Normal file
View File

@@ -0,0 +1,52 @@
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
class UserInfo::Assets
LEVEL_CUSTOMER = 1
LEVEL_AGENT = 2
LEVEL_ADMIN = 3
attr_accessor :current_user_id, :level, :filter_attributes, :user
def initialize(current_user_id)
@current_user_id = current_user_id
@user = User.find_by(id: current_user_id) if current_user_id.present?
set_level
end
def admin?
check_level?(UserInfo::Assets::LEVEL_ADMIN)
end
def agent?
check_level?(UserInfo::Assets::LEVEL_AGENT)
end
def customer?
check_level?(UserInfo::Assets::LEVEL_CUSTOMER)
end
def set_level
if user.blank?
self.level = nil
return
end
self.level = UserInfo::Assets::LEVEL_CUSTOMER
Permission.where(id: user.permissions_with_child_ids).each do |permission|
case permission.name
when %r{^admin\.}
self.level = UserInfo::Assets::LEVEL_ADMIN
break
when 'ticket.agent'
self.level = UserInfo::Assets::LEVEL_AGENT
end
end
end
def check_level?(check)
return true if user.blank?
level >= check
end
end

View File

@@ -0,0 +1,16 @@
FROM node:8-alpine
ENV GULP_DIR "/tmp/gulp"
RUN apk update && apk add bash
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
CMD bash # If you want to override CMD
RUN npm install -g gulp
COPY docker-entrypoint.sh /
# enable volume to generate build files into the hosts FS
VOLUME ["$GULP_DIR"]
# start
ENTRYPOINT ["/docker-entrypoint.sh"]

View File

@@ -0,0 +1,5 @@
# Zammad Chat build
This folder contains a `docker` image and the required files to build the Zammad Chat from coffeescript and eco files. This workaround is required for now because of the outdated NodeJS 8 dependency.
The build process can easily be started by executing the `build.sh` file. There is nothing more to it except of having `docker` installed and running.

8
public/assets/chat/build.sh Executable file
View File

@@ -0,0 +1,8 @@
#!/bin/bash
set -o errexit
set -o pipefail
docker build --no-cache -t zammad/chat-build:latest .
docker run --rm -v "$(pwd)/:/tmp/gulp" zammad/chat-build:latest

View File

@@ -762,7 +762,11 @@ do(window) ->
console.log('p', docType, text)
if docType is 'html'
html = document.createElement('div')
html.innerHTML = text
# can't log because might contain malicious content
# @log.debug 'HTML clipboard', text
sanitized = DOMPurify.sanitize(text)
@log.debug 'sanitized HTML clipboard', sanitized
html.innerHTML = sanitized
match = false
htmlTmp = text
regex = new RegExp('<(/w|w)\:[A-Za-z]')

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -718,7 +718,9 @@ do($ = window.jQuery, window) ->
text = text.replace(/<div><\/div>/g, '<div><br></div>')
console.log('p', docType, text)
if docType is 'html'
html = $("<div>#{text}</div>")
sanitized = DOMPurify.sanitize(text)
@log.debug 'sanitized HTML clipboard', sanitized
html = $("<div>#{sanitized}</div>")
match = false
htmlTmp = text
regex = new RegExp('<(/w|w)\:[A-Za-z]')

View File

@@ -314,6 +314,7 @@
line-height: 1.4em;
font-size: inherit;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
border: none;
background: none;
@@ -329,6 +330,7 @@
.zammad-chat-button {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
font-family: inherit;
font-size: inherit;
@@ -349,6 +351,7 @@
.zammad-chat-button:disabled,
.zammad-chat-input:disabled {
cursor: not-allowed;
opacity: 0.3; }
.zammad-chat-is-hidden {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,5 @@
#!/bin/bash
cd "${GULP_DIR}" || exit
gulp js css no-jquery

View File

@@ -25,11 +25,14 @@ gulp.task('js', function(){
var templates = gulp.src('views/*.eco')
.pipe(eco({namespace: 'zammadChatTemplates'}));
var purify = gulp.src('purify.min.js');
var js = gulp.src('chat.coffee')
.pipe(plumber())
.pipe(coffee({bare: true}).on('error', gutil.log));
return merge(templates, js)
.add(purify)
.pipe(concat('chat.js'))
.pipe(gulp.dest('./'))
.pipe(uglify())
@@ -42,11 +45,14 @@ gulp.task('no-jquery', function(){
var templates = gulp.src('views/*.eco')
.pipe(eco({namespace: 'zammadChatTemplates'}));
var purify = gulp.src('purify.min.js');
var js = gulp.src('chat-no-jquery.coffee')
.pipe(plumber())
.pipe(coffee({bare: true}).on('error', gutil.log));
return merge(templates, js)
.add(purify)
.pipe(concat('chat-no-jquery.js'))
.pipe(gulp.dest('./'))
.pipe(uglify())

3
public/assets/chat/purify.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -12,7 +12,6 @@ if [ "$LEVEL" == '1' ]; then
cp test/integration/aaa_auto_wizard_base_setup_test.rb test/browser/aaa_auto_wizard_base_setup_test.rb
rm test/browser/abb_one_group_test.rb
rm test/browser/admin_channel_email_test.rb
rm test/browser/admin_calendar_sla_test.rb
rm test/browser/admin_drag_drop_to_new_group_test.rb
rm test/browser/admin_overview_test.rb
rm test/browser/admin_permissions_granular_vs_full_test.rb
@@ -41,18 +40,12 @@ if [ "$LEVEL" == '1' ]; then
rm test/browser/agent_ticket_task_changed_test.rb
rm test/browser/agent_ticket_text_module_test.rb
rm test/browser/agent_ticket_time_accounting_test.rb
rm test/browser/agent_ticket_update1_test.rb
rm test/browser/agent_ticket_update2_test.rb
rm test/browser/agent_ticket_update3_test.rb
rm test/browser/agent_ticket_update4_test.rb
rm test/browser/agent_ticket_update5_test.rb
rm test/browser/agent_ticket_update_with_attachment_refresh_test.rb
rm test/browser/agent_ticket_update_and_reload_test.rb
rm test/browser/agent_ticket_zoom_hide_test.rb
rm test/browser/agent_user_manage_test.rb
rm test/browser/agent_user_profile_test.rb
# test/browser/auth_test.rb
rm test/browser/customer_ticket_create_fields_test.rb
rm test/browser/customer_ticket_create_test.rb
rm test/browser/first_steps_test.rb
rm test/browser/integration_test.rb
@@ -67,7 +60,6 @@ if [ "$LEVEL" == '1' ]; then
# test/browser/swich_to_user_test.rb
# test/browser/taskbar_session_test.rb
# test/browser/taskbar_task_test.rb
# test/browser/translation_test.rb
rm test/browser/user_access_permissions_test.rb
rm test/browser/user_switch_cache_test.rb
@@ -78,7 +70,6 @@ elif [ "$LEVEL" == '2' ]; then
# test/browser/aaa_getting_started_test.rb
# test/browser/abb_one_group_test.rb
rm test/browser/admin_channel_email_test.rb
rm test/browser/admin_calendar_sla_test.rb
rm test/browser/admin_drag_drop_to_new_group_test.rb
rm test/browser/admin_overview_test.rb
rm test/browser/admin_permissions_granular_vs_full_test.rb
@@ -107,18 +98,12 @@ elif [ "$LEVEL" == '2' ]; then
rm test/browser/agent_ticket_task_changed_test.rb
# test/browser/agent_ticket_text_module_test.rb
# test/browser/agent_ticket_time_accounting_test.rb
# test/browser/agent_ticket_update1_test.rb
# test/browser/agent_ticket_update2_test.rb
# test/browser/agent_ticket_update3_test.rb
# test/browser/agent_ticket_update4_test.rb
# rm test/browser/agent_ticket_update5_test.rb
# rm test/browser/agent_ticket_update_with_attachment_refresh_test.rb
# test/browser/agent_ticket_update_and_reload_test.rb
# test/browser/agent_ticket_zoom_hide_test.rb
rm test/browser/agent_user_manage_test.rb
rm test/browser/agent_user_profile_test.rb
rm test/browser/auth_test.rb
rm test/browser/customer_ticket_create_fields_test.rb
rm test/browser/customer_ticket_create_test.rb
rm test/browser/first_steps_test.rb
rm test/browser/integration_test.rb
@@ -133,7 +118,6 @@ elif [ "$LEVEL" == '2' ]; then
rm test/browser/switch_to_user_test.rb
rm test/browser/taskbar_session_test.rb
rm test/browser/taskbar_task_test.rb
rm test/browser/translation_test.rb
# test/browser/user_access_permissions_test.rb
# test/browser/user_switch_cache_test.rb
@@ -144,7 +128,6 @@ elif [ "$LEVEL" == '3' ]; then
# test/browser/aaa_getting_started_test.rb
# test/browser/abb_one_group_test.rb
rm test/browser/admin_channel_email_test.rb
rm test/browser/admin_calendar_sla_test.rb
rm test/browser/admin_drag_drop_to_new_group_test.rb
rm test/browser/admin_overview_test.rb
rm test/browser/admin_permissions_granular_vs_full_test.rb
@@ -173,18 +156,12 @@ elif [ "$LEVEL" == '3' ]; then
# test/browser/agent_ticket_task_changed_test.rb
rm test/browser/agent_ticket_text_module_test.rb
rm test/browser/agent_ticket_time_accounting_test.rb
rm test/browser/agent_ticket_update1_test.rb
rm test/browser/agent_ticket_update2_test.rb
rm test/browser/agent_ticket_update3_test.rb
rm test/browser/agent_ticket_update4_test.rb
rm test/browser/agent_ticket_update5_test.rb
rm test/browser/agent_ticket_update_with_attachment_refresh_test.rb
rm test/browser/agent_ticket_update_and_reload_test.rb
rm test/browser/agent_ticket_zoom_hide_test.rb
rm test/browser/agent_user_manage_test.rb
rm test/browser/agent_user_profile_test.rb
rm test/browser/auth_test.rb
rm test/browser/customer_ticket_create_fields_test.rb
rm test/browser/customer_ticket_create_test.rb
rm test/browser/first_steps_test.rb
rm test/browser/integration_test.rb
@@ -199,7 +176,6 @@ elif [ "$LEVEL" == '3' ]; then
rm test/browser/switch_to_user_test.rb
rm test/browser/taskbar_session_test.rb
rm test/browser/taskbar_task_test.rb
rm test/browser/translation_test.rb
rm test/browser/user_access_permissions_test.rb
rm test/browser/user_switch_cache_test.rb
@@ -210,7 +186,6 @@ elif [ "$LEVEL" == '4' ]; then
# test/browser/aaa_getting_started_test.rb
# test/browser/abb_one_group_test.rb
rm test/browser/admin_channel_email_test.rb
rm test/browser/admin_calendar_sla_test.rb
rm test/browser/admin_drag_drop_to_new_group_test.rb
rm test/browser/admin_overview_test.rb
rm test/browser/admin_permissions_granular_vs_full_test.rb
@@ -239,18 +214,12 @@ elif [ "$LEVEL" == '4' ]; then
rm test/browser/agent_ticket_task_changed_test.rb
rm test/browser/agent_ticket_text_module_test.rb
rm test/browser/agent_ticket_time_accounting_test.rb
rm test/browser/agent_ticket_update1_test.rb
rm test/browser/agent_ticket_update2_test.rb
rm test/browser/agent_ticket_update3_test.rb
rm test/browser/agent_ticket_update4_test.rb
rm test/browser/agent_ticket_update5_test.rb
rm test/browser/agent_ticket_update_with_attachment_refresh_test.rb
rm test/browser/agent_ticket_update_and_reload_test.rb
rm test/browser/agent_ticket_zoom_hide_test.rb
rm test/browser/agent_user_manage_test.rb
rm test/browser/agent_user_profile_test.rb
rm test/browser/auth_test.rb
# test/browser/customer_ticket_create_fields_test.rb
# test/browser/customer_ticket_create_test.rb
rm test/browser/first_steps_test.rb
rm test/browser/integration_test.rb
@@ -265,7 +234,6 @@ elif [ "$LEVEL" == '4' ]; then
rm test/browser/switch_to_user_test.rb
rm test/browser/taskbar_session_test.rb
rm test/browser/taskbar_task_test.rb
rm test/browser/translation_test.rb
rm test/browser/user_access_permissions_test.rb
rm test/browser/user_switch_cache_test.rb
@@ -275,7 +243,6 @@ elif [ "$LEVEL" == '5' ]; then
# only profile action & admin
# test/browser/abb_one_group_test.rb
# test/browser/admin_channel_email_test.rb
# test/browser/admin_calendar_sla_test.rb
# rm test/browser/admin_drag_drop_to_new_group_test.rb
# test/browser/admin_overview_test.rb
# rm test/browser/admin_permissions_granular_vs_full_test.rb
@@ -304,18 +271,12 @@ elif [ "$LEVEL" == '5' ]; then
rm test/browser/agent_ticket_task_changed_test.rb
rm test/browser/agent_ticket_text_module_test.rb
rm test/browser/agent_ticket_time_accounting_test.rb
rm test/browser/agent_ticket_update1_test.rb
rm test/browser/agent_ticket_update2_test.rb
rm test/browser/agent_ticket_update3_test.rb
rm test/browser/agent_ticket_update4_test.rb
rm test/browser/agent_ticket_update5_test.rb
rm test/browser/agent_ticket_update_with_attachment_refresh_test.rb
rm test/browser/agent_ticket_update_and_reload_test.rb
rm test/browser/agent_ticket_zoom_hide_test.rb
# test/browser/agent_user_manage_test.rb
# test/browser/agent_user_profile_test.rb
rm test/browser/auth_test.rb
rm test/browser/customer_ticket_create_fields_test.rb
rm test/browser/customer_ticket_create_test.rb
rm test/browser/first_steps_test.rb
rm test/browser/integration_test.rb
@@ -330,7 +291,6 @@ elif [ "$LEVEL" == '5' ]; then
rm test/browser/switch_to_user_test.rb
rm test/browser/taskbar_session_test.rb
rm test/browser/taskbar_task_test.rb
rm test/browser/translation_test.rb
rm test/browser/user_access_permissions_test.rb
rm test/browser/user_switch_cache_test.rb
@@ -343,7 +303,6 @@ elif [ "$LEVEL" == '6' ]; then
cp test/integration/aaa_auto_wizard_base_setup_test.rb test/browser/aaa_auto_wizard_base_setup_test.rb
rm test/browser/abb_one_group_test.rb
rm test/browser/admin_channel_email_test.rb
rm test/browser/admin_calendar_sla_test.rb
rm test/browser/admin_drag_drop_to_new_group_test.rb
rm test/browser/admin_overview_test.rb
rm test/browser/admin_permissions_granular_vs_full_test.rb
@@ -372,18 +331,12 @@ elif [ "$LEVEL" == '6' ]; then
rm test/browser/agent_ticket_task_changed_test.rb
rm test/browser/agent_ticket_text_module_test.rb
rm test/browser/agent_ticket_time_accounting_test.rb
rm test/browser/agent_ticket_update1_test.rb
rm test/browser/agent_ticket_update2_test.rb
rm test/browser/agent_ticket_update3_test.rb
rm test/browser/agent_ticket_update4_test.rb
rm test/browser/agent_ticket_update5_test.rb
rm test/browser/agent_ticket_update_with_attachment_refresh_test.rb
rm test/browser/agent_ticket_update_and_reload_test.rb
rm test/browser/agent_ticket_zoom_hide_test.rb
rm test/browser/agent_user_manage_test.rb
rm test/browser/agent_user_profile_test.rb
rm test/browser/auth_test.rb
rm test/browser/customer_ticket_create_fields_test.rb
rm test/browser/customer_ticket_create_test.rb
# test/browser/first_steps_test.rb
# test/browser/integration_test.rb
@@ -398,7 +351,6 @@ elif [ "$LEVEL" == '6' ]; then
rm test/browser/switch_to_user_test.rb
rm test/browser/taskbar_session_test.rb
rm test/browser/taskbar_task_test.rb
rm test/browser/translation_test.rb
rm test/browser/user_access_permissions_test.rb
rm test/browser/user_switch_cache_test.rb

View File

@@ -0,0 +1,88 @@
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
require 'rails_helper'
RSpec.describe ApplicationController::HasDownload::DownloadFile do
subject(:download_file) { described_class.new(stored_file.id, disposition: 'inline') }
let(:file_content_type) { 'application/pdf' }
let(:file_data) { 'A example file.' }
let(:file_name) { 'example.pdf' }
let(:stored_file) do
Store.add(
object: 'Ticket',
o_id: 1,
data: file_data,
filename: file_name,
preferences: {
'Content-Type' => file_content_type,
},
created_by_id: 1,
)
end
describe '#disposition' do
context "with given object dispostion 'inline'" do
context 'with allowed inline content type (from ActiveStorage.content_types_allowed_inline)' do
it 'disposition is inline' do
expect(download_file.disposition).to eq('inline')
end
end
context 'with binary content type (ActiveStorage.content_types_to_serve_as_binary)' do
let(:file_content_type) { 'image/svg+xml' }
it 'disposition forced to attachment' do
expect(download_file.disposition).to eq('attachment')
end
end
end
context "with given object dispostion 'attachment'" do
subject(:download_file) { described_class.new(stored_file.id, disposition: 'attachment') }
it 'disposition is attachment' do
expect(download_file.disposition).to eq('attachment')
end
end
end
describe '#content_type' do
context 'with none binary content type' do
it 'check content type' do
expect(download_file.content_type).to eq('application/pdf')
end
end
context 'with forced active storage binary content type' do
let(:file_content_type) { 'image/svg+xml' }
it 'check content type' do
expect(download_file.content_type).to eq('application/octet-stream')
end
end
end
describe '#content' do
context 'with not resizable file' do
it 'check that normal content will be returned' do
expect(download_file.content('preview')).to eq('A example file.')
end
end
context 'with image content type' do
let(:file_content_type) { 'image/jpg' }
let(:file_data) { File.binread(Rails.root.join('test/data/upload/upload2.jpg')) }
let(:file_name) { 'image.jpg' }
it 'check that inline content will be returned' do
expect(download_file.content('inline')).to not_eq(file_data)
end
it 'check that preview content will be returned' do
expect(download_file.content('preview')).to not_eq(file_data)
end
end
end
end

View File

@@ -0,0 +1,18 @@
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
require 'rails_helper'
RSpec.describe MaintenanceImproveSettingPreferences, type: :db_migration do
context 'when having old setting preferences without protected flag' do
before do
setting.preferences.delete(:protected)
setting.save!
end
let(:setting) { Setting.find_by(name: 'application_secret') }
it 'add protected flag' do
expect { migrate }.to change { setting.reload.preferences[:protected] }.to(true)
end
end
end

View File

@@ -8,6 +8,7 @@ FactoryBot.define do
iconset { 'FontAwesome' }
color_highlight { '#AAA' }
color_header { '#EEE' }
color_header_link { '#FFF000' }
homepage_layout { 'grid' }
category_layout { 'list' }

View File

@@ -2,11 +2,12 @@
FactoryBot.define do
factory :notification_factory_renderer, class: 'NotificationFactory::Renderer' do
objects { nil }
objects { nil }
locale { 'en-en' }
template { '' }
escape { true }
trusted { false }
initialize_with { new(objects: objects, locale: locale, template: template, escape: escape) }
initialize_with { new(objects: objects, locale: locale, template: template, escape: escape, trusted: trusted) }
end
end

View File

@@ -12,6 +12,21 @@ RSpec.describe NotificationFactory::Renderer do
expect(renderer.render).to eq ''
end
context 'when rendering templates with ERB tags' do
let(:template) { '<%% <%= "<%" %> %%>' }
it 'ignores pre-existing ERB tags in an untrusted template' do
renderer = build :notification_factory_renderer, template: template
expect(renderer.render).to eq '<% <%= "<%" %> %%>'
end
it 'executes pre-existing ERB tags in a trusted template' do
renderer = build :notification_factory_renderer, template: template, trusted: true
expect(renderer.render).to eq '<% <% %%>'
end
end
it 'correctly renders chained object references' do
user = User.where(firstname: 'Nicole').first
ticket = create :ticket, customer: user
@@ -19,7 +34,6 @@ RSpec.describe NotificationFactory::Renderer do
objects: { ticket: ticket },
template: '#{ticket.customer.firstname.downcase}'
expect(renderer.render).to eq 'nicole'
ticket.destroy
end
it 'correctly renders multiple value calls' do
@@ -28,7 +42,21 @@ RSpec.describe NotificationFactory::Renderer do
objects: { ticket: ticket },
template: '#{ticket.created_at.value.value.value.value.to_s.first}'
expect(renderer.render).to eq '2'
ticket.destroy
end
it 'raises a StandardError when rendering a template with a broken syntax' do
renderer = build :notification_factory_renderer, template: 'test <% if %>', objects: {}, trusted: true
expect { renderer.render }.to raise_error(StandardError)
end
it 'raises a StandardError when rendering a template calling a non existant method' do
renderer = build :notification_factory_renderer, template: 'test <% Ticket.non_existant_method %>', objects: {}, trusted: true
expect { renderer.render }.to raise_error(StandardError)
end
it 'raises a StandardError when rendering a template referencing a non existant object' do
renderer = build :notification_factory_renderer, template: 'test <% NonExistantObject.first %>', objects: {}, trusted: true
expect { renderer.render }.to raise_error(StandardError)
end
context 'when handling ObjectManager::Attribute usage', db_strategy: :reset do
@@ -44,13 +72,6 @@ RSpec.describe NotificationFactory::Renderer do
template: '#{ticket.select} _SEPERATOR_ #{ticket.select.value}'
expect(renderer.render).to eq 'key_1 _SEPERATOR_ value_1'
ticket.destroy
ObjectManager::Attribute.remove(
object: 'Ticket',
name: 'select',
)
ObjectManager::Attribute.migration_execute
end
it 'correctly renders select attributes on chained user object' do
@@ -69,13 +90,6 @@ RSpec.describe NotificationFactory::Renderer do
template: '#{ticket.customer.select} _SEPERATOR_ #{ticket.customer.select.value}'
expect(renderer.render).to eq 'key_2 _SEPERATOR_ value_2'
ticket.destroy
ObjectManager::Attribute.remove(
object: 'User',
name: 'select',
)
ObjectManager::Attribute.migration_execute
end
it 'correctly renders select attributes on chained group object' do
@@ -94,13 +108,6 @@ RSpec.describe NotificationFactory::Renderer do
template: '#{ticket.group.select} _SEPERATOR_ #{ticket.group.select.value}'
expect(renderer.render).to eq 'key_3 _SEPERATOR_ value_3'
ticket.destroy
ObjectManager::Attribute.remove(
object: 'Group',
name: 'select',
)
ObjectManager::Attribute.migration_execute
end
it 'correctly renders select attributes on chained organization object' do
@@ -118,13 +125,6 @@ RSpec.describe NotificationFactory::Renderer do
template: '#{ticket.customer.organization.select} _SEPERATOR_ #{ticket.customer.organization.select.value}'
expect(renderer.render).to eq 'key_2 _SEPERATOR_ value_2'
ticket.destroy
ObjectManager::Attribute.remove(
object: 'Organization',
name: 'select',
)
ObjectManager::Attribute.migration_execute
end
it 'correctly renders tree select attributes' do
@@ -138,13 +138,6 @@ RSpec.describe NotificationFactory::Renderer do
template: '#{ticket.tree_select} _SEPERATOR_ #{ticket.tree_select.value}'
expect(renderer.render).to eq 'Incident::Hardware::Laptop _SEPERATOR_ Incident::Hardware::Laptop'
ticket.destroy
ObjectManager::Attribute.remove(
object: 'Ticket',
name: 'tree_select',
)
ObjectManager::Attribute.migration_execute
end
end
end

View File

@@ -4,9 +4,11 @@ require 'rails_helper'
RSpec.describe NotificationFactory::Template do
subject(:template) do
described_class.new(template_string, escape)
described_class.new(template_string, escape, trusted)
end
let(:trusted) { false }
describe '#to_s' do
context 'for empty input template (incl. whitespace-only)' do
let(:template_string) { "\#{ }" }
@@ -28,6 +30,28 @@ RSpec.describe NotificationFactory::Template do
end
end
context 'for sanitizing the template string' do
let(:escape) { false }
context 'for strings containing ERB' do
let(:template_string) { '<%% <% "<%" %> <%# comment %> <%= "<%" %> <%- "" %> %%>' }
context 'for untrusted templates' do
it 'mutes all pre-existing ERB tags' do
expect(template.to_s).to eq('<%% <%% "<%%" %> <%%# comment %> <%%= "<%%" %> <%%- "" %> %%>')
end
end
context 'for trusted templates' do
let(:trusted) { true }
it 'keeps all pre-existing ERB tags' do
expect(template.to_s).to eq(template_string)
end
end
end
end
context 'for input template using #t helper' do
let(:template_string) { "\#{t('some text')}" }
let(:escape) { false }

View File

@@ -1236,6 +1236,12 @@ RSpec.describe Channel::EmailParser, type: :model do
end
describe 'XSS protection' do
before do
# XSS processing may run into a timeout on slow CI systems, so turn the timeout off for the test.
stub_const("#{HtmlSanitizer}::PROCESSING_TIMEOUT", nil)
end
let(:article) { described_class.new.process({}, raw_mail).second }
let(:raw_mail) { <<~RAW.chomp }
@@ -1252,7 +1258,7 @@ RSpec.describe Channel::EmailParser, type: :model do
let(:content_type) { 'text/html' }
it 'removes injected <script> tags from body' do
expect(article.body).to eq("no HTML alert('XSS')")
expect(article.body).to eq('no HTML')
end
end

View File

@@ -5,8 +5,8 @@ RSpec.shared_examples 'HasXssSanitizedNote' do |model_factory:|
context 'with injected JS' do
subject { create(model_factory, note: 'test 123 <script type="text/javascript">alert("XSS!");</script> <b>some text</b>') }
it 'strips out <script> tag' do
expect(subject.note).to eq('test 123 alert("XSS!"); <b>some text</b>')
it 'strips out <script> tag with content' do
expect(subject.note).to eq('test 123 <b>some text</b>')
end
end
end

View File

@@ -1633,4 +1633,88 @@ RSpec.describe CoreWorkflow, type: :model do
end
end
end
describe 'Saved conditions break on selections without reloading #3758', db_strategy: :reset do
let(:field_name) { SecureRandom.uuid }
let(:screens) do
{
edit: {
'ticket.agent' => {
shown: true,
},
},
}
end
let!(:workflow) do
create(:core_workflow,
object: 'Ticket',
condition_saved: {
"ticket.#{field_name}": {
operator: 'is_not',
value: 'true',
},
})
end
let(:payload) do
base_payload.merge('params' => { 'id' => ticket.id }, 'screen' => 'edit')
end
before do
create(:object_manager_attribute_boolean, object_name: 'Ticket', name: field_name, display: field_name, screens: screens)
ObjectManager::Attribute.migration_execute
end
it 'does match the workflow because saved value is false' do
expect(result[:matched_workflows]).to include(workflow.id)
end
context 'when params contain boolean field true' do
let(:payload) do
base_payload.merge('params' => { 'id' => ticket.id, field_name => true }, 'screen' => 'edit')
end
it 'does match the workflow because saved value is false' do
expect(result[:matched_workflows]).to include(workflow.id)
end
end
end
describe 'Core Workflow: Add organization condition attributes for object User #3779' do
let(:organization) { create(:organization, note: 'hello') }
let!(:base_payload) do
{
'event' => 'core_workflow',
'request_id' => 'default',
'class_name' => 'User',
'screen' => 'create',
'params' => {},
}
end
let!(:workflow) do
create(:core_workflow,
object: 'User',
condition_selected: {
'organization.note': {
operator: 'is',
value: 'hello',
},
})
end
context 'when new user has no organization' do
it 'does not match the workflow' do
expect(result[:matched_workflows]).not_to include(workflow.id)
end
end
context 'when new user is part of the organization' do
let(:payload) do
base_payload.merge('params' => { 'organization_id' => organization.id.to_s })
end
it 'does match the workflow' do
expect(result[:matched_workflows]).to include(workflow.id)
end
end
end
end

View File

@@ -56,9 +56,9 @@ RSpec.describe KnowledgeBase, type: :model do
let(:allowed_values) { ['#aaa', '#ff0000', 'rgb(0,100,100)', 'hsl(0,100%,50%)'] }
let(:not_allowed_values) { ['aaa', '#aa', '#ff000', 'rgb(0,100,100', 'def(0,100%,0.5)', 'test'] }
it { is_expected.to allow_values(*allowed_values).for(:color_header) }
it { is_expected.to allow_values(*allowed_values).for(:color_highlight) }
it { is_expected.not_to allow_values(*not_allowed_values).for(:color_header) }
it { is_expected.not_to allow_values(*not_allowed_values).for(:color_highlight) }
%i[color_header color_header_link color_highlight].each do |attr|
it { is_expected.to allow_values(*allowed_values).for(attr) }
it { is_expected.not_to allow_values(*not_allowed_values).for(attr) }
end
end
end

View File

@@ -0,0 +1,82 @@
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
require 'rails_helper'
RSpec.describe Package, type: :model do
let(:package_zpm_files_json) do
<<-JSON
[
{
"permission": "644",
"location": "example.rb",
"content": "YWJjw6TDtsO8w58="
},
{
"permission": "644",
"location": "app/controllers/test_controller.rb",
"content": "YWJjw6TDtsO8w58="
}
]
JSON
end
let(:package_zpm_json) do
<<-JSON
{
"name": "UnitTestSample",
"version": "1.0.1",
"vendor": "Zammad Foundation",
"license": "ABC",
"url": "https://zammad.org/",
"description": [
{
"language": "en",
"text": "some description"
}
],
"files": #{package_zpm_files_json}
}
JSON
end
context 'with different file locations' do
context 'with correct file locations' do
it 'installation should work' do
expect(described_class.install(string: package_zpm_json)).to be_truthy
end
end
shared_examples 'check not allowed file location' do |file_location|
let(:package_zpm_files_json) do
<<-JSON
[
{
"permission": "644",
"location": "example.rb",
"content": "YWJjw6TDtsO8w58="
},
{
"permission": "644",
"location": "#{file_location}",
"content": "YWJjw6TDtsO8w58="
}
]
JSON
end
it 'installation should raise a error and package/store should not be present, because of not allowed file location' do
expect { described_class.install(string: package_zpm_json) }
.to raise_error(RuntimeError)
.and change(described_class, :count).by(0)
.and change(Store, :count).by(0)
end
end
context "with not allowed file location part: '..'" do
include_examples 'check not allowed file location', '../../../../../tmp/test_controller.rb'
end
context "with not allowed file location part: '%2e%2e'" do
include_examples 'check not allowed file location', '%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/tmp/test_controller.rb'
end
end
end

View File

@@ -87,11 +87,11 @@ RSpec.describe Ticket::Article, type: :model do
context 'when body contains only injected JS' do
let(:body) { <<~RAW.chomp }
<script type="text/javascript">alert("XSS!");</script>
<script type="text/javascript">alert("XSS!");</script> some other text
RAW
it 'removes <script> tags' do
expect(article.body).to eq('alert("XSS!");')
expect(article.body).to eq(' some other text')
end
end
@@ -102,7 +102,7 @@ RSpec.describe Ticket::Article, type: :model do
it 'removes <script> tags' do
expect(article.body).to eq(<<~SANITIZED.chomp)
please tell me this doesn't work: alert("XSS!");
please tell me this doesn't work:#{' '}
SANITIZED
end
end

View File

@@ -573,6 +573,38 @@ RSpec.describe User, type: :model do
end
end
end
describe '#check_login' do
let(:agent) { create(:agent) }
it 'does use the origin login' do
new_agent = create(:agent)
expect(new_agent.login).not_to end_with('1')
end
it 'does number up agent logins (1)' do
new_agent = create(:agent, login: agent.login)
expect(new_agent.login).to eq("#{agent.login}1")
end
it 'does number up agent logins (5)' do
new_agent = create(:agent, login: agent.login)
4.times do
new_agent = create(:agent, login: agent.login)
end
expect(new_agent.login).to eq("#{agent.login}5")
end
it 'does backup with uuid in cases of many duplicates' do
new_agent = create(:agent, login: agent.login)
20.times do
new_agent = create(:agent, login: agent.login)
end
expect(new_agent.login.sub!(agent.login, '')).to be_a_uuid
end
end
end
describe 'Attributes:' do

View File

@@ -126,6 +126,21 @@ describe UserPolicy do
it { is_expected.to permit_action(:show) }
it { is_expected.to forbid_actions(%i[update destroy]) }
end
context 'when record is both admin and customer' do
let(:record) { create(:customer, role_ids: Role.signup_role_ids.push(Role.find_by(name: 'Admin').id)) }
it { is_expected.to permit_action(:show) }
it { is_expected.to forbid_actions(%i[update destroy]) }
end
context 'when record is both agent and customer' do
let(:record) { create(:customer, role_ids: Role.signup_role_ids.push(Role.find_by(name: 'Agent').id)) }
it { is_expected.to permit_action(:show) }
it { is_expected.to forbid_actions(%i[update destroy]) }
end
end
context 'when user is a customer' do
@@ -169,5 +184,18 @@ describe UserPolicy do
it { is_expected.to permit_action(:show) }
it { is_expected.to forbid_actions(%i[update destroy]) }
end
context 'when record is both admin and customer' do
let(:record) { create(:customer, role_ids: Role.signup_role_ids.push(Role.find_by(name: 'Admin').id)) }
it { is_expected.to forbid_actions(%i[show update destroy]) }
end
context 'when record is both agent and customer' do
let(:record) { create(:customer, role_ids: Role.signup_role_ids.push(Role.find_by(name: 'Agent').id)) }
it { is_expected.to forbid_actions(%i[show update destroy]) }
end
end
end

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