Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad12ad4e01 | ||
|
|
ce5b9bd9de | ||
|
|
59cfc7c69a | ||
|
|
d9df6de234 | ||
|
|
4f94b01d56 | ||
|
|
678b5ada31 | ||
|
|
4ca7c25480 | ||
|
|
b9bb88e96d | ||
|
|
274e5a849f | ||
|
|
cb48cd2dcd | ||
|
|
6de1053d4b | ||
|
|
0f4c6ddda3 | ||
|
|
485196b013 | ||
|
|
3ddb52c9b9 | ||
|
|
2346eaa005 | ||
|
|
d17e3c5b55 | ||
|
|
8762f96810 | ||
|
|
6281816d4d | ||
|
|
372bbb0751 | ||
|
|
fa8588855c | ||
|
|
905bdd512b | ||
|
|
fd99f097d5 | ||
|
|
880b2fd131 | ||
|
|
c4ccbc46ff | ||
|
|
d638f02a12 | ||
|
|
497b6aaeff | ||
|
|
4e05f0c121 | ||
|
|
f18c0c9571 | ||
|
|
d288b48b0a | ||
|
|
94011846d6 | ||
|
|
09fc94cf72 | ||
|
|
c34452f65e | ||
|
|
2b77711150 | ||
|
|
57945692cc | ||
|
|
dfb2abe380 | ||
|
|
07c6bdacfb | ||
|
|
ff03e664db | ||
|
|
c72006216e | ||
|
|
6efafd37ae | ||
|
|
35f45f690f | ||
|
|
23c8db7e58 | ||
|
|
8382a71813 | ||
|
|
9bb1cf6d00 | ||
|
|
2bba743dc3 | ||
|
|
d7b0c639c4 | ||
|
|
13668dfc8b | ||
|
|
1e70665d1a | ||
|
|
4b0d4f798f | ||
|
|
9ad7c43cd5 | ||
|
|
ab9fa3d23d | ||
|
|
5222f4ba6d | ||
|
|
9689d3d97e | ||
|
|
6a10308f29 | ||
|
|
ab3cede8ef | ||
|
|
fa3cb1925d | ||
|
|
a7def1d62d | ||
|
|
a3df6b8581 | ||
|
|
dd201db791 | ||
|
|
60c0b08d21 | ||
|
|
95571b70c9 | ||
|
|
678161be2e | ||
|
|
008053f730 | ||
|
|
ed1782fafc | ||
|
|
72f39d0d9d | ||
|
|
7a156c5d48 | ||
|
|
f31aeec8db | ||
|
|
f67cf3ab1e | ||
|
|
b125b3603e | ||
|
|
b1e8b3afae | ||
|
|
573c975bad | ||
|
|
25155eb6a3 |
@@ -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
|
||||
|
||||
217
CHANGELOG.md
217
CHANGELOG.md
@@ -1,15 +1,216 @@
|
||||
# Change Log
|
||||
|
||||
## [5.0.0](https://github.com/zammad/zammad/tree/5.0.0) (2021-xx-xx)
|
||||
## [5.0.2](https://github.com/zammad/zammad/tree/5.0.2) (2021-10-28)
|
||||
[Full Changelog](https://github.com/zammad/zammad/compare/5.0.1...5.0.2)
|
||||
|
||||
**Fixed bugs:**
|
||||
- When looking for customers, it is no longer possible to change into organizations [3815](https://github.com/zammad/zammad/issues/3815) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)]
|
||||
- Owner should get cleared if not listed in changed group [3818](https://github.com/zammad/zammad/issues/3818) [[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)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)]
|
||||
- Custom date and datetime attributes are filled with dates on creation of tickets/users after update from 4.1 to 5.x [3810](https://github.com/zammad/zammad/issues/3810) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
|
||||
- ActionController::UnknownHttpMethod FRAGE [3807](https://github.com/zammad/zammad/issues/3807) [[bug](https://github.com/zammad/zammad/labels/bug)] [[blocker](https://github.com/zammad/zammad/labels/blocker)] [[overviews](https://github.com/zammad/zammad/labels/overviews)] [[macros](https://github.com/zammad/zammad/labels/macros)]
|
||||
- Remote change of the group id does show it falsly as user change and not render the new value to the ticket [3801](https://github.com/zammad/zammad/issues/3801) [[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)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)]
|
||||
- Adding private keys allows adding certificates [3727](https://github.com/zammad/zammad/issues/3727) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
- Able to create custom fields for existing relation (e. g. ticket.state) - will lead to non bootable Zammad [3811](https://github.com/zammad/zammad/issues/3811) [[bug](https://github.com/zammad/zammad/labels/bug)] [[regression](https://github.com/zammad/zammad/labels/regression)]
|
||||
- Sort order group_by broken (alphabetical) [3800](https://github.com/zammad/zammad/issues/3800) [[bug](https://github.com/zammad/zammad/labels/bug)] [[overviews](https://github.com/zammad/zammad/labels/overviews)] [[regression](https://github.com/zammad/zammad/labels/regression)]
|
||||
- Ticket owner selection is not updated if owner selection should be empty [3809](https://github.com/zammad/zammad/issues/3809) [[bug](https://github.com/zammad/zammad/labels/bug)] [[core workflows](https://github.com/zammad/zammad/labels/core workflows)]
|
||||
- Chat can't be closed after timeout [2471](https://github.com/zammad/zammad/issues/2471) [[bug](https://github.com/zammad/zammad/labels/bug)] [[chat](https://github.com/zammad/zammad/labels/chat)]
|
||||
- Support workflow mechanism to do pending reminder state hide pending time use case [3790](https://github.com/zammad/zammad/issues/3790) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
|
||||
- Cache.clear in postinstall.sh throws ugly errors on fresh installations [3808](https://github.com/zammad/zammad/issues/3808) [[bug](https://github.com/zammad/zammad/labels/bug)] [[blocker](https://github.com/zammad/zammad/labels/blocker)] [[regression](https://github.com/zammad/zammad/labels/regression)]
|
||||
- Example payload in webhook view leads to 500 error [3794](https://github.com/zammad/zammad/issues/3794) [[bug](https://github.com/zammad/zammad/labels/bug)] [[regression](https://github.com/zammad/zammad/labels/regression)]
|
||||
- OS package upgrade fails (activity_stream_object_id) [3797](https://github.com/zammad/zammad/issues/3797) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
|
||||
|
||||
## [5.0.1](https://github.com/zammad/zammad/tree/5.0.1) (2021-10-08)
|
||||
[Full Changelog](https://github.com/zammad/zammad/compare/5.0.0...5.0.1)
|
||||
|
||||
**Fixed bugs:**
|
||||
- Bug Report 4.1.x Overview Sort - Grouped by user [3737](https://github.com/zammad/zammad/issues/3737) [[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)] [[regression](https://github.com/zammad/zammad/labels/regression)]
|
||||
- Article box opening on tickets with no changes [3789](https://github.com/zammad/zammad/issues/3789) [[bug](https://github.com/zammad/zammad/labels/bug)] [[UX/UI](https://github.com/zammad/zammad/labels/UX/UI)]
|
||||
- UploadCacheCleanupJob does not execute [3787](https://github.com/zammad/zammad/issues/3787) [[bug](https://github.com/zammad/zammad/labels/bug)] [[prioritised by payment](https://github.com/zammad/zammad/labels/prioritised by payment)]
|
||||
- lib/fill_db.rb fails to work in production environments [3788](https://github.com/zammad/zammad/issues/3788) [[bug](https://github.com/zammad/zammad/labels/bug)]
|
||||
|
||||
|
||||
## [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)]
|
||||
|
||||
4
Gemfile
4
Gemfile
@@ -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?
|
||||
|
||||
@@ -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)
|
||||
@@ -424,7 +423,7 @@ GEM
|
||||
binding_of_caller (~> 1.0)
|
||||
pry (~> 0.13)
|
||||
public_suffix (4.0.6)
|
||||
puma (4.3.8)
|
||||
puma (4.3.10)
|
||||
nio4r (~> 2.0)
|
||||
pundit (2.1.1)
|
||||
activesupport (>= 3.0.0)
|
||||
@@ -711,7 +710,6 @@ DEPENDENCIES
|
||||
omniauth-twitter
|
||||
omniauth-weibo-oauth2
|
||||
openssl
|
||||
os
|
||||
overcommit
|
||||
pg (= 0.21.0)
|
||||
pry-rails
|
||||
|
||||
@@ -493,6 +493,25 @@ class App.ControllerTable extends App.Controller
|
||||
sortable: @dndCallback
|
||||
))
|
||||
|
||||
getGroupByKeyName: (object, groupBy) ->
|
||||
reference_key = groupBy + '_id'
|
||||
|
||||
if reference_key of object
|
||||
attribute = _.findWhere(object.constructor.configure_attributes, { name: reference_key })
|
||||
|
||||
return App[attribute.relation]?.find(object[reference_key])?.displayName() || reference_key
|
||||
|
||||
groupBy
|
||||
|
||||
sortObjectKeys: (objects, direction) ->
|
||||
sorted = Object.keys(objects).sort()
|
||||
|
||||
switch direction
|
||||
when 'DESC'
|
||||
sorted.reverse()
|
||||
else
|
||||
sorted
|
||||
|
||||
renderTableRows: (sort = false) =>
|
||||
if sort is true
|
||||
@sortList()
|
||||
@@ -506,11 +525,11 @@ class App.ControllerTable extends App.Controller
|
||||
objectsToShow = @objectsOfPage(@pagerShownPage)
|
||||
if @groupBy
|
||||
# group by raw (and not printable) value so dates work also
|
||||
objectsGrouped = _.groupBy(objectsToShow, (object) => object[@groupBy])
|
||||
objectsGrouped = _.groupBy(objectsToShow, (object) => object[@getGroupByKeyName(object, @groupBy)])
|
||||
else
|
||||
objectsGrouped = { '': objectsToShow }
|
||||
|
||||
for groupValue in Object.keys(objectsGrouped).sort()
|
||||
for groupValue in @sortObjectKeys(objectsGrouped, @groupDirection)
|
||||
groupObjects = objectsGrouped[groupValue]
|
||||
|
||||
for object in groupObjects
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -39,6 +39,7 @@ class App.UiElement.core_workflow_perform extends App.UiElement.ApplicationSelec
|
||||
operatorsType =
|
||||
'boolean$': ['show', 'hide', 'remove', 'set_mandatory', 'set_optional', 'set_readonly', 'unset_readonly', 'add_option', 'remove_option', 'set_fixed_to']
|
||||
'integer$': ['show', 'hide', 'remove', 'set_mandatory', 'set_optional', 'set_readonly', 'unset_readonly']
|
||||
'^date': ['show', 'hide', 'remove', 'set_mandatory', 'set_optional', 'set_readonly', 'unset_readonly']
|
||||
'^select$': ['show', 'hide', 'remove', 'set_mandatory', 'set_optional', 'set_readonly', 'unset_readonly', 'add_option', 'remove_option', 'set_fixed_to', 'select', 'auto_select']
|
||||
'^tree_select$': ['show', 'hide', 'remove', 'set_mandatory', 'set_optional', 'set_readonly', 'unset_readonly', 'add_option', 'remove_option', 'set_fixed_to', 'select', 'auto_select']
|
||||
'^input$': ['show', 'hide', 'remove', 'set_mandatory', 'set_optional', 'set_readonly', 'unset_readonly', 'fill_in', 'fill_in_empty']
|
||||
@@ -63,8 +64,9 @@ class App.UiElement.core_workflow_perform extends App.UiElement.ApplicationSelec
|
||||
continue
|
||||
|
||||
for row in App[groupMeta.model].configure_attributes
|
||||
continue if !_.contains(['input', 'select', 'integer', 'boolean', 'tree_select'], row.tag)
|
||||
continue if groupKey is 'ticket' && _.contains(['number', 'organization_id', 'title'], row.name)
|
||||
continue if !_.contains(['input', 'select', 'integer', 'boolean', 'tree_select', 'date', 'datetime'], row.tag)
|
||||
continue if _.contains(['created_at', 'updated_at'], row.name)
|
||||
continue if groupKey is 'ticket' && _.contains(['number', 'organization_id', 'title', 'escalation_at', 'first_response_escalation_at', 'update_escalation_at', 'close_escalation_at', 'last_contact_at', 'last_contact_agent_at', 'last_contact_customer_at', 'first_response_at', 'close_at'], row.name)
|
||||
|
||||
# ignore passwords and relations
|
||||
if row.type isnt 'password' && row.name.substr(row.name.length-4,4) isnt '_ids' && row.searchable isnt false
|
||||
@@ -128,9 +130,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: ->
|
||||
|
||||
@@ -244,7 +244,7 @@ class App.UiElement.object_manager_attribute extends App.UiElement.ApplicationUi
|
||||
params: params
|
||||
)
|
||||
configureAttributes = [
|
||||
{ name: 'data_option::diff', display: 'Default time Diff (minutes)', tag: 'integer', null: false, default: 24 },
|
||||
{ name: 'data_option::diff', display: 'Default time Diff (minutes)', tag: 'integer', null: true },
|
||||
]
|
||||
datetimeDiff = new App.ControllerForm(
|
||||
model:
|
||||
@@ -258,7 +258,7 @@ class App.UiElement.object_manager_attribute extends App.UiElement.ApplicationUi
|
||||
|
||||
@date: (item, localParams, params) ->
|
||||
configureAttributes = [
|
||||
{ name: 'data_option::diff', display: 'Default time Diff (hours)', tag: 'integer', null: false, default: 24 },
|
||||
{ name: 'data_option::diff', display: 'Default time Diff (hours)', tag: 'integer', null: true },
|
||||
]
|
||||
dateDiff = new App.ControllerForm(
|
||||
model:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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%)'
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@@ -153,7 +153,7 @@ class Object extends App.ControllerObserver
|
||||
elLocal.find('.js-userList').html(members)
|
||||
)
|
||||
|
||||
if @organization.member_ids.length < @memberLimit
|
||||
if @organization.member_ids.length <= @memberLimit
|
||||
@el.find('.js-showMoreMembers').parent().addClass('hidden')
|
||||
else
|
||||
@el.find('.js-showMoreMembers').parent().removeClass('hidden')
|
||||
|
||||
@@ -206,22 +206,13 @@ class App.TicketOverview extends App.Controller
|
||||
article: article
|
||||
)
|
||||
ticket.article = article
|
||||
ticket.ajax().update(
|
||||
ticket.attributes()
|
||||
# this option will prevent callbacks and invalid data states in case of an error
|
||||
failResponseNoTrigger: true
|
||||
ticket.save(
|
||||
done: (r) =>
|
||||
@batchCountIndex++
|
||||
|
||||
# refresh view after all tickets are proceeded
|
||||
if @batchCountIndex == @batchCount
|
||||
App.Event.trigger('overview:fetch')
|
||||
fail: (record, settings, details) ->
|
||||
console.log('record, settings, details', record, settings, details)
|
||||
App.Event.trigger('notify', {
|
||||
type: 'error'
|
||||
msg: App.i18n.translateContent('Bulk action stopped %s!', error)
|
||||
})
|
||||
)
|
||||
return
|
||||
|
||||
@@ -234,21 +225,13 @@ class App.TicketOverview extends App.Controller
|
||||
ticket.owner_id = id
|
||||
if !_.isEmpty(groupId)
|
||||
ticket.group_id = groupId
|
||||
ticket.ajax().update(
|
||||
ticket.attributes()
|
||||
# this option will prevent callbacks and invalid data states in case of an error
|
||||
failResponseNoTrigger: true
|
||||
ticket.save(
|
||||
done: (r) =>
|
||||
@batchCountIndex++
|
||||
|
||||
# refresh view after all tickets are proceeded
|
||||
if @batchCountIndex == @batchCount
|
||||
App.Event.trigger('overview:fetch')
|
||||
fail: (record, settings, details) ->
|
||||
App.Event.trigger('notify', {
|
||||
type: 'error'
|
||||
msg: App.i18n.translateContent('Bulk action stopped %s!', settings.error)
|
||||
})
|
||||
)
|
||||
return
|
||||
|
||||
@@ -259,21 +242,13 @@ class App.TicketOverview extends App.Controller
|
||||
#console.log "perform action #{action} with id #{id} on ", $(item).val()
|
||||
ticket = App.Ticket.find($(item).val())
|
||||
ticket.group_id = id
|
||||
ticket.ajax().update(
|
||||
ticket.attributes()
|
||||
# this option will prevent callbacks and invalid data states in case of an error
|
||||
failResponseNoTrigger: true
|
||||
ticket.save(
|
||||
done: (r) =>
|
||||
@batchCountIndex++
|
||||
|
||||
# refresh view after all tickets are proceeded
|
||||
if @batchCountIndex == @batchCount
|
||||
App.Event.trigger('overview:fetch')
|
||||
fail: (record, settings, details) ->
|
||||
App.Event.trigger('notify', {
|
||||
type: 'error'
|
||||
msg: App.i18n.translateContent('Bulk action stopped %s!', error)
|
||||
})
|
||||
)
|
||||
return
|
||||
|
||||
@@ -1314,7 +1289,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', =>
|
||||
|
||||
@@ -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: =>
|
||||
|
||||
@@ -640,6 +641,7 @@ class App.TicketZoom extends App.Controller
|
||||
# update changes in ui
|
||||
currentStore = @currentStore()
|
||||
modelDiff = @formDiff(currentParams, currentStore)
|
||||
return if _.isEmpty(modelDiff)
|
||||
|
||||
# set followup state if needed
|
||||
@setDefaultFollowUpState(modelDiff, currentStore)
|
||||
@@ -734,6 +736,14 @@ class App.TicketZoom extends App.Controller
|
||||
|
||||
# do not compare null or undefined value
|
||||
if currentStore.ticket
|
||||
|
||||
# make sure that the compared state is same in local storage and
|
||||
# rendered html. Else we could have race conditions of data
|
||||
# which is not rendered yet
|
||||
renderedUpdatedAt = @el.find('.edit').attr('data-ticket-updated-at')
|
||||
return if !renderedUpdatedAt
|
||||
return if currentStore.ticket.updated_at.toString() isnt renderedUpdatedAt
|
||||
|
||||
for key, value of currentStore.ticket
|
||||
if value is null || value is undefined
|
||||
currentStore.ticket[key] = ''
|
||||
|
||||
@@ -98,7 +98,7 @@ class App.TicketZoomArticleNew extends App.Controller
|
||||
@controllerBind('ui:rerender', =>
|
||||
@adjustedTextarea = false
|
||||
@defaults = @ui.taskGet('article')
|
||||
@attachments = @defaults.attachments
|
||||
@attachments = @defaults.attachments || []
|
||||
@render()
|
||||
)
|
||||
|
||||
@@ -117,7 +117,7 @@ class App.TicketZoomArticleNew extends App.Controller
|
||||
|
||||
@tokanice(@type)
|
||||
|
||||
if @defaults.body or @isIE10()
|
||||
if @defaults.body or @attachments.length > 0 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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,49 +25,44 @@ 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'
|
||||
@controllerFormSidebarTicket = new App.ControllerForm(
|
||||
elReplace: @el
|
||||
model: { className: 'Ticket', configure_attributes: @formMeta.configure_attributes || App.Ticket.configure_attributes }
|
||||
screen: 'edit'
|
||||
handlersConfig: handlers
|
||||
filter: @formMeta.filter
|
||||
formMeta: @formMeta
|
||||
params: defaults
|
||||
isDisabled: !ticket.editable()
|
||||
taskKey: @taskKey
|
||||
core_workflow: {
|
||||
callbacks: [@markForm]
|
||||
}
|
||||
#bookmarkable: true
|
||||
)
|
||||
else
|
||||
@controllerFormSidebarTicket = new App.ControllerForm(
|
||||
elReplace: @el
|
||||
model: { className: 'Ticket', configure_attributes: @formMeta.configure_attributes || App.Ticket.configure_attributes }
|
||||
screen: 'edit'
|
||||
handlersConfig: handlers
|
||||
filter: @formMeta.filter
|
||||
formMeta: @formMeta
|
||||
params: defaults
|
||||
isDisabled: ticket.editable()
|
||||
taskKey: @taskKey
|
||||
core_workflow: {
|
||||
callbacks: [@markForm]
|
||||
}
|
||||
#bookmarkable: true
|
||||
)
|
||||
editable = @ticket.editable()
|
||||
if followUpPossible == 'new_ticket' && ticketState != 'closed' || followUpPossible != 'new_ticket' || @permissionCheck('admin') || @ticket.currentView() is 'agent'
|
||||
editable = !editable
|
||||
|
||||
# reset updated_at for the sidbar because we render a new state
|
||||
# it is used to compare the ticket with the rendered data later
|
||||
# and needed to prevent race conditions
|
||||
@el.removeAttr('data-ticket-updated-at')
|
||||
|
||||
@controllerFormSidebarTicket = new App.ControllerForm(
|
||||
elReplace: @el
|
||||
model: { className: 'Ticket', configure_attributes: @formMeta.configure_attributes || App.Ticket.configure_attributes }
|
||||
screen: 'edit'
|
||||
handlersConfig: handlers
|
||||
filter: @formMeta.filter
|
||||
formMeta: @formMeta
|
||||
params: defaults
|
||||
isDisabled: editable
|
||||
taskKey: @taskKey
|
||||
core_workflow: {
|
||||
callbacks: [@markForm]
|
||||
}
|
||||
#bookmarkable: true
|
||||
)
|
||||
|
||||
# set updated_at for the sidbar because we render a new state
|
||||
@el.attr('data-ticket-updated-at', defaults.updated_at)
|
||||
@markForm(true)
|
||||
|
||||
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 +132,7 @@ class SidebarTicket extends App.Controller
|
||||
|
||||
@edit = new Edit(
|
||||
object_id: @ticket.id
|
||||
ticket: @ticket
|
||||
el: localEl.find('.edit')
|
||||
taskGet: @taskGet
|
||||
formMeta: @formMeta
|
||||
|
||||
@@ -33,7 +33,7 @@ class App.WidgetOrganization extends App.Controller
|
||||
elLocal.find('.js-userList').html(members)
|
||||
)
|
||||
|
||||
if @organization.member_ids.length < @memberLimit
|
||||
if @organization.member_ids.length <= @memberLimit
|
||||
@el.find('.js-showMoreMembers').parent().addClass('hidden')
|
||||
else
|
||||
@el.find('.js-showMoreMembers').parent().removeClass('hidden')
|
||||
|
||||
@@ -14,6 +14,7 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
|
||||
'click': 'stopPropagation'
|
||||
'change .js-objectId': 'executeCallback'
|
||||
'click .js-remove': 'removeThisToken'
|
||||
'click .js-showMoreMembers': 'showMoreMembers'
|
||||
|
||||
elements:
|
||||
'.recipientList': 'recipientList'
|
||||
@@ -251,14 +252,42 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
|
||||
objectCount: objectCount
|
||||
)
|
||||
|
||||
showMoreMembers: (e) ->
|
||||
@preventDefaultAndStopPropagation(e)
|
||||
|
||||
memberElement = $(e.target).closest('.js-showMoreMembers')
|
||||
oldMemberLimit = memberElement.attr('organization-member-limit')
|
||||
newMemberLimit = (parseInt(oldMemberLimit / 25) + 1) * 25
|
||||
memberElement.attr('organization-member-limit', newMemberLimit)
|
||||
|
||||
@renderMembers(memberElement, oldMemberLimit, newMemberLimit)
|
||||
|
||||
renderMembers: (element, fromMemberLimit, toMemberLimit) ->
|
||||
id = element.closest('.recipientList-organizationMembers').attr('organization-id')
|
||||
organization = App.Organization.find(id)
|
||||
|
||||
# only first 10 members else we would need more ajax requests
|
||||
organization.members(fromMemberLimit, toMemberLimit, (users) =>
|
||||
for user in users
|
||||
element.before(@buildObjectItem(user))
|
||||
|
||||
if element.closest('ul').hasClass('is-shown')
|
||||
@showOrganizationMembers(undefined, element.closest('ul'))
|
||||
)
|
||||
|
||||
if organization.member_ids.length <= toMemberLimit
|
||||
element.addClass('hidden')
|
||||
else
|
||||
element.removeClass('hidden')
|
||||
|
||||
buildOrganizationMembers: (organization) =>
|
||||
organizationMemebers = $( App.view(@templateOrganizationItemMembers)(
|
||||
organizationMembers = $( App.view(@templateOrganizationItemMembers)(
|
||||
organization: organization
|
||||
) )
|
||||
if organization[@referenceAttribute]
|
||||
for objectId in organization[@referenceAttribute]
|
||||
object = App[@objectSingle].fullLocal(objectId)
|
||||
organizationMemebers.append(@buildObjectItem(object))
|
||||
|
||||
@renderMembers(organizationMembers.find('.js-showMoreMembers'), 0, 10)
|
||||
|
||||
organizationMembers
|
||||
|
||||
buildObjectItem: (object) =>
|
||||
icon = @objectIcon
|
||||
@@ -404,8 +433,7 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
|
||||
e.stopPropagation()
|
||||
listEntry = $(e.currentTarget)
|
||||
|
||||
organizationId = listEntry.data('organization-id')
|
||||
|
||||
organizationId = listEntry.data('organization-id') || listEntry.attr('organization-id')
|
||||
@organizationList = @$("[organization-id=#{ organizationId }]")
|
||||
|
||||
return if !@organizationList.get(0)
|
||||
|
||||
98
app/assets/javascripts/app/lib/app_post/html5_upload.coffee
Normal file
98
app/assets/javascripts/app/lib/app_post/html5_upload.coffee
Normal 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')
|
||||
@@ -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:
|
||||
|
||||
@@ -234,12 +234,11 @@ class Singleton extends Base
|
||||
|
||||
failResponse: (options) =>
|
||||
(xhr, statusText, error, settings) =>
|
||||
if options.failResponseNoTrigger isnt true
|
||||
switch settings.type
|
||||
when 'POST' then @createFailed()
|
||||
when 'DELETE' then @destroyFailed()
|
||||
# add errors to calllback
|
||||
@record.trigger('ajaxError', @record, xhr, statusText, error, settings)
|
||||
switch settings.type
|
||||
when 'POST' then @createFailed()
|
||||
when 'DELETE' then @destroyFailed()
|
||||
# add errors to calllback
|
||||
@record.trigger('ajaxError', @record, xhr, statusText, error, settings)
|
||||
|
||||
#options.fail?.call(@record, settings)
|
||||
detailsRaw = xhr.responseText
|
||||
@@ -247,8 +246,7 @@ class Singleton extends Base
|
||||
details = JSON.parse(detailsRaw)
|
||||
options.fail?.call(@record, settings, details)
|
||||
|
||||
if options.failResponseNoTrigger isnt true
|
||||
@record.trigger('destroy', @record)
|
||||
@record.trigger('destroy', @record)
|
||||
# /add errors to calllback
|
||||
|
||||
createFailed: ->
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -36,7 +36,7 @@ Using **Organisations** you can **group** customers. This has among others two i
|
||||
userResult = ->
|
||||
users = []
|
||||
for user_id in member_ids
|
||||
user = App.User.find(user_id)
|
||||
user = App.User.fullLocal(user_id)
|
||||
continue if !user
|
||||
users.push(user)
|
||||
return users
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -4,4 +4,9 @@
|
||||
<%- @Icon('arrow-left') %>
|
||||
<span class="btn-label"><%- @T('Back') %></span>
|
||||
</div>
|
||||
</ul>
|
||||
|
||||
<li class="recipientList-controls js-showMoreMembers" organization-member-limit="10">
|
||||
<div class="btn btn--action btn--onDark">
|
||||
<span class="btn-label"><%- @T('show more') %></span>
|
||||
</div>
|
||||
</ul>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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: %>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<% if @object.member_ids: %>
|
||||
<hr>
|
||||
<div class="popover-block">
|
||||
<label><%- @T('Members') %></label>
|
||||
<label><%- @T('Members') %> (<%= @object.member_ids.length %>)</label>
|
||||
<div class="userList js-userList"></div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
44
app/controllers/application_controller/has_download.rb
Normal file
44
app/controllers/application_controller/has_download.rb
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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!'
|
||||
|
||||
@@ -42,9 +42,7 @@ class Integration::SMIMEController < ApplicationController
|
||||
string = params[:file].read.force_encoding('utf-8')
|
||||
end
|
||||
|
||||
items = string.scan(%r{.+?-+END(?: TRUSTED)? CERTIFICATE-+}mi).each_with_object([]) do |cert, result|
|
||||
result << SMIMECertificate.create!(public_key: cert)
|
||||
end
|
||||
items = SMIMECertificate.create_certificates(string)
|
||||
|
||||
render json: {
|
||||
result: 'ok',
|
||||
@@ -73,14 +71,8 @@ class Integration::SMIMEController < ApplicationController
|
||||
|
||||
raise "Parameter 'data' or 'file' required." if string.blank?
|
||||
|
||||
private_key = OpenSSL::PKey.read(string, params[:secret])
|
||||
modulus = private_key.public_key.n.to_s(16)
|
||||
|
||||
certificate = SMIMECertificate.find_by(modulus: modulus)
|
||||
|
||||
raise Exceptions::UnprocessableEntity, 'Unable for find certificate for this private key.' if !certificate
|
||||
|
||||
certificate.update!(private_key: string, private_key_secret: params[:secret])
|
||||
SMIMECertificate.create_certificates(string)
|
||||
SMIMECertificate.create_private_keys(string, params[:secret])
|
||||
|
||||
render json: {
|
||||
result: 'ok',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,9 +4,7 @@ class WebhooksController < ApplicationController
|
||||
prepend_before_action { authentication_check && authorize! }
|
||||
|
||||
def preview
|
||||
access_condition = Ticket.access_condition(current_user, 'read')
|
||||
|
||||
ticket = Ticket.where(access_condition).last
|
||||
ticket = TicketPolicy::ReadScope.new(current_user).resolve.last
|
||||
|
||||
render json: JSON.pretty_generate({
|
||||
ticket: TriggerWebhookJob::RecordPayload.generate(ticket),
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
class UploadCacheCleanupJob < ApplicationJob
|
||||
def perform
|
||||
taskbar_form_ids = Taskbar.with_form_id.filter_map(&:persisted_form_id)
|
||||
return if store_object_id.blank?
|
||||
|
||||
Store.where(store_object_id: store_object_id).where('created_at < ?', 1.month.ago).where.not(o_id: taskbar_form_ids).find_each do |store|
|
||||
Store.remove_item(store.id)
|
||||
@@ -12,6 +13,6 @@ class UploadCacheCleanupJob < ApplicationJob
|
||||
private
|
||||
|
||||
def store_object_id
|
||||
Store::Object.lookup(name: 'UploadCache').id
|
||||
Store::Object.lookup(name: 'UploadCache')&.id
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -72,7 +72,6 @@ add avatar by url
|
||||
=end
|
||||
|
||||
def self.add(data)
|
||||
|
||||
# lookups
|
||||
if data[:object]
|
||||
object_id = ObjectLookup.by_name(data[:object])
|
||||
|
||||
@@ -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
|
||||
@@ -214,13 +238,10 @@ class CoreWorkflow::Attributes
|
||||
end
|
||||
|
||||
def saved_attribute_value(attribute)
|
||||
saved_attribute_value = saved_only&.try(attribute[:name])
|
||||
|
||||
# special case for owner_id
|
||||
if saved_only&.class == Ticket && attribute[:name] == 'owner_id' && saved_attribute_value == 1
|
||||
saved_attribute_value = nil
|
||||
end
|
||||
return if saved_only&.class == Ticket && attribute[:name] == 'owner_id'
|
||||
|
||||
saved_attribute_value
|
||||
saved_only&.try(attribute[:name])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
class CoreWorkflow::Attributes::User < CoreWorkflow::Attributes::Base
|
||||
|
||||
def values
|
||||
return ticket_owner_id_bulk if @attributes.payload['screen'] == 'overview_bulk'
|
||||
return ticket_owner_id if @attributes.payload['class_name'] == 'Ticket' && @attribute[:name] == 'owner_id'
|
||||
if @attribute[:name] == 'owner_id' && @attributes.payload['class_name'] == 'Ticket'
|
||||
return ticket_owner_id_bulk if @attributes.payload['screen'] == 'overview_bulk'
|
||||
|
||||
return ticket_owner_id
|
||||
end
|
||||
|
||||
[]
|
||||
end
|
||||
@@ -35,7 +38,10 @@ class CoreWorkflow::Attributes::User < CoreWorkflow::Attributes::Base
|
||||
def ticket_owner_id
|
||||
return [''] if @attributes.selected_only.group_id.blank?
|
||||
|
||||
group_owner_ids
|
||||
owner_ids = group_owner_ids
|
||||
return [''] if owner_ids.blank?
|
||||
|
||||
owner_ids
|
||||
end
|
||||
|
||||
def group_owner_ids
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
14
app/models/group/assets.rb
Normal file
14
app/models/group/assets.rb
Normal 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
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -820,7 +820,7 @@ is certain attribute used by triggers, overviews or schedulers
|
||||
end
|
||||
|
||||
record = object_lookup.name.constantize.new
|
||||
if record.respond_to?(name.to_sym) && record.attributes.key?(name) && new_record?
|
||||
if new_record? && (record.respond_to?(name.to_sym) || record.attributes.key?(name))
|
||||
errors.add(:name, "#{name} already exists!")
|
||||
end
|
||||
|
||||
@@ -932,12 +932,7 @@ is certain attribute used by triggers, overviews or schedulers
|
||||
[{ failed: local_data_option[:future].nil?,
|
||||
message: 'must have boolean value for :future' },
|
||||
{ failed: local_data_option[:past].nil?,
|
||||
message: 'must have boolean value for :past' },
|
||||
{ failed: local_data_option[:diff].nil?,
|
||||
message: 'must have integer value for :diff (in hours)' }]
|
||||
when 'date'
|
||||
[{ failed: local_data_option[:diff].nil?,
|
||||
message: 'must have integer value for :diff (in days)' }]
|
||||
message: 'must have boolean value for :past' }]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
@@ -15,25 +15,30 @@ class ObjectManager
|
||||
method_name = "#{attr}="
|
||||
|
||||
return if !record.respond_to? method_name
|
||||
return if record.send(attr).present?
|
||||
return if record.send("#{attr}_came_from_user?")
|
||||
|
||||
record.send method_name, build_value(config)
|
||||
end
|
||||
|
||||
def build_value(config)
|
||||
case config[:data_type]
|
||||
when 'date'
|
||||
config[:diff].days.from_now
|
||||
when 'datetime'
|
||||
config[:diff].hours.from_now
|
||||
else
|
||||
config[:default]
|
||||
end
|
||||
method_name = "build_value_#{config[:data_type]}"
|
||||
|
||||
return send(method_name, config) if respond_to?(method_name, true)
|
||||
|
||||
config[:default]
|
||||
end
|
||||
|
||||
def build_value_date(config)
|
||||
config[:diff]&.days&.from_now
|
||||
end
|
||||
|
||||
def build_value_datetime(config)
|
||||
config[:diff]&.hours&.from_now&.change(usec: 0, sec: 0)
|
||||
end
|
||||
|
||||
def attributes_for(record)
|
||||
query = ObjectManager::Attribute.active.editable.for_object(record.class)
|
||||
cache_key = "#{query.cache_key}/attribute_defaults"
|
||||
cache_key = "#{query.cache_key_with_version}/attribute_defaults"
|
||||
|
||||
Rails.cache.fetch cache_key do
|
||||
query
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,6 +3,28 @@
|
||||
class SMIMECertificate < ApplicationModel
|
||||
validates :fingerprint, uniqueness: { case_sensitive: true }
|
||||
|
||||
def self.parts(raw)
|
||||
raw.scan(%r{-----BEGIN[^-]+-----.+?-----END[^-]+-----}m)
|
||||
end
|
||||
|
||||
def self.create_private_keys(raw, secret)
|
||||
parts(raw).select { |part| part.include?('PRIVATE KEY') }.each do |part|
|
||||
private_key = OpenSSL::PKey.read(part, secret)
|
||||
modulus = private_key.public_key.n.to_s(16)
|
||||
certificate = find_by(modulus: modulus)
|
||||
|
||||
raise Exceptions::UnprocessableEntity, 'Unable for find certificate for this private key.' if !certificate
|
||||
|
||||
certificate.update!(private_key: part, private_key_secret: secret)
|
||||
end
|
||||
end
|
||||
|
||||
def self.create_certificates(raw)
|
||||
parts(raw).select { |part| part.include?('CERTIFICATE') }.each_with_object([]) do |part, result|
|
||||
result << create!(public_key: part)
|
||||
end
|
||||
end
|
||||
|
||||
def self.parse(raw)
|
||||
OpenSSL::X509::Certificate.new(raw.gsub(%r{(?:TRUSTED\s)?(CERTIFICATE---)}, '\1'))
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -13,7 +13,7 @@ class TicketPolicy < ApplicationPolicy
|
||||
super
|
||||
end
|
||||
|
||||
def resolve # rubocop:disable Metrics/AbcSize
|
||||
def resolve # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
||||
raise NoMethodError, <<~ERR.chomp if instance_of?(TicketPolicy::BaseScope)
|
||||
specify an access type using a subclass of TicketPolicy::BaseScope
|
||||
ERR
|
||||
@@ -26,12 +26,19 @@ class TicketPolicy < ApplicationPolicy
|
||||
bind.push(user.group_ids_access(self.class::ACCESS_TYPE))
|
||||
end
|
||||
|
||||
if user.organization&.shared
|
||||
sql.push('(tickets.customer_id = ? OR tickets.organization_id = ?)')
|
||||
bind.push(user.id, user.organization.id)
|
||||
else
|
||||
sql.push('tickets.customer_id = ?')
|
||||
bind.push(user.id)
|
||||
if user.permissions?('ticket.customer')
|
||||
if user.organization&.shared
|
||||
sql.push('(tickets.customer_id = ? OR tickets.organization_id = ?)')
|
||||
bind.push(user.id, user.organization.id)
|
||||
else
|
||||
sql.push('tickets.customer_id = ?')
|
||||
bind.push(user.id)
|
||||
end
|
||||
end
|
||||
|
||||
# The report permission can access all tickets.
|
||||
if sql.empty? && !user.permissions?('report')
|
||||
sql.push '0 = 1' # Forbid unlimited access for all other permissions.
|
||||
end
|
||||
|
||||
scope.where sql.join(' OR '), *bind
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -28,4 +28,8 @@
|
||||
.header {
|
||||
background-color: <%= knowledge_base.color_header %>;
|
||||
}
|
||||
|
||||
.header .menu-item {
|
||||
color: <%= knowledge_base.color_header_link %>;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ...!"
|
||||
@@ -274,6 +274,10 @@ function elasticsearch_searchindex_rebuild () {
|
||||
function update_or_install () {
|
||||
|
||||
if [ -f ${ZAMMAD_DIR}/config/database.yml ]; then
|
||||
|
||||
echo "# Clear cache..."
|
||||
zammad run rails r Cache.clear
|
||||
|
||||
update_database
|
||||
|
||||
update_translations
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
11
db/migrate/20210923172256_issue_2619_kb_header_link_color.rb
Normal file
11
db/migrate/20210923172256_issue_2619_kb_header_link_color.rb
Normal 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
|
||||
11
db/migrate/20210929161701_reload_after_core_workflow.rb
Normal file
11
db/migrate/20210929161701_reload_after_core_workflow.rb
Normal 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
|
||||
@@ -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
|
||||
11
db/migrate/20211005110047_issue3787_fix_job.rb
Normal file
11
db/migrate/20211005110047_issue3787_fix_job.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class Issue3787FixJob < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
|
||||
# return if it's a new setup
|
||||
return if !Setting.exists?(name: 'system_init_done')
|
||||
|
||||
Scheduler.find_by(name: 'Delete old upload cache entries.').update(error_message: nil, status: nil, active: true)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,18 @@
|
||||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class Issue3810CustomDateAttributeNoDefault < ActiveRecord::Migration[6.0]
|
||||
def up
|
||||
return if !Setting.exists?(name: 'system_init_done')
|
||||
|
||||
ObjectManager::Attribute
|
||||
.where(data_type: %i[date datetime])
|
||||
.each { |elem| update_single(elem) }
|
||||
end
|
||||
|
||||
def update_single(elem)
|
||||
elem.data_option[:diff] = nil
|
||||
elem.save!
|
||||
rescue => e
|
||||
Rails.logger.error e
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,12 @@
|
||||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class MaintenanceRemoveActiveLdapSessions < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
return if !Setting.exists?(name: 'system_init_done')
|
||||
|
||||
# Only relevant for when ldap integration is used.
|
||||
return if !Setting.get('ldap_integration')
|
||||
|
||||
ActiveRecord::SessionStore::Session.destroy_all
|
||||
end
|
||||
end
|
||||
@@ -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',
|
||||
|
||||
@@ -9,6 +9,7 @@ Setting.create_if_not_exists(
|
||||
state: SecureRandom.hex(128),
|
||||
preferences: {
|
||||
permission: ['admin'],
|
||||
protected: true,
|
||||
},
|
||||
frontend: false
|
||||
)
|
||||
|
||||
@@ -21,6 +21,7 @@ class Auth
|
||||
end
|
||||
|
||||
def valid?
|
||||
return false if password.blank? && password_required?
|
||||
return false if !perform?
|
||||
|
||||
authenticated?
|
||||
@@ -28,6 +29,10 @@ class Auth
|
||||
|
||||
private
|
||||
|
||||
def password_required?
|
||||
true
|
||||
end
|
||||
|
||||
def perform?
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
@@ -20,6 +20,13 @@ class Auth
|
||||
false
|
||||
end
|
||||
|
||||
# No password required for developer mode and test environment.
|
||||
#
|
||||
# @returns [Boolean] false
|
||||
def password_required?
|
||||
false
|
||||
end
|
||||
|
||||
# Overwrites the default behaviour to check for a allowed environment.
|
||||
#
|
||||
# @returns [Boolean] true if the environment is development or test.
|
||||
|
||||
@@ -21,7 +21,6 @@ class Auth
|
||||
#
|
||||
# @returns [Boolean] true if a internal password for the user is present.
|
||||
def perform?
|
||||
return false if password.blank?
|
||||
return false if !user.verified && user.source == 'signup'
|
||||
|
||||
user.password.present?
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
require 'faker'
|
||||
|
||||
# rubocop:disable Rails/Output
|
||||
module FillDb
|
||||
|
||||
@@ -55,7 +53,7 @@ or if you only want to create 100 tickets
|
||||
else
|
||||
(1..organizations).each do
|
||||
ActiveRecord::Base.transaction do
|
||||
organization = Organization.create!(name: "FillOrganization::#{Faker::Number.number(digits: 6)}", active: true)
|
||||
organization = Organization.create!(name: "FillOrganization::#{counter}", active: true)
|
||||
organization_pool.push organization
|
||||
end
|
||||
end
|
||||
@@ -72,7 +70,7 @@ or if you only want to create 100 tickets
|
||||
|
||||
(1..agents).each do
|
||||
ActiveRecord::Base.transaction do
|
||||
suffix = Faker::Number.number(digits: 5).to_s
|
||||
suffix = counter.to_s
|
||||
user = User.create_or_update(
|
||||
login: "filldb-agent-#{suffix}",
|
||||
firstname: "agent #{suffix}",
|
||||
@@ -102,7 +100,7 @@ or if you only want to create 100 tickets
|
||||
|
||||
(1..customers).each do
|
||||
ActiveRecord::Base.transaction do
|
||||
suffix = Faker::Number.number(digits: 5).to_s
|
||||
suffix = counter.to_s
|
||||
organization = nil
|
||||
if organization_pool.present? && true_or_false.sample
|
||||
organization = organization_pool.sample
|
||||
@@ -132,7 +130,7 @@ or if you only want to create 100 tickets
|
||||
else
|
||||
(1..groups).each do
|
||||
ActiveRecord::Base.transaction do
|
||||
group = Group.create!(name: "FillGroup::#{Faker::Number.number(digits: 6)}", active: true)
|
||||
group = Group.create!(name: "FillGroup::#{counter}", active: true)
|
||||
group_pool.push group
|
||||
Role.where(name: 'Agent').first.users.where(active: true).each do |user|
|
||||
user_groups = user.groups
|
||||
@@ -150,7 +148,7 @@ or if you only want to create 100 tickets
|
||||
(1..overviews).each do
|
||||
ActiveRecord::Base.transaction do
|
||||
Overview.create!(
|
||||
name: "Filloverview::#{Faker::Number.number(digits: 6)}",
|
||||
name: "Filloverview::#{counter}",
|
||||
role_ids: [Role.find_by(name: 'Agent').id],
|
||||
condition: {
|
||||
'ticket.state_id' => {
|
||||
@@ -185,7 +183,7 @@ or if you only want to create 100 tickets
|
||||
customer = customer_pool.sample
|
||||
agent = agent_pool.sample
|
||||
ticket = Ticket.create!(
|
||||
title: "some title äöüß#{Faker::Number.number(digits: 6)}",
|
||||
title: "some title äöüß#{counter}",
|
||||
group: group_pool.sample,
|
||||
customer: customer,
|
||||
owner: agent,
|
||||
@@ -200,8 +198,8 @@ or if you only want to create 100 tickets
|
||||
ticket_id: ticket.id,
|
||||
from: customer.email,
|
||||
to: 'some_recipient@example.com',
|
||||
subject: "some subject#{Faker::Number.number(digits: 6)}",
|
||||
message_id: "some@id-#{Faker::Number.number(digits: 6)}",
|
||||
subject: "some subject#{counter}",
|
||||
message_id: "some@id-#{counter}",
|
||||
body: 'some message ...',
|
||||
internal: false,
|
||||
sender: Ticket::Article::Sender.where(name: 'Customer').first,
|
||||
@@ -214,5 +212,10 @@ or if you only want to create 100 tickets
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.counter
|
||||
@counter ||= SecureRandom.random_number(1_000_000)
|
||||
@counter += 1
|
||||
end
|
||||
end
|
||||
# rubocop:enable Rails/Output
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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}, '')
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
52
lib/user_info/assets.rb
Normal 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
|
||||
16
public/assets/chat/Dockerfile
Normal file
16
public/assets/chat/Dockerfile
Normal 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"]
|
||||
5
public/assets/chat/README.md
Normal file
5
public/assets/chat/README.md
Normal 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
8
public/assets/chat/build.sh
Executable 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
|
||||
@@ -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]')
|
||||
@@ -1093,16 +1097,14 @@ do(window) ->
|
||||
return
|
||||
if @initDelayId
|
||||
clearTimeout(@initDelayId)
|
||||
if !@sessionId
|
||||
@log.debug 'can\'t close widget without sessionId'
|
||||
return
|
||||
if @sessionId
|
||||
@log.debug 'session close before widget close'
|
||||
@sessionClose()
|
||||
|
||||
@log.debug 'close widget'
|
||||
|
||||
event.stopPropagation() if event
|
||||
|
||||
@sessionClose()
|
||||
|
||||
if @isFullscreen
|
||||
@enableScrollOnRoot()
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user