@@ -1,110 +1,113 | |||||
1 | # Redmine - project management software |
|
1 | # Redmine - project management software | |
2 | # Copyright (C) 2006-2016 Jean-Philippe Lang |
|
2 | # Copyright (C) 2006-2016 Jean-Philippe Lang | |
3 | # |
|
3 | # | |
4 | # This program is free software; you can redistribute it and/or |
|
4 | # This program is free software; you can redistribute it and/or | |
5 | # modify it under the terms of the GNU General Public License |
|
5 | # modify it under the terms of the GNU General Public License | |
6 | # as published by the Free Software Foundation; either version 2 |
|
6 | # as published by the Free Software Foundation; either version 2 | |
7 | # of the License, or (at your option) any later version. |
|
7 | # of the License, or (at your option) any later version. | |
8 | # |
|
8 | # | |
9 | # This program is distributed in the hope that it will be useful, |
|
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. |
|
12 | # GNU General Public License for more details. | |
13 | # |
|
13 | # | |
14 | # You should have received a copy of the GNU General Public License |
|
14 | # You should have received a copy of the GNU General Public License | |
15 | # along with this program; if not, write to the Free Software |
|
15 | # along with this program; if not, write to the Free Software | |
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
17 |
|
17 | |||
18 | class JournalsController < ApplicationController |
|
18 | class JournalsController < ApplicationController | |
19 | before_filter :find_journal, :only => [:edit, :update, :diff] |
|
19 | before_filter :find_journal, :only => [:edit, :update, :diff] | |
20 | before_filter :find_issue, :only => [:new] |
|
20 | before_filter :find_issue, :only => [:new] | |
21 | before_filter :find_optional_project, :only => [:index] |
|
21 | before_filter :find_optional_project, :only => [:index] | |
22 | before_filter :authorize, :only => [:new, :edit, :update, :diff] |
|
22 | before_filter :authorize, :only => [:new, :edit, :update, :diff] | |
23 | accept_rss_auth :index |
|
23 | accept_rss_auth :index | |
24 | menu_item :issues |
|
24 | menu_item :issues | |
25 |
|
25 | |||
26 | helper :issues |
|
26 | helper :issues | |
27 | helper :custom_fields |
|
27 | helper :custom_fields | |
28 | helper :queries |
|
28 | helper :queries | |
29 | include QueriesHelper |
|
29 | include QueriesHelper | |
30 | helper :sort |
|
30 | helper :sort | |
31 | include SortHelper |
|
31 | include SortHelper | |
32 |
|
32 | |||
33 | def index |
|
33 | def index | |
34 | retrieve_query |
|
34 | retrieve_query | |
35 | sort_init 'id', 'desc' |
|
35 | sort_init 'id', 'desc' | |
36 | sort_update(@query.sortable_columns) |
|
36 | sort_update(@query.sortable_columns) | |
37 | if @query.valid? |
|
37 | if @query.valid? | |
38 | @journals = @query.journals(:order => "#{Journal.table_name}.created_on DESC", |
|
38 | @journals = @query.journals(:order => "#{Journal.table_name}.created_on DESC", | |
39 | :limit => 25) |
|
39 | :limit => 25) | |
40 | end |
|
40 | end | |
41 | @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name) |
|
41 | @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name) | |
42 | render :layout => false, :content_type => 'application/atom+xml' |
|
42 | render :layout => false, :content_type => 'application/atom+xml' | |
43 | rescue ActiveRecord::RecordNotFound |
|
43 | rescue ActiveRecord::RecordNotFound | |
44 | render_404 |
|
44 | render_404 | |
45 | end |
|
45 | end | |
46 |
|
46 | |||
47 | def diff |
|
47 | def diff | |
48 | @issue = @journal.issue |
|
48 | @issue = @journal.issue | |
49 | if params[:detail_id].present? |
|
49 | if params[:detail_id].present? | |
50 | @detail = @journal.details.find_by_id(params[:detail_id]) |
|
50 | @detail = @journal.details.find_by_id(params[:detail_id]) | |
51 | else |
|
51 | else | |
52 | @detail = @journal.details.detect {|d| d.property == 'attr' && d.prop_key == 'description'} |
|
52 | @detail = @journal.details.detect {|d| d.property == 'attr' && d.prop_key == 'description'} | |
53 | end |
|
53 | end | |
54 | unless @issue && @detail |
|
54 | unless @issue && @detail | |
55 | render_404 |
|
55 | render_404 | |
56 | return false |
|
56 | return false | |
57 | end |
|
57 | end | |
58 | if @detail.property == 'cf' |
|
58 | if @detail.property == 'cf' | |
59 | unless @detail.custom_field && @detail.custom_field.visible_by?(@issue.project, User.current) |
|
59 | unless @detail.custom_field && @detail.custom_field.visible_by?(@issue.project, User.current) | |
60 | raise ::Unauthorized |
|
60 | raise ::Unauthorized | |
61 | end |
|
61 | end | |
62 | end |
|
62 | end | |
63 | @diff = Redmine::Helpers::Diff.new(@detail.value, @detail.old_value) |
|
63 | @diff = Redmine::Helpers::Diff.new(@detail.value, @detail.old_value) | |
64 | end |
|
64 | end | |
65 |
|
65 | |||
66 | def new |
|
66 | def new | |
67 | @journal = Journal.visible.find(params[:journal_id]) if params[:journal_id] |
|
67 | @journal = Journal.visible.find(params[:journal_id]) if params[:journal_id] | |
68 | if @journal |
|
68 | if @journal | |
69 | user = @journal.user |
|
69 | user = @journal.user | |
70 | text = @journal.notes |
|
70 | text = @journal.notes | |
71 | else |
|
71 | else | |
72 | user = @issue.author |
|
72 | user = @issue.author | |
73 | text = @issue.description |
|
73 | text = @issue.description | |
74 | end |
|
74 | end | |
75 | # Replaces pre blocks with [...] |
|
75 | # Replaces pre blocks with [...] | |
76 | text = text.to_s.strip.gsub(%r{<pre>(.*?)</pre>}m, '[...]') |
|
76 | text = text.to_s.strip.gsub(%r{<pre>(.*?)</pre>}m, '[...]') | |
77 | @content = "#{ll(Setting.default_language, :text_user_wrote, user)}\n> " |
|
77 | @content = "#{ll(Setting.default_language, :text_user_wrote, user)}\n> " | |
78 | @content << text.gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n" |
|
78 | @content << text.gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n" | |
79 | rescue ActiveRecord::RecordNotFound |
|
79 | rescue ActiveRecord::RecordNotFound | |
80 | render_404 |
|
80 | render_404 | |
81 | end |
|
81 | end | |
82 |
|
82 | |||
83 | def edit |
|
83 | def edit | |
84 | (render_403; return false) unless @journal.editable_by?(User.current) |
|
84 | (render_403; return false) unless @journal.editable_by?(User.current) | |
85 | respond_to do |format| |
|
85 | respond_to do |format| | |
86 | # TODO: implement non-JS journal update |
|
86 | # TODO: implement non-JS journal update | |
87 | format.js |
|
87 | format.js | |
88 | end |
|
88 | end | |
89 | end |
|
89 | end | |
90 |
|
90 | |||
91 | def update |
|
91 | def update | |
92 | (render_403; return false) unless @journal.editable_by?(User.current) |
|
92 | (render_403; return false) unless @journal.editable_by?(User.current) | |
93 |
@journal. |
|
93 | @journal.notes = params[:notes] if params[:notes] | |
|
94 | @journal.private_notes = params[:private_notes].present? | |||
|
95 | (render_403; return false) if @journal.private_notes_changed? && User.current.allowed_to?(:set_notes_private, @journal.issue.project) == false | |||
|
96 | @journal.save if @journal.changed? | |||
94 | @journal.destroy if @journal.details.empty? && @journal.notes.blank? |
|
97 | @journal.destroy if @journal.details.empty? && @journal.notes.blank? | |
95 | call_hook(:controller_journals_edit_post, { :journal => @journal, :params => params}) |
|
98 | call_hook(:controller_journals_edit_post, { :journal => @journal, :params => params}) | |
96 | respond_to do |format| |
|
99 | respond_to do |format| | |
97 | format.html { redirect_to issue_path(@journal.journalized) } |
|
100 | format.html { redirect_to issue_path(@journal.journalized) } | |
98 | format.js |
|
101 | format.js | |
99 | end |
|
102 | end | |
100 | end |
|
103 | end | |
101 |
|
104 | |||
102 | private |
|
105 | private | |
103 |
|
106 | |||
104 | def find_journal |
|
107 | def find_journal | |
105 | @journal = Journal.visible.find(params[:id]) |
|
108 | @journal = Journal.visible.find(params[:id]) | |
106 | @project = @journal.journalized.project |
|
109 | @project = @journal.journalized.project | |
107 | rescue ActiveRecord::RecordNotFound |
|
110 | rescue ActiveRecord::RecordNotFound | |
108 | render_404 |
|
111 | render_404 | |
109 | end |
|
112 | end | |
110 | end |
|
113 | end |
@@ -1,61 +1,67 | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 | # |
|
2 | # | |
3 | # Redmine - project management software |
|
3 | # Redmine - project management software | |
4 | # Copyright (C) 2006-2016 Jean-Philippe Lang |
|
4 | # Copyright (C) 2006-2016 Jean-Philippe Lang | |
5 | # |
|
5 | # | |
6 | # This program is free software; you can redistribute it and/or |
|
6 | # This program is free software; you can redistribute it and/or | |
7 | # modify it under the terms of the GNU General Public License |
|
7 | # modify it under the terms of the GNU General Public License | |
8 | # as published by the Free Software Foundation; either version 2 |
|
8 | # as published by the Free Software Foundation; either version 2 | |
9 | # of the License, or (at your option) any later version. |
|
9 | # of the License, or (at your option) any later version. | |
10 | # |
|
10 | # | |
11 | # This program is distributed in the hope that it will be useful, |
|
11 | # This program is distributed in the hope that it will be useful, | |
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | # GNU General Public License for more details. |
|
14 | # GNU General Public License for more details. | |
15 | # |
|
15 | # | |
16 | # You should have received a copy of the GNU General Public License |
|
16 | # You should have received a copy of the GNU General Public License | |
17 | # along with this program; if not, write to the Free Software |
|
17 | # along with this program; if not, write to the Free Software | |
18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
19 |
|
19 | |||
20 | module JournalsHelper |
|
20 | module JournalsHelper | |
21 |
|
21 | |||
22 | # Returns the attachments of a journal that are displayed as thumbnails |
|
22 | # Returns the attachments of a journal that are displayed as thumbnails | |
23 | def journal_thumbnail_attachments(journal) |
|
23 | def journal_thumbnail_attachments(journal) | |
24 | ids = journal.details.select {|d| d.property == 'attachment' && d.value.present?}.map(&:prop_key) |
|
24 | ids = journal.details.select {|d| d.property == 'attachment' && d.value.present?}.map(&:prop_key) | |
25 | ids.any? ? Attachment.where(:id => ids).select(&:thumbnailable?) : [] |
|
25 | ids.any? ? Attachment.where(:id => ids).select(&:thumbnailable?) : [] | |
26 | end |
|
26 | end | |
27 |
|
27 | |||
28 | def render_notes(issue, journal, options={}) |
|
28 | def render_notes(issue, journal, options={}) | |
29 | content = '' |
|
29 | content = '' | |
30 | editable = User.current.logged? && (User.current.allowed_to?(:edit_issue_notes, issue.project) || (journal.user == User.current && User.current.allowed_to?(:edit_own_issue_notes, issue.project))) |
|
30 | editable = User.current.logged? && (User.current.allowed_to?(:edit_issue_notes, issue.project) || (journal.user == User.current && User.current.allowed_to?(:edit_own_issue_notes, issue.project))) | |
31 | links = [] |
|
31 | links = [] | |
32 | if !journal.notes.blank? |
|
32 | if !journal.notes.blank? | |
33 | links << link_to(l(:button_quote), |
|
33 | links << link_to(l(:button_quote), | |
34 | quoted_issue_path(issue, :journal_id => journal), |
|
34 | quoted_issue_path(issue, :journal_id => journal), | |
35 | :remote => true, |
|
35 | :remote => true, | |
36 | :method => 'post', |
|
36 | :method => 'post', | |
37 | :title => l(:button_quote), |
|
37 | :title => l(:button_quote), | |
38 | :class => 'icon-only icon-comment' |
|
38 | :class => 'icon-only icon-comment' | |
39 | ) if options[:reply_links] |
|
39 | ) if options[:reply_links] | |
40 | links << link_to(l(:button_edit), |
|
40 | links << link_to(l(:button_edit), | |
41 | edit_journal_path(journal), |
|
41 | edit_journal_path(journal), | |
42 | :remote => true, |
|
42 | :remote => true, | |
43 | :method => 'get', |
|
43 | :method => 'get', | |
44 | :title => l(:button_edit), |
|
44 | :title => l(:button_edit), | |
45 | :class => 'icon-only icon-edit' |
|
45 | :class => 'icon-only icon-edit' | |
46 | ) if editable |
|
46 | ) if editable | |
47 | links << link_to(l(:button_delete), |
|
47 | links << link_to(l(:button_delete), | |
48 | journal_path(journal, :notes => ""), |
|
48 | journal_path(journal, :notes => ""), | |
49 | :remote => true, |
|
49 | :remote => true, | |
50 | :method => 'put', :data => {:confirm => l(:text_are_you_sure)}, |
|
50 | :method => 'put', :data => {:confirm => l(:text_are_you_sure)}, | |
51 | :title => l(:button_delete), |
|
51 | :title => l(:button_delete), | |
52 | :class => 'icon-only icon-del' |
|
52 | :class => 'icon-only icon-del' | |
53 | ) if editable |
|
53 | ) if editable | |
54 | end |
|
54 | end | |
55 | content << content_tag('div', links.join(' ').html_safe, :class => 'contextual') unless links.empty? |
|
55 | content << content_tag('div', links.join(' ').html_safe, :class => 'contextual') unless links.empty? | |
56 | content << textilizable(journal, :notes) |
|
56 | content << textilizable(journal, :notes) | |
57 | css_classes = "wiki" |
|
57 | css_classes = "wiki" | |
58 | css_classes << " editable" if editable |
|
58 | css_classes << " editable" if editable | |
59 | content_tag('div', content.html_safe, :id => "journal-#{journal.id}-notes", :class => css_classes) |
|
59 | content_tag('div', content.html_safe, :id => "journal-#{journal.id}-notes", :class => css_classes) | |
60 | end |
|
60 | end | |
|
61 | ||||
|
62 | def render_private_notes(journal) | |||
|
63 | content = journal.private_notes? ? l(:field_is_private) : '' | |||
|
64 | css_classes = journal.private_notes? ? 'private' : '' | |||
|
65 | content_tag('span', content.html_safe, :id => "journal-#{journal.id}-private_notes", :class => css_classes) | |||
|
66 | end | |||
61 | end |
|
67 | end |
@@ -1,30 +1,30 | |||||
1 | <% reply_links = issue.notes_addable? -%> |
|
1 | <% reply_links = issue.notes_addable? -%> | |
2 | <% for journal in journals %> |
|
2 | <% for journal in journals %> | |
3 | <div id="change-<%= journal.id %>" class="<%= journal.css_classes %>"> |
|
3 | <div id="change-<%= journal.id %>" class="<%= journal.css_classes %>"> | |
4 | <div id="note-<%= journal.indice %>"> |
|
4 | <div id="note-<%= journal.indice %>"> | |
5 | <h4><a href="#note-<%= journal.indice %>" class="journal-link">#<%= journal.indice %></a> |
|
5 | <h4><a href="#note-<%= journal.indice %>" class="journal-link">#<%= journal.indice %></a> | |
6 | <%= avatar(journal.user, :size => "24") %> |
|
6 | <%= avatar(journal.user, :size => "24") %> | |
7 | <%= authoring journal.created_on, journal.user, :label => :label_updated_time_by %> |
|
7 | <%= authoring journal.created_on, journal.user, :label => :label_updated_time_by %> | |
8 | <%= content_tag('span', l(:field_is_private), :class => 'private') if journal.private_notes? %></h4> |
|
8 | <%= render_private_notes(journal) %></h4> | |
9 |
|
9 | |||
10 | <% if journal.details.any? %> |
|
10 | <% if journal.details.any? %> | |
11 | <ul class="details"> |
|
11 | <ul class="details"> | |
12 | <% details_to_strings(journal.visible_details).each do |string| %> |
|
12 | <% details_to_strings(journal.visible_details).each do |string| %> | |
13 | <li><%= string %></li> |
|
13 | <li><%= string %></li> | |
14 | <% end %> |
|
14 | <% end %> | |
15 | </ul> |
|
15 | </ul> | |
16 | <% if Setting.thumbnails_enabled? && (thumbnail_attachments = journal_thumbnail_attachments(journal)).any? %> |
|
16 | <% if Setting.thumbnails_enabled? && (thumbnail_attachments = journal_thumbnail_attachments(journal)).any? %> | |
17 | <div class="thumbnails"> |
|
17 | <div class="thumbnails"> | |
18 | <% thumbnail_attachments.each do |attachment| %> |
|
18 | <% thumbnail_attachments.each do |attachment| %> | |
19 | <div><%= thumbnail_tag(attachment) %></div> |
|
19 | <div><%= thumbnail_tag(attachment) %></div> | |
20 | <% end %> |
|
20 | <% end %> | |
21 | </div> |
|
21 | </div> | |
22 | <% end %> |
|
22 | <% end %> | |
23 | <% end %> |
|
23 | <% end %> | |
24 | <%= render_notes(issue, journal, :reply_links => reply_links) unless journal.notes.blank? %> |
|
24 | <%= render_notes(issue, journal, :reply_links => reply_links) unless journal.notes.blank? %> | |
25 | </div> |
|
25 | </div> | |
26 | </div> |
|
26 | </div> | |
27 | <%= call_hook(:view_issues_history_journal_bottom, { :journal => journal }) %> |
|
27 | <%= call_hook(:view_issues_history_journal_bottom, { :journal => journal }) %> | |
28 | <% end %> |
|
28 | <% end %> | |
29 |
|
29 | |||
30 | <% heads_for_wiki_formatter if User.current.allowed_to?(:edit_issue_notes, issue.project) || User.current.allowed_to?(:edit_own_issue_notes, issue.project) %> |
|
30 | <% heads_for_wiki_formatter if User.current.allowed_to?(:edit_issue_notes, issue.project) || User.current.allowed_to?(:edit_own_issue_notes, issue.project) %> |
@@ -1,19 +1,22 | |||||
1 | <%= form_tag(journal_path(@journal), |
|
1 | <%= form_tag(journal_path(@journal), | |
2 | :remote => true, |
|
2 | :remote => true, | |
3 | :method => 'put', |
|
3 | :method => 'put', | |
4 | :id => "journal-#{@journal.id}-form") do %> |
|
4 | :id => "journal-#{@journal.id}-form") do %> | |
5 | <%= label_tag "notes", l(:description_notes), :class => "hidden-for-sighted" %> |
|
5 | <%= label_tag "notes", l(:description_notes), :class => "hidden-for-sighted" %> | |
6 | <%= text_area_tag :notes, @journal.notes, |
|
6 | <%= text_area_tag :notes, @journal.notes, | |
7 | :id => "journal_#{@journal.id}_notes", |
|
7 | :id => "journal_#{@journal.id}_notes", | |
8 | :class => 'wiki-edit', |
|
8 | :class => 'wiki-edit', | |
9 | :rows => (@journal.notes.blank? ? 10 : [[10, @journal.notes.length / 50].max, 100].min) %> |
|
9 | :rows => (@journal.notes.blank? ? 10 : [[10, @journal.notes.length / 50].max, 100].min) %> | |
|
10 | <% if @journal.issue.safe_attribute? 'private_notes' %> | |||
|
11 | <%= check_box_tag 'private_notes', '1', @journal.private_notes, :id => "journal_#{@journal.id}_private_notes" %> <label for="journal_<%= @journal.id %>_private_notes"><%= l(:field_private_notes) %></label> | |||
|
12 | <% end %> | |||
10 | <%= call_hook(:view_journals_notes_form_after_notes, { :journal => @journal}) %> |
|
13 | <%= call_hook(:view_journals_notes_form_after_notes, { :journal => @journal}) %> | |
11 | <p><%= submit_tag l(:button_save) %> |
|
14 | <p><%= submit_tag l(:button_save) %> | |
12 | <%= preview_link preview_edit_issue_path(:project_id => @project, :id => @journal.issue), |
|
15 | <%= preview_link preview_edit_issue_path(:project_id => @project, :id => @journal.issue), | |
13 | "journal-#{@journal.id}-form", |
|
16 | "journal-#{@journal.id}-form", | |
14 | "journal_#{@journal.id}_preview" %> | |
|
17 | "journal_#{@journal.id}_preview" %> | | |
15 | <%= link_to l(:button_cancel), '#', :onclick => "$('#journal-#{@journal.id}-form').remove(); $('#journal-#{@journal.id}-notes').show(); return false;" %></p> |
|
18 | <%= link_to l(:button_cancel), '#', :onclick => "$('#journal-#{@journal.id}-form').remove(); $('#journal-#{@journal.id}-notes').show(); return false;" %></p> | |
16 |
|
19 | |||
17 | <div id="journal_<%= @journal.id %>_preview" class="wiki"></div> |
|
20 | <div id="journal_<%= @journal.id %>_preview" class="wiki"></div> | |
18 | <% end %> |
|
21 | <% end %> | |
19 | <%= wikitoolbar_for "journal_#{@journal.id}_notes" %> |
|
22 | <%= wikitoolbar_for "journal_#{@journal.id}_notes" %> |
@@ -1,9 +1,11 | |||||
1 | <% if @journal.frozen? %> |
|
1 | <% if @journal.frozen? %> | |
2 | $("#change-<%= @journal.id %>").remove(); |
|
2 | $("#change-<%= @journal.id %>").remove(); | |
3 | <% else %> |
|
3 | <% else %> | |
|
4 | $("#change-<%= @journal.id %>").attr('class', '<%= @journal.css_classes %>'); | |||
4 | $("#journal-<%= @journal.id %>-notes").replaceWith('<%= escape_javascript(render_notes(@journal.issue, @journal, :reply_links => authorize_for('issues', 'edit'))) %>'); |
|
5 | $("#journal-<%= @journal.id %>-notes").replaceWith('<%= escape_javascript(render_notes(@journal.issue, @journal, :reply_links => authorize_for('issues', 'edit'))) %>'); | |
|
6 | $("#journal-<%= @journal.id %>-private_notes").replaceWith('<%= escape_javascript(render_private_notes(@journal)) %>'); | |||
5 | $("#journal-<%= @journal.id %>-notes").show(); |
|
7 | $("#journal-<%= @journal.id %>-notes").show(); | |
6 | $("#journal-<%= @journal.id %>-form").remove(); |
|
8 | $("#journal-<%= @journal.id %>-form").remove(); | |
7 | <% end %> |
|
9 | <% end %> | |
8 |
|
10 | |||
9 | <%= call_hook(:view_journals_update_js_bottom, { :journal => @journal }) %> |
|
11 | <%= call_hook(:view_journals_update_js_bottom, { :journal => @journal }) %> |
@@ -1,221 +1,254 | |||||
1 | # Redmine - project management software |
|
1 | # Redmine - project management software | |
2 | # Copyright (C) 2006-2016 Jean-Philippe Lang |
|
2 | # Copyright (C) 2006-2016 Jean-Philippe Lang | |
3 | # |
|
3 | # | |
4 | # This program is free software; you can redistribute it and/or |
|
4 | # This program is free software; you can redistribute it and/or | |
5 | # modify it under the terms of the GNU General Public License |
|
5 | # modify it under the terms of the GNU General Public License | |
6 | # as published by the Free Software Foundation; either version 2 |
|
6 | # as published by the Free Software Foundation; either version 2 | |
7 | # of the License, or (at your option) any later version. |
|
7 | # of the License, or (at your option) any later version. | |
8 | # |
|
8 | # | |
9 | # This program is distributed in the hope that it will be useful, |
|
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. |
|
12 | # GNU General Public License for more details. | |
13 | # |
|
13 | # | |
14 | # You should have received a copy of the GNU General Public License |
|
14 | # You should have received a copy of the GNU General Public License | |
15 | # along with this program; if not, write to the Free Software |
|
15 | # along with this program; if not, write to the Free Software | |
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
17 |
|
17 | |||
18 | require File.expand_path('../../test_helper', __FILE__) |
|
18 | require File.expand_path('../../test_helper', __FILE__) | |
19 |
|
19 | |||
20 | class JournalsControllerTest < ActionController::TestCase |
|
20 | class JournalsControllerTest < ActionController::TestCase | |
21 | fixtures :projects, :users, :members, :member_roles, :roles, :issues, :journals, :journal_details, :enabled_modules, |
|
21 | fixtures :projects, :users, :members, :member_roles, :roles, :issues, :journals, :journal_details, :enabled_modules, | |
22 | :trackers, :issue_statuses, :enumerations, :custom_fields, :custom_values, :custom_fields_projects, :projects_trackers |
|
22 | :trackers, :issue_statuses, :enumerations, :custom_fields, :custom_values, :custom_fields_projects, :projects_trackers | |
23 |
|
23 | |||
24 | def setup |
|
24 | def setup | |
25 | User.current = nil |
|
25 | User.current = nil | |
26 | end |
|
26 | end | |
27 |
|
27 | |||
28 | def test_index |
|
28 | def test_index | |
29 | get :index, :project_id => 1 |
|
29 | get :index, :project_id => 1 | |
30 | assert_response :success |
|
30 | assert_response :success | |
31 | assert_not_nil assigns(:journals) |
|
31 | assert_not_nil assigns(:journals) | |
32 | assert_equal 'application/atom+xml', @response.content_type |
|
32 | assert_equal 'application/atom+xml', @response.content_type | |
33 | end |
|
33 | end | |
34 |
|
34 | |||
35 | def test_index_with_invalid_query_id |
|
35 | def test_index_with_invalid_query_id | |
36 | get :index, :project_id => 1, :query_id => 999 |
|
36 | get :index, :project_id => 1, :query_id => 999 | |
37 | assert_response 404 |
|
37 | assert_response 404 | |
38 | end |
|
38 | end | |
39 |
|
39 | |||
40 | def test_index_should_return_privates_notes_with_permission_only |
|
40 | def test_index_should_return_privates_notes_with_permission_only | |
41 | journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Privates notes', :private_notes => true, :user_id => 1) |
|
41 | journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Privates notes', :private_notes => true, :user_id => 1) | |
42 | @request.session[:user_id] = 2 |
|
42 | @request.session[:user_id] = 2 | |
43 |
|
43 | |||
44 | get :index, :project_id => 1 |
|
44 | get :index, :project_id => 1 | |
45 | assert_response :success |
|
45 | assert_response :success | |
46 | assert_include journal, assigns(:journals) |
|
46 | assert_include journal, assigns(:journals) | |
47 |
|
47 | |||
48 | Role.find(1).remove_permission! :view_private_notes |
|
48 | Role.find(1).remove_permission! :view_private_notes | |
49 | get :index, :project_id => 1 |
|
49 | get :index, :project_id => 1 | |
50 | assert_response :success |
|
50 | assert_response :success | |
51 | assert_not_include journal, assigns(:journals) |
|
51 | assert_not_include journal, assigns(:journals) | |
52 | end |
|
52 | end | |
53 |
|
53 | |||
54 | def test_index_should_show_visible_custom_fields_only |
|
54 | def test_index_should_show_visible_custom_fields_only | |
55 | Issue.destroy_all |
|
55 | Issue.destroy_all | |
56 | field_attributes = {:field_format => 'string', :is_for_all => true, :is_filter => true, :trackers => Tracker.all} |
|
56 | field_attributes = {:field_format => 'string', :is_for_all => true, :is_filter => true, :trackers => Tracker.all} | |
57 | @fields = [] |
|
57 | @fields = [] | |
58 | @fields << (@field1 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 1', :visible => true))) |
|
58 | @fields << (@field1 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 1', :visible => true))) | |
59 | @fields << (@field2 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 2', :visible => false, :role_ids => [1, 2]))) |
|
59 | @fields << (@field2 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 2', :visible => false, :role_ids => [1, 2]))) | |
60 | @fields << (@field3 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 3', :visible => false, :role_ids => [1, 3]))) |
|
60 | @fields << (@field3 = IssueCustomField.create!(field_attributes.merge(:name => 'Field 3', :visible => false, :role_ids => [1, 3]))) | |
61 | @issue = Issue.generate!( |
|
61 | @issue = Issue.generate!( | |
62 | :author_id => 1, |
|
62 | :author_id => 1, | |
63 | :project_id => 1, |
|
63 | :project_id => 1, | |
64 | :tracker_id => 1, |
|
64 | :tracker_id => 1, | |
65 | :custom_field_values => {@field1.id => 'Value0', @field2.id => 'Value1', @field3.id => 'Value2'} |
|
65 | :custom_field_values => {@field1.id => 'Value0', @field2.id => 'Value1', @field3.id => 'Value2'} | |
66 | ) |
|
66 | ) | |
67 | @issue.init_journal(User.find(1)) |
|
67 | @issue.init_journal(User.find(1)) | |
68 | @issue.update_attribute :custom_field_values, {@field1.id => 'NewValue0', @field2.id => 'NewValue1', @field3.id => 'NewValue2'} |
|
68 | @issue.update_attribute :custom_field_values, {@field1.id => 'NewValue0', @field2.id => 'NewValue1', @field3.id => 'NewValue2'} | |
69 |
|
69 | |||
70 |
|
70 | |||
71 | user_with_role_on_other_project = User.generate! |
|
71 | user_with_role_on_other_project = User.generate! | |
72 | User.add_to_project(user_with_role_on_other_project, Project.find(2), Role.find(3)) |
|
72 | User.add_to_project(user_with_role_on_other_project, Project.find(2), Role.find(3)) | |
73 | users_to_test = { |
|
73 | users_to_test = { | |
74 | User.find(1) => [@field1, @field2, @field3], |
|
74 | User.find(1) => [@field1, @field2, @field3], | |
75 | User.find(3) => [@field1, @field2], |
|
75 | User.find(3) => [@field1, @field2], | |
76 | user_with_role_on_other_project => [@field1], # should see field1 only on Project 1 |
|
76 | user_with_role_on_other_project => [@field1], # should see field1 only on Project 1 | |
77 | User.generate! => [@field1], |
|
77 | User.generate! => [@field1], | |
78 | User.anonymous => [@field1] |
|
78 | User.anonymous => [@field1] | |
79 | } |
|
79 | } | |
80 |
|
80 | |||
81 | users_to_test.each do |user, visible_fields| |
|
81 | users_to_test.each do |user, visible_fields| | |
82 | get :index, :format => 'atom', :key => user.rss_key |
|
82 | get :index, :format => 'atom', :key => user.rss_key | |
83 | @fields.each_with_index do |field, i| |
|
83 | @fields.each_with_index do |field, i| | |
84 | if visible_fields.include?(field) |
|
84 | if visible_fields.include?(field) | |
85 | assert_select "content[type=html]", { :text => /NewValue#{i}/, :count => 1 }, "User #{user.id} was not able to view #{field.name} in API" |
|
85 | assert_select "content[type=html]", { :text => /NewValue#{i}/, :count => 1 }, "User #{user.id} was not able to view #{field.name} in API" | |
86 | else |
|
86 | else | |
87 | assert_select "content[type=html]", { :text => /NewValue#{i}/, :count => 0 }, "User #{user.id} was able to view #{field.name} in API" |
|
87 | assert_select "content[type=html]", { :text => /NewValue#{i}/, :count => 0 }, "User #{user.id} was able to view #{field.name} in API" | |
88 | end |
|
88 | end | |
89 | end |
|
89 | end | |
90 | end |
|
90 | end | |
91 |
|
91 | |||
92 | end |
|
92 | end | |
93 |
|
93 | |||
94 | def test_diff_for_description_change |
|
94 | def test_diff_for_description_change | |
95 | get :diff, :id => 3, :detail_id => 4 |
|
95 | get :diff, :id => 3, :detail_id => 4 | |
96 | assert_response :success |
|
96 | assert_response :success | |
97 | assert_template 'diff' |
|
97 | assert_template 'diff' | |
98 |
|
98 | |||
99 | assert_select 'span.diff_out', :text => /removed/ |
|
99 | assert_select 'span.diff_out', :text => /removed/ | |
100 | assert_select 'span.diff_in', :text => /added/ |
|
100 | assert_select 'span.diff_in', :text => /added/ | |
101 | end |
|
101 | end | |
102 |
|
102 | |||
103 | def test_diff_for_custom_field |
|
103 | def test_diff_for_custom_field | |
104 | field = IssueCustomField.create!(:name => "Long field", :field_format => 'text') |
|
104 | field = IssueCustomField.create!(:name => "Long field", :field_format => 'text') | |
105 | journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Notes', :user_id => 1) |
|
105 | journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Notes', :user_id => 1) | |
106 | detail = JournalDetail.create!(:journal => journal, :property => 'cf', :prop_key => field.id, |
|
106 | detail = JournalDetail.create!(:journal => journal, :property => 'cf', :prop_key => field.id, | |
107 | :old_value => 'Foo', :value => 'Bar') |
|
107 | :old_value => 'Foo', :value => 'Bar') | |
108 |
|
108 | |||
109 | get :diff, :id => journal.id, :detail_id => detail.id |
|
109 | get :diff, :id => journal.id, :detail_id => detail.id | |
110 | assert_response :success |
|
110 | assert_response :success | |
111 | assert_template 'diff' |
|
111 | assert_template 'diff' | |
112 |
|
112 | |||
113 | assert_select 'span.diff_out', :text => /Foo/ |
|
113 | assert_select 'span.diff_out', :text => /Foo/ | |
114 | assert_select 'span.diff_in', :text => /Bar/ |
|
114 | assert_select 'span.diff_in', :text => /Bar/ | |
115 | end |
|
115 | end | |
116 |
|
116 | |||
117 | def test_diff_for_custom_field_should_be_denied_if_custom_field_is_not_visible |
|
117 | def test_diff_for_custom_field_should_be_denied_if_custom_field_is_not_visible | |
118 | field = IssueCustomField.create!(:name => "Long field", :field_format => 'text', :visible => false, :role_ids => [1]) |
|
118 | field = IssueCustomField.create!(:name => "Long field", :field_format => 'text', :visible => false, :role_ids => [1]) | |
119 | journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Notes', :user_id => 1) |
|
119 | journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Notes', :user_id => 1) | |
120 | detail = JournalDetail.create!(:journal => journal, :property => 'cf', :prop_key => field.id, |
|
120 | detail = JournalDetail.create!(:journal => journal, :property => 'cf', :prop_key => field.id, | |
121 | :old_value => 'Foo', :value => 'Bar') |
|
121 | :old_value => 'Foo', :value => 'Bar') | |
122 |
|
122 | |||
123 | get :diff, :id => journal.id, :detail_id => detail.id |
|
123 | get :diff, :id => journal.id, :detail_id => detail.id | |
124 | assert_response 302 |
|
124 | assert_response 302 | |
125 | end |
|
125 | end | |
126 |
|
126 | |||
127 | def test_diff_should_default_to_description_diff |
|
127 | def test_diff_should_default_to_description_diff | |
128 | get :diff, :id => 3 |
|
128 | get :diff, :id => 3 | |
129 | assert_response :success |
|
129 | assert_response :success | |
130 | assert_template 'diff' |
|
130 | assert_template 'diff' | |
131 |
|
131 | |||
132 | assert_select 'span.diff_out', :text => /removed/ |
|
132 | assert_select 'span.diff_out', :text => /removed/ | |
133 | assert_select 'span.diff_in', :text => /added/ |
|
133 | assert_select 'span.diff_in', :text => /added/ | |
134 | end |
|
134 | end | |
135 |
|
135 | |||
136 | def test_reply_to_issue |
|
136 | def test_reply_to_issue | |
137 | @request.session[:user_id] = 2 |
|
137 | @request.session[:user_id] = 2 | |
138 | xhr :get, :new, :id => 6 |
|
138 | xhr :get, :new, :id => 6 | |
139 | assert_response :success |
|
139 | assert_response :success | |
140 | assert_template 'new' |
|
140 | assert_template 'new' | |
141 | assert_equal 'text/javascript', response.content_type |
|
141 | assert_equal 'text/javascript', response.content_type | |
142 | assert_include '> This is an issue', response.body |
|
142 | assert_include '> This is an issue', response.body | |
143 | end |
|
143 | end | |
144 |
|
144 | |||
145 | def test_reply_to_issue_without_permission |
|
145 | def test_reply_to_issue_without_permission | |
146 | @request.session[:user_id] = 7 |
|
146 | @request.session[:user_id] = 7 | |
147 | xhr :get, :new, :id => 6 |
|
147 | xhr :get, :new, :id => 6 | |
148 | assert_response 403 |
|
148 | assert_response 403 | |
149 | end |
|
149 | end | |
150 |
|
150 | |||
151 | def test_reply_to_note |
|
151 | def test_reply_to_note | |
152 | @request.session[:user_id] = 2 |
|
152 | @request.session[:user_id] = 2 | |
153 | xhr :get, :new, :id => 6, :journal_id => 4 |
|
153 | xhr :get, :new, :id => 6, :journal_id => 4 | |
154 | assert_response :success |
|
154 | assert_response :success | |
155 | assert_template 'new' |
|
155 | assert_template 'new' | |
156 | assert_equal 'text/javascript', response.content_type |
|
156 | assert_equal 'text/javascript', response.content_type | |
157 | assert_include '> A comment with a private version', response.body |
|
157 | assert_include '> A comment with a private version', response.body | |
158 | end |
|
158 | end | |
159 |
|
159 | |||
160 | def test_reply_to_private_note_should_fail_without_permission |
|
160 | def test_reply_to_private_note_should_fail_without_permission | |
161 | journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Privates notes', :private_notes => true) |
|
161 | journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Privates notes', :private_notes => true) | |
162 | @request.session[:user_id] = 2 |
|
162 | @request.session[:user_id] = 2 | |
163 |
|
163 | |||
164 | xhr :get, :new, :id => 2, :journal_id => journal.id |
|
164 | xhr :get, :new, :id => 2, :journal_id => journal.id | |
165 | assert_response :success |
|
165 | assert_response :success | |
166 | assert_template 'new' |
|
166 | assert_template 'new' | |
167 | assert_equal 'text/javascript', response.content_type |
|
167 | assert_equal 'text/javascript', response.content_type | |
168 | assert_include '> Privates notes', response.body |
|
168 | assert_include '> Privates notes', response.body | |
169 |
|
169 | |||
170 | Role.find(1).remove_permission! :view_private_notes |
|
170 | Role.find(1).remove_permission! :view_private_notes | |
171 | xhr :get, :new, :id => 2, :journal_id => journal.id |
|
171 | xhr :get, :new, :id => 2, :journal_id => journal.id | |
172 | assert_response 404 |
|
172 | assert_response 404 | |
173 | end |
|
173 | end | |
174 |
|
174 | |||
175 | def test_edit_xhr |
|
175 | def test_edit_xhr | |
176 | @request.session[:user_id] = 1 |
|
176 | @request.session[:user_id] = 1 | |
177 | xhr :get, :edit, :id => 2 |
|
177 | xhr :get, :edit, :id => 2 | |
178 | assert_response :success |
|
178 | assert_response :success | |
179 | assert_template 'edit' |
|
179 | assert_template 'edit' | |
180 | assert_equal 'text/javascript', response.content_type |
|
180 | assert_equal 'text/javascript', response.content_type | |
181 | assert_include 'textarea', response.body |
|
181 | assert_include 'textarea', response.body | |
182 | end |
|
182 | end | |
183 |
|
183 | |||
184 | def test_edit_private_note_should_fail_without_permission |
|
184 | def test_edit_private_note_should_fail_without_permission | |
185 | journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Privates notes', :private_notes => true) |
|
185 | journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Privates notes', :private_notes => true) | |
186 | @request.session[:user_id] = 2 |
|
186 | @request.session[:user_id] = 2 | |
187 | Role.find(1).add_permission! :edit_issue_notes |
|
187 | Role.find(1).add_permission! :edit_issue_notes | |
188 |
|
188 | |||
189 | xhr :get, :edit, :id => journal.id |
|
189 | xhr :get, :edit, :id => journal.id | |
190 | assert_response :success |
|
190 | assert_response :success | |
191 | assert_template 'edit' |
|
191 | assert_template 'edit' | |
192 | assert_equal 'text/javascript', response.content_type |
|
192 | assert_equal 'text/javascript', response.content_type | |
193 | assert_include 'textarea', response.body |
|
193 | assert_include 'textarea', response.body | |
194 |
|
194 | |||
195 | Role.find(1).remove_permission! :view_private_notes |
|
195 | Role.find(1).remove_permission! :view_private_notes | |
196 | xhr :get, :edit, :id => journal.id |
|
196 | xhr :get, :edit, :id => journal.id | |
197 | assert_response 404 |
|
197 | assert_response 404 | |
198 | end |
|
198 | end | |
199 |
|
199 | |||
200 | def test_update_xhr |
|
200 | def test_update_xhr | |
201 | @request.session[:user_id] = 1 |
|
201 | @request.session[:user_id] = 1 | |
202 | xhr :post, :update, :id => 2, :notes => 'Updated notes' |
|
202 | xhr :post, :update, :id => 2, :notes => 'Updated notes' | |
203 | assert_response :success |
|
203 | assert_response :success | |
204 | assert_template 'update' |
|
204 | assert_template 'update' | |
205 | assert_equal 'text/javascript', response.content_type |
|
205 | assert_equal 'text/javascript', response.content_type | |
206 | assert_equal 'Updated notes', Journal.find(2).notes |
|
206 | assert_equal 'Updated notes', Journal.find(2).notes | |
207 | assert_include 'journal-2-notes', response.body |
|
207 | assert_include 'journal-2-notes', response.body | |
208 | end |
|
208 | end | |
209 |
|
209 | |||
|
210 | def test_update_xhr_with_private_notes_checked | |||
|
211 | @request.session[:user_id] = 1 | |||
|
212 | xhr :post, :update, :id => 2, :private_notes => '1' | |||
|
213 | assert_response :success | |||
|
214 | assert_template 'update' | |||
|
215 | assert_equal 'text/javascript', response.content_type | |||
|
216 | assert_equal true, Journal.find(2).private_notes | |||
|
217 | assert_include 'change-2', response.body | |||
|
218 | assert_include 'journal-2-private_notes', response.body | |||
|
219 | end | |||
|
220 | ||||
|
221 | def test_update_xhr_with_private_notes_unchecked | |||
|
222 | Journal.find(2).update_attributes(:private_notes => true) | |||
|
223 | @request.session[:user_id] = 1 | |||
|
224 | xhr :post, :update, :id => 2 | |||
|
225 | assert_response :success | |||
|
226 | assert_template 'update' | |||
|
227 | assert_equal 'text/javascript', response.content_type | |||
|
228 | assert_equal false, Journal.find(2).private_notes | |||
|
229 | assert_include 'change-2', response.body | |||
|
230 | assert_include 'journal-2-private_notes', response.body | |||
|
231 | end | |||
|
232 | ||||
|
233 | def test_update_xhr_with_private_notes_changes_and_without_set_private_notes_permission | |||
|
234 | @request.session[:user_id] = 2 | |||
|
235 | Role.find(1).add_permission! :edit_issue_notes | |||
|
236 | Role.find(1).add_permission! :view_private_notes | |||
|
237 | Role.find(1).remove_permission! :set_notes_private | |||
|
238 | ||||
|
239 | xhr :post, :update, :id => 2, :private_notes => '1' | |||
|
240 | assert_response 403 | |||
|
241 | end | |||
|
242 | ||||
210 | def test_update_xhr_with_empty_notes_should_delete_the_journal |
|
243 | def test_update_xhr_with_empty_notes_should_delete_the_journal | |
211 | @request.session[:user_id] = 1 |
|
244 | @request.session[:user_id] = 1 | |
212 | assert_difference 'Journal.count', -1 do |
|
245 | assert_difference 'Journal.count', -1 do | |
213 | xhr :post, :update, :id => 2, :notes => '' |
|
246 | xhr :post, :update, :id => 2, :notes => '' | |
214 | assert_response :success |
|
247 | assert_response :success | |
215 | assert_template 'update' |
|
248 | assert_template 'update' | |
216 | assert_equal 'text/javascript', response.content_type |
|
249 | assert_equal 'text/javascript', response.content_type | |
217 | end |
|
250 | end | |
218 | assert_nil Journal.find_by_id(2) |
|
251 | assert_nil Journal.find_by_id(2) | |
219 | assert_include 'change-2', response.body |
|
252 | assert_include 'change-2', response.body | |
220 | end |
|
253 | end | |
221 | end |
|
254 | end |
General Comments 0
You need to be logged in to leave comments.
Login now