diff --git a/.circleci/install.sh b/.circleci/install.sh
index 4eccd04e4..047315c7f 100755
--- a/.circleci/install.sh
+++ b/.circleci/install.sh
@@ -10,7 +10,7 @@ DB_CONFIG="test:\n adapter: postgresql\n database: zammad_test\n host: 127.0.
# install build dependencies
sudo apt-get update
-sudo apt-get install -y --no-install-recommends autoconf automake autotools-dev bison build-essential curl git-core libffi-dev libgdbm-dev libgmp-dev libmariadbclient-dev-compat libncurses5-dev libreadline-dev libsqlite3-dev libssl-dev libtool libxml2-dev libxslt1-dev libyaml-0-2 libyaml-dev patch pkg-config postfix sqlite3 zlib1g-dev
+sudo apt-get install -y --no-install-recommends autoconf automake autotools-dev bison build-essential curl git-core libffi-dev libgdbm-dev libgmp-dev libmariadbclient-dev-compat libncurses5-dev libreadline-dev libsqlite3-dev libssl-dev libtool libxml2-dev libxslt1-dev libyaml-0-2 libyaml-dev patch pkg-config postfix sqlite3 zlib1g-dev libimlib2 libimlib2-dev
if [ "${CIRCLE_JOB}" == "install-mysql" ]; then
DB_ADAPTER="mysql2"
diff --git a/.pkgr.yml b/.pkgr.yml
index 6e32b62eb..b5b36456d 100644
--- a/.pkgr.yml
+++ b/.pkgr.yml
@@ -10,36 +10,69 @@ targets:
- nginx
- postgresql-server
- which
- debian-8:
- dependencies:
- - curl
- - elasticsearch
- - nginx|apache2
- - postgresql|mysql-server|mariadb-server|sqlite
+ - epel-release
+ - imlib2
+ - imlib2-devel
+ build_dependencies:
+ - http://download.fedoraproject.org/pub/epel/7/x86_64/Packages/i/imlib2-1.4.5-9.el7.x86_64.rpm
+ - http://download.fedoraproject.org/pub/epel/7/x86_64/Packages/i/imlib2-devel-1.4.5-9.el7.x86_64.rpm
debian-9:
dependencies:
- curl
- elasticsearch
- nginx|apache2
- postgresql|mariadb-server|sqlite
+ - libimlib2
+ - libimlib2-dev
+ build_dependencies:
+ - libimlib2
+ - libimlib2-dev
ubuntu-16.04:
dependencies:
- curl
- elasticsearch
- nginx|apache2
- postgresql|mysql-server|mariadb-server|sqlite
+ - libimlib2
+ - libimlib2-dev
+ build_dependencies:
+ - libimlib2
+ - libimlib2-dev
ubuntu-18.04:
dependencies:
- curl
- elasticsearch
- nginx|apache2
- postgresql|mysql-server|mariadb-server|sqlite
+ - libimlib2
+ build_dependencies:
+ - libimlib2-dev
sles-12:
dependencies:
- curl
- elasticsearch
- nginx
- postgresql-server
+ - imlib2
+ - imlib2-devel
+ build_dependencies:
+ - imlib2
+ - imlib2-devel
+ sles-12:
+ dependencies:
+ - curl
+ - elasticsearch
+ - nginx
+ - postgresql-server
+ - imlib2
+ - libImlib2-1
+ - imlib2
+ - imlib2-devel
+ build_dependencies:
+ - https://ftp.gwdg.de/pub/opensuse/discontinued/distribution/12.3/repo/oss/suse/x86_64/imlib2-1.4.5-12.1.1.x86_64.rpm
+ - https://ftp.gwdg.de/pub/opensuse/discontinued/distribution/12.3/repo/oss/suse/x86_64/imlib2-devel-1.4.5-12.1.1.x86_64.rpm
+ - https://ftp.gwdg.de/pub/opensuse/discontinued/distribution/12.3/repo/oss/suse/x86_64/imlib2-filters-1.4.5-12.1.1.x86_64.rpm
+ - https://ftp.gwdg.de/pub/opensuse/discontinued/distribution/12.3/repo/oss/suse/x86_64/libImlib2-1-1.4.5-12.1.1.x86_64.rpm
before:
- contrib/packager.io/before.sh
after:
diff --git a/Gemfile b/Gemfile
index e52605d67..cca8287d4 100644
--- a/Gemfile
+++ b/Gemfile
@@ -116,6 +116,9 @@ gem 'autodiscover', git: 'https://github.com/zammad-deps/autodiscover'
gem 'rubyntlm', git: 'https://github.com/wimm/rubyntlm'
gem 'viewpoint'
+# image processing
+gem 'rszr'
+
# Gems used only for develop/test and not required
# in production environments by default.
group :development, :test do
diff --git a/Gemfile.lock b/Gemfile.lock
index 338636dc6..e358c64c6 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -408,6 +408,7 @@ GEM
rspec-mocks (~> 3.8.0)
rspec-support (~> 3.8.0)
rspec-support (3.8.0)
+ rszr (0.3.2)
rubocop (0.64.0)
jaro_winkler (~> 1.5.1)
parallel (~> 1.10)
@@ -584,6 +585,7 @@ DEPENDENCIES
rb-fsevent
rchardet (>= 1.8.0)
rspec-rails
+ rszr
rubocop
rubyntlm!
sassc-rails
diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/article_image_view.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/article_image_view.coffee
index df8661ed2..de542b3fc 100644
--- a/app/assets/javascripts/app/controllers/ticket_zoom/article_image_view.coffee
+++ b/app/assets/javascripts/app/controllers/ticket_zoom/article_image_view.coffee
@@ -11,9 +11,33 @@ class App.TicketZoomArticleImageView extends App.ControllerModal
'click .js-cancel': 'cancel'
'click .js-close': 'cancel'
+ constructor: ->
+ super
+ @unbindAll()
+ $(document).bind('keydown.image_preview', 'right', (e) =>
+ nextElement = @parentElement.closest('.attachment').next('.attachment.attachment--preview')
+ return if nextElement.length is 0
+ @close()
+ nextElement.find('img').click()
+ )
+ $(document).bind('keydown.image_preview', 'left', (e) =>
+ prevElement = @parentElement.closest('.attachment').prev('.attachment.attachment--preview')
+ return if prevElement.length is 0
+ @close()
+ prevElement.find('img').click()
+ )
+
content: ->
+ @image = @image.replace(/view=preview/, 'view=inline')
"
<% if attachment.preferences && attachment.preferences['Content-Type'] && @ContentTypeIcon(attachment.preferences['Content-Type']): %>
<% if @canPreview(attachment.preferences['Content-Type']): %>
-
+
<% else: %>
<%- @Icon( @ContentTypeIcon(attachment.preferences['Content-Type']) ) %>
<% end %>
diff --git a/app/controllers/ticket_articles_controller.rb b/app/controllers/ticket_articles_controller.rb
index 681ffb17b..138a23fdd 100644
--- a/app/controllers/ticket_articles_controller.rb
+++ b/app/controllers/ticket_articles_controller.rb
@@ -255,8 +255,21 @@ class TicketArticlesController < ApplicationController
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(
- file.content,
+ content,
filename: file.filename,
type: file.preferences['Content-Type'] || file.preferences['Mime-Type'] || 'application/octet-stream',
disposition: disposition
diff --git a/app/models/store.rb b/app/models/store.rb
index 5a79c1966..4827b2c5a 100644
--- a/app/models/store.rb
+++ b/app/models/store.rb
@@ -34,7 +34,7 @@ returns
=end
def self.add(data)
- data = data.stringify_keys
+ data.deep_stringify_keys!
# lookup store_object.id
store_object = Store::Object.create_if_not_exists(name: data['object'])
@@ -50,9 +50,34 @@ returns
data.delete('data')
data.delete('object')
+ data['preferences'] ||= {}
+ ['Mime-Type', 'Content-Type', 'mime_type', 'content_type'].each do |key|
+ next if data['preferences'][key].blank?
+ next if !data['preferences'][key].match(%r{image/(jpeg|jpg|png)}i)
+
+ data['preferences']['resizable'] = true
+ break
+ end
+
# store meta data
store = Store.create!(data)
+ begin
+ if store.preferences[:resizable] == true
+ if store.content_preview(silence: true)
+ store.preferences[:content_preview] = true
+ end
+ if store.content_inline(silence: true)
+ store.preferences[:content_inline] = true
+ end
+ store.save!
+ end
+ rescue => e
+ logger.error e
+ store.preferences[:resizable] = false
+ store.save!
+ end
+
store
end
@@ -165,6 +190,52 @@ returns
=begin
+get content of file in preview size
+
+ store = Store.find(store_id)
+ content_as_string = store.content_preview
+
+returns
+
+ content_as_string
+
+=end
+
+ def content_preview(options = {})
+ file = Store::File.find_by(id: store_file_id)
+ if !file
+ raise "No such file #{store_file_id}!"
+ end
+ raise 'Unable to generate preview' if options[:silence] != true && preferences[:content_preview] != true
+
+ image_resize(file.content, 200)
+ end
+
+=begin
+
+get content of file in inline size
+
+ store = Store.find(store_id)
+ content_as_string = store.content_inline
+
+returns
+
+ content_as_string
+
+=end
+
+ def content_inline(options = {})
+ file = Store::File.find_by(id: store_file_id)
+ if !file
+ raise "No such file #{store_file_id}!"
+ end
+ raise 'Unable to generate inline' if options[:silence] != true && preferences[:content_inline] != true
+
+ image_resize(file.content, 1800)
+ end
+
+=begin
+
get content of file
store = Store.find(store_id)
@@ -204,4 +275,32 @@ returns
file.provider
end
+
+ private
+
+ def image_resize(content, width)
+ local_sha = Digest::SHA256.hexdigest(content)
+
+ cache_key = "image-resize-#{local_sha}_#{width}"
+ all = nil
+ image = Cache.get(cache_key)
+ return image if image
+
+ temp_file = ::Tempfile.new
+ temp_file.binmode
+ temp_file.write(content)
+ temp_file.close
+ image = Rszr::Image.load(temp_file.path)
+ return if image.width < width
+
+ image.resize!(width, :auto)
+ temp_file_resize = ::Tempfile.new.path
+ image.save(temp_file_resize)
+ image_resized = ::File.binread(temp_file_resize)
+
+ Cache.write(cache_key, image_resized, { expires_in: 6.months })
+
+ image_resized
+ end
+
end
diff --git a/app/models/ticket/article.rb b/app/models/ticket/article.rb
index e5c1c56f7..d4f23bf83 100644
--- a/app/models/ticket/article.rb
+++ b/app/models/ticket/article.rb
@@ -82,7 +82,7 @@ returns
article['attachments'].each do |file|
next if !file[:preferences] || !file[:preferences]['Content-ID'] || (file[:preferences]['Content-ID'] != cid && file[:preferences]['Content-ID'] != "<#{cid}>" )
- replace = "#{tag_start}/api/v1/ticket_attachment/#{article['ticket_id']}/#{article['id']}/#{file[:id]}\"#{tag_end}>"
+ replace = "#{tag_start}/api/v1/ticket_attachment/#{article['ticket_id']}/#{article['id']}/#{file[:id]}?view=inline\"#{tag_end}>"
inline_attachments[file[:id]] = true
break
end
diff --git a/db/migrate/20190131000001_setting_change_ticket_zoom_attachment_preview.rb b/db/migrate/20190131000001_setting_change_ticket_zoom_attachment_preview.rb
new file mode 100644
index 000000000..296db7b3f
--- /dev/null
+++ b/db/migrate/20190131000001_setting_change_ticket_zoom_attachment_preview.rb
@@ -0,0 +1,34 @@
+class SettingChangeTicketZoomAttachmentPreview < ActiveRecord::Migration[5.1]
+ def up
+ # return if it's a new setup
+ return if !Setting.find_by(name: 'system_init_done')
+
+ Setting.create_or_update(
+ title: 'Sidebar Attachments',
+ name: 'ui_ticket_zoom_attachments_preview',
+ area: 'UI::TicketZoom::Preview',
+ description: 'Enables preview of attachments.',
+ options: {
+ form: [
+ {
+ display: '',
+ null: true,
+ name: 'ui_ticket_zoom_attachments_preview',
+ tag: 'boolean',
+ translate: true,
+ options: {
+ true => 'yes',
+ false => 'no',
+ },
+ },
+ ],
+ },
+ state: true,
+ preferences: {
+ prio: 400,
+ permission: ['admin.ui'],
+ },
+ frontend: true
+ )
+ end
+end
diff --git a/db/seeds/settings.rb b/db/seeds/settings.rb
index 778538f0c..0d6ce04e1 100644
--- a/db/seeds/settings.rb
+++ b/db/seeds/settings.rb
@@ -793,7 +793,7 @@ Setting.create_if_not_exists(
},
],
},
- state: false,
+ state: true,
preferences: {
prio: 400,
permission: ['admin.ui'],
diff --git a/public/assets/chat/chat-no-jquery.coffee b/public/assets/chat/chat-no-jquery.coffee
index f43d1285b..7b8fe5cc7 100644
--- a/public/assets/chat/chat-no-jquery.coffee
+++ b/public/assets/chat/chat-no-jquery.coffee
@@ -501,7 +501,7 @@ do(window) ->
stopPropagation: (event) ->
event.stopPropagation()
- onPaste: (e) =>
+ onDrop: (e) =>
e.stopPropagation()
e.preventDefault()
diff --git a/test/data/image/1000x1000.png b/test/data/image/1000x1000.png
new file mode 100644
index 000000000..02756786d
Binary files /dev/null and b/test/data/image/1000x1000.png differ
diff --git a/test/data/image/1x1.png b/test/data/image/1x1.png
new file mode 100644
index 000000000..a3e716292
Binary files /dev/null and b/test/data/image/1x1.png differ
diff --git a/test/unit/store_test.rb b/test/unit/store_test.rb
index 492fd313b..92b8657fc 100644
--- a/test/unit/store_test.rb
+++ b/test/unit/store_test.rb
@@ -151,4 +151,124 @@ class StoreTest < ActiveSupport::TestCase
assert_not(attachments[0])
end
end
+
+ test 'test resizable' do
+
+ # not possible
+ store = Store.add(
+ object: 'SomeObject1',
+ o_id: rand(1_234_567_890),
+ data: File.binread(Rails.root.join('test', 'data', 'upload', 'upload1.txt')),
+ filename: 'test1.pdf',
+ preferences: {
+ content_type: 'text/plain',
+ content_id: 234,
+ },
+ created_by_id: 1,
+ )
+ assert_not(store.preferences.key?(:resizable))
+ assert_not(store.preferences.key?(:content_inline))
+ assert_not(store.preferences.key?(:content_preview))
+ assert_raises(RuntimeError) do
+ store.content_inline
+ end
+ assert_raises(RuntimeError) do
+ store.content_preview
+ end
+
+ # not possible
+ store = Store.add(
+ object: 'SomeObject2',
+ o_id: rand(1_234_567_890),
+ data: File.binread(Rails.root.join('test', 'data', 'upload', 'upload1.txt')),
+ filename: 'test1.pdf',
+ preferences: {
+ content_type: 'image/jpg',
+ content_id: 234,
+ },
+ created_by_id: 1,
+ )
+ assert_equal(store.preferences[:resizable], false)
+ assert_not(store.preferences.key?(:content_inline))
+ assert_not(store.preferences.key?(:content_preview))
+ assert_raises(RuntimeError) do
+ store.content_inline
+ end
+ assert_raises(RuntimeError) do
+ store.content_preview
+ end
+
+ # possible (preview and inline)
+ store = Store.add(
+ object: 'SomeObject3',
+ o_id: rand(1_234_567_890),
+ data: File.binread(Rails.root.join('test', 'data', 'upload', 'upload2.jpg')),
+ filename: 'test1.pdf',
+ preferences: {
+ content_type: 'image/jpg',
+ content_id: 234,
+ },
+ created_by_id: 1,
+ )
+ assert_equal(store.preferences[:resizable], true)
+ assert_equal(store.preferences[:content_inline], true)
+ assert_equal(store.preferences[:content_preview], true)
+
+ temp_file = ::Tempfile.new.path
+ File.binwrite(temp_file, store.content_inline)
+ image = Rszr::Image.load(temp_file)
+ assert_equal(image.width, 1800)
+
+ temp_file = ::Tempfile.new.path
+ File.binwrite(temp_file, store.content_preview)
+ image = Rszr::Image.load(temp_file)
+ assert_equal(image.width, 200)
+
+ # possible (preview only)
+ store = Store.add(
+ object: 'SomeObject4',
+ o_id: rand(1_234_567_890),
+ data: File.binread(Rails.root.join('test', 'data', 'image', '1000x1000.png')),
+ filename: 'test1.png',
+ preferences: {
+ content_type: 'image/png',
+ content_id: 234,
+ },
+ created_by_id: 1,
+ )
+ assert_equal(store.preferences[:resizable], true)
+ assert_nil(store.preferences[:content_inline])
+ assert_equal(store.preferences[:content_preview], true)
+ assert_raises(RuntimeError) do
+ store.content_inline
+ end
+
+ temp_file = ::Tempfile.new.path
+ File.binwrite(temp_file, store.content_preview)
+ image = Rszr::Image.load(temp_file)
+ assert_equal(image.width, 200)
+
+ # possible (now preview or inline needed)
+ store = Store.add(
+ object: 'SomeObject5',
+ o_id: rand(1_234_567_890),
+ data: File.binread(Rails.root.join('test', 'data', 'image', '1x1.png')),
+ filename: 'test1.png',
+ preferences: {
+ content_type: 'image/png',
+ content_id: 234,
+ },
+ created_by_id: 1,
+ )
+ assert_equal(store.preferences[:resizable], true)
+ assert_nil(store.preferences[:content_inline])
+ assert_nil(store.preferences[:content_preview])
+ assert_raises(RuntimeError) do
+ store.content_inline
+ end
+ assert_raises(RuntimeError) do
+ store.content_preview
+ end
+
+ end
end