Merge branch 'develop' into private-rl_2278_email_display_name

This commit is contained in:
Martin Edenhofer 2018-10-29 13:59:36 +01:00
commit 9fc841d7a1
20 changed files with 244 additions and 88 deletions

View file

@ -1,7 +1,7 @@
# Change Log
## [2.7.0](https://github.com/zammad/zammad/tree/2.7.0) (2018-xx-xx)
[Full Changelog](https://github.com/zammad/zammad/compare/2.6.0...2.7.0)
## [2.8.0](https://github.com/zammad/zammad/tree/2.8.0) (2018-xx-xx)
[Full Changelog](https://github.com/zammad/zammad/compare/2.7.0...2.8.0)
**Implemented enhancements:**
@ -13,4 +13,3 @@
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*

View file

@ -1 +1 @@
2.7.x
2.8.x

View file

@ -364,6 +364,11 @@ class App.UiElement.ticket_perform_action
object: 'Ticket'
display: 'Ticket'
},
{
prefix: 'article'
object: 'TicketArticle'
display: 'Article'
},
{
prefix: 'user'
object: 'User'

View file

@ -193,7 +193,7 @@ class Graph extends App.ControllerContent
else if @params.timeRange is 'week'
xaxis = [[0, 'Mon'], [1, 'Tue'], [2, 'Wed'], [3, 'Thr'], [4, 'Fri'], [5, 'Sat'], [6, 'Sun'] ]
else
xaxis = [[0, 'Jan'], [1, 'Feb'], [2, 'Mar'], [3, 'Apr'], [4, 'Mai'], [5, 'Jun'], [6, 'Jul'], [7, 'Aug'], [8, 'Sep'], [9, 'Oct'], [10, 'Nov'], [11, 'Dec']]
xaxis = [[0, 'Jan'], [1, 'Feb'], [2, 'Mar'], [3, 'Apr'], [4, 'May'], [5, 'Jun'], [6, 'Jul'], [7, 'Aug'], [8, 'Sep'], [9, 'Oct'], [10, 'Nov'], [11, 'Dec']]
dataPlot = []
for key, value of data
@ -489,7 +489,7 @@ class TimePicker extends App.Controller
value: 4,
},
{
display: 'Mai'
display: 'May'
value: 5,
},
{

View file

@ -61,7 +61,7 @@ class Index extends App.ControllerSubContent
value: 4,
},
{
display: 'Mai'
display: 'May'
value: 5,
},
{

View file

@ -1028,7 +1028,7 @@ class App.Utils
# the article we are replying to is an incoming call
else if article.from?.match(/@/)
articleNew.to = article.from
articleNew.to = App.Utils.parseAddressListLocal(article.from).join(', ')
# if sender is customer but in article.from is no email, try to get
# customers email via customer user
@ -1146,7 +1146,7 @@ class App.Utils
html.find('img').each( (index) ->
src = $(@).attr('src')
if !src.match(/^data:/i)
if !src.match(/^(data|cid):/i) # <img src="cid: ..."> may mean broken emails (see issue #2305)
base64 = App.Utils._htmlImage2DataUrl(@)
$(@).attr('src', base64)
)

View file

@ -105,6 +105,7 @@ class App.User extends App.Model
placement: placement
vip: vip
url: @imageUrl()
initials: @initials()
isOutOfOffice: ->
return false if @out_of_office isnt true

View file

@ -1,3 +1,3 @@
<span class="avatar <%- @cssClass %>" style="background-image: url(<%- @url %>)"<%- @placement %><%- @data %>>
<span class="avatar <%- @cssClass %>" style="background-image: url(<%- @url %>)"<%- @placement %><%- @data %> data-initials="<%- @initials %>">
<%- @Icon('crown') if @vip %>
</span>

View file

@ -201,6 +201,35 @@ th.js-tableHead:not([data-column-key="icon"]) {
.avatar {
border: 1px solid black;
&:not(.avatar--unique) {
&:before {
content: attr(data-initials);
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
text-align: center;
font-size: 13px;
line-height: 40px;
}
&.size-30:before {
font-size: 10px;
line-height: 32px;
}
&.size-50:before {
font-size: 16px;
line-height: 52px;
}
&.size-80:before {
font-size: 26px;
line-height: 84px;
}
}
}

View file

@ -6,6 +6,7 @@ class Observer::Ticket::Article::CommunicateSms::BackgroundJob
def perform
article = Ticket::Article.find(@article_id)
# set retry count
article.preferences['delivery_retry'] ||= 0
article.preferences['delivery_retry'] += 1

View file

@ -7,8 +7,8 @@ class Observer::Ticket::Article::CommunicateTelegram::BackgroundJob
article = Ticket::Article.find(@article_id)
# set retry count
record.preferences['delivery_retry'] ||= 0
record.preferences['delivery_retry'] += 1
article.preferences['delivery_retry'] ||= 0
article.preferences['delivery_retry'] += 1
ticket = Ticket.lookup(id: article.ticket_id)
log_error(article, "Can't find ticket.preferences for Ticket.find(#{article.ticket_id})") if !ticket.preferences

View file

@ -7,8 +7,8 @@ class Observer::Ticket::Article::CommunicateTwitter::BackgroundJob
article = Ticket::Article.find(@article_id)
# set retry count
record.preferences['delivery_retry'] ||= 0
record.preferences['delivery_retry'] += 1
article.preferences['delivery_retry'] ||= 0
article.preferences['delivery_retry'] += 1
ticket = Ticket.lookup(id: article.ticket_id)
log_error(article, "Can't find ticket.preferences for Ticket.find(#{article.ticket_id})") if !ticket.preferences
@ -62,7 +62,7 @@ class Observer::Ticket::Article::CommunicateTwitter::BackgroundJob
mention_ids.push user.id
end
article.to = to
article.preferences['twitter'] = {
article.preferences['twitter'] = TweetBase.preferences_cleanup(
mention_ids: mention_ids,
geo: tweet.geo,
retweeted: tweet.retweeted?,
@ -74,7 +74,7 @@ class Observer::Ticket::Article::CommunicateTwitter::BackgroundJob
favorited: tweet.favorited?,
truncated: tweet.truncated?,
created_at: tweet.created_at,
}
)
end
else
raise "Unknown tweet type '#{tweet.class}'"

View file

@ -1,40 +0,0 @@
class FixedTwitterTicketArticlePreferences4 < ActiveRecord::Migration[5.0]
def up
# return if it's a new setup
return if !Setting.find_by(name: 'system_init_done')
# find article preferences with Twitter::NullObject and replace it with nill to prevent elasticsearch index issue
article_type_ids = Ticket::Article::Type.where(name: ['twitter status', 'twitter direct-message']).pluck(:id)
article_ids = Ticket::Article.where(type_id: article_type_ids).pluck(:id)
article_ids.each do |article_id|
article = Ticket::Article.find(article_id)
next if !article.preferences
changed = false
article.preferences.each_value do |value|
next if value.class != ActiveSupport::HashWithIndifferentAccess
value.each do |sub_key, sub_level|
if sub_level.class == NilClass
value[sub_key] = nil
next
end
if sub_level.class == Twitter::Place
value[sub_key] = sub_level.attrs
changed = true
next
end
next if sub_level.class != Twitter::NullObject
value[sub_key] = nil
changed = true
end
end
next if !changed
article.save!
end
end
end

View file

@ -1,4 +1,4 @@
class FixedTwitterTicketArticlePreferences5 < ActiveRecord::Migration[5.0]
class FixedTwitterTicketArticlePreferences7 < ActiveRecord::Migration[5.0]
def up
# return if it's a new setup
@ -21,7 +21,7 @@ class FixedTwitterTicketArticlePreferences5 < ActiveRecord::Migration[5.0]
next
end
if sub_level.class == Twitter::Place || sub_level.class == Twitter::Geo
value[sub_key] = sub_level.attrs
value[sub_key] = sub_level.to_h
changed = true
next
end
@ -31,6 +31,17 @@ class FixedTwitterTicketArticlePreferences5 < ActiveRecord::Migration[5.0]
changed = true
end
end
if article.preferences[:twitter]&.key?(:geo) && article.preferences[:twitter][:geo].nil?
article.preferences[:twitter][:geo] = {}
changed = true
end
if article.preferences[:twitter]&.key?(:place) && article.preferences[:twitter][:place].nil?
article.preferences[:twitter][:place] = {}
changed = true
end
next if !changed
article.save!

View file

@ -6,7 +6,14 @@ class CtiGenericApi2 < ActiveRecord::Migration[5.1]
return if !column_exists?(:cti_logs, :initialized_at)
return if column_exists?(:cti_logs, :initialized_at_cleanup)
add_column :cti_logs, :initialized_at_cleanup, :timestamp, limit: 3, null: true
if ActiveRecord::Base.connection_config[:adapter] == 'mysql2'
# disable the MySQL strict_mode for the current connection
execute("SET sql_mode = ''")
add_column :cti_logs, :initialized_at_cleanup, :timestamp, limit: 3, null: true, default: '0000-00-00 00:00:00'
else
add_column :cti_logs, :initialized_at_cleanup, :timestamp, limit: 3, null: true
end
Cti::Log.connection.schema_cache.clear!
Cti::Log.reset_column_information

View file

@ -208,7 +208,7 @@ class TweetBase
end
article_preferences = {
twitter: twitter_preferences,
twitter: self.class.preferences_cleanup(twitter_preferences),
links: [
{
url: "https://twitter.com/statuses/#{tweet.id}",
@ -228,7 +228,7 @@ class TweetBase
type_id: Ticket::Article::Type.find_by(name: article_type).id,
sender_id: Ticket::Article::Sender.find_by(name: 'Customer').id,
internal: false,
preferences: preferences_cleanup(article_preferences),
preferences: article_preferences,
)
end
@ -366,10 +366,50 @@ class TweetBase
false
end
def preferences_cleanup(preferences)
=begin
replace Twitter::Place and Twitter::Geo as hash and replace Twitter::NullObject with nil
preferences = TweetBase.preferences_cleanup(
twitter: twitter_preferences,
links: [
{
url: 'https://twitter.com/statuses/123',
target: '_blank',
name: 'on Twitter',
},
],
)
or
preferences = {
twitter: TweetBase.preferences_cleanup(twitter_preferences),
links: [
{
url: 'https://twitter.com/statuses/123',
target: '_blank',
name: 'on Twitter',
},
],
}
=end
def self.preferences_cleanup(preferences)
# replace Twitter::NullObject with nill to prevent elasticsearch index issue
preferences.each_value do |value|
preferences.each do |key, value|
if value.class == Twitter::Place || value.class == Twitter::Geo
preferences[key] = value.to_h
next
end
if value.class == Twitter::NullObject
preferences[key] = nil
next
end
next if !value.is_a?(Hash)
value.each do |sub_key, sub_level|
@ -378,7 +418,7 @@ class TweetBase
next
end
if sub_level.class == Twitter::Place || sub_level.class == Twitter::Geo
value[sub_key] = sub_level.attrs
value[sub_key] = sub_level.to_h
next
end
next if sub_level.class != Twitter::NullObject
@ -386,6 +426,23 @@ class TweetBase
value[sub_key] = nil
end
end
if preferences[:twitter]
if preferences[:twitter][:geo].blank?
preferences[:twitter][:geo] = {}
end
if preferences[:twitter][:place].blank?
preferences[:twitter][:place] = {}
end
else
if preferences[:geo].blank?
preferences[:geo] = {}
end
if preferences[:place].blank?
preferences[:place] = {}
end
end
preferences
end

View file

@ -2944,7 +2944,7 @@ test('check getRecipientArticle format', function() {
sender: {
name: 'Customer',
},
from: article_customer.email,
from: 'article lastname <article_customer@example.com>',
to: 'some group',
message_id: 'message_id22',
created_by: {
@ -3079,6 +3079,11 @@ test("htmlImage2DataUrl", function() {
result = App.Utils.htmlImage2DataUrl(source)
equal(result, should, source)
source = '<img src="cid:1234">some test'
should = '<img src="cid:1234">some test'
result = App.Utils.htmlImage2DataUrl(source)
equal(result, should, source)
});
source = '<img src="/assets/images/avatar-bg.png">some test'

View file

@ -13,7 +13,7 @@ class AdminCalendarSlaTest < TestCase
calendar_name = "ZZZ some calendar #{rand(99_999_999)}"
sla_name = "ZZZ some sla #{rand(99_999_999)}"
timezone = 'Europe/Berlin'
timezone_verify = 'Europe/Berlin (GMT+2)'
timezone_verify = "Europe/Berlin\s\\(GMT\\+(2|1)\\)"
calendar_create(
data: {
name: calendar_name,
@ -66,7 +66,7 @@ class AdminCalendarSlaTest < TestCase
)
watch_for(
css: '.content.active .modal input.js-input',
value: Regexp.quote(timezone_verify),
value: timezone_verify,
timeout: 4,
)
modal_close()

View file

@ -991,7 +991,7 @@ html.html2html_strict
html = '<img style="width: 181px; height: 125px" src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/...">'
(body, attachments_inline) = HtmlSanitizer.replace_inline_images(html)
assert_match(/<img style="width: 181px; height: 125px" src="cid:.+?">/, body)
assert(1, attachments_inline.count)
assert_equal(1, attachments_inline.count)
assert_equal('image1.jpeg', attachments_inline[0][:filename])
assert_equal('image/jpeg', attachments_inline[0][:preferences]['Content-Type'])
assert_match(/@#{Setting.get('fqdn')}/, attachments_inline[0][:preferences]['Content-ID'])
@ -1000,7 +1000,7 @@ html.html2html_strict
html = '<img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/..." style="width: 181px; height: 125px" alt="abc">'
(body, attachments_inline) = HtmlSanitizer.replace_inline_images(html)
assert_match(/<img src="cid:.+?" style="width: 181px; height: 125px" alt="abc">/, body)
assert(1, attachments_inline.count)
assert_equal(1, attachments_inline.count)
assert_equal('image1.jpeg', attachments_inline[0][:filename])
assert_equal('image/jpeg', attachments_inline[0][:preferences]['Content-Type'])
assert_match(/@#{Setting.get('fqdn')}/, attachments_inline[0][:preferences]['Content-ID'])
@ -1009,7 +1009,7 @@ html.html2html_strict
html = '<img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/..." style="width: 181px; height: 125px" alt="abc"><invalid what ever'
(body, attachments_inline) = HtmlSanitizer.replace_inline_images(html)
assert_match(/<img src="cid:.+?" style="width: 181px; height: 125px" alt="abc">/, body)
assert(1, attachments_inline.count)
assert_equal(1, attachments_inline.count)
assert_equal('image1.jpeg', attachments_inline[0][:filename])
assert_equal('image/jpeg', attachments_inline[0][:preferences]['Content-Type'])
assert_match(/@#{Setting.get('fqdn')}/, attachments_inline[0][:preferences]['Content-ID'])
@ -1018,12 +1018,12 @@ html.html2html_strict
html = '<img src="/some_one.png" style="width: 181px; height: 125px" alt="abc">'
(body, attachments_inline) = HtmlSanitizer.replace_inline_images(html)
assert_match(/<img src="\/some_one.png" style="width: 181px; height: 125px" alt="abc">/, body)
assert(0, attachments_inline.count)
assert_equal(0, attachments_inline.count)
html = '<div><img style="width: 181px; height: 125px" src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/..."><p>123</p><img style="width: 181px; height: 125px" src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/..."></div>'
(body, attachments_inline) = HtmlSanitizer.replace_inline_images(html)
assert_match(/<div>\s+<img style="width: 181px; height: 125px" src="cid:.+?"><p>123<\/p>\s+<img style="width: 181px; height: 125px" src="cid:.+?">\s+<\/div>/, body)
assert(2, attachments_inline.count)
assert_equal(2, attachments_inline.count)
assert_equal('image1.jpeg', attachments_inline[0][:filename])
assert_equal('image/jpeg', attachments_inline[0][:preferences]['Content-Type'])
assert_match(/@#{Setting.get('fqdn')}/, attachments_inline[0][:preferences]['Content-ID'])

View file

@ -40,7 +40,7 @@ class TicketArticleTwitter < ActiveSupport::TestCase
truncated: false
}
preferences = {
twitter: twitter_preferences,
twitter: TweetBase.preferences_cleanup(twitter_preferences),
links: [
{
url: 'https://twitter.com/statuses/123',
@ -56,18 +56,58 @@ class TicketArticleTwitter < ActiveSupport::TestCase
from: '@example',
body: 'some tweet',
internal: false,
preferences: TweetBase.new.preferences_cleanup(preferences),
preferences: preferences,
updated_by_id: 1,
created_by_id: 1,
)
assert(article1.preferences[:twitter])
assert_equal(1_234_567_890, article1.preferences[:twitter][:mention_ids][0])
assert_nil(article1.preferences[:twitter][:geo])
assert_equal(NilClass, article1.preferences[:twitter][:geo].class)
assert_nil(article1.preferences[:twitter][:place])
assert_equal(NilClass, article1.preferences[:twitter][:place].class)
assert_equal(ActiveSupport::HashWithIndifferentAccess, article1.preferences[:twitter][:geo].class)
assert(article1.preferences[:twitter][:geo].blank?)
assert_equal(ActiveSupport::HashWithIndifferentAccess, article1.preferences[:twitter][:place].class)
assert(article1.preferences[:twitter][:place].blank?)
twitter_preferences = {
twitter_preferences = {
mention_ids: [1_234_567_890],
geo: Twitter::NullObject.new,
retweeted: false,
possibly_sensitive: false,
in_reply_to_user_id: 1_234_567_890,
place: Twitter::NullObject.new,
retweet_count: 0,
source: '<a href="http://example.com/software/tweetbot/mac" rel="nofollow">Tweetbot for Mac</a>',
favorited: false,
truncated: false
}
preferences = TweetBase.preferences_cleanup(
twitter: twitter_preferences,
links: [
{
url: 'https://twitter.com/statuses/123',
target: '_blank',
name: 'on Twitter',
},
],
)
article2 = Ticket::Article.create!(
ticket_id: ticket1.id,
type_id: Ticket::Article::Type.find_by(name: 'twitter status').id,
sender_id: Ticket::Article::Sender.find_by(name: 'Customer').id,
from: '@example',
body: 'some tweet',
internal: false,
preferences: preferences,
updated_by_id: 1,
created_by_id: 1,
)
assert(article2.preferences[:twitter])
assert_equal(1_234_567_890, article2.preferences[:twitter][:mention_ids][0])
assert_equal(ActiveSupport::HashWithIndifferentAccess, article2.preferences[:twitter][:geo].class)
assert(article2.preferences[:twitter][:geo].blank?)
assert_equal(ActiveSupport::HashWithIndifferentAccess, article2.preferences[:twitter][:place].class)
assert(article2.preferences[:twitter][:place].blank?)
twitter_preferences = {
mention_ids: [1_234_567_890],
geo: Twitter::Geo.new(coordinates: [1, 1]),
retweeted: false,
@ -80,7 +120,7 @@ class TicketArticleTwitter < ActiveSupport::TestCase
truncated: false
}
preferences = {
twitter: twitter_preferences,
twitter: TweetBase.preferences_cleanup(twitter_preferences),
links: [
{
url: 'https://twitter.com/statuses/123',
@ -90,23 +130,64 @@ class TicketArticleTwitter < ActiveSupport::TestCase
],
}
article2 = Ticket::Article.create!(
article3 = Ticket::Article.create!(
ticket_id: ticket1.id,
type_id: Ticket::Article::Type.find_by(name: 'twitter status').id,
sender_id: Ticket::Article::Sender.find_by(name: 'Customer').id,
from: '@example',
body: 'some tweet',
internal: false,
preferences: TweetBase.new.preferences_cleanup(preferences),
preferences: preferences,
updated_by_id: 1,
created_by_id: 1,
)
assert(article2.preferences[:twitter])
assert_equal(1_234_567_890, article2.preferences[:twitter][:mention_ids][0])
assert_equal(ActiveSupport::HashWithIndifferentAccess, article2.preferences[:twitter][:geo].class)
assert_equal({ 'coordinates' => [1, 1] }, article2.preferences[:twitter][:geo])
assert_equal(ActiveSupport::HashWithIndifferentAccess, article2.preferences[:twitter][:place].class)
assert_equal({ 'country' => 'da', 'name' => 'do', 'woeid' => 1, 'id' => 1 }, article2.preferences[:twitter][:place])
assert(article3.preferences[:twitter])
assert_equal(1_234_567_890, article3.preferences[:twitter][:mention_ids][0])
assert_equal(ActiveSupport::HashWithIndifferentAccess, article3.preferences[:twitter][:geo].class)
assert_equal({ 'coordinates' => [1, 1] }, article3.preferences[:twitter][:geo])
assert_equal(ActiveSupport::HashWithIndifferentAccess, article3.preferences[:twitter][:place].class)
assert_equal({ 'country' => 'da', 'name' => 'do', 'woeid' => 1, 'id' => 1 }, article3.preferences[:twitter][:place])
twitter_preferences = {
mention_ids: [1_234_567_890],
geo: Twitter::Geo.new(coordinates: [1, 1]),
retweeted: false,
possibly_sensitive: false,
in_reply_to_user_id: 1_234_567_890,
place: Twitter::Place.new(country: 'da', name: 'do', woeid: 1, id: 1),
retweet_count: 0,
source: '<a href="http://example.com/software/tweetbot/mac" rel="nofollow">Tweetbot for Mac</a>',
favorited: false,
truncated: false
}
preferences = TweetBase.preferences_cleanup(
twitter: twitter_preferences,
links: [
{
url: 'https://twitter.com/statuses/123',
target: '_blank',
name: 'on Twitter',
},
],
)
article4 = Ticket::Article.create!(
ticket_id: ticket1.id,
type_id: Ticket::Article::Type.find_by(name: 'twitter status').id,
sender_id: Ticket::Article::Sender.find_by(name: 'Customer').id,
from: '@example',
body: 'some tweet',
internal: false,
preferences: preferences,
updated_by_id: 1,
created_by_id: 1,
)
assert(article4.preferences[:twitter])
assert_equal(1_234_567_890, article4.preferences[:twitter][:mention_ids][0])
assert_equal(ActiveSupport::HashWithIndifferentAccess, article4.preferences[:twitter][:geo].class)
assert_equal({ 'coordinates' => [1, 1] }, article4.preferences[:twitter][:geo])
assert_equal(ActiveSupport::HashWithIndifferentAccess, article4.preferences[:twitter][:place].class)
assert_equal({ 'country' => 'da', 'name' => 'do', 'woeid' => 1, 'id' => 1 }, article4.preferences[:twitter][:place])
end