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