##// END OF EJS Templates
Wrap journal attributes with a journal parameter and use safe_attributes (#22575)....
Jean-Philippe Lang -
r15239:1f9bbd6b42b3
parent child
Show More
@@ -1,113 +1,111
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class JournalsController < ApplicationController
18 class JournalsController < ApplicationController
19 before_filter :find_journal, :only => [:edit, :update, :diff]
19 before_filter :find_journal, :only => [:edit, :update, :diff]
20 before_filter :find_issue, :only => [:new]
20 before_filter :find_issue, :only => [:new]
21 before_filter :find_optional_project, :only => [:index]
21 before_filter :find_optional_project, :only => [:index]
22 before_filter :authorize, :only => [:new, :edit, :update, :diff]
22 before_filter :authorize, :only => [:new, :edit, :update, :diff]
23 accept_rss_auth :index
23 accept_rss_auth :index
24 menu_item :issues
24 menu_item :issues
25
25
26 helper :issues
26 helper :issues
27 helper :custom_fields
27 helper :custom_fields
28 helper :queries
28 helper :queries
29 include QueriesHelper
29 include QueriesHelper
30 helper :sort
30 helper :sort
31 include SortHelper
31 include SortHelper
32
32
33 def index
33 def index
34 retrieve_query
34 retrieve_query
35 sort_init 'id', 'desc'
35 sort_init 'id', 'desc'
36 sort_update(@query.sortable_columns)
36 sort_update(@query.sortable_columns)
37 if @query.valid?
37 if @query.valid?
38 @journals = @query.journals(:order => "#{Journal.table_name}.created_on DESC",
38 @journals = @query.journals(:order => "#{Journal.table_name}.created_on DESC",
39 :limit => 25)
39 :limit => 25)
40 end
40 end
41 @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
41 @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
42 render :layout => false, :content_type => 'application/atom+xml'
42 render :layout => false, :content_type => 'application/atom+xml'
43 rescue ActiveRecord::RecordNotFound
43 rescue ActiveRecord::RecordNotFound
44 render_404
44 render_404
45 end
45 end
46
46
47 def diff
47 def diff
48 @issue = @journal.issue
48 @issue = @journal.issue
49 if params[:detail_id].present?
49 if params[:detail_id].present?
50 @detail = @journal.details.find_by_id(params[:detail_id])
50 @detail = @journal.details.find_by_id(params[:detail_id])
51 else
51 else
52 @detail = @journal.details.detect {|d| d.property == 'attr' && d.prop_key == 'description'}
52 @detail = @journal.details.detect {|d| d.property == 'attr' && d.prop_key == 'description'}
53 end
53 end
54 unless @issue && @detail
54 unless @issue && @detail
55 render_404
55 render_404
56 return false
56 return false
57 end
57 end
58 if @detail.property == 'cf'
58 if @detail.property == 'cf'
59 unless @detail.custom_field && @detail.custom_field.visible_by?(@issue.project, User.current)
59 unless @detail.custom_field && @detail.custom_field.visible_by?(@issue.project, User.current)
60 raise ::Unauthorized
60 raise ::Unauthorized
61 end
61 end
62 end
62 end
63 @diff = Redmine::Helpers::Diff.new(@detail.value, @detail.old_value)
63 @diff = Redmine::Helpers::Diff.new(@detail.value, @detail.old_value)
64 end
64 end
65
65
66 def new
66 def new
67 @journal = Journal.visible.find(params[:journal_id]) if params[:journal_id]
67 @journal = Journal.visible.find(params[:journal_id]) if params[:journal_id]
68 if @journal
68 if @journal
69 user = @journal.user
69 user = @journal.user
70 text = @journal.notes
70 text = @journal.notes
71 else
71 else
72 user = @issue.author
72 user = @issue.author
73 text = @issue.description
73 text = @issue.description
74 end
74 end
75 # Replaces pre blocks with [...]
75 # Replaces pre blocks with [...]
76 text = text.to_s.strip.gsub(%r{<pre>(.*?)</pre>}m, '[...]')
76 text = text.to_s.strip.gsub(%r{<pre>(.*?)</pre>}m, '[...]')
77 @content = "#{ll(Setting.default_language, :text_user_wrote, user)}\n> "
77 @content = "#{ll(Setting.default_language, :text_user_wrote, user)}\n> "
78 @content << text.gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n"
78 @content << text.gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n"
79 rescue ActiveRecord::RecordNotFound
79 rescue ActiveRecord::RecordNotFound
80 render_404
80 render_404
81 end
81 end
82
82
83 def edit
83 def edit
84 (render_403; return false) unless @journal.editable_by?(User.current)
84 (render_403; return false) unless @journal.editable_by?(User.current)
85 respond_to do |format|
85 respond_to do |format|
86 # TODO: implement non-JS journal update
86 # TODO: implement non-JS journal update
87 format.js
87 format.js
88 end
88 end
89 end
89 end
90
90
91 def update
91 def update
92 (render_403; return false) unless @journal.editable_by?(User.current)
92 (render_403; return false) unless @journal.editable_by?(User.current)
93 @journal.notes = params[:notes] if params[:notes]
93 @journal.safe_attributes = params[:journal]
94 @journal.private_notes = params[:private_notes].present?
94 @journal.save
95 (render_403; return false) if @journal.private_notes_changed? && User.current.allowed_to?(:set_notes_private, @journal.issue.project) == false
96 @journal.save if @journal.changed?
97 @journal.destroy if @journal.details.empty? && @journal.notes.blank?
95 @journal.destroy if @journal.details.empty? && @journal.notes.blank?
98 call_hook(:controller_journals_edit_post, { :journal => @journal, :params => params})
96 call_hook(:controller_journals_edit_post, { :journal => @journal, :params => params})
99 respond_to do |format|
97 respond_to do |format|
100 format.html { redirect_to issue_path(@journal.journalized) }
98 format.html { redirect_to issue_path(@journal.journalized) }
101 format.js
99 format.js
102 end
100 end
103 end
101 end
104
102
105 private
103 private
106
104
107 def find_journal
105 def find_journal
108 @journal = Journal.visible.find(params[:id])
106 @journal = Journal.visible.find(params[:id])
109 @project = @journal.journalized.project
107 @project = @journal.journalized.project
110 rescue ActiveRecord::RecordNotFound
108 rescue ActiveRecord::RecordNotFound
111 render_404
109 render_404
112 end
110 end
113 end
111 end
@@ -1,67 +1,67
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2016 Jean-Philippe Lang
4 # Copyright (C) 2006-2016 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 module JournalsHelper
20 module JournalsHelper
21
21
22 # Returns the attachments of a journal that are displayed as thumbnails
22 # Returns the attachments of a journal that are displayed as thumbnails
23 def journal_thumbnail_attachments(journal)
23 def journal_thumbnail_attachments(journal)
24 ids = journal.details.select {|d| d.property == 'attachment' && d.value.present?}.map(&:prop_key)
24 ids = journal.details.select {|d| d.property == 'attachment' && d.value.present?}.map(&:prop_key)
25 ids.any? ? Attachment.where(:id => ids).select(&:thumbnailable?) : []
25 ids.any? ? Attachment.where(:id => ids).select(&:thumbnailable?) : []
26 end
26 end
27
27
28 def render_notes(issue, journal, options={})
28 def render_notes(issue, journal, options={})
29 content = ''
29 content = ''
30 editable = User.current.logged? && (User.current.allowed_to?(:edit_issue_notes, issue.project) || (journal.user == User.current && User.current.allowed_to?(:edit_own_issue_notes, issue.project)))
30 editable = User.current.logged? && (User.current.allowed_to?(:edit_issue_notes, issue.project) || (journal.user == User.current && User.current.allowed_to?(:edit_own_issue_notes, issue.project)))
31 links = []
31 links = []
32 if !journal.notes.blank?
32 if !journal.notes.blank?
33 links << link_to(l(:button_quote),
33 links << link_to(l(:button_quote),
34 quoted_issue_path(issue, :journal_id => journal),
34 quoted_issue_path(issue, :journal_id => journal),
35 :remote => true,
35 :remote => true,
36 :method => 'post',
36 :method => 'post',
37 :title => l(:button_quote),
37 :title => l(:button_quote),
38 :class => 'icon-only icon-comment'
38 :class => 'icon-only icon-comment'
39 ) if options[:reply_links]
39 ) if options[:reply_links]
40 links << link_to(l(:button_edit),
40 links << link_to(l(:button_edit),
41 edit_journal_path(journal),
41 edit_journal_path(journal),
42 :remote => true,
42 :remote => true,
43 :method => 'get',
43 :method => 'get',
44 :title => l(:button_edit),
44 :title => l(:button_edit),
45 :class => 'icon-only icon-edit'
45 :class => 'icon-only icon-edit'
46 ) if editable
46 ) if editable
47 links << link_to(l(:button_delete),
47 links << link_to(l(:button_delete),
48 journal_path(journal, :notes => ""),
48 journal_path(journal, :journal => {:notes => ""}),
49 :remote => true,
49 :remote => true,
50 :method => 'put', :data => {:confirm => l(:text_are_you_sure)},
50 :method => 'put', :data => {:confirm => l(:text_are_you_sure)},
51 :title => l(:button_delete),
51 :title => l(:button_delete),
52 :class => 'icon-only icon-del'
52 :class => 'icon-only icon-del'
53 ) if editable
53 ) if editable
54 end
54 end
55 content << content_tag('div', links.join(' ').html_safe, :class => 'contextual') unless links.empty?
55 content << content_tag('div', links.join(' ').html_safe, :class => 'contextual') unless links.empty?
56 content << textilizable(journal, :notes)
56 content << textilizable(journal, :notes)
57 css_classes = "wiki"
57 css_classes = "wiki"
58 css_classes << " editable" if editable
58 css_classes << " editable" if editable
59 content_tag('div', content.html_safe, :id => "journal-#{journal.id}-notes", :class => css_classes)
59 content_tag('div', content.html_safe, :id => "journal-#{journal.id}-notes", :class => css_classes)
60 end
60 end
61
61
62 def render_private_notes(journal)
62 def render_private_notes(journal)
63 content = journal.private_notes? ? l(:field_is_private) : ''
63 content = journal.private_notes? ? l(:field_is_private) : ''
64 css_classes = journal.private_notes? ? 'private' : ''
64 css_classes = journal.private_notes? ? 'private' : ''
65 content_tag('span', content.html_safe, :id => "journal-#{journal.id}-private_notes", :class => css_classes)
65 content_tag('span', content.html_safe, :id => "journal-#{journal.id}-private_notes", :class => css_classes)
66 end
66 end
67 end
67 end
@@ -1,305 +1,312
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Journal < ActiveRecord::Base
18 class Journal < ActiveRecord::Base
19 include Redmine::SafeAttributes
20
19 belongs_to :journalized, :polymorphic => true
21 belongs_to :journalized, :polymorphic => true
20 # added as a quick fix to allow eager loading of the polymorphic association
22 # added as a quick fix to allow eager loading of the polymorphic association
21 # since always associated to an issue, for now
23 # since always associated to an issue, for now
22 belongs_to :issue, :foreign_key => :journalized_id
24 belongs_to :issue, :foreign_key => :journalized_id
23
25
24 belongs_to :user
26 belongs_to :user
25 has_many :details, :class_name => "JournalDetail", :dependent => :delete_all, :inverse_of => :journal
27 has_many :details, :class_name => "JournalDetail", :dependent => :delete_all, :inverse_of => :journal
26 attr_accessor :indice
28 attr_accessor :indice
27 attr_protected :id
29 attr_protected :id
28
30
29 acts_as_event :title => Proc.new {|o| status = ((s = o.new_status) ? " (#{s})" : nil); "#{o.issue.tracker} ##{o.issue.id}#{status}: #{o.issue.subject}" },
31 acts_as_event :title => Proc.new {|o| status = ((s = o.new_status) ? " (#{s})" : nil); "#{o.issue.tracker} ##{o.issue.id}#{status}: #{o.issue.subject}" },
30 :description => :notes,
32 :description => :notes,
31 :author => :user,
33 :author => :user,
32 :group => :issue,
34 :group => :issue,
33 :type => Proc.new {|o| (s = o.new_status) ? (s.is_closed? ? 'issue-closed' : 'issue-edit') : 'issue-note' },
35 :type => Proc.new {|o| (s = o.new_status) ? (s.is_closed? ? 'issue-closed' : 'issue-edit') : 'issue-note' },
34 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.issue.id, :anchor => "change-#{o.id}"}}
36 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.issue.id, :anchor => "change-#{o.id}"}}
35
37
36 acts_as_activity_provider :type => 'issues',
38 acts_as_activity_provider :type => 'issues',
37 :author_key => :user_id,
39 :author_key => :user_id,
38 :scope => preload({:issue => :project}, :user).
40 :scope => preload({:issue => :project}, :user).
39 joins("LEFT OUTER JOIN #{JournalDetail.table_name} ON #{JournalDetail.table_name}.journal_id = #{Journal.table_name}.id").
41 joins("LEFT OUTER JOIN #{JournalDetail.table_name} ON #{JournalDetail.table_name}.journal_id = #{Journal.table_name}.id").
40 where("#{Journal.table_name}.journalized_type = 'Issue' AND" +
42 where("#{Journal.table_name}.journalized_type = 'Issue' AND" +
41 " (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')").uniq
43 " (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')").uniq
42
44
43 before_create :split_private_notes
45 before_create :split_private_notes
44 after_create :send_notification
46 after_create :send_notification
45
47
46 scope :visible, lambda {|*args|
48 scope :visible, lambda {|*args|
47 user = args.shift || User.current
49 user = args.shift || User.current
48 joins(:issue => :project).
50 joins(:issue => :project).
49 where(Issue.visible_condition(user, *args)).
51 where(Issue.visible_condition(user, *args)).
50 where("(#{Journal.table_name}.private_notes = ? OR (#{Project.allowed_to_condition(user, :view_private_notes, *args)}))", false)
52 where("(#{Journal.table_name}.private_notes = ? OR (#{Project.allowed_to_condition(user, :view_private_notes, *args)}))", false)
51 }
53 }
52
54
55 safe_attributes 'notes',
56 :if => lambda {|journal, user| journal.new_record? || journal.editable_by?(user)}
57 safe_attributes 'private_notes',
58 :if => lambda {|journal, user| user.allowed_to?(:set_notes_private, journal.project)}
59
53 def initialize(*args)
60 def initialize(*args)
54 super
61 super
55 if journalized
62 if journalized
56 if journalized.new_record?
63 if journalized.new_record?
57 self.notify = false
64 self.notify = false
58 else
65 else
59 start
66 start
60 end
67 end
61 end
68 end
62 end
69 end
63
70
64 def save(*args)
71 def save(*args)
65 journalize_changes
72 journalize_changes
66 # Do not save an empty journal
73 # Do not save an empty journal
67 (details.empty? && notes.blank?) ? false : super
74 (details.empty? && notes.blank?) ? false : super
68 end
75 end
69
76
70 # Returns journal details that are visible to user
77 # Returns journal details that are visible to user
71 def visible_details(user=User.current)
78 def visible_details(user=User.current)
72 details.select do |detail|
79 details.select do |detail|
73 if detail.property == 'cf'
80 if detail.property == 'cf'
74 detail.custom_field && detail.custom_field.visible_by?(project, user)
81 detail.custom_field && detail.custom_field.visible_by?(project, user)
75 elsif detail.property == 'relation'
82 elsif detail.property == 'relation'
76 Issue.find_by_id(detail.value || detail.old_value).try(:visible?, user)
83 Issue.find_by_id(detail.value || detail.old_value).try(:visible?, user)
77 else
84 else
78 true
85 true
79 end
86 end
80 end
87 end
81 end
88 end
82
89
83 def each_notification(users, &block)
90 def each_notification(users, &block)
84 if users.any?
91 if users.any?
85 users_by_details_visibility = users.group_by do |user|
92 users_by_details_visibility = users.group_by do |user|
86 visible_details(user)
93 visible_details(user)
87 end
94 end
88 users_by_details_visibility.each do |visible_details, users|
95 users_by_details_visibility.each do |visible_details, users|
89 if notes? || visible_details.any?
96 if notes? || visible_details.any?
90 yield(users)
97 yield(users)
91 end
98 end
92 end
99 end
93 end
100 end
94 end
101 end
95
102
96 # Returns the JournalDetail for the given attribute, or nil if the attribute
103 # Returns the JournalDetail for the given attribute, or nil if the attribute
97 # was not updated
104 # was not updated
98 def detail_for_attribute(attribute)
105 def detail_for_attribute(attribute)
99 details.detect {|detail| detail.prop_key == attribute}
106 details.detect {|detail| detail.prop_key == attribute}
100 end
107 end
101
108
102 # Returns the new status if the journal contains a status change, otherwise nil
109 # Returns the new status if the journal contains a status change, otherwise nil
103 def new_status
110 def new_status
104 s = new_value_for('status_id')
111 s = new_value_for('status_id')
105 s ? IssueStatus.find_by_id(s.to_i) : nil
112 s ? IssueStatus.find_by_id(s.to_i) : nil
106 end
113 end
107
114
108 def new_value_for(prop)
115 def new_value_for(prop)
109 detail_for_attribute(prop).try(:value)
116 detail_for_attribute(prop).try(:value)
110 end
117 end
111
118
112 def editable_by?(usr)
119 def editable_by?(usr)
113 usr && usr.logged? && (usr.allowed_to?(:edit_issue_notes, project) || (self.user == usr && usr.allowed_to?(:edit_own_issue_notes, project)))
120 usr && usr.logged? && (usr.allowed_to?(:edit_issue_notes, project) || (self.user == usr && usr.allowed_to?(:edit_own_issue_notes, project)))
114 end
121 end
115
122
116 def project
123 def project
117 journalized.respond_to?(:project) ? journalized.project : nil
124 journalized.respond_to?(:project) ? journalized.project : nil
118 end
125 end
119
126
120 def attachments
127 def attachments
121 journalized.respond_to?(:attachments) ? journalized.attachments : nil
128 journalized.respond_to?(:attachments) ? journalized.attachments : nil
122 end
129 end
123
130
124 # Returns a string of css classes
131 # Returns a string of css classes
125 def css_classes
132 def css_classes
126 s = 'journal'
133 s = 'journal'
127 s << ' has-notes' unless notes.blank?
134 s << ' has-notes' unless notes.blank?
128 s << ' has-details' unless details.blank?
135 s << ' has-details' unless details.blank?
129 s << ' private-notes' if private_notes?
136 s << ' private-notes' if private_notes?
130 s
137 s
131 end
138 end
132
139
133 def notify?
140 def notify?
134 @notify != false
141 @notify != false
135 end
142 end
136
143
137 def notify=(arg)
144 def notify=(arg)
138 @notify = arg
145 @notify = arg
139 end
146 end
140
147
141 def notified_users
148 def notified_users
142 notified = journalized.notified_users
149 notified = journalized.notified_users
143 if private_notes?
150 if private_notes?
144 notified = notified.select {|user| user.allowed_to?(:view_private_notes, journalized.project)}
151 notified = notified.select {|user| user.allowed_to?(:view_private_notes, journalized.project)}
145 end
152 end
146 notified
153 notified
147 end
154 end
148
155
149 def recipients
156 def recipients
150 notified_users.map(&:mail)
157 notified_users.map(&:mail)
151 end
158 end
152
159
153 def notified_watchers
160 def notified_watchers
154 notified = journalized.notified_watchers
161 notified = journalized.notified_watchers
155 if private_notes?
162 if private_notes?
156 notified = notified.select {|user| user.allowed_to?(:view_private_notes, journalized.project)}
163 notified = notified.select {|user| user.allowed_to?(:view_private_notes, journalized.project)}
157 end
164 end
158 notified
165 notified
159 end
166 end
160
167
161 def watcher_recipients
168 def watcher_recipients
162 notified_watchers.map(&:mail)
169 notified_watchers.map(&:mail)
163 end
170 end
164
171
165 # Sets @custom_field instance variable on journals details using a single query
172 # Sets @custom_field instance variable on journals details using a single query
166 def self.preload_journals_details_custom_fields(journals)
173 def self.preload_journals_details_custom_fields(journals)
167 field_ids = journals.map(&:details).flatten.select {|d| d.property == 'cf'}.map(&:prop_key).uniq
174 field_ids = journals.map(&:details).flatten.select {|d| d.property == 'cf'}.map(&:prop_key).uniq
168 if field_ids.any?
175 if field_ids.any?
169 fields_by_id = CustomField.where(:id => field_ids).inject({}) {|h, f| h[f.id] = f; h}
176 fields_by_id = CustomField.where(:id => field_ids).inject({}) {|h, f| h[f.id] = f; h}
170 journals.each do |journal|
177 journals.each do |journal|
171 journal.details.each do |detail|
178 journal.details.each do |detail|
172 if detail.property == 'cf'
179 if detail.property == 'cf'
173 detail.instance_variable_set "@custom_field", fields_by_id[detail.prop_key.to_i]
180 detail.instance_variable_set "@custom_field", fields_by_id[detail.prop_key.to_i]
174 end
181 end
175 end
182 end
176 end
183 end
177 end
184 end
178 journals
185 journals
179 end
186 end
180
187
181 # Stores the values of the attributes and custom fields of the journalized object
188 # Stores the values of the attributes and custom fields of the journalized object
182 def start
189 def start
183 if journalized
190 if journalized
184 @attributes_before_change = journalized.journalized_attribute_names.inject({}) do |h, attribute|
191 @attributes_before_change = journalized.journalized_attribute_names.inject({}) do |h, attribute|
185 h[attribute] = journalized.send(attribute)
192 h[attribute] = journalized.send(attribute)
186 h
193 h
187 end
194 end
188 @custom_values_before_change = journalized.custom_field_values.inject({}) do |h, c|
195 @custom_values_before_change = journalized.custom_field_values.inject({}) do |h, c|
189 h[c.custom_field_id] = c.value
196 h[c.custom_field_id] = c.value
190 h
197 h
191 end
198 end
192 end
199 end
193 self
200 self
194 end
201 end
195
202
196 # Adds a journal detail for an attachment that was added or removed
203 # Adds a journal detail for an attachment that was added or removed
197 def journalize_attachment(attachment, added_or_removed)
204 def journalize_attachment(attachment, added_or_removed)
198 key = (added_or_removed == :removed ? :old_value : :value)
205 key = (added_or_removed == :removed ? :old_value : :value)
199 details << JournalDetail.new(
206 details << JournalDetail.new(
200 :property => 'attachment',
207 :property => 'attachment',
201 :prop_key => attachment.id,
208 :prop_key => attachment.id,
202 key => attachment.filename
209 key => attachment.filename
203 )
210 )
204 end
211 end
205
212
206 # Adds a journal detail for an issue relation that was added or removed
213 # Adds a journal detail for an issue relation that was added or removed
207 def journalize_relation(relation, added_or_removed)
214 def journalize_relation(relation, added_or_removed)
208 key = (added_or_removed == :removed ? :old_value : :value)
215 key = (added_or_removed == :removed ? :old_value : :value)
209 details << JournalDetail.new(
216 details << JournalDetail.new(
210 :property => 'relation',
217 :property => 'relation',
211 :prop_key => relation.relation_type_for(journalized),
218 :prop_key => relation.relation_type_for(journalized),
212 key => relation.other_issue(journalized).try(:id)
219 key => relation.other_issue(journalized).try(:id)
213 )
220 )
214 end
221 end
215
222
216 private
223 private
217
224
218 # Generates journal details for attribute and custom field changes
225 # Generates journal details for attribute and custom field changes
219 def journalize_changes
226 def journalize_changes
220 # attributes changes
227 # attributes changes
221 if @attributes_before_change
228 if @attributes_before_change
222 journalized.journalized_attribute_names.each {|attribute|
229 journalized.journalized_attribute_names.each {|attribute|
223 before = @attributes_before_change[attribute]
230 before = @attributes_before_change[attribute]
224 after = journalized.send(attribute)
231 after = journalized.send(attribute)
225 next if before == after || (before.blank? && after.blank?)
232 next if before == after || (before.blank? && after.blank?)
226 add_attribute_detail(attribute, before, after)
233 add_attribute_detail(attribute, before, after)
227 }
234 }
228 end
235 end
229 if @custom_values_before_change
236 if @custom_values_before_change
230 # custom fields changes
237 # custom fields changes
231 journalized.custom_field_values.each {|c|
238 journalized.custom_field_values.each {|c|
232 before = @custom_values_before_change[c.custom_field_id]
239 before = @custom_values_before_change[c.custom_field_id]
233 after = c.value
240 after = c.value
234 next if before == after || (before.blank? && after.blank?)
241 next if before == after || (before.blank? && after.blank?)
235
242
236 if before.is_a?(Array) || after.is_a?(Array)
243 if before.is_a?(Array) || after.is_a?(Array)
237 before = [before] unless before.is_a?(Array)
244 before = [before] unless before.is_a?(Array)
238 after = [after] unless after.is_a?(Array)
245 after = [after] unless after.is_a?(Array)
239
246
240 # values removed
247 # values removed
241 (before - after).reject(&:blank?).each do |value|
248 (before - after).reject(&:blank?).each do |value|
242 add_custom_value_detail(c, value, nil)
249 add_custom_value_detail(c, value, nil)
243 end
250 end
244 # values added
251 # values added
245 (after - before).reject(&:blank?).each do |value|
252 (after - before).reject(&:blank?).each do |value|
246 add_custom_value_detail(c, nil, value)
253 add_custom_value_detail(c, nil, value)
247 end
254 end
248 else
255 else
249 add_custom_value_detail(c, before, after)
256 add_custom_value_detail(c, before, after)
250 end
257 end
251 }
258 }
252 end
259 end
253 start
260 start
254 end
261 end
255
262
256 # Adds a journal detail for an attribute change
263 # Adds a journal detail for an attribute change
257 def add_attribute_detail(attribute, old_value, value)
264 def add_attribute_detail(attribute, old_value, value)
258 add_detail('attr', attribute, old_value, value)
265 add_detail('attr', attribute, old_value, value)
259 end
266 end
260
267
261 # Adds a journal detail for a custom field value change
268 # Adds a journal detail for a custom field value change
262 def add_custom_value_detail(custom_value, old_value, value)
269 def add_custom_value_detail(custom_value, old_value, value)
263 add_detail('cf', custom_value.custom_field_id, old_value, value)
270 add_detail('cf', custom_value.custom_field_id, old_value, value)
264 end
271 end
265
272
266 # Adds a journal detail
273 # Adds a journal detail
267 def add_detail(property, prop_key, old_value, value)
274 def add_detail(property, prop_key, old_value, value)
268 details << JournalDetail.new(
275 details << JournalDetail.new(
269 :property => property,
276 :property => property,
270 :prop_key => prop_key,
277 :prop_key => prop_key,
271 :old_value => old_value,
278 :old_value => old_value,
272 :value => value
279 :value => value
273 )
280 )
274 end
281 end
275
282
276 def split_private_notes
283 def split_private_notes
277 if private_notes?
284 if private_notes?
278 if notes.present?
285 if notes.present?
279 if details.any?
286 if details.any?
280 # Split the journal (notes/changes) so we don't have half-private journals
287 # Split the journal (notes/changes) so we don't have half-private journals
281 journal = Journal.new(:journalized => journalized, :user => user, :notes => nil, :private_notes => false)
288 journal = Journal.new(:journalized => journalized, :user => user, :notes => nil, :private_notes => false)
282 journal.details = details
289 journal.details = details
283 journal.save
290 journal.save
284 self.details = []
291 self.details = []
285 self.created_on = journal.created_on
292 self.created_on = journal.created_on
286 end
293 end
287 else
294 else
288 # Blank notes should not be private
295 # Blank notes should not be private
289 self.private_notes = false
296 self.private_notes = false
290 end
297 end
291 end
298 end
292 true
299 true
293 end
300 end
294
301
295 def send_notification
302 def send_notification
296 if notify? && (Setting.notified_events.include?('issue_updated') ||
303 if notify? && (Setting.notified_events.include?('issue_updated') ||
297 (Setting.notified_events.include?('issue_note_added') && notes.present?) ||
304 (Setting.notified_events.include?('issue_note_added') && notes.present?) ||
298 (Setting.notified_events.include?('issue_status_updated') && new_status.present?) ||
305 (Setting.notified_events.include?('issue_status_updated') && new_status.present?) ||
299 (Setting.notified_events.include?('issue_assigned_to_updated') && detail_for_attribute('assigned_to_id').present?) ||
306 (Setting.notified_events.include?('issue_assigned_to_updated') && detail_for_attribute('assigned_to_id').present?) ||
300 (Setting.notified_events.include?('issue_priority_updated') && new_value_for('priority_id').present?)
307 (Setting.notified_events.include?('issue_priority_updated') && new_value_for('priority_id').present?)
301 )
308 )
302 Mailer.deliver_issue_edit(self)
309 Mailer.deliver_issue_edit(self)
303 end
310 end
304 end
311 end
305 end
312 end
@@ -1,22 +1,24
1 <%= form_tag(journal_path(@journal),
1 <%= form_tag(journal_path(@journal),
2 :remote => true,
2 :remote => true,
3 :method => 'put',
3 :method => 'put',
4 :id => "journal-#{@journal.id}-form") do %>
4 :id => "journal-#{@journal.id}-form") do %>
5 <%= label_tag "notes", l(:description_notes), :class => "hidden-for-sighted" %>
5 <%= label_tag "notes", l(:description_notes), :class => "hidden-for-sighted" %>
6 <%= text_area_tag :notes, @journal.notes,
6 <%= text_area_tag 'journal[notes]', @journal.notes,
7 :id => "journal_#{@journal.id}_notes",
7 :id => "journal_#{@journal.id}_notes",
8 :class => 'wiki-edit',
8 :class => 'wiki-edit',
9 :rows => (@journal.notes.blank? ? 10 : [[10, @journal.notes.length / 50].max, 100].min) %>
9 :rows => (@journal.notes.blank? ? 10 : [[10, @journal.notes.length / 50].max, 100].min) %>
10 <% if @journal.issue.safe_attribute? 'private_notes' %>
10 <% if @journal.safe_attribute? 'private_notes' %>
11 <%= check_box_tag 'private_notes', '1', @journal.private_notes, :id => "journal_#{@journal.id}_private_notes" %> <label for="journal_<%= @journal.id %>_private_notes"><%= l(:field_private_notes) %></label>
11 <%= hidden_field_tag 'journal[private_notes]', '0' %>
12 <%= check_box_tag 'journal[private_notes]', '1', @journal.private_notes, :id => "journal_#{@journal.id}_private_notes" %>
13 <label for="journal_<%= @journal.id %>_private_notes"><%= l(:field_private_notes) %></label>
12 <% end %>
14 <% end %>
13 <%= call_hook(:view_journals_notes_form_after_notes, { :journal => @journal}) %>
15 <%= call_hook(:view_journals_notes_form_after_notes, { :journal => @journal}) %>
14 <p><%= submit_tag l(:button_save) %>
16 <p><%= submit_tag l(:button_save) %>
15 <%= preview_link preview_edit_issue_path(:project_id => @project, :id => @journal.issue),
17 <%= preview_link preview_edit_issue_path(:project_id => @project, :id => @journal.issue),
16 "journal-#{@journal.id}-form",
18 "journal-#{@journal.id}-form",
17 "journal_#{@journal.id}_preview" %> |
19 "journal_#{@journal.id}_preview" %> |
18 <%= link_to l(:button_cancel), '#', :onclick => "$('#journal-#{@journal.id}-form').remove(); $('#journal-#{@journal.id}-notes').show(); return false;" %></p>
20 <%= link_to l(:button_cancel), '#', :onclick => "$('#journal-#{@journal.id}-form').remove(); $('#journal-#{@journal.id}-notes').show(); return false;" %></p>
19
21
20 <div id="journal_<%= @journal.id %>_preview" class="wiki"></div>
22 <div id="journal_<%= @journal.id %>_preview" class="wiki"></div>
21 <% end %>
23 <% end %>
22 <%= wikitoolbar_for "journal_#{@journal.id}_notes" %>
24 <%= wikitoolbar_for "journal_#{@journal.id}_notes" %>
@@ -1,254 +1,255
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class JournalsControllerTest < ActionController::TestCase
20 class JournalsControllerTest < ActionController::TestCase
21 fixtures :projects, :users, :members, :member_roles, :roles, :issues, :journals, :journal_details, :enabled_modules,
21 fixtures :projects, :users, :members, :member_roles, :roles, :issues, :journals, :journal_details, :enabled_modules,
22 :trackers, :issue_statuses, :enumerations, :custom_fields, :custom_values, :custom_fields_projects, :projects_trackers
22 :trackers, :issue_statuses, :enumerations, :custom_fields, :custom_values, :custom_fields_projects, :projects_trackers
23
23
24 def setup
24 def setup
25 User.current = nil
25 User.current = nil
26 end
26 end
27
27
28 def test_index
28 def test_index
29 get :index, :project_id => 1
29 get :index, :project_id => 1
30 assert_response :success
30 assert_response :success
31 assert_not_nil assigns(:journals)
31 assert_not_nil assigns(:journals)
32 assert_equal 'application/atom+xml', @response.content_type
32 assert_equal 'application/atom+xml', @response.content_type
33 end
33 end
34
34
35 def test_index_with_invalid_query_id
35 def test_index_with_invalid_query_id
36 get :index, :project_id => 1, :query_id => 999
36 get :index, :project_id => 1, :query_id => 999
37 assert_response 404
37 assert_response 404
38 end
38 end
39
39
40 def test_index_should_return_privates_notes_with_permission_only
40 def test_index_should_return_privates_notes_with_permission_only
41 journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Privates notes', :private_notes => true, :user_id => 1)
41 journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Privates notes', :private_notes => true, :user_id => 1)
42 @request.session[:user_id] = 2
42 @request.session[:user_id] = 2
43
43
44 get :index, :project_id => 1
44 get :index, :project_id => 1
45 assert_response :success
45 assert_response :success
46 assert_include journal, assigns(:journals)
46 assert_include journal, assigns(:journals)
47
47
48 Role.find(1).remove_permission! :view_private_notes
48 Role.find(1).remove_permission! :view_private_notes
49 get :index, :project_id => 1
49 get :index, :project_id => 1
50 assert_response :success
50 assert_response :success
51 assert_not_include journal, assigns(:journals)
51 assert_not_include journal, assigns(:journals)
52 end
52 end
53
53
54 def test_index_should_show_visible_custom_fields_only
54 def test_index_should_show_visible_custom_fields_only
55 Issue.destroy_all
55 Issue.destroy_all
56 field_attributes = {:field_format => 'string', :is_for_all => true, :is_filter => true, :trackers => Tracker.all}
56 field_attributes = {:field_format => 'string', :is_for_all => true, :is_filter => true, :trackers => Tracker.all}
57 @fields = []
57 @fields = []
58 @fields << (@field1 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 1', :visible => true)))
58 @fields << (@field1 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 1', :visible => true)))
59 @fields << (@field2 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 2', :visible => false, :role_ids => [1, 2])))
59 @fields << (@field2 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 2', :visible => false, :role_ids => [1, 2])))
60 @fields << (@field3 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 3', :visible => false, :role_ids => [1, 3])))
60 @fields << (@field3 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 3', :visible => false, :role_ids => [1, 3])))
61 @issue = Issue.generate!(
61 @issue = Issue.generate!(
62 :author_id => 1,
62 :author_id => 1,
63 :project_id => 1,
63 :project_id => 1,
64 :tracker_id => 1,
64 :tracker_id => 1,
65 :custom_field_values => {@field1.id => 'Value0', @field2.id => 'Value1', @field3.id => 'Value2'}
65 :custom_field_values => {@field1.id => 'Value0', @field2.id => 'Value1', @field3.id => 'Value2'}
66 )
66 )
67 @issue.init_journal(User.find(1))
67 @issue.init_journal(User.find(1))
68 @issue.update_attribute :custom_field_values, {@field1.id => 'NewValue0', @field2.id => 'NewValue1', @field3.id => 'NewValue2'}
68 @issue.update_attribute :custom_field_values, {@field1.id => 'NewValue0', @field2.id => 'NewValue1', @field3.id => 'NewValue2'}
69
69
70
70
71 user_with_role_on_other_project = User.generate!
71 user_with_role_on_other_project = User.generate!
72 User.add_to_project(user_with_role_on_other_project, Project.find(2), Role.find(3))
72 User.add_to_project(user_with_role_on_other_project, Project.find(2), Role.find(3))
73 users_to_test = {
73 users_to_test = {
74 User.find(1) => [@field1, @field2, @field3],
74 User.find(1) => [@field1, @field2, @field3],
75 User.find(3) => [@field1, @field2],
75 User.find(3) => [@field1, @field2],
76 user_with_role_on_other_project => [@field1], # should see field1 only on Project 1
76 user_with_role_on_other_project => [@field1], # should see field1 only on Project 1
77 User.generate! => [@field1],
77 User.generate! => [@field1],
78 User.anonymous => [@field1]
78 User.anonymous => [@field1]
79 }
79 }
80
80
81 users_to_test.each do |user, visible_fields|
81 users_to_test.each do |user, visible_fields|
82 get :index, :format => 'atom', :key => user.rss_key
82 get :index, :format => 'atom', :key => user.rss_key
83 @fields.each_with_index do |field, i|
83 @fields.each_with_index do |field, i|
84 if visible_fields.include?(field)
84 if visible_fields.include?(field)
85 assert_select "content[type=html]", { :text => /NewValue#{i}/, :count => 1 }, "User #{user.id} was not able to view #{field.name} in API"
85 assert_select "content[type=html]", { :text => /NewValue#{i}/, :count => 1 }, "User #{user.id} was not able to view #{field.name} in API"
86 else
86 else
87 assert_select "content[type=html]", { :text => /NewValue#{i}/, :count => 0 }, "User #{user.id} was able to view #{field.name} in API"
87 assert_select "content[type=html]", { :text => /NewValue#{i}/, :count => 0 }, "User #{user.id} was able to view #{field.name} in API"
88 end
88 end
89 end
89 end
90 end
90 end
91
91
92 end
92 end
93
93
94 def test_diff_for_description_change
94 def test_diff_for_description_change
95 get :diff, :id => 3, :detail_id => 4
95 get :diff, :id => 3, :detail_id => 4
96 assert_response :success
96 assert_response :success
97 assert_template 'diff'
97 assert_template 'diff'
98
98
99 assert_select 'span.diff_out', :text => /removed/
99 assert_select 'span.diff_out', :text => /removed/
100 assert_select 'span.diff_in', :text => /added/
100 assert_select 'span.diff_in', :text => /added/
101 end
101 end
102
102
103 def test_diff_for_custom_field
103 def test_diff_for_custom_field
104 field = IssueCustomField.create!(:name => "Long field", :field_format => 'text')
104 field = IssueCustomField.create!(:name => "Long field", :field_format => 'text')
105 journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Notes', :user_id => 1)
105 journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Notes', :user_id => 1)
106 detail = JournalDetail.create!(:journal => journal, :property => 'cf', :prop_key => field.id,
106 detail = JournalDetail.create!(:journal => journal, :property => 'cf', :prop_key => field.id,
107 :old_value => 'Foo', :value => 'Bar')
107 :old_value => 'Foo', :value => 'Bar')
108
108
109 get :diff, :id => journal.id, :detail_id => detail.id
109 get :diff, :id => journal.id, :detail_id => detail.id
110 assert_response :success
110 assert_response :success
111 assert_template 'diff'
111 assert_template 'diff'
112
112
113 assert_select 'span.diff_out', :text => /Foo/
113 assert_select 'span.diff_out', :text => /Foo/
114 assert_select 'span.diff_in', :text => /Bar/
114 assert_select 'span.diff_in', :text => /Bar/
115 end
115 end
116
116
117 def test_diff_for_custom_field_should_be_denied_if_custom_field_is_not_visible
117 def test_diff_for_custom_field_should_be_denied_if_custom_field_is_not_visible
118 field = IssueCustomField.create!(:name => "Long field", :field_format => 'text', :visible => false, :role_ids => [1])
118 field = IssueCustomField.create!(:name => "Long field", :field_format => 'text', :visible => false, :role_ids => [1])
119 journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Notes', :user_id => 1)
119 journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Notes', :user_id => 1)
120 detail = JournalDetail.create!(:journal => journal, :property => 'cf', :prop_key => field.id,
120 detail = JournalDetail.create!(:journal => journal, :property => 'cf', :prop_key => field.id,
121 :old_value => 'Foo', :value => 'Bar')
121 :old_value => 'Foo', :value => 'Bar')
122
122
123 get :diff, :id => journal.id, :detail_id => detail.id
123 get :diff, :id => journal.id, :detail_id => detail.id
124 assert_response 302
124 assert_response 302
125 end
125 end
126
126
127 def test_diff_should_default_to_description_diff
127 def test_diff_should_default_to_description_diff
128 get :diff, :id => 3
128 get :diff, :id => 3
129 assert_response :success
129 assert_response :success
130 assert_template 'diff'
130 assert_template 'diff'
131
131
132 assert_select 'span.diff_out', :text => /removed/
132 assert_select 'span.diff_out', :text => /removed/
133 assert_select 'span.diff_in', :text => /added/
133 assert_select 'span.diff_in', :text => /added/
134 end
134 end
135
135
136 def test_reply_to_issue
136 def test_reply_to_issue
137 @request.session[:user_id] = 2
137 @request.session[:user_id] = 2
138 xhr :get, :new, :id => 6
138 xhr :get, :new, :id => 6
139 assert_response :success
139 assert_response :success
140 assert_template 'new'
140 assert_template 'new'
141 assert_equal 'text/javascript', response.content_type
141 assert_equal 'text/javascript', response.content_type
142 assert_include '> This is an issue', response.body
142 assert_include '> This is an issue', response.body
143 end
143 end
144
144
145 def test_reply_to_issue_without_permission
145 def test_reply_to_issue_without_permission
146 @request.session[:user_id] = 7
146 @request.session[:user_id] = 7
147 xhr :get, :new, :id => 6
147 xhr :get, :new, :id => 6
148 assert_response 403
148 assert_response 403
149 end
149 end
150
150
151 def test_reply_to_note
151 def test_reply_to_note
152 @request.session[:user_id] = 2
152 @request.session[:user_id] = 2
153 xhr :get, :new, :id => 6, :journal_id => 4
153 xhr :get, :new, :id => 6, :journal_id => 4
154 assert_response :success
154 assert_response :success
155 assert_template 'new'
155 assert_template 'new'
156 assert_equal 'text/javascript', response.content_type
156 assert_equal 'text/javascript', response.content_type
157 assert_include '> A comment with a private version', response.body
157 assert_include '> A comment with a private version', response.body
158 end
158 end
159
159
160 def test_reply_to_private_note_should_fail_without_permission
160 def test_reply_to_private_note_should_fail_without_permission
161 journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Privates notes', :private_notes => true)
161 journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Privates notes', :private_notes => true)
162 @request.session[:user_id] = 2
162 @request.session[:user_id] = 2
163
163
164 xhr :get, :new, :id => 2, :journal_id => journal.id
164 xhr :get, :new, :id => 2, :journal_id => journal.id
165 assert_response :success
165 assert_response :success
166 assert_template 'new'
166 assert_template 'new'
167 assert_equal 'text/javascript', response.content_type
167 assert_equal 'text/javascript', response.content_type
168 assert_include '> Privates notes', response.body
168 assert_include '> Privates notes', response.body
169
169
170 Role.find(1).remove_permission! :view_private_notes
170 Role.find(1).remove_permission! :view_private_notes
171 xhr :get, :new, :id => 2, :journal_id => journal.id
171 xhr :get, :new, :id => 2, :journal_id => journal.id
172 assert_response 404
172 assert_response 404
173 end
173 end
174
174
175 def test_edit_xhr
175 def test_edit_xhr
176 @request.session[:user_id] = 1
176 @request.session[:user_id] = 1
177 xhr :get, :edit, :id => 2
177 xhr :get, :edit, :id => 2
178 assert_response :success
178 assert_response :success
179 assert_template 'edit'
179 assert_template 'edit'
180 assert_equal 'text/javascript', response.content_type
180 assert_equal 'text/javascript', response.content_type
181 assert_include 'textarea', response.body
181 assert_include 'textarea', response.body
182 end
182 end
183
183
184 def test_edit_private_note_should_fail_without_permission
184 def test_edit_private_note_should_fail_without_permission
185 journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Privates notes', :private_notes => true)
185 journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Privates notes', :private_notes => true)
186 @request.session[:user_id] = 2
186 @request.session[:user_id] = 2
187 Role.find(1).add_permission! :edit_issue_notes
187 Role.find(1).add_permission! :edit_issue_notes
188
188
189 xhr :get, :edit, :id => journal.id
189 xhr :get, :edit, :id => journal.id
190 assert_response :success
190 assert_response :success
191 assert_template 'edit'
191 assert_template 'edit'
192 assert_equal 'text/javascript', response.content_type
192 assert_equal 'text/javascript', response.content_type
193 assert_include 'textarea', response.body
193 assert_include 'textarea', response.body
194
194
195 Role.find(1).remove_permission! :view_private_notes
195 Role.find(1).remove_permission! :view_private_notes
196 xhr :get, :edit, :id => journal.id
196 xhr :get, :edit, :id => journal.id
197 assert_response 404
197 assert_response 404
198 end
198 end
199
199
200 def test_update_xhr
200 def test_update_xhr
201 @request.session[:user_id] = 1
201 @request.session[:user_id] = 1
202 xhr :post, :update, :id => 2, :notes => 'Updated notes'
202 xhr :post, :update, :id => 2, :journal => {:notes => 'Updated notes'}
203 assert_response :success
203 assert_response :success
204 assert_template 'update'
204 assert_template 'update'
205 assert_equal 'text/javascript', response.content_type
205 assert_equal 'text/javascript', response.content_type
206 assert_equal 'Updated notes', Journal.find(2).notes
206 assert_equal 'Updated notes', Journal.find(2).notes
207 assert_include 'journal-2-notes', response.body
207 assert_include 'journal-2-notes', response.body
208 end
208 end
209
209
210 def test_update_xhr_with_private_notes_checked
210 def test_update_xhr_with_private_notes_checked
211 @request.session[:user_id] = 1
211 @request.session[:user_id] = 1
212 xhr :post, :update, :id => 2, :private_notes => '1'
212 xhr :post, :update, :id => 2, :journal => {:private_notes => '1'}
213 assert_response :success
213 assert_response :success
214 assert_template 'update'
214 assert_template 'update'
215 assert_equal 'text/javascript', response.content_type
215 assert_equal 'text/javascript', response.content_type
216 assert_equal true, Journal.find(2).private_notes
216 assert_equal true, Journal.find(2).private_notes
217 assert_include 'change-2', response.body
217 assert_include 'change-2', response.body
218 assert_include 'journal-2-private_notes', response.body
218 assert_include 'journal-2-private_notes', response.body
219 end
219 end
220
220
221 def test_update_xhr_with_private_notes_unchecked
221 def test_update_xhr_with_private_notes_unchecked
222 Journal.find(2).update_attributes(:private_notes => true)
222 Journal.find(2).update_attributes(:private_notes => true)
223 @request.session[:user_id] = 1
223 @request.session[:user_id] = 1
224 xhr :post, :update, :id => 2
224 xhr :post, :update, :id => 2, :journal => {:private_notes => '0'}
225 assert_response :success
225 assert_response :success
226 assert_template 'update'
226 assert_template 'update'
227 assert_equal 'text/javascript', response.content_type
227 assert_equal 'text/javascript', response.content_type
228 assert_equal false, Journal.find(2).private_notes
228 assert_equal false, Journal.find(2).private_notes
229 assert_include 'change-2', response.body
229 assert_include 'change-2', response.body
230 assert_include 'journal-2-private_notes', response.body
230 assert_include 'journal-2-private_notes', response.body
231 end
231 end
232
232
233 def test_update_xhr_with_private_notes_changes_and_without_set_private_notes_permission
233 def test_update_xhr_without_set_private_notes_permission_should_ignore_private_notes
234 @request.session[:user_id] = 2
234 @request.session[:user_id] = 2
235 Role.find(1).add_permission! :edit_issue_notes
235 Role.find(1).add_permission! :edit_issue_notes
236 Role.find(1).add_permission! :view_private_notes
236 Role.find(1).add_permission! :view_private_notes
237 Role.find(1).remove_permission! :set_notes_private
237 Role.find(1).remove_permission! :set_notes_private
238
238
239 xhr :post, :update, :id => 2, :private_notes => '1'
239 xhr :post, :update, :id => 2, :journal => {:private_notes => '1'}
240 assert_response 403
240 assert_response :success
241 assert_equal false, Journal.find(2).private_notes
241 end
242 end
242
243
243 def test_update_xhr_with_empty_notes_should_delete_the_journal
244 def test_update_xhr_with_empty_notes_should_delete_the_journal
244 @request.session[:user_id] = 1
245 @request.session[:user_id] = 1
245 assert_difference 'Journal.count', -1 do
246 assert_difference 'Journal.count', -1 do
246 xhr :post, :update, :id => 2, :notes => ''
247 xhr :post, :update, :id => 2, :journal => {:notes => ''}
247 assert_response :success
248 assert_response :success
248 assert_template 'update'
249 assert_template 'update'
249 assert_equal 'text/javascript', response.content_type
250 assert_equal 'text/javascript', response.content_type
250 end
251 end
251 assert_nil Journal.find_by_id(2)
252 assert_nil Journal.find_by_id(2)
252 assert_include 'change-2', response.body
253 assert_include 'change-2', response.body
253 end
254 end
254 end
255 end
General Comments 0
You need to be logged in to leave comments. Login now