##// END OF EJS Templates
Use regular edit/update actions and named routes for JournalsController....
Jean-Philippe Lang -
r14692:6bb1ea8ae8a1
parent child
Show More
@@ -1,109 +1,110
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2015 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 before_filter :find_journal, :only => [:edit, :diff]
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 before_filter :authorize, :only => [:new, :edit, :diff]
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 if request.post?
85 respond_to do |format|
86 # TODO: implement non-JS journal update
87 format.js
88 end
89 end
90
91 def update
92 (render_403; return false) unless @journal.editable_by?(User.current)
86 93 @journal.update_attributes(:notes => params[:notes]) if params[:notes]
87 94 @journal.destroy if @journal.details.empty? && @journal.notes.blank?
88 95 call_hook(:controller_journals_edit_post, { :journal => @journal, :params => params})
89 96 respond_to do |format|
90 97 format.html { redirect_to issue_path(@journal.journalized) }
91 format.js { render :action => 'update' }
92 end
93 else
94 respond_to do |format|
95 # TODO: implement non-JS journal update
96 98 format.js
97 99 end
98 100 end
99 end
100 101
101 102 private
102 103
103 104 def find_journal
104 105 @journal = Journal.visible.find(params[:id])
105 106 @project = @journal.journalized.project
106 107 rescue ActiveRecord::RecordNotFound
107 108 render_404
108 109 end
109 110 end
@@ -1,515 +1,514
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2015 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 IssuesHelper
21 21 include ApplicationHelper
22 22 include Redmine::Export::PDF::IssuesPdfHelper
23 23
24 24 def issue_list(issues, &block)
25 25 ancestors = []
26 26 issues.each do |issue|
27 27 while (ancestors.any? && !issue.is_descendant_of?(ancestors.last))
28 28 ancestors.pop
29 29 end
30 30 yield issue, ancestors.size
31 31 ancestors << issue unless issue.leaf?
32 32 end
33 33 end
34 34
35 35 def grouped_issue_list(issues, query, issue_count_by_group, &block)
36 36 previous_group, first = false, true
37 37 totals_by_group = query.totalable_columns.inject({}) do |h, column|
38 38 h[column] = query.total_by_group_for(column)
39 39 h
40 40 end
41 41 issue_list(issues) do |issue, level|
42 42 group_name = group_count = nil
43 43 if query.grouped?
44 44 group = query.group_by_column.value(issue)
45 45 if first || group != previous_group
46 46 if group.blank? && group != false
47 47 group_name = "(#{l(:label_blank_value)})"
48 48 else
49 49 group_name = format_object(group)
50 50 end
51 51 group_name ||= ""
52 52 group_count = issue_count_by_group[group]
53 53 group_totals = totals_by_group.map {|column, t| total_tag(column, t[group] || 0)}.join(" ").html_safe
54 54 end
55 55 end
56 56 yield issue, level, group_name, group_count, group_totals
57 57 previous_group, first = group, false
58 58 end
59 59 end
60 60
61 61 # Renders a HTML/CSS tooltip
62 62 #
63 63 # To use, a trigger div is needed. This is a div with the class of "tooltip"
64 64 # that contains this method wrapped in a span with the class of "tip"
65 65 #
66 66 # <div class="tooltip"><%= link_to_issue(issue) %>
67 67 # <span class="tip"><%= render_issue_tooltip(issue) %></span>
68 68 # </div>
69 69 #
70 70 def render_issue_tooltip(issue)
71 71 @cached_label_status ||= l(:field_status)
72 72 @cached_label_start_date ||= l(:field_start_date)
73 73 @cached_label_due_date ||= l(:field_due_date)
74 74 @cached_label_assigned_to ||= l(:field_assigned_to)
75 75 @cached_label_priority ||= l(:field_priority)
76 76 @cached_label_project ||= l(:field_project)
77 77
78 78 link_to_issue(issue) + "<br /><br />".html_safe +
79 79 "<strong>#{@cached_label_project}</strong>: #{link_to_project(issue.project)}<br />".html_safe +
80 80 "<strong>#{@cached_label_status}</strong>: #{h(issue.status.name)}<br />".html_safe +
81 81 "<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />".html_safe +
82 82 "<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />".html_safe +
83 83 "<strong>#{@cached_label_assigned_to}</strong>: #{h(issue.assigned_to)}<br />".html_safe +
84 84 "<strong>#{@cached_label_priority}</strong>: #{h(issue.priority.name)}".html_safe
85 85 end
86 86
87 87 def issue_heading(issue)
88 88 h("#{issue.tracker} ##{issue.id}")
89 89 end
90 90
91 91 def render_issue_subject_with_tree(issue)
92 92 s = ''
93 93 ancestors = issue.root? ? [] : issue.ancestors.visible.to_a
94 94 ancestors.each do |ancestor|
95 95 s << '<div>' + content_tag('p', link_to_issue(ancestor, :project => (issue.project_id != ancestor.project_id)))
96 96 end
97 97 s << '<div>'
98 98 subject = h(issue.subject)
99 99 if issue.is_private?
100 100 subject = content_tag('span', l(:field_is_private), :class => 'private') + ' ' + subject
101 101 end
102 102 s << content_tag('h3', subject)
103 103 s << '</div>' * (ancestors.size + 1)
104 104 s.html_safe
105 105 end
106 106
107 107 def render_descendants_tree(issue)
108 108 s = '<form><table class="list issues">'
109 109 issue_list(issue.descendants.visible.preload(:status, :priority, :tracker).sort_by(&:lft)) do |child, level|
110 110 css = "issue issue-#{child.id} hascontextmenu #{issue.css_classes}"
111 111 css << " idnt idnt-#{level}" if level > 0
112 112 s << content_tag('tr',
113 113 content_tag('td', check_box_tag("ids[]", child.id, false, :id => nil), :class => 'checkbox') +
114 114 content_tag('td', link_to_issue(child, :project => (issue.project_id != child.project_id)), :class => 'subject', :style => 'width: 50%') +
115 115 content_tag('td', h(child.status)) +
116 116 content_tag('td', link_to_user(child.assigned_to)) +
117 117 content_tag('td', progress_bar(child.done_ratio)),
118 118 :class => css)
119 119 end
120 120 s << '</table></form>'
121 121 s.html_safe
122 122 end
123 123
124 124 def issue_estimated_hours_details(issue)
125 125 if issue.total_estimated_hours.present?
126 126 if issue.total_estimated_hours == issue.estimated_hours
127 127 l_hours_short(issue.estimated_hours)
128 128 else
129 129 s = issue.estimated_hours.present? ? l_hours_short(issue.estimated_hours) : ""
130 130 s << " (#{l(:label_total)}: #{l_hours_short(issue.total_estimated_hours)})"
131 131 s.html_safe
132 132 end
133 133 end
134 134 end
135 135
136 136 def issue_spent_hours_details(issue)
137 137 if issue.total_spent_hours > 0
138 138 if issue.total_spent_hours == issue.spent_hours
139 139 link_to(l_hours_short(issue.spent_hours), issue_time_entries_path(issue))
140 140 else
141 141 s = issue.spent_hours > 0 ? l_hours_short(issue.spent_hours) : ""
142 142 s << " (#{l(:label_total)}: #{link_to l_hours_short(issue.total_spent_hours), issue_time_entries_path(issue)})"
143 143 s.html_safe
144 144 end
145 145 end
146 146 end
147 147
148 148 # Returns an array of error messages for bulk edited issues
149 149 def bulk_edit_error_messages(issues)
150 150 messages = {}
151 151 issues.each do |issue|
152 152 issue.errors.full_messages.each do |message|
153 153 messages[message] ||= []
154 154 messages[message] << issue
155 155 end
156 156 end
157 157 messages.map { |message, issues|
158 158 "#{message}: " + issues.map {|i| "##{i.id}"}.join(', ')
159 159 }
160 160 end
161 161
162 162 # Returns a link for adding a new subtask to the given issue
163 163 def link_to_new_subtask(issue)
164 164 attrs = {
165 165 :tracker_id => issue.tracker,
166 166 :parent_issue_id => issue
167 167 }
168 168 link_to(l(:button_add), new_project_issue_path(issue.project, :issue => attrs))
169 169 end
170 170
171 171 class IssueFieldsRows
172 172 include ActionView::Helpers::TagHelper
173 173
174 174 def initialize
175 175 @left = []
176 176 @right = []
177 177 end
178 178
179 179 def left(*args)
180 180 args.any? ? @left << cells(*args) : @left
181 181 end
182 182
183 183 def right(*args)
184 184 args.any? ? @right << cells(*args) : @right
185 185 end
186 186
187 187 def size
188 188 @left.size > @right.size ? @left.size : @right.size
189 189 end
190 190
191 191 def to_html
192 192 content =
193 193 content_tag('div', @left.reduce(&:+), :class => 'splitcontentleft') +
194 194 content_tag('div', @right.reduce(&:+), :class => 'splitcontentleft')
195 195
196 196 content_tag('div', content, :class => 'splitcontent')
197 197 end
198 198
199 199 def cells(label, text, options={})
200 200 options[:class] = [options[:class] || "", 'attribute'].join(' ')
201 201 content_tag 'div',
202 202 content_tag('div', label + ":", :class => 'label') + content_tag('div', text, :class => 'value'),
203 203 options
204 204 end
205 205 end
206 206
207 207 def issue_fields_rows
208 208 r = IssueFieldsRows.new
209 209 yield r
210 210 r.to_html
211 211 end
212 212
213 213 def render_custom_fields_rows(issue)
214 214 values = issue.visible_custom_field_values
215 215 return if values.empty?
216 216 half = (values.size / 2.0).ceil
217 217 issue_fields_rows do |rows|
218 218 values.each_with_index do |value, i|
219 219 css = "cf_#{value.custom_field.id}"
220 220 m = (i < half ? :left : :right)
221 221 rows.send m, custom_field_name_tag(value.custom_field), show_value(value), :class => css
222 222 end
223 223 end
224 224 end
225 225
226 226 # Returns the path for updating the issue form
227 227 # with project as the current project
228 228 def update_issue_form_path(project, issue)
229 229 options = {:format => 'js'}
230 230 if issue.new_record?
231 231 if project
232 232 new_project_issue_path(project, options)
233 233 else
234 234 new_issue_path(options)
235 235 end
236 236 else
237 237 edit_issue_path(issue, options)
238 238 end
239 239 end
240 240
241 241 # Returns the number of descendants for an array of issues
242 242 def issues_descendant_count(issues)
243 243 ids = issues.reject(&:leaf?).map {|issue| issue.descendants.ids}.flatten.uniq
244 244 ids -= issues.map(&:id)
245 245 ids.size
246 246 end
247 247
248 248 def issues_destroy_confirmation_message(issues)
249 249 issues = [issues] unless issues.is_a?(Array)
250 250 message = l(:text_issues_destroy_confirmation)
251 251
252 252 descendant_count = issues_descendant_count(issues)
253 253 if descendant_count > 0
254 254 message << "\n" + l(:text_issues_destroy_descendants_confirmation, :count => descendant_count)
255 255 end
256 256 message
257 257 end
258 258
259 259 # Returns an array of users that are proposed as watchers
260 260 # on the new issue form
261 261 def users_for_new_issue_watchers(issue)
262 262 users = issue.watcher_users
263 263 if issue.project.users.count <= 20
264 264 users = (users + issue.project.users.sort).uniq
265 265 end
266 266 users
267 267 end
268 268
269 269 def sidebar_queries
270 270 unless @sidebar_queries
271 271 @sidebar_queries = IssueQuery.visible.
272 272 order("#{Query.table_name}.name ASC").
273 273 # Project specific queries and global queries
274 274 where(@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id]).
275 275 to_a
276 276 end
277 277 @sidebar_queries
278 278 end
279 279
280 280 def query_links(title, queries)
281 281 return '' if queries.empty?
282 282 # links to #index on issues/show
283 283 url_params = controller_name == 'issues' ? {:controller => 'issues', :action => 'index', :project_id => @project} : params
284 284
285 285 content_tag('h3', title) + "\n" +
286 286 content_tag('ul',
287 287 queries.collect {|query|
288 288 css = 'query'
289 289 css << ' selected' if query == @query
290 290 content_tag('li', link_to(query.name, url_params.merge(:query_id => query), :class => css))
291 291 }.join("\n").html_safe,
292 292 :class => 'queries'
293 293 ) + "\n"
294 294 end
295 295
296 296 def render_sidebar_queries
297 297 out = ''.html_safe
298 298 out << query_links(l(:label_my_queries), sidebar_queries.select(&:is_private?))
299 299 out << query_links(l(:label_query_plural), sidebar_queries.reject(&:is_private?))
300 300 out
301 301 end
302 302
303 303 def email_issue_attributes(issue, user)
304 304 items = []
305 305 %w(author status priority assigned_to category fixed_version).each do |attribute|
306 306 unless issue.disabled_core_fields.include?(attribute+"_id")
307 307 items << "#{l("field_#{attribute}")}: #{issue.send attribute}"
308 308 end
309 309 end
310 310 issue.visible_custom_field_values(user).each do |value|
311 311 items << "#{value.custom_field.name}: #{show_value(value, false)}"
312 312 end
313 313 items
314 314 end
315 315
316 316 def render_email_issue_attributes(issue, user, html=false)
317 317 items = email_issue_attributes(issue, user)
318 318 if html
319 319 content_tag('ul', items.map{|s| content_tag('li', s)}.join("\n").html_safe)
320 320 else
321 321 items.map{|s| "* #{s}"}.join("\n")
322 322 end
323 323 end
324 324
325 325 # Returns the textual representation of a journal details
326 326 # as an array of strings
327 327 def details_to_strings(details, no_html=false, options={})
328 328 options[:only_path] = (options[:only_path] == false ? false : true)
329 329 strings = []
330 330 values_by_field = {}
331 331 details.each do |detail|
332 332 if detail.property == 'cf'
333 333 field = detail.custom_field
334 334 if field && field.multiple?
335 335 values_by_field[field] ||= {:added => [], :deleted => []}
336 336 if detail.old_value
337 337 values_by_field[field][:deleted] << detail.old_value
338 338 end
339 339 if detail.value
340 340 values_by_field[field][:added] << detail.value
341 341 end
342 342 next
343 343 end
344 344 end
345 345 strings << show_detail(detail, no_html, options)
346 346 end
347 347 if values_by_field.present?
348 348 multiple_values_detail = Struct.new(:property, :prop_key, :custom_field, :old_value, :value)
349 349 values_by_field.each do |field, changes|
350 350 if changes[:added].any?
351 351 detail = multiple_values_detail.new('cf', field.id.to_s, field)
352 352 detail.value = changes[:added]
353 353 strings << show_detail(detail, no_html, options)
354 354 end
355 355 if changes[:deleted].any?
356 356 detail = multiple_values_detail.new('cf', field.id.to_s, field)
357 357 detail.old_value = changes[:deleted]
358 358 strings << show_detail(detail, no_html, options)
359 359 end
360 360 end
361 361 end
362 362 strings
363 363 end
364 364
365 365 # Returns the textual representation of a single journal detail
366 366 def show_detail(detail, no_html=false, options={})
367 367 multiple = false
368 368 show_diff = false
369 369
370 370 case detail.property
371 371 when 'attr'
372 372 field = detail.prop_key.to_s.gsub(/\_id$/, "")
373 373 label = l(("field_" + field).to_sym)
374 374 case detail.prop_key
375 375 when 'due_date', 'start_date'
376 376 value = format_date(detail.value.to_date) if detail.value
377 377 old_value = format_date(detail.old_value.to_date) if detail.old_value
378 378
379 379 when 'project_id', 'status_id', 'tracker_id', 'assigned_to_id',
380 380 'priority_id', 'category_id', 'fixed_version_id'
381 381 value = find_name_by_reflection(field, detail.value)
382 382 old_value = find_name_by_reflection(field, detail.old_value)
383 383
384 384 when 'estimated_hours'
385 385 value = "%0.02f" % detail.value.to_f unless detail.value.blank?
386 386 old_value = "%0.02f" % detail.old_value.to_f unless detail.old_value.blank?
387 387
388 388 when 'parent_id'
389 389 label = l(:field_parent_issue)
390 390 value = "##{detail.value}" unless detail.value.blank?
391 391 old_value = "##{detail.old_value}" unless detail.old_value.blank?
392 392
393 393 when 'is_private'
394 394 value = l(detail.value == "0" ? :general_text_No : :general_text_Yes) unless detail.value.blank?
395 395 old_value = l(detail.old_value == "0" ? :general_text_No : :general_text_Yes) unless detail.old_value.blank?
396 396
397 397 when 'description'
398 398 show_diff = true
399 399 end
400 400 when 'cf'
401 401 custom_field = detail.custom_field
402 402 if custom_field
403 403 label = custom_field.name
404 404 if custom_field.format.class.change_as_diff
405 405 show_diff = true
406 406 else
407 407 multiple = custom_field.multiple?
408 408 value = format_value(detail.value, custom_field) if detail.value
409 409 old_value = format_value(detail.old_value, custom_field) if detail.old_value
410 410 end
411 411 end
412 412 when 'attachment'
413 413 label = l(:label_attachment)
414 414 when 'relation'
415 415 if detail.value && !detail.old_value
416 416 rel_issue = Issue.visible.find_by_id(detail.value)
417 417 value = rel_issue.nil? ? "#{l(:label_issue)} ##{detail.value}" :
418 418 (no_html ? rel_issue : link_to_issue(rel_issue, :only_path => options[:only_path]))
419 419 elsif detail.old_value && !detail.value
420 420 rel_issue = Issue.visible.find_by_id(detail.old_value)
421 421 old_value = rel_issue.nil? ? "#{l(:label_issue)} ##{detail.old_value}" :
422 422 (no_html ? rel_issue : link_to_issue(rel_issue, :only_path => options[:only_path]))
423 423 end
424 424 relation_type = IssueRelation::TYPES[detail.prop_key]
425 425 label = l(relation_type[:name]) if relation_type
426 426 end
427 427 call_hook(:helper_issues_show_detail_after_setting,
428 428 {:detail => detail, :label => label, :value => value, :old_value => old_value })
429 429
430 430 label ||= detail.prop_key
431 431 value ||= detail.value
432 432 old_value ||= detail.old_value
433 433
434 434 unless no_html
435 435 label = content_tag('strong', label)
436 436 old_value = content_tag("i", h(old_value)) if detail.old_value
437 437 if detail.old_value && detail.value.blank? && detail.property != 'relation'
438 438 old_value = content_tag("del", old_value)
439 439 end
440 440 if detail.property == 'attachment' && value.present? &&
441 441 atta = detail.journal.journalized.attachments.detect {|a| a.id == detail.prop_key.to_i}
442 442 # Link to the attachment if it has not been removed
443 443 value = link_to_attachment(atta, :download => true, :only_path => options[:only_path])
444 444 if options[:only_path] != false && atta.is_text?
445 445 value += link_to('',
446 446 { :controller => 'attachments', :action => 'show',
447 447 :id => atta, :filename => atta.filename },
448 448 :class => 'icon icon-magnifier')
449 449 end
450 450 else
451 451 value = content_tag("i", h(value)) if value
452 452 end
453 453 end
454 454
455 455 if show_diff
456 456 s = l(:text_journal_changed_no_detail, :label => label)
457 457 unless no_html
458 458 diff_link = link_to 'diff',
459 {:controller => 'journals', :action => 'diff', :id => detail.journal_id,
460 :detail_id => detail.id, :only_path => options[:only_path]},
459 diff_journal_url(detail.journal_id, :detail_id => detail.id, :only_path => options[:only_path]),
461 460 :title => l(:label_view_diff)
462 461 s << " (#{ diff_link })"
463 462 end
464 463 s.html_safe
465 464 elsif detail.value.present?
466 465 case detail.property
467 466 when 'attr', 'cf'
468 467 if detail.old_value.present?
469 468 l(:text_journal_changed, :label => label, :old => old_value, :new => value).html_safe
470 469 elsif multiple
471 470 l(:text_journal_added, :label => label, :value => value).html_safe
472 471 else
473 472 l(:text_journal_set_to, :label => label, :value => value).html_safe
474 473 end
475 474 when 'attachment', 'relation'
476 475 l(:text_journal_added, :label => label, :value => value).html_safe
477 476 end
478 477 else
479 478 l(:text_journal_deleted, :label => label, :old => old_value).html_safe
480 479 end
481 480 end
482 481
483 482 # Find the name of an associated record stored in the field attribute
484 483 def find_name_by_reflection(field, id)
485 484 unless id.present?
486 485 return nil
487 486 end
488 487 @detail_value_name_by_reflection ||= Hash.new do |hash, key|
489 488 association = Issue.reflect_on_association(key.first.to_sym)
490 489 name = nil
491 490 if association
492 491 record = association.klass.find_by_id(key.last)
493 492 if record
494 493 name = record.name.force_encoding('UTF-8')
495 494 end
496 495 end
497 496 hash[key] = name
498 497 end
499 498 @detail_value_name_by_reflection[[field, id]]
500 499 end
501 500
502 501 # Renders issue children recursively
503 502 def render_api_issue_children(issue, api)
504 503 return if issue.leaf?
505 504 api.array :children do
506 505 issue.children.each do |child|
507 506 api.issue(:id => child.id) do
508 507 api.tracker(:id => child.tracker_id, :name => child.tracker.name) unless child.tracker.nil?
509 508 api.subject child.subject
510 509 render_api_issue_children(child, api)
511 510 end
512 511 end
513 512 end
514 513 end
515 514 end
@@ -1,61 +1,61
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2015 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('',
34 {:controller => 'journals', :action => 'new', :id => issue, :journal_id => journal},
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('',
41 {:controller => 'journals', :action => 'edit', :id => journal},
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('',
48 {:controller => 'journals', :action => 'edit', :id => journal, :notes => ""},
48 journal_path(journal, :notes => ""),
49 49 :remote => true,
50 :method => :post, :data => {:confirm => l(:text_are_you_sure)},
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 end
@@ -1,18 +1,19
1 <%= form_tag({:controller => 'journals', :action => 'edit', :id => @journal},
1 <%= form_tag(journal_path(@journal),
2 2 :remote => true,
3 :method => 'put',
3 4 :id => "journal-#{@journal.id}-form") do %>
4 5 <%= label_tag "notes", l(:description_notes), :class => "hidden-for-sighted" %>
5 6 <%= text_area_tag :notes, @journal.notes,
6 7 :id => "journal_#{@journal.id}_notes",
7 8 :class => 'wiki-edit',
8 9 :rows => (@journal.notes.blank? ? 10 : [[10, @journal.notes.length / 50].max, 100].min) %>
9 10 <%= call_hook(:view_journals_notes_form_after_notes, { :journal => @journal}) %>
10 11 <p><%= submit_tag l(:button_save) %>
11 12 <%= preview_link preview_edit_issue_path(:project_id => @project, :id => @journal.issue),
12 13 "journal-#{@journal.id}-form",
13 14 "journal_#{@journal.id}_preview" %> |
14 15 <%= link_to l(:button_cancel), '#', :onclick => "$('#journal-#{@journal.id}-form').remove(); $('#journal-#{@journal.id}-notes').show(); return false;" %></p>
15 16
16 17 <div id="journal_<%= @journal.id %>_preview" class="wiki"></div>
17 18 <% end %>
18 19 <%= wikitoolbar_for "journal_#{@journal.id}_notes" %>
@@ -1,376 +1,379
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2015 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 Rails.application.routes.draw do
19 19 root :to => 'welcome#index', :as => 'home'
20 20
21 21 match 'login', :to => 'account#login', :as => 'signin', :via => [:get, :post]
22 22 match 'logout', :to => 'account#logout', :as => 'signout', :via => [:get, :post]
23 23 match 'account/register', :to => 'account#register', :via => [:get, :post], :as => 'register'
24 24 match 'account/lost_password', :to => 'account#lost_password', :via => [:get, :post], :as => 'lost_password'
25 25 match 'account/activate', :to => 'account#activate', :via => :get
26 26 get 'account/activation_email', :to => 'account#activation_email', :as => 'activation_email'
27 27
28 28 match '/news/preview', :controller => 'previews', :action => 'news', :as => 'preview_news', :via => [:get, :post, :put, :patch]
29 29 match '/issues/preview/new/:project_id', :to => 'previews#issue', :as => 'preview_new_issue', :via => [:get, :post, :put, :patch]
30 30 match '/issues/preview/edit/:id', :to => 'previews#issue', :as => 'preview_edit_issue', :via => [:get, :post, :put, :patch]
31 31 match '/issues/preview', :to => 'previews#issue', :as => 'preview_issue', :via => [:get, :post, :put, :patch]
32 32
33 33 match 'projects/:id/wiki', :to => 'wikis#edit', :via => :post
34 34 match 'projects/:id/wiki/destroy', :to => 'wikis#destroy', :via => [:get, :post]
35 35
36 36 match 'boards/:board_id/topics/new', :to => 'messages#new', :via => [:get, :post], :as => 'new_board_message'
37 37 get 'boards/:board_id/topics/:id', :to => 'messages#show', :as => 'board_message'
38 38 match 'boards/:board_id/topics/quote/:id', :to => 'messages#quote', :via => [:get, :post]
39 39 get 'boards/:board_id/topics/:id/edit', :to => 'messages#edit'
40 40
41 41 post 'boards/:board_id/topics/preview', :to => 'messages#preview', :as => 'preview_board_message'
42 42 post 'boards/:board_id/topics/:id/replies', :to => 'messages#reply'
43 43 post 'boards/:board_id/topics/:id/edit', :to => 'messages#edit'
44 44 post 'boards/:board_id/topics/:id/destroy', :to => 'messages#destroy'
45 45
46 46 # Misc issue routes. TODO: move into resources
47 47 match '/issues/auto_complete', :to => 'auto_completes#issues', :via => :get, :as => 'auto_complete_issues'
48 48 match '/issues/context_menu', :to => 'context_menus#issues', :as => 'issues_context_menu', :via => [:get, :post]
49 49 match '/issues/changes', :to => 'journals#index', :as => 'issue_changes', :via => :get
50 50 match '/issues/:id/quoted', :to => 'journals#new', :id => /\d+/, :via => :post, :as => 'quoted_issue'
51 51
52 match '/journals/diff/:id', :to => 'journals#diff', :id => /\d+/, :via => :get
53 match '/journals/edit/:id', :to => 'journals#edit', :id => /\d+/, :via => [:get, :post]
52 resources :journals, :only => [:edit, :update] do
53 member do
54 get 'diff'
55 end
56 end
54 57
55 58 get '/projects/:project_id/issues/gantt', :to => 'gantts#show', :as => 'project_gantt'
56 59 get '/issues/gantt', :to => 'gantts#show'
57 60
58 61 get '/projects/:project_id/issues/calendar', :to => 'calendars#show', :as => 'project_calendar'
59 62 get '/issues/calendar', :to => 'calendars#show'
60 63
61 64 get 'projects/:id/issues/report', :to => 'reports#issue_report', :as => 'project_issues_report'
62 65 get 'projects/:id/issues/report/:detail', :to => 'reports#issue_report_details', :as => 'project_issues_report_details'
63 66
64 67 get '/issues/imports/new', :to => 'imports#new', :as => 'new_issues_import'
65 68 post '/imports', :to => 'imports#create', :as => 'imports'
66 69 get '/imports/:id', :to => 'imports#show', :as => 'import'
67 70 match '/imports/:id/settings', :to => 'imports#settings', :via => [:get, :post], :as => 'import_settings'
68 71 match '/imports/:id/mapping', :to => 'imports#mapping', :via => [:get, :post], :as => 'import_mapping'
69 72 match '/imports/:id/run', :to => 'imports#run', :via => [:get, :post], :as => 'import_run'
70 73
71 74 match 'my/account', :controller => 'my', :action => 'account', :via => [:get, :post]
72 75 match 'my/account/destroy', :controller => 'my', :action => 'destroy', :via => [:get, :post]
73 76 match 'my/page', :controller => 'my', :action => 'page', :via => :get
74 77 match 'my', :controller => 'my', :action => 'index', :via => :get # Redirects to my/page
75 78 get 'my/api_key', :to => 'my#show_api_key', :as => 'my_api_key'
76 79 post 'my/api_key', :to => 'my#reset_api_key'
77 80 post 'my/rss_key', :to => 'my#reset_rss_key', :as => 'my_rss_key'
78 81 match 'my/password', :controller => 'my', :action => 'password', :via => [:get, :post]
79 82 match 'my/page_layout', :controller => 'my', :action => 'page_layout', :via => :get
80 83 match 'my/add_block', :controller => 'my', :action => 'add_block', :via => :post
81 84 match 'my/remove_block', :controller => 'my', :action => 'remove_block', :via => :post
82 85 match 'my/order_blocks', :controller => 'my', :action => 'order_blocks', :via => :post
83 86
84 87 resources :users do
85 88 resources :memberships, :controller => 'principal_memberships'
86 89 resources :email_addresses, :only => [:index, :create, :update, :destroy]
87 90 end
88 91
89 92 post 'watchers/watch', :to => 'watchers#watch', :as => 'watch'
90 93 delete 'watchers/watch', :to => 'watchers#unwatch'
91 94 get 'watchers/new', :to => 'watchers#new'
92 95 post 'watchers', :to => 'watchers#create'
93 96 post 'watchers/append', :to => 'watchers#append'
94 97 delete 'watchers', :to => 'watchers#destroy'
95 98 get 'watchers/autocomplete_for_user', :to => 'watchers#autocomplete_for_user'
96 99 # Specific routes for issue watchers API
97 100 post 'issues/:object_id/watchers', :to => 'watchers#create', :object_type => 'issue'
98 101 delete 'issues/:object_id/watchers/:user_id' => 'watchers#destroy', :object_type => 'issue'
99 102
100 103 resources :projects do
101 104 member do
102 105 get 'settings(/:tab)', :action => 'settings', :as => 'settings'
103 106 post 'modules'
104 107 post 'archive'
105 108 post 'unarchive'
106 109 post 'close'
107 110 post 'reopen'
108 111 match 'copy', :via => [:get, :post]
109 112 end
110 113
111 114 shallow do
112 115 resources :memberships, :controller => 'members', :only => [:index, :show, :new, :create, :update, :destroy] do
113 116 collection do
114 117 get 'autocomplete'
115 118 end
116 119 end
117 120 end
118 121
119 122 resource :enumerations, :controller => 'project_enumerations', :only => [:update, :destroy]
120 123
121 124 get 'issues/:copy_from/copy', :to => 'issues#new', :as => 'copy_issue'
122 125 resources :issues, :only => [:index, :new, :create]
123 126 # Used when updating the form of a new issue
124 127 post 'issues/new', :to => 'issues#new'
125 128
126 129 resources :files, :only => [:index, :new, :create]
127 130
128 131 resources :versions, :except => [:index, :show, :edit, :update, :destroy] do
129 132 collection do
130 133 put 'close_completed'
131 134 end
132 135 end
133 136 get 'versions.:format', :to => 'versions#index'
134 137 get 'roadmap', :to => 'versions#index', :format => false
135 138 get 'versions', :to => 'versions#index'
136 139
137 140 resources :news, :except => [:show, :edit, :update, :destroy]
138 141 resources :time_entries, :controller => 'timelog', :except => [:show, :edit, :update, :destroy] do
139 142 get 'report', :on => :collection
140 143 end
141 144 resources :queries, :only => [:new, :create]
142 145 shallow do
143 146 resources :issue_categories
144 147 end
145 148 resources :documents, :except => [:show, :edit, :update, :destroy]
146 149 resources :boards
147 150 shallow do
148 151 resources :repositories, :except => [:index, :show] do
149 152 member do
150 153 match 'committers', :via => [:get, :post]
151 154 end
152 155 end
153 156 end
154 157
155 158 match 'wiki/index', :controller => 'wiki', :action => 'index', :via => :get
156 159 resources :wiki, :except => [:index, :new, :create], :as => 'wiki_page' do
157 160 member do
158 161 get 'rename'
159 162 post 'rename'
160 163 get 'history'
161 164 get 'diff'
162 165 match 'preview', :via => [:post, :put, :patch]
163 166 post 'protect'
164 167 post 'add_attachment'
165 168 end
166 169 collection do
167 170 get 'export'
168 171 get 'date_index'
169 172 end
170 173 end
171 174 match 'wiki', :controller => 'wiki', :action => 'show', :via => :get
172 175 get 'wiki/:id/:version', :to => 'wiki#show', :constraints => {:version => /\d+/}
173 176 delete 'wiki/:id/:version', :to => 'wiki#destroy_version'
174 177 get 'wiki/:id/:version/annotate', :to => 'wiki#annotate'
175 178 get 'wiki/:id/:version/diff', :to => 'wiki#diff'
176 179 end
177 180
178 181 resources :issues do
179 182 member do
180 183 # Used when updating the form of an existing issue
181 184 patch 'edit', :to => 'issues#edit'
182 185 end
183 186 collection do
184 187 match 'bulk_edit', :via => [:get, :post]
185 188 post 'bulk_update'
186 189 end
187 190 resources :time_entries, :controller => 'timelog', :except => [:show, :edit, :update, :destroy] do
188 191 collection do
189 192 get 'report'
190 193 end
191 194 end
192 195 shallow do
193 196 resources :relations, :controller => 'issue_relations', :only => [:index, :show, :create, :destroy]
194 197 end
195 198 end
196 199 # Used when updating the form of a new issue outside a project
197 200 post '/issues/new', :to => 'issues#new'
198 201 match '/issues', :controller => 'issues', :action => 'destroy', :via => :delete
199 202
200 203 resources :queries, :except => [:show]
201 204
202 205 resources :news, :only => [:index, :show, :edit, :update, :destroy]
203 206 match '/news/:id/comments', :to => 'comments#create', :via => :post
204 207 match '/news/:id/comments/:comment_id', :to => 'comments#destroy', :via => :delete
205 208
206 209 resources :versions, :only => [:show, :edit, :update, :destroy] do
207 210 post 'status_by', :on => :member
208 211 end
209 212
210 213 resources :documents, :only => [:show, :edit, :update, :destroy] do
211 214 post 'add_attachment', :on => :member
212 215 end
213 216
214 217 match '/time_entries/context_menu', :to => 'context_menus#time_entries', :as => :time_entries_context_menu, :via => [:get, :post]
215 218
216 219 resources :time_entries, :controller => 'timelog', :except => :destroy do
217 220 collection do
218 221 get 'report'
219 222 get 'bulk_edit'
220 223 post 'bulk_update'
221 224 end
222 225 end
223 226 match '/time_entries/:id', :to => 'timelog#destroy', :via => :delete, :id => /\d+/
224 227 # TODO: delete /time_entries for bulk deletion
225 228 match '/time_entries/destroy', :to => 'timelog#destroy', :via => :delete
226 229 # Used to update the new time entry form
227 230 post '/time_entries/new', :to => 'timelog#new'
228 231
229 232 get 'projects/:id/activity', :to => 'activities#index', :as => :project_activity
230 233 get 'activity', :to => 'activities#index'
231 234
232 235 # repositories routes
233 236 get 'projects/:id/repository/:repository_id/statistics', :to => 'repositories#stats'
234 237 get 'projects/:id/repository/:repository_id/graph', :to => 'repositories#graph'
235 238
236 239 get 'projects/:id/repository/:repository_id/changes(/*path)',
237 240 :to => 'repositories#changes',
238 241 :format => false
239 242
240 243 get 'projects/:id/repository/:repository_id/revisions/:rev', :to => 'repositories#revision'
241 244 get 'projects/:id/repository/:repository_id/revision', :to => 'repositories#revision'
242 245 post 'projects/:id/repository/:repository_id/revisions/:rev/issues', :to => 'repositories#add_related_issue'
243 246 delete 'projects/:id/repository/:repository_id/revisions/:rev/issues/:issue_id', :to => 'repositories#remove_related_issue'
244 247 get 'projects/:id/repository/:repository_id/revisions', :to => 'repositories#revisions'
245 248 get 'projects/:id/repository/:repository_id/revisions/:rev/:action(/*path)',
246 249 :controller => 'repositories',
247 250 :format => false,
248 251 :constraints => {
249 252 :action => /(browse|show|entry|raw|annotate|diff)/,
250 253 :rev => /[a-z0-9\.\-_]+/
251 254 }
252 255
253 256 get 'projects/:id/repository/statistics', :to => 'repositories#stats'
254 257 get 'projects/:id/repository/graph', :to => 'repositories#graph'
255 258
256 259 get 'projects/:id/repository/changes(/*path)',
257 260 :to => 'repositories#changes',
258 261 :format => false
259 262
260 263 get 'projects/:id/repository/revisions', :to => 'repositories#revisions'
261 264 get 'projects/:id/repository/revisions/:rev', :to => 'repositories#revision'
262 265 get 'projects/:id/repository/revision', :to => 'repositories#revision'
263 266 post 'projects/:id/repository/revisions/:rev/issues', :to => 'repositories#add_related_issue'
264 267 delete 'projects/:id/repository/revisions/:rev/issues/:issue_id', :to => 'repositories#remove_related_issue'
265 268 get 'projects/:id/repository/revisions/:rev/:action(/*path)',
266 269 :controller => 'repositories',
267 270 :format => false,
268 271 :constraints => {
269 272 :action => /(browse|show|entry|raw|annotate|diff)/,
270 273 :rev => /[a-z0-9\.\-_]+/
271 274 }
272 275 get 'projects/:id/repository/:repository_id/:action(/*path)',
273 276 :controller => 'repositories',
274 277 :action => /(browse|show|entry|raw|changes|annotate|diff)/,
275 278 :format => false
276 279 get 'projects/:id/repository/:action(/*path)',
277 280 :controller => 'repositories',
278 281 :action => /(browse|show|entry|raw|changes|annotate|diff)/,
279 282 :format => false
280 283
281 284 get 'projects/:id/repository/:repository_id', :to => 'repositories#show', :path => nil
282 285 get 'projects/:id/repository', :to => 'repositories#show', :path => nil
283 286
284 287 # additional routes for having the file name at the end of url
285 288 get 'attachments/:id/:filename', :to => 'attachments#show', :id => /\d+/, :filename => /.*/, :as => 'named_attachment'
286 289 get 'attachments/download/:id/:filename', :to => 'attachments#download', :id => /\d+/, :filename => /.*/, :as => 'download_named_attachment'
287 290 get 'attachments/download/:id', :to => 'attachments#download', :id => /\d+/
288 291 get 'attachments/thumbnail/:id(/:size)', :to => 'attachments#thumbnail', :id => /\d+/, :size => /\d+/, :as => 'thumbnail'
289 292 resources :attachments, :only => [:show, :destroy]
290 293 get 'attachments/:object_type/:object_id/edit', :to => 'attachments#edit', :as => :object_attachments_edit
291 294 patch 'attachments/:object_type/:object_id', :to => 'attachments#update', :as => :object_attachments
292 295
293 296 resources :groups do
294 297 resources :memberships, :controller => 'principal_memberships'
295 298 member do
296 299 get 'autocomplete_for_user'
297 300 end
298 301 end
299 302
300 303 get 'groups/:id/users/new', :to => 'groups#new_users', :id => /\d+/, :as => 'new_group_users'
301 304 post 'groups/:id/users', :to => 'groups#add_users', :id => /\d+/, :as => 'group_users'
302 305 delete 'groups/:id/users/:user_id', :to => 'groups#remove_user', :id => /\d+/, :as => 'group_user'
303 306
304 307 resources :trackers, :except => :show do
305 308 collection do
306 309 match 'fields', :via => [:get, :post]
307 310 end
308 311 end
309 312 resources :issue_statuses, :except => :show do
310 313 collection do
311 314 post 'update_issue_done_ratio'
312 315 end
313 316 end
314 317 resources :custom_fields, :except => :show do
315 318 resources :enumerations, :controller => 'custom_field_enumerations', :except => [:show, :new, :edit]
316 319 put 'enumerations', :to => 'custom_field_enumerations#update_each'
317 320 end
318 321 resources :roles do
319 322 collection do
320 323 match 'permissions', :via => [:get, :post]
321 324 end
322 325 end
323 326 resources :enumerations, :except => :show
324 327 match 'enumerations/:type', :to => 'enumerations#index', :via => :get
325 328
326 329 get 'projects/:id/search', :controller => 'search', :action => 'index'
327 330 get 'search', :controller => 'search', :action => 'index'
328 331
329 332
330 333 get 'mail_handler', :to => 'mail_handler#new'
331 334 post 'mail_handler', :to => 'mail_handler#index'
332 335
333 336 get 'admin', :to => 'admin#index'
334 337 get 'admin/projects', :to => 'admin#projects'
335 338 get 'admin/plugins', :to => 'admin#plugins'
336 339 get 'admin/info', :to => 'admin#info'
337 340 post 'admin/test_email', :to => 'admin#test_email', :as => 'test_email'
338 341 post 'admin/default_configuration', :to => 'admin#default_configuration'
339 342
340 343 resources :auth_sources do
341 344 member do
342 345 get 'test_connection', :as => 'try_connection'
343 346 end
344 347 collection do
345 348 get 'autocomplete_for_new_user'
346 349 end
347 350 end
348 351
349 352 match 'workflows', :controller => 'workflows', :action => 'index', :via => :get
350 353 match 'workflows/edit', :controller => 'workflows', :action => 'edit', :via => [:get, :post]
351 354 match 'workflows/permissions', :controller => 'workflows', :action => 'permissions', :via => [:get, :post]
352 355 match 'workflows/copy', :controller => 'workflows', :action => 'copy', :via => [:get, :post]
353 356 match 'settings', :controller => 'settings', :action => 'index', :via => :get
354 357 match 'settings/edit', :controller => 'settings', :action => 'edit', :via => [:get, :post]
355 358 match 'settings/plugin/:id', :controller => 'settings', :action => 'plugin', :via => [:get, :post], :as => 'plugin_settings'
356 359
357 360 match 'sys/projects', :to => 'sys#projects', :via => :get
358 361 match 'sys/projects/:id/repository', :to => 'sys#create_project_repository', :via => :post
359 362 match 'sys/fetch_changesets', :to => 'sys#fetch_changesets', :via => [:get, :post]
360 363
361 364 match 'uploads', :to => 'attachments#upload', :via => :post
362 365
363 366 get 'robots.txt', :to => 'welcome#robots'
364 367
365 368 Dir.glob File.expand_path("plugins/*", Rails.root) do |plugin_dir|
366 369 file = File.join(plugin_dir, "config/routes.rb")
367 370 if File.exists?(file)
368 371 begin
369 372 instance_eval File.read(file)
370 373 rescue Exception => e
371 374 puts "An error occurred while loading the routes definition of #{File.basename(plugin_dir)} plugin (#{file}): #{e.message}."
372 375 exit 1
373 376 end
374 377 end
375 378 end
376 379 end
@@ -1,276 +1,276
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2015 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 'redmine/core_ext'
19 19
20 20 begin
21 21 require 'rmagick' unless Object.const_defined?(:Magick)
22 22 rescue LoadError
23 23 # RMagick is not available
24 24 end
25 25 begin
26 26 require 'redcarpet' unless Object.const_defined?(:Redcarpet)
27 27 rescue LoadError
28 28 # Redcarpet is not available
29 29 end
30 30
31 31 require 'redmine/scm/base'
32 32 require 'redmine/access_control'
33 33 require 'redmine/access_keys'
34 34 require 'redmine/activity'
35 35 require 'redmine/activity/fetcher'
36 36 require 'redmine/ciphering'
37 37 require 'redmine/codeset_util'
38 38 require 'redmine/field_format'
39 39 require 'redmine/menu_manager'
40 40 require 'redmine/notifiable'
41 41 require 'redmine/platform'
42 42 require 'redmine/mime_type'
43 43 require 'redmine/notifiable'
44 44 require 'redmine/search'
45 45 require 'redmine/syntax_highlighting'
46 46 require 'redmine/thumbnail'
47 47 require 'redmine/unified_diff'
48 48 require 'redmine/utils'
49 49 require 'redmine/version'
50 50 require 'redmine/wiki_formatting'
51 51
52 52 require 'redmine/default_data/loader'
53 53 require 'redmine/helpers/calendar'
54 54 require 'redmine/helpers/diff'
55 55 require 'redmine/helpers/gantt'
56 56 require 'redmine/helpers/time_report'
57 57 require 'redmine/views/other_formats_builder'
58 58 require 'redmine/views/labelled_form_builder'
59 59 require 'redmine/views/builders'
60 60
61 61 require 'redmine/themes'
62 62 require 'redmine/hook'
63 63 require 'redmine/hook/listener'
64 64 require 'redmine/hook/view_listener'
65 65 require 'redmine/plugin'
66 66
67 67 Redmine::Scm::Base.add "Subversion"
68 68 Redmine::Scm::Base.add "Darcs"
69 69 Redmine::Scm::Base.add "Mercurial"
70 70 Redmine::Scm::Base.add "Cvs"
71 71 Redmine::Scm::Base.add "Bazaar"
72 72 Redmine::Scm::Base.add "Git"
73 73 Redmine::Scm::Base.add "Filesystem"
74 74
75 75 # Permissions
76 76 Redmine::AccessControl.map do |map|
77 77 map.permission :view_project, {:projects => [:show], :activities => [:index]}, :public => true, :read => true
78 78 map.permission :search_project, {:search => :index}, :public => true, :read => true
79 79 map.permission :add_project, {:projects => [:new, :create]}, :require => :loggedin
80 80 map.permission :edit_project, {:projects => [:settings, :edit, :update]}, :require => :member
81 81 map.permission :close_project, {:projects => [:close, :reopen]}, :require => :member, :read => true
82 82 map.permission :select_project_modules, {:projects => :modules}, :require => :member
83 83 map.permission :view_members, {:members => [:index, :show]}, :public => true, :read => true
84 84 map.permission :manage_members, {:projects => :settings, :members => [:index, :show, :new, :create, :update, :destroy, :autocomplete]}, :require => :member
85 85 map.permission :manage_versions, {:projects => :settings, :versions => [:new, :create, :edit, :update, :close_completed, :destroy]}, :require => :member
86 86 map.permission :add_subprojects, {:projects => [:new, :create]}, :require => :member
87 87
88 88 map.project_module :issue_tracking do |map|
89 89 # Issue categories
90 90 map.permission :manage_categories, {:projects => :settings, :issue_categories => [:index, :show, :new, :create, :edit, :update, :destroy]}, :require => :member
91 91 # Issues
92 92 map.permission :view_issues, {:issues => [:index, :show],
93 93 :auto_complete => [:issues],
94 94 :context_menus => [:issues],
95 95 :versions => [:index, :show, :status_by],
96 96 :journals => [:index, :diff],
97 97 :queries => :index,
98 98 :reports => [:issue_report, :issue_report_details]},
99 99 :read => true
100 100 map.permission :add_issues, {:issues => [:new, :create], :attachments => :upload}
101 101 map.permission :edit_issues, {:issues => [:edit, :update, :bulk_edit, :bulk_update], :journals => [:new], :attachments => :upload}
102 102 map.permission :copy_issues, {:issues => [:new, :create, :bulk_edit, :bulk_update], :attachments => :upload}
103 103 map.permission :manage_issue_relations, {:issue_relations => [:index, :show, :create, :destroy]}
104 104 map.permission :manage_subtasks, {}
105 105 map.permission :set_issues_private, {}
106 106 map.permission :set_own_issues_private, {}, :require => :loggedin
107 107 map.permission :add_issue_notes, {:issues => [:edit, :update], :journals => [:new], :attachments => :upload}
108 map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin
109 map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin
108 map.permission :edit_issue_notes, {:journals => [:edit, :update]}, :require => :loggedin
109 map.permission :edit_own_issue_notes, {:journals => [:edit, :update]}, :require => :loggedin
110 110 map.permission :view_private_notes, {}, :read => true, :require => :member
111 111 map.permission :set_notes_private, {}, :require => :member
112 112 map.permission :delete_issues, {:issues => :destroy}, :require => :member
113 113 # Queries
114 114 map.permission :manage_public_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :member
115 115 map.permission :save_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :loggedin
116 116 # Watchers
117 117 map.permission :view_issue_watchers, {}, :read => true
118 118 map.permission :add_issue_watchers, {:watchers => [:new, :create, :append, :autocomplete_for_user]}
119 119 map.permission :delete_issue_watchers, {:watchers => :destroy}
120 120 map.permission :import_issues, {:imports => [:new, :create, :settings, :mapping, :run, :show]}
121 121 end
122 122
123 123 map.project_module :time_tracking do |map|
124 124 map.permission :log_time, {:timelog => [:new, :create]}, :require => :loggedin
125 125 map.permission :view_time_entries, {:timelog => [:index, :report, :show]}, :read => true
126 126 map.permission :edit_time_entries, {:timelog => [:edit, :update, :destroy, :bulk_edit, :bulk_update]}, :require => :member
127 127 map.permission :edit_own_time_entries, {:timelog => [:edit, :update, :destroy,:bulk_edit, :bulk_update]}, :require => :loggedin
128 128 map.permission :manage_project_activities, {:project_enumerations => [:update, :destroy]}, :require => :member
129 129 end
130 130
131 131 map.project_module :news do |map|
132 132 map.permission :manage_news, {:news => [:new, :create, :edit, :update, :destroy], :comments => [:destroy], :attachments => :upload}, :require => :member
133 133 map.permission :view_news, {:news => [:index, :show]}, :public => true, :read => true
134 134 map.permission :comment_news, {:comments => :create}
135 135 end
136 136
137 137 map.project_module :documents do |map|
138 138 map.permission :add_documents, {:documents => [:new, :create, :add_attachment], :attachments => :upload}, :require => :loggedin
139 139 map.permission :edit_documents, {:documents => [:edit, :update, :add_attachment], :attachments => :upload}, :require => :loggedin
140 140 map.permission :delete_documents, {:documents => [:destroy]}, :require => :loggedin
141 141 map.permission :view_documents, {:documents => [:index, :show, :download]}, :read => true
142 142 end
143 143
144 144 map.project_module :files do |map|
145 145 map.permission :manage_files, {:files => [:new, :create], :attachments => :upload}, :require => :loggedin
146 146 map.permission :view_files, {:files => :index, :versions => :download}, :read => true
147 147 end
148 148
149 149 map.project_module :wiki do |map|
150 150 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
151 151 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
152 152 map.permission :delete_wiki_pages, {:wiki => [:destroy, :destroy_version]}, :require => :member
153 153 map.permission :view_wiki_pages, {:wiki => [:index, :show, :special, :date_index]}, :read => true
154 154 map.permission :export_wiki_pages, {:wiki => [:export]}, :read => true
155 155 map.permission :view_wiki_edits, {:wiki => [:history, :diff, :annotate]}, :read => true
156 156 map.permission :edit_wiki_pages, :wiki => [:edit, :update, :preview, :add_attachment], :attachments => :upload
157 157 map.permission :delete_wiki_pages_attachments, {}
158 158 map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member
159 159 end
160 160
161 161 map.project_module :repository do |map|
162 162 map.permission :manage_repository, {:repositories => [:new, :create, :edit, :update, :committers, :destroy]}, :require => :member
163 163 map.permission :browse_repository, {:repositories => [:show, :browse, :entry, :raw, :annotate, :changes, :diff, :stats, :graph]}, :read => true
164 164 map.permission :view_changesets, {:repositories => [:show, :revisions, :revision]}, :read => true
165 165 map.permission :commit_access, {}
166 166 map.permission :manage_related_issues, {:repositories => [:add_related_issue, :remove_related_issue]}
167 167 end
168 168
169 169 map.project_module :boards do |map|
170 170 map.permission :manage_boards, {:boards => [:new, :create, :edit, :update, :destroy]}, :require => :member
171 171 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true, :read => true
172 172 map.permission :add_messages, {:messages => [:new, :reply, :quote], :attachments => :upload}
173 173 map.permission :edit_messages, {:messages => :edit, :attachments => :upload}, :require => :member
174 174 map.permission :edit_own_messages, {:messages => :edit, :attachments => :upload}, :require => :loggedin
175 175 map.permission :delete_messages, {:messages => :destroy}, :require => :member
176 176 map.permission :delete_own_messages, {:messages => :destroy}, :require => :loggedin
177 177 end
178 178
179 179 map.project_module :calendar do |map|
180 180 map.permission :view_calendar, {:calendars => [:show, :update]}, :read => true
181 181 end
182 182
183 183 map.project_module :gantt do |map|
184 184 map.permission :view_gantt, {:gantts => [:show, :update]}, :read => true
185 185 end
186 186 end
187 187
188 188 Redmine::MenuManager.map :top_menu do |menu|
189 189 menu.push :home, :home_path
190 190 menu.push :my_page, { :controller => 'my', :action => 'page' }, :if => Proc.new { User.current.logged? }
191 191 menu.push :projects, { :controller => 'projects', :action => 'index' }, :caption => :label_project_plural
192 192 menu.push :administration, { :controller => 'admin', :action => 'index' }, :if => Proc.new { User.current.admin? }, :last => true
193 193 menu.push :help, Redmine::Info.help_url, :last => true
194 194 end
195 195
196 196 Redmine::MenuManager.map :account_menu do |menu|
197 197 menu.push :login, :signin_path, :if => Proc.new { !User.current.logged? }
198 198 menu.push :register, :register_path, :if => Proc.new { !User.current.logged? && Setting.self_registration? }
199 199 menu.push :my_account, { :controller => 'my', :action => 'account' }, :if => Proc.new { User.current.logged? }
200 200 menu.push :logout, :signout_path, :html => {:method => 'post'}, :if => Proc.new { User.current.logged? }
201 201 end
202 202
203 203 Redmine::MenuManager.map :application_menu do |menu|
204 204 # Empty
205 205 end
206 206
207 207 Redmine::MenuManager.map :admin_menu do |menu|
208 208 menu.push :projects, {:controller => 'admin', :action => 'projects'}, :caption => :label_project_plural
209 209 menu.push :users, {:controller => 'users'}, :caption => :label_user_plural
210 210 menu.push :groups, {:controller => 'groups'}, :caption => :label_group_plural
211 211 menu.push :roles, {:controller => 'roles'}, :caption => :label_role_and_permissions
212 212 menu.push :trackers, {:controller => 'trackers'}, :caption => :label_tracker_plural
213 213 menu.push :issue_statuses, {:controller => 'issue_statuses'}, :caption => :label_issue_status_plural,
214 214 :html => {:class => 'issue_statuses'}
215 215 menu.push :workflows, {:controller => 'workflows', :action => 'edit'}, :caption => :label_workflow
216 216 menu.push :custom_fields, {:controller => 'custom_fields'}, :caption => :label_custom_field_plural,
217 217 :html => {:class => 'custom_fields'}
218 218 menu.push :enumerations, {:controller => 'enumerations'}
219 219 menu.push :settings, {:controller => 'settings'}
220 220 menu.push :ldap_authentication, {:controller => 'auth_sources', :action => 'index'},
221 221 :html => {:class => 'server_authentication'}
222 222 menu.push :plugins, {:controller => 'admin', :action => 'plugins'}, :last => true
223 223 menu.push :info, {:controller => 'admin', :action => 'info'}, :caption => :label_information_plural, :last => true
224 224 end
225 225
226 226 Redmine::MenuManager.map :project_menu do |menu|
227 227 menu.push :overview, { :controller => 'projects', :action => 'show' }
228 228 menu.push :activity, { :controller => 'activities', :action => 'index' }
229 229 menu.push :roadmap, { :controller => 'versions', :action => 'index' }, :param => :project_id,
230 230 :if => Proc.new { |p| p.shared_versions.any? }
231 231 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
232 232 menu.push :new_issue, { :controller => 'issues', :action => 'new', :copy_from => nil }, :param => :project_id, :caption => :label_issue_new,
233 233 :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) },
234 234 :if => Proc.new { |p| p.trackers.any? },
235 235 :permission => :add_issues
236 236 menu.push :gantt, { :controller => 'gantts', :action => 'show' }, :param => :project_id, :caption => :label_gantt
237 237 menu.push :calendar, { :controller => 'calendars', :action => 'show' }, :param => :project_id, :caption => :label_calendar
238 238 menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
239 239 menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural
240 240 menu.push :wiki, { :controller => 'wiki', :action => 'show', :id => nil }, :param => :project_id,
241 241 :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
242 242 menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id,
243 243 :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural
244 244 menu.push :files, { :controller => 'files', :action => 'index' }, :caption => :label_file_plural, :param => :project_id
245 245 menu.push :repository, { :controller => 'repositories', :action => 'show', :repository_id => nil, :path => nil, :rev => nil },
246 246 :if => Proc.new { |p| p.repository && !p.repository.new_record? }
247 247 menu.push :settings, { :controller => 'projects', :action => 'settings' }, :last => true
248 248 end
249 249
250 250 Redmine::Activity.map do |activity|
251 251 activity.register :issues, :class_name => %w(Issue Journal)
252 252 activity.register :changesets
253 253 activity.register :news
254 254 activity.register :documents, :class_name => %w(Document Attachment)
255 255 activity.register :files, :class_name => 'Attachment'
256 256 activity.register :wiki_edits, :class_name => 'WikiContent::Version', :default => false
257 257 activity.register :messages, :default => false
258 258 activity.register :time_entries, :default => false
259 259 end
260 260
261 261 Redmine::Search.map do |search|
262 262 search.register :issues
263 263 search.register :news
264 264 search.register :documents
265 265 search.register :changesets
266 266 search.register :wiki_pages
267 267 search.register :messages
268 268 search.register :projects
269 269 end
270 270
271 271 Redmine::WikiFormatting.map do |format|
272 272 format.register :textile
273 273 format.register :markdown if Object.const_defined?(:Redcarpet)
274 274 end
275 275
276 276 ActionView::Template.register_template_handler :rsb, Redmine::Views::ApiTemplateHandler
@@ -1,221 +1,221
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2015 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, :edit, :id => 2, :notes => 'Updated notes'
202 xhr :post, :update, :id => 2, :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_empty_notes_should_delete_the_journal
211 211 @request.session[:user_id] = 1
212 212 assert_difference 'Journal.count', -1 do
213 xhr :post, :edit, :id => 2, :notes => ''
213 xhr :post, :update, :id => 2, :notes => ''
214 214 assert_response :success
215 215 assert_template 'update'
216 216 assert_equal 'text/javascript', response.content_type
217 217 end
218 218 assert_nil Journal.find_by_id(2)
219 219 assert_include 'change-2', response.body
220 220 end
221 221 end
@@ -1,29 +1,29
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2015 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 RoutingJournalsTest < Redmine::RoutingTest
21 21 def test_journals
22 22 should_route 'POST /issues/1/quoted' => 'journals#new', :id => '1'
23 23 should_route 'GET /issues/changes' => 'journals#index'
24 should_route 'GET /journals/diff/1' => 'journals#diff', :id => '1'
24 should_route 'GET /journals/1/diff' => 'journals#diff', :id => '1'
25 25
26 should_route 'GET /journals/edit/1' => 'journals#edit', :id => '1'
27 should_route 'POST /journals/edit/1' => 'journals#edit', :id => '1'
26 should_route 'GET /journals/1/edit' => 'journals#edit', :id => '1'
27 should_route 'PUT /journals/1' => 'journals#update', :id => '1'
28 28 end
29 29 end
@@ -1,853 +1,853
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2015 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 MailerTest < ActiveSupport::TestCase
21 21 include Redmine::I18n
22 22 include Rails::Dom::Testing::Assertions
23 23 fixtures :projects, :enabled_modules, :issues, :users, :email_addresses, :members,
24 24 :member_roles, :roles, :documents, :attachments, :news,
25 25 :tokens, :journals, :journal_details, :changesets,
26 26 :trackers, :projects_trackers,
27 27 :issue_statuses, :enumerations, :messages, :boards, :repositories,
28 28 :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions,
29 29 :versions,
30 30 :comments
31 31
32 32 def setup
33 33 ActionMailer::Base.deliveries.clear
34 34 Setting.host_name = 'mydomain.foo'
35 35 Setting.protocol = 'http'
36 36 Setting.plain_text_mail = '0'
37 37 Setting.default_language = 'en'
38 38 User.current = nil
39 39 end
40 40
41 41 def test_generated_links_in_emails
42 42 Setting.host_name = 'mydomain.foo'
43 43 Setting.protocol = 'https'
44 44
45 45 journal = Journal.find(3)
46 46 assert Mailer.deliver_issue_edit(journal)
47 47
48 48 mail = last_email
49 49 assert_not_nil mail
50 50
51 51 assert_select_email do
52 52 # link to the main ticket
53 53 assert_select 'a[href=?]',
54 54 'https://mydomain.foo/issues/2#change-3',
55 55 :text => 'Feature request #2: Add ingredients categories'
56 56 # link to a referenced ticket
57 57 assert_select 'a[href=?][title=?]',
58 58 'https://mydomain.foo/issues/1',
59 59 "Bug: Cannot print recipes (New)",
60 60 :text => '#1'
61 61 # link to a changeset
62 62 assert_select 'a[href=?][title=?]',
63 63 'https://mydomain.foo/projects/ecookbook/repository/revisions/2',
64 64 'This commit fixes #1, #2 and references #1 & #3',
65 65 :text => 'r2'
66 66 # link to a description diff
67 67 assert_select 'a[href^=?][title=?]',
68 68 # should be https://mydomain.foo/journals/diff/3?detail_id=4
69 69 # but the Rails 4.2 DOM assertion doesn't handle the ? in the
70 70 # attribute value
71 'https://mydomain.foo/journals/diff/3',
71 'https://mydomain.foo/journals/3/diff',
72 72 'View differences',
73 73 :text => 'diff'
74 74 # link to an attachment
75 75 assert_select 'a[href=?]',
76 76 'https://mydomain.foo/attachments/download/4/source.rb',
77 77 :text => 'source.rb'
78 78 end
79 79 end
80 80
81 81 def test_generated_links_with_prefix
82 82 relative_url_root = Redmine::Utils.relative_url_root
83 83 Setting.host_name = 'mydomain.foo/rdm'
84 84 Setting.protocol = 'http'
85 85
86 86 journal = Journal.find(3)
87 87 assert Mailer.deliver_issue_edit(journal)
88 88
89 89 mail = last_email
90 90 assert_not_nil mail
91 91
92 92 assert_select_email do
93 93 # link to the main ticket
94 94 assert_select 'a[href=?]',
95 95 'http://mydomain.foo/rdm/issues/2#change-3',
96 96 :text => 'Feature request #2: Add ingredients categories'
97 97 # link to a referenced ticket
98 98 assert_select 'a[href=?][title=?]',
99 99 'http://mydomain.foo/rdm/issues/1',
100 100 "Bug: Cannot print recipes (New)",
101 101 :text => '#1'
102 102 # link to a changeset
103 103 assert_select 'a[href=?][title=?]',
104 104 'http://mydomain.foo/rdm/projects/ecookbook/repository/revisions/2',
105 105 'This commit fixes #1, #2 and references #1 & #3',
106 106 :text => 'r2'
107 107 # link to a description diff
108 108 assert_select 'a[href^=?][title=?]',
109 109 # should be http://mydomain.foo/rdm/journals/diff/3?detail_id=4
110 110 # but the Rails 4.2 DOM assertion doesn't handle the ? in the
111 111 # attribute value
112 'http://mydomain.foo/rdm/journals/diff/3',
112 'http://mydomain.foo/rdm/journals/3/diff',
113 113 'View differences',
114 114 :text => 'diff'
115 115 # link to an attachment
116 116 assert_select 'a[href=?]',
117 117 'http://mydomain.foo/rdm/attachments/download/4/source.rb',
118 118 :text => 'source.rb'
119 119 end
120 120 end
121 121
122 122 def test_generated_links_with_port_and_prefix
123 123 with_settings :host_name => '10.0.0.1:81/redmine', :protocol => 'http' do
124 124 Mailer.test_email(User.find(1)).deliver
125 125 mail = last_email
126 126 assert_not_nil mail
127 127 assert_include 'http://10.0.0.1:81/redmine', mail_body(mail)
128 128 end
129 129 end
130 130
131 131 def test_generated_links_with_port
132 132 with_settings :host_name => '10.0.0.1:81', :protocol => 'http' do
133 133 Mailer.test_email(User.find(1)).deliver
134 134 mail = last_email
135 135 assert_not_nil mail
136 136 assert_include 'http://10.0.0.1:81', mail_body(mail)
137 137 end
138 138 end
139 139
140 140 def test_issue_edit_should_generate_url_with_hostname_for_relations
141 141 journal = Journal.new(:journalized => Issue.find(1), :user => User.find(1), :created_on => Time.now)
142 142 journal.details << JournalDetail.new(:property => 'relation', :prop_key => 'label_relates_to', :value => 2)
143 143 Mailer.deliver_issue_edit(journal)
144 144 assert_not_nil last_email
145 145 assert_select_email do
146 146 assert_select 'a[href=?]', 'http://mydomain.foo/issues/2', :text => 'Feature request #2'
147 147 end
148 148 end
149 149
150 150 def test_generated_links_with_prefix_and_no_relative_url_root
151 151 relative_url_root = Redmine::Utils.relative_url_root
152 152 Setting.host_name = 'mydomain.foo/rdm'
153 153 Setting.protocol = 'http'
154 154 Redmine::Utils.relative_url_root = nil
155 155
156 156 journal = Journal.find(3)
157 157 assert Mailer.deliver_issue_edit(journal)
158 158
159 159 mail = last_email
160 160 assert_not_nil mail
161 161
162 162 assert_select_email do
163 163 # link to the main ticket
164 164 assert_select 'a[href=?]',
165 165 'http://mydomain.foo/rdm/issues/2#change-3',
166 166 :text => 'Feature request #2: Add ingredients categories'
167 167 # link to a referenced ticket
168 168 assert_select 'a[href=?][title=?]',
169 169 'http://mydomain.foo/rdm/issues/1',
170 170 "Bug: Cannot print recipes (New)",
171 171 :text => '#1'
172 172 # link to a changeset
173 173 assert_select 'a[href=?][title=?]',
174 174 'http://mydomain.foo/rdm/projects/ecookbook/repository/revisions/2',
175 175 'This commit fixes #1, #2 and references #1 & #3',
176 176 :text => 'r2'
177 177 # link to a description diff
178 178 assert_select 'a[href^=?][title=?]',
179 179 # should be http://mydomain.foo/rdm/journals/diff/3?detail_id=4
180 180 # but the Rails 4.2 DOM assertion doesn't handle the ? in the
181 181 # attribute value
182 'http://mydomain.foo/rdm/journals/diff/3',
182 'http://mydomain.foo/rdm/journals/3/diff',
183 183 'View differences',
184 184 :text => 'diff'
185 185 # link to an attachment
186 186 assert_select 'a[href=?]',
187 187 'http://mydomain.foo/rdm/attachments/download/4/source.rb',
188 188 :text => 'source.rb'
189 189 end
190 190 ensure
191 191 # restore it
192 192 Redmine::Utils.relative_url_root = relative_url_root
193 193 end
194 194
195 195 def test_email_headers
196 196 issue = Issue.find(1)
197 197 Mailer.deliver_issue_add(issue)
198 198 mail = last_email
199 199 assert_not_nil mail
200 200 assert_equal 'All', mail.header['X-Auto-Response-Suppress'].to_s
201 201 assert_equal 'auto-generated', mail.header['Auto-Submitted'].to_s
202 202 assert_equal '<redmine.example.net>', mail.header['List-Id'].to_s
203 203 end
204 204
205 205 def test_email_headers_should_include_sender
206 206 issue = Issue.find(1)
207 207 Mailer.deliver_issue_add(issue)
208 208 mail = last_email
209 209 assert_equal issue.author.login, mail.header['X-Redmine-Sender'].to_s
210 210 end
211 211
212 212 def test_plain_text_mail
213 213 Setting.plain_text_mail = 1
214 214 journal = Journal.find(2)
215 215 Mailer.deliver_issue_edit(journal)
216 216 mail = last_email
217 217 assert_equal "text/plain; charset=UTF-8", mail.content_type
218 218 assert_equal 0, mail.parts.size
219 219 assert !mail.encoded.include?('href')
220 220 end
221 221
222 222 def test_html_mail
223 223 Setting.plain_text_mail = 0
224 224 journal = Journal.find(2)
225 225 Mailer.deliver_issue_edit(journal)
226 226 mail = last_email
227 227 assert_equal 2, mail.parts.size
228 228 assert mail.encoded.include?('href')
229 229 end
230 230
231 231 def test_from_header
232 232 with_settings :mail_from => 'redmine@example.net' do
233 233 Mailer.test_email(User.find(1)).deliver
234 234 end
235 235 mail = last_email
236 236 assert_equal 'redmine@example.net', mail.from_addrs.first
237 237 end
238 238
239 239 def test_from_header_with_phrase
240 240 with_settings :mail_from => 'Redmine app <redmine@example.net>' do
241 241 Mailer.test_email(User.find(1)).deliver
242 242 end
243 243 mail = last_email
244 244 assert_equal 'redmine@example.net', mail.from_addrs.first
245 245 assert_equal 'Redmine app <redmine@example.net>', mail.header['From'].to_s
246 246 end
247 247
248 248 def test_should_not_send_email_without_recipient
249 249 news = News.first
250 250 user = news.author
251 251 # Remove members except news author
252 252 news.project.memberships.each {|m| m.destroy unless m.user == user}
253 253
254 254 user.pref.no_self_notified = false
255 255 user.pref.save
256 256 User.current = user
257 257 Mailer.news_added(news.reload).deliver
258 258 assert_equal 1, last_email.bcc.size
259 259
260 260 # nobody to notify
261 261 user.pref.no_self_notified = true
262 262 user.pref.save
263 263 User.current = user
264 264 ActionMailer::Base.deliveries.clear
265 265 Mailer.news_added(news.reload).deliver
266 266 assert ActionMailer::Base.deliveries.empty?
267 267 end
268 268
269 269 def test_issue_add_message_id
270 270 issue = Issue.find(2)
271 271 Mailer.deliver_issue_add(issue)
272 272 mail = last_email
273 273 assert_match /^redmine\.issue-2\.20060719190421\.[a-f0-9]+@example\.net/, mail.message_id
274 274 assert_include "redmine.issue-2.20060719190421@example.net", mail.references
275 275 end
276 276
277 277 def test_issue_edit_message_id
278 278 journal = Journal.find(3)
279 279 journal.issue = Issue.find(2)
280 280
281 281 Mailer.deliver_issue_edit(journal)
282 282 mail = last_email
283 283 assert_match /^redmine\.journal-3\.\d+\.[a-f0-9]+@example\.net/, mail.message_id
284 284 assert_include "redmine.issue-2.20060719190421@example.net", mail.references
285 285 assert_select_email do
286 286 # link to the update
287 287 assert_select "a[href=?]",
288 288 "http://mydomain.foo/issues/#{journal.journalized_id}#change-#{journal.id}"
289 289 end
290 290 end
291 291
292 292 def test_message_posted_message_id
293 293 message = Message.find(1)
294 294 Mailer.message_posted(message).deliver
295 295 mail = last_email
296 296 assert_match /^redmine\.message-1\.\d+\.[a-f0-9]+@example\.net/, mail.message_id
297 297 assert_include "redmine.message-1.20070512151532@example.net", mail.references
298 298 assert_select_email do
299 299 # link to the message
300 300 assert_select "a[href=?]",
301 301 "http://mydomain.foo/boards/#{message.board.id}/topics/#{message.id}",
302 302 :text => message.subject
303 303 end
304 304 end
305 305
306 306 def test_reply_posted_message_id
307 307 message = Message.find(3)
308 308 Mailer.message_posted(message).deliver
309 309 mail = last_email
310 310 assert_match /^redmine\.message-3\.\d+\.[a-f0-9]+@example\.net/, mail.message_id
311 311 assert_include "redmine.message-1.20070512151532@example.net", mail.references
312 312 assert_select_email do
313 313 # link to the reply
314 314 assert_select "a[href=?]",
315 315 "http://mydomain.foo/boards/#{message.board.id}/topics/#{message.root.id}?r=#{message.id}#message-#{message.id}",
316 316 :text => message.subject
317 317 end
318 318 end
319 319
320 320 test "#issue_add should notify project members" do
321 321 issue = Issue.find(1)
322 322 assert Mailer.deliver_issue_add(issue)
323 323 assert last_email.bcc.include?('dlopper@somenet.foo')
324 324 end
325 325
326 326 def test_issue_add_should_send_mail_to_all_user_email_address
327 327 EmailAddress.create!(:user_id => 3, :address => 'otheremail@somenet.foo')
328 328 issue = Issue.find(1)
329 329 assert Mailer.deliver_issue_add(issue)
330 330 assert last_email.bcc.include?('dlopper@somenet.foo')
331 331 assert last_email.bcc.include?('otheremail@somenet.foo')
332 332 end
333 333
334 334 test "#issue_add should not notify project members that are not allow to view the issue" do
335 335 issue = Issue.find(1)
336 336 Role.find(2).remove_permission!(:view_issues)
337 337 assert Mailer.deliver_issue_add(issue)
338 338 assert !last_email.bcc.include?('dlopper@somenet.foo')
339 339 end
340 340
341 341 test "#issue_add should notify issue watchers" do
342 342 issue = Issue.find(1)
343 343 user = User.find(9)
344 344 # minimal email notification options
345 345 user.pref.no_self_notified = '1'
346 346 user.pref.save
347 347 user.mail_notification = false
348 348 user.save
349 349
350 350 Watcher.create!(:watchable => issue, :user => user)
351 351 assert Mailer.deliver_issue_add(issue)
352 352 assert last_email.bcc.include?(user.mail)
353 353 end
354 354
355 355 test "#issue_add should not notify watchers not allowed to view the issue" do
356 356 issue = Issue.find(1)
357 357 user = User.find(9)
358 358 Watcher.create!(:watchable => issue, :user => user)
359 359 Role.non_member.remove_permission!(:view_issues)
360 360 assert Mailer.deliver_issue_add(issue)
361 361 assert !last_email.bcc.include?(user.mail)
362 362 end
363 363
364 364 def test_issue_add_should_include_enabled_fields
365 365 issue = Issue.find(2)
366 366 assert Mailer.deliver_issue_add(issue)
367 367 assert_mail_body_match '* Target version: 1.0', last_email
368 368 assert_select_email do
369 369 assert_select 'li', :text => 'Target version: 1.0'
370 370 end
371 371 end
372 372
373 373 def test_issue_add_should_not_include_disabled_fields
374 374 issue = Issue.find(2)
375 375 tracker = issue.tracker
376 376 tracker.core_fields -= ['fixed_version_id']
377 377 tracker.save!
378 378 assert Mailer.deliver_issue_add(issue)
379 379 assert_mail_body_no_match 'Target version', last_email
380 380 assert_select_email do
381 381 assert_select 'li', :text => /Target version/, :count => 0
382 382 end
383 383 end
384 384
385 385 # test mailer methods for each language
386 386 def test_issue_add
387 387 issue = Issue.find(1)
388 388 with_each_language_as_default do
389 389 assert Mailer.deliver_issue_add(issue)
390 390 end
391 391 end
392 392
393 393 def test_issue_edit
394 394 journal = Journal.find(1)
395 395 with_each_language_as_default do
396 396 assert Mailer.deliver_issue_edit(journal)
397 397 end
398 398 end
399 399
400 400 def test_issue_edit_should_send_private_notes_to_users_with_permission_only
401 401 journal = Journal.find(1)
402 402 journal.private_notes = true
403 403 journal.save!
404 404
405 405 Role.find(2).add_permission! :view_private_notes
406 406 Mailer.deliver_issue_edit(journal)
407 407 assert_equal %w(dlopper@somenet.foo jsmith@somenet.foo), ActionMailer::Base.deliveries.last.bcc.sort
408 408
409 409 Role.find(2).remove_permission! :view_private_notes
410 410 Mailer.deliver_issue_edit(journal)
411 411 assert_equal %w(jsmith@somenet.foo), ActionMailer::Base.deliveries.last.bcc.sort
412 412 end
413 413
414 414 def test_issue_edit_should_send_private_notes_to_watchers_with_permission_only
415 415 Issue.find(1).set_watcher(User.find_by_login('someone'))
416 416 journal = Journal.find(1)
417 417 journal.private_notes = true
418 418 journal.save!
419 419
420 420 Role.non_member.add_permission! :view_private_notes
421 421 Mailer.deliver_issue_edit(journal)
422 422 assert_include 'someone@foo.bar', ActionMailer::Base.deliveries.last.bcc.sort
423 423
424 424 Role.non_member.remove_permission! :view_private_notes
425 425 Mailer.deliver_issue_edit(journal)
426 426 assert_not_include 'someone@foo.bar', ActionMailer::Base.deliveries.last.bcc.sort
427 427 end
428 428
429 429 def test_issue_edit_should_mark_private_notes
430 430 journal = Journal.find(2)
431 431 journal.private_notes = true
432 432 journal.save!
433 433
434 434 with_settings :default_language => 'en' do
435 435 Mailer.deliver_issue_edit(journal)
436 436 end
437 437 assert_mail_body_match '(Private notes)', last_email
438 438 end
439 439
440 440 def test_issue_edit_with_relation_should_notify_users_who_can_see_the_related_issue
441 441 issue = Issue.generate!
442 442 issue.init_journal(User.find(1))
443 443 private_issue = Issue.generate!(:is_private => true)
444 444 IssueRelation.create!(:issue_from => issue, :issue_to => private_issue, :relation_type => 'relates')
445 445 issue.reload
446 446 assert_equal 1, issue.journals.size
447 447 journal = issue.journals.first
448 448 ActionMailer::Base.deliveries.clear
449 449
450 450 Mailer.deliver_issue_edit(journal)
451 451 last_email.bcc.each do |email|
452 452 user = User.find_by_mail(email)
453 453 assert private_issue.visible?(user), "Issue was not visible to #{user}"
454 454 end
455 455 end
456 456
457 457 def test_document_added
458 458 document = Document.find(1)
459 459 with_each_language_as_default do
460 460 assert Mailer.document_added(document).deliver
461 461 end
462 462 end
463 463
464 464 def test_attachments_added
465 465 attachements = [ Attachment.find_by_container_type('Document') ]
466 466 with_each_language_as_default do
467 467 assert Mailer.attachments_added(attachements).deliver
468 468 end
469 469 end
470 470
471 471 def test_version_file_added
472 472 attachements = [ Attachment.find_by_container_type('Version') ]
473 473 assert Mailer.attachments_added(attachements).deliver
474 474 assert_not_nil last_email.bcc
475 475 assert last_email.bcc.any?
476 476 assert_select_email do
477 477 assert_select "a[href=?]", "http://mydomain.foo/projects/ecookbook/files"
478 478 end
479 479 end
480 480
481 481 def test_project_file_added
482 482 attachements = [ Attachment.find_by_container_type('Project') ]
483 483 assert Mailer.attachments_added(attachements).deliver
484 484 assert_not_nil last_email.bcc
485 485 assert last_email.bcc.any?
486 486 assert_select_email do
487 487 assert_select "a[href=?]", "http://mydomain.foo/projects/ecookbook/files"
488 488 end
489 489 end
490 490
491 491 def test_news_added
492 492 news = News.first
493 493 with_each_language_as_default do
494 494 assert Mailer.news_added(news).deliver
495 495 end
496 496 end
497 497
498 498 def test_news_added_should_notify_project_news_watchers
499 499 user1 = User.generate!
500 500 user2 = User.generate!
501 501 news = News.find(1)
502 502 news.project.enabled_module('news').add_watcher(user1)
503 503
504 504 Mailer.news_added(news).deliver
505 505 assert_include user1.mail, last_email.bcc
506 506 assert_not_include user2.mail, last_email.bcc
507 507 end
508 508
509 509 def test_news_comment_added
510 510 comment = Comment.find(2)
511 511 with_each_language_as_default do
512 512 assert Mailer.news_comment_added(comment).deliver
513 513 end
514 514 end
515 515
516 516 def test_message_posted
517 517 message = Message.first
518 518 recipients = ([message.root] + message.root.children).collect {|m| m.author.mail if m.author}
519 519 recipients = recipients.compact.uniq
520 520 with_each_language_as_default do
521 521 assert Mailer.message_posted(message).deliver
522 522 end
523 523 end
524 524
525 525 def test_wiki_content_added
526 526 content = WikiContent.find(1)
527 527 with_each_language_as_default do
528 528 assert_difference 'ActionMailer::Base.deliveries.size' do
529 529 assert Mailer.wiki_content_added(content).deliver
530 530 assert_select_email do
531 531 assert_select 'a[href=?]',
532 532 'http://mydomain.foo/projects/ecookbook/wiki/CookBook_documentation',
533 533 :text => 'CookBook documentation'
534 534 end
535 535 end
536 536 end
537 537 end
538 538
539 539 def test_wiki_content_updated
540 540 content = WikiContent.find(1)
541 541 with_each_language_as_default do
542 542 assert_difference 'ActionMailer::Base.deliveries.size' do
543 543 assert Mailer.wiki_content_updated(content).deliver
544 544 assert_select_email do
545 545 assert_select 'a[href=?]',
546 546 'http://mydomain.foo/projects/ecookbook/wiki/CookBook_documentation',
547 547 :text => 'CookBook documentation'
548 548 end
549 549 end
550 550 end
551 551 end
552 552
553 553 def test_account_information
554 554 user = User.find(2)
555 555 valid_languages.each do |lang|
556 556 user.update_attribute :language, lang.to_s
557 557 user.reload
558 558 assert Mailer.account_information(user, 'pAsswORd').deliver
559 559 end
560 560 end
561 561
562 562 def test_lost_password
563 563 token = Token.find(2)
564 564 valid_languages.each do |lang|
565 565 token.user.update_attribute :language, lang.to_s
566 566 token.reload
567 567 assert Mailer.lost_password(token).deliver
568 568 end
569 569 end
570 570
571 571 def test_register
572 572 token = Token.find(1)
573 573 Setting.host_name = 'redmine.foo'
574 574 Setting.protocol = 'https'
575 575
576 576 valid_languages.each do |lang|
577 577 token.user.update_attribute :language, lang.to_s
578 578 token.reload
579 579 ActionMailer::Base.deliveries.clear
580 580 assert Mailer.register(token).deliver
581 581 mail = last_email
582 582 assert_select_email do
583 583 assert_select "a[href=?]",
584 584 "https://redmine.foo/account/activate?token=#{token.value}",
585 585 :text => "https://redmine.foo/account/activate?token=#{token.value}"
586 586 end
587 587 end
588 588 end
589 589
590 590 def test_test
591 591 user = User.find(1)
592 592 valid_languages.each do |lang|
593 593 user.update_attribute :language, lang.to_s
594 594 assert Mailer.test_email(user).deliver
595 595 end
596 596 end
597 597
598 598 def test_reminders
599 599 Mailer.reminders(:days => 42)
600 600 assert_equal 1, ActionMailer::Base.deliveries.size
601 601 mail = last_email
602 602 assert mail.bcc.include?('dlopper@somenet.foo')
603 603 assert_mail_body_match 'Bug #3: Error 281 when updating a recipe', mail
604 604 assert_equal '1 issue(s) due in the next 42 days', mail.subject
605 605 end
606 606
607 607 def test_reminders_should_not_include_closed_issues
608 608 with_settings :default_language => 'en' do
609 609 Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 5,
610 610 :subject => 'Closed issue', :assigned_to_id => 3,
611 611 :due_date => 5.days.from_now,
612 612 :author_id => 2)
613 613 ActionMailer::Base.deliveries.clear
614 614
615 615 Mailer.reminders(:days => 42)
616 616 assert_equal 1, ActionMailer::Base.deliveries.size
617 617 mail = last_email
618 618 assert mail.bcc.include?('dlopper@somenet.foo')
619 619 assert_mail_body_no_match 'Closed issue', mail
620 620 end
621 621 end
622 622
623 623 def test_reminders_for_users
624 624 Mailer.reminders(:days => 42, :users => ['5'])
625 625 assert_equal 0, ActionMailer::Base.deliveries.size # No mail for dlopper
626 626 Mailer.reminders(:days => 42, :users => ['3'])
627 627 assert_equal 1, ActionMailer::Base.deliveries.size # No mail for dlopper
628 628 mail = last_email
629 629 assert mail.bcc.include?('dlopper@somenet.foo')
630 630 assert_mail_body_match 'Bug #3: Error 281 when updating a recipe', mail
631 631 end
632 632
633 633 def test_reminder_should_include_issues_assigned_to_groups
634 634 with_settings :default_language => 'en' do
635 635 group = Group.generate!
636 636 group.users << User.find(2)
637 637 group.users << User.find(3)
638 638
639 639 Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 1,
640 640 :subject => 'Assigned to group', :assigned_to => group,
641 641 :due_date => 5.days.from_now,
642 642 :author_id => 2)
643 643 ActionMailer::Base.deliveries.clear
644 644
645 645 Mailer.reminders(:days => 7)
646 646 assert_equal 2, ActionMailer::Base.deliveries.size
647 647 assert_equal %w(dlopper@somenet.foo jsmith@somenet.foo), ActionMailer::Base.deliveries.map(&:bcc).flatten.sort
648 648 ActionMailer::Base.deliveries.each do |mail|
649 649 assert_mail_body_match 'Assigned to group', mail
650 650 end
651 651 end
652 652 end
653 653
654 654 def test_reminders_with_version_option
655 655 with_settings :default_language => 'en' do
656 656 version = Version.generate!(:name => 'Acme', :project_id => 1)
657 657 Issue.generate!(:assigned_to => User.find(2), :due_date => 5.days.from_now)
658 658 Issue.generate!(:assigned_to => User.find(3), :due_date => 5.days.from_now, :fixed_version => version)
659 659 ActionMailer::Base.deliveries.clear
660 660
661 661 Mailer.reminders(:days => 42, :version => 'acme')
662 662 assert_equal 1, ActionMailer::Base.deliveries.size
663 663
664 664 mail = last_email
665 665 assert mail.bcc.include?('dlopper@somenet.foo')
666 666 end
667 667 end
668 668
669 669 def test_mailer_should_not_change_locale
670 670 # Set current language to italian
671 671 set_language_if_valid 'it'
672 672 # Send an email to a french user
673 673 user = User.find(1)
674 674 user.language = 'fr'
675 675 Mailer.account_activated(user).deliver
676 676 mail = last_email
677 677 assert_mail_body_match 'Votre compte', mail
678 678
679 679 assert_equal :it, current_language
680 680 end
681 681
682 682 def test_with_deliveries_off
683 683 Mailer.with_deliveries false do
684 684 Mailer.test_email(User.find(1)).deliver
685 685 end
686 686 assert ActionMailer::Base.deliveries.empty?
687 687 # should restore perform_deliveries
688 688 assert ActionMailer::Base.perform_deliveries
689 689 end
690 690
691 691 def test_token_for_should_strip_trailing_gt_from_address_with_full_name
692 692 with_settings :mail_from => "Redmine Mailer<no-reply@redmine.org>" do
693 693 assert_match /\Aredmine.issue-\d+\.\d+\.[0-9a-f]+@redmine.org\z/, Mailer.token_for(Issue.generate!)
694 694 end
695 695 end
696 696
697 697 def test_layout_should_include_the_emails_header
698 698 with_settings :emails_header => "*Header content*" do
699 699 with_settings :plain_text_mail => 0 do
700 700 assert Mailer.test_email(User.find(1)).deliver
701 701 assert_select_email do
702 702 assert_select ".header" do
703 703 assert_select "strong", :text => "Header content"
704 704 end
705 705 end
706 706 end
707 707 with_settings :plain_text_mail => 1 do
708 708 assert Mailer.test_email(User.find(1)).deliver
709 709 mail = last_email
710 710 assert_not_nil mail
711 711 assert_include "*Header content*", mail.body.decoded
712 712 end
713 713 end
714 714 end
715 715
716 716 def test_layout_should_not_include_empty_emails_header
717 717 with_settings :emails_header => "", :plain_text_mail => 0 do
718 718 assert Mailer.test_email(User.find(1)).deliver
719 719 assert_select_email do
720 720 assert_select ".header", false
721 721 end
722 722 end
723 723 end
724 724
725 725 def test_layout_should_include_the_emails_footer
726 726 with_settings :emails_footer => "*Footer content*" do
727 727 with_settings :plain_text_mail => 0 do
728 728 assert Mailer.test_email(User.find(1)).deliver
729 729 assert_select_email do
730 730 assert_select ".footer" do
731 731 assert_select "strong", :text => "Footer content"
732 732 end
733 733 end
734 734 end
735 735 with_settings :plain_text_mail => 1 do
736 736 assert Mailer.test_email(User.find(1)).deliver
737 737 mail = last_email
738 738 assert_not_nil mail
739 739 assert_include "\n-- \n", mail.body.decoded
740 740 assert_include "*Footer content*", mail.body.decoded
741 741 end
742 742 end
743 743 end
744 744
745 745 def test_layout_should_not_include_empty_emails_footer
746 746 with_settings :emails_footer => "" do
747 747 with_settings :plain_text_mail => 0 do
748 748 assert Mailer.test_email(User.find(1)).deliver
749 749 assert_select_email do
750 750 assert_select ".footer", false
751 751 end
752 752 end
753 753 with_settings :plain_text_mail => 1 do
754 754 assert Mailer.test_email(User.find(1)).deliver
755 755 mail = last_email
756 756 assert_not_nil mail
757 757 assert_not_include "\n-- \n", mail.body.decoded
758 758 end
759 759 end
760 760 end
761 761
762 762 def test_should_escape_html_templates_only
763 763 Issue.generate!(:project_id => 1, :tracker_id => 1, :subject => 'Subject with a <tag>')
764 764 mail = last_email
765 765 assert_equal 2, mail.parts.size
766 766 assert_include '<tag>', text_part.body.encoded
767 767 assert_include '&lt;tag&gt;', html_part.body.encoded
768 768 end
769 769
770 770 def test_should_raise_delivery_errors_when_raise_delivery_errors_is_true
771 771 mail = Mailer.test_email(User.find(1))
772 772 mail.delivery_method.stubs(:deliver!).raises(Exception.new("delivery error"))
773 773
774 774 ActionMailer::Base.raise_delivery_errors = true
775 775 assert_raise Exception, "delivery error" do
776 776 mail.deliver
777 777 end
778 778 ensure
779 779 ActionMailer::Base.raise_delivery_errors = false
780 780 end
781 781
782 782 def test_should_log_delivery_errors_when_raise_delivery_errors_is_false
783 783 mail = Mailer.test_email(User.find(1))
784 784 mail.delivery_method.stubs(:deliver!).raises(Exception.new("delivery error"))
785 785
786 786 Rails.logger.expects(:error).with("Email delivery error: delivery error")
787 787 ActionMailer::Base.raise_delivery_errors = false
788 788 assert_nothing_raised do
789 789 mail.deliver
790 790 end
791 791 end
792 792
793 793 def test_with_synched_deliveries_should_yield_with_synced_deliveries
794 794 ActionMailer::Base.delivery_method = :async_smtp
795 795 ActionMailer::Base.async_smtp_settings = {:foo => 'bar'}
796 796
797 797 Mailer.with_synched_deliveries do
798 798 assert_equal :smtp, ActionMailer::Base.delivery_method
799 799 assert_equal({:foo => 'bar'}, ActionMailer::Base.smtp_settings)
800 800 end
801 801 assert_equal :async_smtp, ActionMailer::Base.delivery_method
802 802 ensure
803 803 ActionMailer::Base.delivery_method = :test
804 804 end
805 805
806 806 def test_email_addresses_should_keep_addresses
807 807 assert_equal ["foo@example.net"],
808 808 Mailer.email_addresses("foo@example.net")
809 809
810 810 assert_equal ["foo@example.net", "bar@example.net"],
811 811 Mailer.email_addresses(["foo@example.net", "bar@example.net"])
812 812 end
813 813
814 814 def test_email_addresses_should_replace_users_with_their_email_addresses
815 815 assert_equal ["admin@somenet.foo"],
816 816 Mailer.email_addresses(User.find(1))
817 817
818 818 assert_equal ["admin@somenet.foo", "jsmith@somenet.foo"],
819 819 Mailer.email_addresses(User.where(:id => [1,2])).sort
820 820 end
821 821
822 822 def test_email_addresses_should_include_notified_emails_addresses_only
823 823 EmailAddress.create!(:user_id => 2, :address => "another@somenet.foo", :notify => false)
824 824 EmailAddress.create!(:user_id => 2, :address => "another2@somenet.foo")
825 825
826 826 assert_equal ["another2@somenet.foo", "jsmith@somenet.foo"],
827 827 Mailer.email_addresses(User.find(2)).sort
828 828 end
829 829
830 830 private
831 831
832 832 def last_email
833 833 mail = ActionMailer::Base.deliveries.last
834 834 assert_not_nil mail
835 835 mail
836 836 end
837 837
838 838 def text_part
839 839 last_email.parts.detect {|part| part.content_type.include?('text/plain')}
840 840 end
841 841
842 842 def html_part
843 843 last_email.parts.detect {|part| part.content_type.include?('text/html')}
844 844 end
845 845
846 846 def with_each_language_as_default(&block)
847 847 valid_languages.each do |lang|
848 848 with_settings :default_language => lang.to_s do
849 849 yield lang
850 850 end
851 851 end
852 852 end
853 853 end
General Comments 0
You need to be logged in to leave comments. Login now