##// END OF EJS Templates
Deprecation warnings (#12774)....
Jean-Philippe Lang -
r10909:9394d739b1fc
parent child
Show More
@@ -1,110 +1,110
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 BoardsController < ApplicationController
18 class BoardsController < ApplicationController
19 default_search_scope :messages
19 default_search_scope :messages
20 before_filter :find_project_by_project_id, :find_board_if_available, :authorize
20 before_filter :find_project_by_project_id, :find_board_if_available, :authorize
21 accept_rss_auth :index, :show
21 accept_rss_auth :index, :show
22
22
23 helper :sort
23 helper :sort
24 include SortHelper
24 include SortHelper
25 helper :watchers
25 helper :watchers
26
26
27 def index
27 def index
28 @boards = @project.boards.includes(:last_message => :author).all
28 @boards = @project.boards.includes(:last_message => :author).all
29 # show the board if there is only one
29 # show the board if there is only one
30 if @boards.size == 1
30 if @boards.size == 1
31 @board = @boards.first
31 @board = @boards.first
32 show
32 show
33 end
33 end
34 end
34 end
35
35
36 def show
36 def show
37 respond_to do |format|
37 respond_to do |format|
38 format.html {
38 format.html {
39 sort_init 'updated_on', 'desc'
39 sort_init 'updated_on', 'desc'
40 sort_update 'created_on' => "#{Message.table_name}.created_on",
40 sort_update 'created_on' => "#{Message.table_name}.created_on",
41 'replies' => "#{Message.table_name}.replies_count",
41 'replies' => "#{Message.table_name}.replies_count",
42 'updated_on' => "#{Message.table_name}.updated_on"
42 'updated_on' => "#{Message.table_name}.updated_on"
43
43
44 @topic_count = @board.topics.count
44 @topic_count = @board.topics.count
45 @topic_pages = Paginator.new self, @topic_count, per_page_option, params['page']
45 @topic_pages = Paginator.new @topic_count, per_page_option, params['page']
46 @topics = @board.topics.
46 @topics = @board.topics.
47 reorder("#{Message.table_name}.sticky DESC").
47 reorder("#{Message.table_name}.sticky DESC").
48 includes(:author, {:last_reply => :author}).
48 includes(:author, {:last_reply => :author}).
49 limit(@topic_pages.items_per_page).
49 limit(@topic_pages.items_per_page).
50 offset(@topic_pages.current.offset).
50 offset(@topic_pages.offset).
51 order(sort_clause).
51 order(sort_clause).
52 all
52 all
53 @message = Message.new(:board => @board)
53 @message = Message.new(:board => @board)
54 render :action => 'show', :layout => !request.xhr?
54 render :action => 'show', :layout => !request.xhr?
55 }
55 }
56 format.atom {
56 format.atom {
57 @messages = @board.messages.
57 @messages = @board.messages.
58 reorder('created_on DESC').
58 reorder('created_on DESC').
59 includes(:author, :board).
59 includes(:author, :board).
60 limit(Setting.feeds_limit.to_i).
60 limit(Setting.feeds_limit.to_i).
61 all
61 all
62 render_feed(@messages, :title => "#{@project}: #{@board}")
62 render_feed(@messages, :title => "#{@project}: #{@board}")
63 }
63 }
64 end
64 end
65 end
65 end
66
66
67 def new
67 def new
68 @board = @project.boards.build
68 @board = @project.boards.build
69 @board.safe_attributes = params[:board]
69 @board.safe_attributes = params[:board]
70 end
70 end
71
71
72 def create
72 def create
73 @board = @project.boards.build
73 @board = @project.boards.build
74 @board.safe_attributes = params[:board]
74 @board.safe_attributes = params[:board]
75 if @board.save
75 if @board.save
76 flash[:notice] = l(:notice_successful_create)
76 flash[:notice] = l(:notice_successful_create)
77 redirect_to_settings_in_projects
77 redirect_to_settings_in_projects
78 else
78 else
79 render :action => 'new'
79 render :action => 'new'
80 end
80 end
81 end
81 end
82
82
83 def edit
83 def edit
84 end
84 end
85
85
86 def update
86 def update
87 @board.safe_attributes = params[:board]
87 @board.safe_attributes = params[:board]
88 if @board.save
88 if @board.save
89 redirect_to_settings_in_projects
89 redirect_to_settings_in_projects
90 else
90 else
91 render :action => 'edit'
91 render :action => 'edit'
92 end
92 end
93 end
93 end
94
94
95 def destroy
95 def destroy
96 @board.destroy
96 @board.destroy
97 redirect_to_settings_in_projects
97 redirect_to_settings_in_projects
98 end
98 end
99
99
100 private
100 private
101 def redirect_to_settings_in_projects
101 def redirect_to_settings_in_projects
102 redirect_to settings_project_path(@project, :tab => 'boards')
102 redirect_to settings_project_path(@project, :tab => 'boards')
103 end
103 end
104
104
105 def find_board_if_available
105 def find_board_if_available
106 @board = @project.boards.find(params[:id]) if params[:id]
106 @board = @project.boards.find(params[:id]) if params[:id]
107 rescue ActiveRecord::RecordNotFound
107 rescue ActiveRecord::RecordNotFound
108 render_404
108 render_404
109 end
109 end
110 end
110 end
@@ -1,435 +1,435
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 IssuesController < ApplicationController
18 class IssuesController < ApplicationController
19 menu_item :new_issue, :only => [:new, :create]
19 menu_item :new_issue, :only => [:new, :create]
20 default_search_scope :issues
20 default_search_scope :issues
21
21
22 before_filter :find_issue, :only => [:show, :edit, :update]
22 before_filter :find_issue, :only => [:show, :edit, :update]
23 before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy]
23 before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy]
24 before_filter :find_project, :only => [:new, :create]
24 before_filter :find_project, :only => [:new, :create]
25 before_filter :authorize, :except => [:index]
25 before_filter :authorize, :except => [:index]
26 before_filter :find_optional_project, :only => [:index]
26 before_filter :find_optional_project, :only => [:index]
27 before_filter :check_for_default_issue_status, :only => [:new, :create]
27 before_filter :check_for_default_issue_status, :only => [:new, :create]
28 before_filter :build_new_issue_from_params, :only => [:new, :create]
28 before_filter :build_new_issue_from_params, :only => [:new, :create]
29 accept_rss_auth :index, :show
29 accept_rss_auth :index, :show
30 accept_api_auth :index, :show, :create, :update, :destroy
30 accept_api_auth :index, :show, :create, :update, :destroy
31
31
32 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
32 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
33
33
34 helper :journals
34 helper :journals
35 helper :projects
35 helper :projects
36 include ProjectsHelper
36 include ProjectsHelper
37 helper :custom_fields
37 helper :custom_fields
38 include CustomFieldsHelper
38 include CustomFieldsHelper
39 helper :issue_relations
39 helper :issue_relations
40 include IssueRelationsHelper
40 include IssueRelationsHelper
41 helper :watchers
41 helper :watchers
42 include WatchersHelper
42 include WatchersHelper
43 helper :attachments
43 helper :attachments
44 include AttachmentsHelper
44 include AttachmentsHelper
45 helper :queries
45 helper :queries
46 include QueriesHelper
46 include QueriesHelper
47 helper :repositories
47 helper :repositories
48 include RepositoriesHelper
48 include RepositoriesHelper
49 helper :sort
49 helper :sort
50 include SortHelper
50 include SortHelper
51 include IssuesHelper
51 include IssuesHelper
52 helper :timelog
52 helper :timelog
53 include Redmine::Export::PDF
53 include Redmine::Export::PDF
54
54
55 def index
55 def index
56 retrieve_query
56 retrieve_query
57 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
57 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
58 sort_update(@query.sortable_columns)
58 sort_update(@query.sortable_columns)
59 @query.sort_criteria = sort_criteria.to_a
59 @query.sort_criteria = sort_criteria.to_a
60
60
61 if @query.valid?
61 if @query.valid?
62 case params[:format]
62 case params[:format]
63 when 'csv', 'pdf'
63 when 'csv', 'pdf'
64 @limit = Setting.issues_export_limit.to_i
64 @limit = Setting.issues_export_limit.to_i
65 when 'atom'
65 when 'atom'
66 @limit = Setting.feeds_limit.to_i
66 @limit = Setting.feeds_limit.to_i
67 when 'xml', 'json'
67 when 'xml', 'json'
68 @offset, @limit = api_offset_and_limit
68 @offset, @limit = api_offset_and_limit
69 else
69 else
70 @limit = per_page_option
70 @limit = per_page_option
71 end
71 end
72
72
73 @issue_count = @query.issue_count
73 @issue_count = @query.issue_count
74 @issue_pages = Paginator.new self, @issue_count, @limit, params['page']
74 @issue_pages = Paginator.new @issue_count, @limit, params['page']
75 @offset ||= @issue_pages.current.offset
75 @offset ||= @issue_pages.offset
76 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
76 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
77 :order => sort_clause,
77 :order => sort_clause,
78 :offset => @offset,
78 :offset => @offset,
79 :limit => @limit)
79 :limit => @limit)
80 @issue_count_by_group = @query.issue_count_by_group
80 @issue_count_by_group = @query.issue_count_by_group
81
81
82 respond_to do |format|
82 respond_to do |format|
83 format.html { render :template => 'issues/index', :layout => !request.xhr? }
83 format.html { render :template => 'issues/index', :layout => !request.xhr? }
84 format.api {
84 format.api {
85 Issue.load_visible_relations(@issues) if include_in_api_response?('relations')
85 Issue.load_visible_relations(@issues) if include_in_api_response?('relations')
86 }
86 }
87 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
87 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
88 format.csv { send_data(issues_to_csv(@issues, @project, @query, params), :type => 'text/csv; header=present', :filename => 'export.csv') }
88 format.csv { send_data(issues_to_csv(@issues, @project, @query, params), :type => 'text/csv; header=present', :filename => 'export.csv') }
89 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
89 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
90 end
90 end
91 else
91 else
92 respond_to do |format|
92 respond_to do |format|
93 format.html { render(:template => 'issues/index', :layout => !request.xhr?) }
93 format.html { render(:template => 'issues/index', :layout => !request.xhr?) }
94 format.any(:atom, :csv, :pdf) { render(:nothing => true) }
94 format.any(:atom, :csv, :pdf) { render(:nothing => true) }
95 format.api { render_validation_errors(@query) }
95 format.api { render_validation_errors(@query) }
96 end
96 end
97 end
97 end
98 rescue ActiveRecord::RecordNotFound
98 rescue ActiveRecord::RecordNotFound
99 render_404
99 render_404
100 end
100 end
101
101
102 def show
102 def show
103 @journals = @issue.journals.includes(:user, :details).reorder("#{Journal.table_name}.id ASC").all
103 @journals = @issue.journals.includes(:user, :details).reorder("#{Journal.table_name}.id ASC").all
104 @journals.each_with_index {|j,i| j.indice = i+1}
104 @journals.each_with_index {|j,i| j.indice = i+1}
105 @journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
105 @journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
106 @journals.reverse! if User.current.wants_comments_in_reverse_order?
106 @journals.reverse! if User.current.wants_comments_in_reverse_order?
107
107
108 @changesets = @issue.changesets.visible.all
108 @changesets = @issue.changesets.visible.all
109 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
109 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
110
110
111 @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
111 @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
112 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
112 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
113 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
113 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
114 @priorities = IssuePriority.active
114 @priorities = IssuePriority.active
115 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
115 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
116 respond_to do |format|
116 respond_to do |format|
117 format.html {
117 format.html {
118 retrieve_previous_and_next_issue_ids
118 retrieve_previous_and_next_issue_ids
119 render :template => 'issues/show'
119 render :template => 'issues/show'
120 }
120 }
121 format.api
121 format.api
122 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
122 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
123 format.pdf {
123 format.pdf {
124 pdf = issue_to_pdf(@issue, :journals => @journals)
124 pdf = issue_to_pdf(@issue, :journals => @journals)
125 send_data(pdf, :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf")
125 send_data(pdf, :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf")
126 }
126 }
127 end
127 end
128 end
128 end
129
129
130 # Add a new issue
130 # Add a new issue
131 # The new issue will be created from an existing one if copy_from parameter is given
131 # The new issue will be created from an existing one if copy_from parameter is given
132 def new
132 def new
133 respond_to do |format|
133 respond_to do |format|
134 format.html { render :action => 'new', :layout => !request.xhr? }
134 format.html { render :action => 'new', :layout => !request.xhr? }
135 format.js { render :partial => 'update_form' }
135 format.js { render :partial => 'update_form' }
136 end
136 end
137 end
137 end
138
138
139 def create
139 def create
140 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
140 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
141 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
141 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
142 if @issue.save
142 if @issue.save
143 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
143 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
144 respond_to do |format|
144 respond_to do |format|
145 format.html {
145 format.html {
146 render_attachment_warning_if_needed(@issue)
146 render_attachment_warning_if_needed(@issue)
147 flash[:notice] = l(:notice_issue_successful_create, :id => view_context.link_to("##{@issue.id}", issue_path(@issue), :title => @issue.subject))
147 flash[:notice] = l(:notice_issue_successful_create, :id => view_context.link_to("##{@issue.id}", issue_path(@issue), :title => @issue.subject))
148 if params[:continue]
148 if params[:continue]
149 attrs = {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?}
149 attrs = {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?}
150 redirect_to new_project_issue_path(@issue.project, :issue => attrs)
150 redirect_to new_project_issue_path(@issue.project, :issue => attrs)
151 else
151 else
152 redirect_to issue_path(@issue)
152 redirect_to issue_path(@issue)
153 end
153 end
154 }
154 }
155 format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
155 format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
156 end
156 end
157 return
157 return
158 else
158 else
159 respond_to do |format|
159 respond_to do |format|
160 format.html { render :action => 'new' }
160 format.html { render :action => 'new' }
161 format.api { render_validation_errors(@issue) }
161 format.api { render_validation_errors(@issue) }
162 end
162 end
163 end
163 end
164 end
164 end
165
165
166 def edit
166 def edit
167 return unless update_issue_from_params
167 return unless update_issue_from_params
168
168
169 respond_to do |format|
169 respond_to do |format|
170 format.html { }
170 format.html { }
171 format.xml { }
171 format.xml { }
172 end
172 end
173 end
173 end
174
174
175 def update
175 def update
176 return unless update_issue_from_params
176 return unless update_issue_from_params
177 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
177 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
178 saved = false
178 saved = false
179 begin
179 begin
180 saved = @issue.save_issue_with_child_records(params, @time_entry)
180 saved = @issue.save_issue_with_child_records(params, @time_entry)
181 rescue ActiveRecord::StaleObjectError
181 rescue ActiveRecord::StaleObjectError
182 @conflict = true
182 @conflict = true
183 if params[:last_journal_id]
183 if params[:last_journal_id]
184 @conflict_journals = @issue.journals_after(params[:last_journal_id]).all
184 @conflict_journals = @issue.journals_after(params[:last_journal_id]).all
185 @conflict_journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
185 @conflict_journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
186 end
186 end
187 end
187 end
188
188
189 if saved
189 if saved
190 render_attachment_warning_if_needed(@issue)
190 render_attachment_warning_if_needed(@issue)
191 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
191 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
192
192
193 respond_to do |format|
193 respond_to do |format|
194 format.html { redirect_back_or_default issue_path(@issue) }
194 format.html { redirect_back_or_default issue_path(@issue) }
195 format.api { render_api_ok }
195 format.api { render_api_ok }
196 end
196 end
197 else
197 else
198 respond_to do |format|
198 respond_to do |format|
199 format.html { render :action => 'edit' }
199 format.html { render :action => 'edit' }
200 format.api { render_validation_errors(@issue) }
200 format.api { render_validation_errors(@issue) }
201 end
201 end
202 end
202 end
203 end
203 end
204
204
205 # Bulk edit/copy a set of issues
205 # Bulk edit/copy a set of issues
206 def bulk_edit
206 def bulk_edit
207 @issues.sort!
207 @issues.sort!
208 @copy = params[:copy].present?
208 @copy = params[:copy].present?
209 @notes = params[:notes]
209 @notes = params[:notes]
210
210
211 if User.current.allowed_to?(:move_issues, @projects)
211 if User.current.allowed_to?(:move_issues, @projects)
212 @allowed_projects = Issue.allowed_target_projects_on_move
212 @allowed_projects = Issue.allowed_target_projects_on_move
213 if params[:issue]
213 if params[:issue]
214 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:issue][:project_id].to_s}
214 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:issue][:project_id].to_s}
215 if @target_project
215 if @target_project
216 target_projects = [@target_project]
216 target_projects = [@target_project]
217 end
217 end
218 end
218 end
219 end
219 end
220 target_projects ||= @projects
220 target_projects ||= @projects
221
221
222 if @copy
222 if @copy
223 @available_statuses = [IssueStatus.default]
223 @available_statuses = [IssueStatus.default]
224 else
224 else
225 @available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
225 @available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
226 end
226 end
227 @custom_fields = target_projects.map{|p|p.all_issue_custom_fields}.reduce(:&)
227 @custom_fields = target_projects.map{|p|p.all_issue_custom_fields}.reduce(:&)
228 @assignables = target_projects.map(&:assignable_users).reduce(:&)
228 @assignables = target_projects.map(&:assignable_users).reduce(:&)
229 @trackers = target_projects.map(&:trackers).reduce(:&)
229 @trackers = target_projects.map(&:trackers).reduce(:&)
230 @versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
230 @versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
231 @categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
231 @categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
232 if @copy
232 if @copy
233 @attachments_present = @issues.detect {|i| i.attachments.any?}.present?
233 @attachments_present = @issues.detect {|i| i.attachments.any?}.present?
234 @subtasks_present = @issues.detect {|i| !i.leaf?}.present?
234 @subtasks_present = @issues.detect {|i| !i.leaf?}.present?
235 end
235 end
236
236
237 @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
237 @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
238 render :layout => false if request.xhr?
238 render :layout => false if request.xhr?
239 end
239 end
240
240
241 def bulk_update
241 def bulk_update
242 @issues.sort!
242 @issues.sort!
243 @copy = params[:copy].present?
243 @copy = params[:copy].present?
244 attributes = parse_params_for_bulk_issue_attributes(params)
244 attributes = parse_params_for_bulk_issue_attributes(params)
245
245
246 unsaved_issue_ids = []
246 unsaved_issue_ids = []
247 moved_issues = []
247 moved_issues = []
248
248
249 if @copy && params[:copy_subtasks].present?
249 if @copy && params[:copy_subtasks].present?
250 # Descendant issues will be copied with the parent task
250 # Descendant issues will be copied with the parent task
251 # Don't copy them twice
251 # Don't copy them twice
252 @issues.reject! {|issue| @issues.detect {|other| issue.is_descendant_of?(other)}}
252 @issues.reject! {|issue| @issues.detect {|other| issue.is_descendant_of?(other)}}
253 end
253 end
254
254
255 @issues.each do |issue|
255 @issues.each do |issue|
256 issue.reload
256 issue.reload
257 if @copy
257 if @copy
258 issue = issue.copy({},
258 issue = issue.copy({},
259 :attachments => params[:copy_attachments].present?,
259 :attachments => params[:copy_attachments].present?,
260 :subtasks => params[:copy_subtasks].present?
260 :subtasks => params[:copy_subtasks].present?
261 )
261 )
262 end
262 end
263 journal = issue.init_journal(User.current, params[:notes])
263 journal = issue.init_journal(User.current, params[:notes])
264 issue.safe_attributes = attributes
264 issue.safe_attributes = attributes
265 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
265 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
266 if issue.save
266 if issue.save
267 moved_issues << issue
267 moved_issues << issue
268 else
268 else
269 # Keep unsaved issue ids to display them in flash error
269 # Keep unsaved issue ids to display them in flash error
270 unsaved_issue_ids << issue.id
270 unsaved_issue_ids << issue.id
271 end
271 end
272 end
272 end
273 set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
273 set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
274
274
275 if params[:follow]
275 if params[:follow]
276 if @issues.size == 1 && moved_issues.size == 1
276 if @issues.size == 1 && moved_issues.size == 1
277 redirect_to issue_path(moved_issues.first)
277 redirect_to issue_path(moved_issues.first)
278 elsif moved_issues.map(&:project).uniq.size == 1
278 elsif moved_issues.map(&:project).uniq.size == 1
279 redirect_to project_issues_path(moved_issues.map(&:project).first)
279 redirect_to project_issues_path(moved_issues.map(&:project).first)
280 end
280 end
281 else
281 else
282 redirect_back_or_default _project_issues_path(@project)
282 redirect_back_or_default _project_issues_path(@project)
283 end
283 end
284 end
284 end
285
285
286 def destroy
286 def destroy
287 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
287 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
288 if @hours > 0
288 if @hours > 0
289 case params[:todo]
289 case params[:todo]
290 when 'destroy'
290 when 'destroy'
291 # nothing to do
291 # nothing to do
292 when 'nullify'
292 when 'nullify'
293 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
293 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
294 when 'reassign'
294 when 'reassign'
295 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
295 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
296 if reassign_to.nil?
296 if reassign_to.nil?
297 flash.now[:error] = l(:error_issue_not_found_in_project)
297 flash.now[:error] = l(:error_issue_not_found_in_project)
298 return
298 return
299 else
299 else
300 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
300 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
301 end
301 end
302 else
302 else
303 # display the destroy form if it's a user request
303 # display the destroy form if it's a user request
304 return unless api_request?
304 return unless api_request?
305 end
305 end
306 end
306 end
307 @issues.each do |issue|
307 @issues.each do |issue|
308 begin
308 begin
309 issue.reload.destroy
309 issue.reload.destroy
310 rescue ::ActiveRecord::RecordNotFound # raised by #reload if issue no longer exists
310 rescue ::ActiveRecord::RecordNotFound # raised by #reload if issue no longer exists
311 # nothing to do, issue was already deleted (eg. by a parent)
311 # nothing to do, issue was already deleted (eg. by a parent)
312 end
312 end
313 end
313 end
314 respond_to do |format|
314 respond_to do |format|
315 format.html { redirect_back_or_default _project_issues_path(@project) }
315 format.html { redirect_back_or_default _project_issues_path(@project) }
316 format.api { render_api_ok }
316 format.api { render_api_ok }
317 end
317 end
318 end
318 end
319
319
320 private
320 private
321
321
322 def find_project
322 def find_project
323 project_id = params[:project_id] || (params[:issue] && params[:issue][:project_id])
323 project_id = params[:project_id] || (params[:issue] && params[:issue][:project_id])
324 @project = Project.find(project_id)
324 @project = Project.find(project_id)
325 rescue ActiveRecord::RecordNotFound
325 rescue ActiveRecord::RecordNotFound
326 render_404
326 render_404
327 end
327 end
328
328
329 def retrieve_previous_and_next_issue_ids
329 def retrieve_previous_and_next_issue_ids
330 retrieve_query_from_session
330 retrieve_query_from_session
331 if @query
331 if @query
332 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
332 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
333 sort_update(@query.sortable_columns, 'issues_index_sort')
333 sort_update(@query.sortable_columns, 'issues_index_sort')
334 limit = 500
334 limit = 500
335 issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1), :include => [:assigned_to, :tracker, :priority, :category, :fixed_version])
335 issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1), :include => [:assigned_to, :tracker, :priority, :category, :fixed_version])
336 if (idx = issue_ids.index(@issue.id)) && idx < limit
336 if (idx = issue_ids.index(@issue.id)) && idx < limit
337 if issue_ids.size < 500
337 if issue_ids.size < 500
338 @issue_position = idx + 1
338 @issue_position = idx + 1
339 @issue_count = issue_ids.size
339 @issue_count = issue_ids.size
340 end
340 end
341 @prev_issue_id = issue_ids[idx - 1] if idx > 0
341 @prev_issue_id = issue_ids[idx - 1] if idx > 0
342 @next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1)
342 @next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1)
343 end
343 end
344 end
344 end
345 end
345 end
346
346
347 # Used by #edit and #update to set some common instance variables
347 # Used by #edit and #update to set some common instance variables
348 # from the params
348 # from the params
349 # TODO: Refactor, not everything in here is needed by #edit
349 # TODO: Refactor, not everything in here is needed by #edit
350 def update_issue_from_params
350 def update_issue_from_params
351 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
351 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
352 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
352 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
353 @time_entry.attributes = params[:time_entry]
353 @time_entry.attributes = params[:time_entry]
354
354
355 @issue.init_journal(User.current)
355 @issue.init_journal(User.current)
356
356
357 issue_attributes = params[:issue]
357 issue_attributes = params[:issue]
358 if issue_attributes && params[:conflict_resolution]
358 if issue_attributes && params[:conflict_resolution]
359 case params[:conflict_resolution]
359 case params[:conflict_resolution]
360 when 'overwrite'
360 when 'overwrite'
361 issue_attributes = issue_attributes.dup
361 issue_attributes = issue_attributes.dup
362 issue_attributes.delete(:lock_version)
362 issue_attributes.delete(:lock_version)
363 when 'add_notes'
363 when 'add_notes'
364 issue_attributes = issue_attributes.slice(:notes)
364 issue_attributes = issue_attributes.slice(:notes)
365 when 'cancel'
365 when 'cancel'
366 redirect_to issue_path(@issue)
366 redirect_to issue_path(@issue)
367 return false
367 return false
368 end
368 end
369 end
369 end
370 @issue.safe_attributes = issue_attributes
370 @issue.safe_attributes = issue_attributes
371 @priorities = IssuePriority.active
371 @priorities = IssuePriority.active
372 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
372 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
373 true
373 true
374 end
374 end
375
375
376 # TODO: Refactor, lots of extra code in here
376 # TODO: Refactor, lots of extra code in here
377 # TODO: Changing tracker on an existing issue should not trigger this
377 # TODO: Changing tracker on an existing issue should not trigger this
378 def build_new_issue_from_params
378 def build_new_issue_from_params
379 if params[:id].blank?
379 if params[:id].blank?
380 @issue = Issue.new
380 @issue = Issue.new
381 if params[:copy_from]
381 if params[:copy_from]
382 begin
382 begin
383 @copy_from = Issue.visible.find(params[:copy_from])
383 @copy_from = Issue.visible.find(params[:copy_from])
384 @copy_attachments = params[:copy_attachments].present? || request.get?
384 @copy_attachments = params[:copy_attachments].present? || request.get?
385 @copy_subtasks = params[:copy_subtasks].present? || request.get?
385 @copy_subtasks = params[:copy_subtasks].present? || request.get?
386 @issue.copy_from(@copy_from, :attachments => @copy_attachments, :subtasks => @copy_subtasks)
386 @issue.copy_from(@copy_from, :attachments => @copy_attachments, :subtasks => @copy_subtasks)
387 rescue ActiveRecord::RecordNotFound
387 rescue ActiveRecord::RecordNotFound
388 render_404
388 render_404
389 return
389 return
390 end
390 end
391 end
391 end
392 @issue.project = @project
392 @issue.project = @project
393 else
393 else
394 @issue = @project.issues.visible.find(params[:id])
394 @issue = @project.issues.visible.find(params[:id])
395 end
395 end
396
396
397 @issue.project = @project
397 @issue.project = @project
398 @issue.author ||= User.current
398 @issue.author ||= User.current
399 # Tracker must be set before custom field values
399 # Tracker must be set before custom field values
400 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
400 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
401 if @issue.tracker.nil?
401 if @issue.tracker.nil?
402 render_error l(:error_no_tracker_in_project)
402 render_error l(:error_no_tracker_in_project)
403 return false
403 return false
404 end
404 end
405 @issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
405 @issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
406 @issue.safe_attributes = params[:issue]
406 @issue.safe_attributes = params[:issue]
407
407
408 @priorities = IssuePriority.active
408 @priorities = IssuePriority.active
409 @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
409 @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
410 @available_watchers = (@issue.project.users.sort + @issue.watcher_users).uniq
410 @available_watchers = (@issue.project.users.sort + @issue.watcher_users).uniq
411 end
411 end
412
412
413 def check_for_default_issue_status
413 def check_for_default_issue_status
414 if IssueStatus.default.nil?
414 if IssueStatus.default.nil?
415 render_error l(:error_no_default_issue_status)
415 render_error l(:error_no_default_issue_status)
416 return false
416 return false
417 end
417 end
418 end
418 end
419
419
420 def parse_params_for_bulk_issue_attributes(params)
420 def parse_params_for_bulk_issue_attributes(params)
421 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
421 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
422 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
422 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
423 if custom = attributes[:custom_field_values]
423 if custom = attributes[:custom_field_values]
424 custom.reject! {|k,v| v.blank?}
424 custom.reject! {|k,v| v.blank?}
425 custom.keys.each do |k|
425 custom.keys.each do |k|
426 if custom[k].is_a?(Array)
426 if custom[k].is_a?(Array)
427 custom[k] << '' if custom[k].delete('__none__')
427 custom[k] << '' if custom[k].delete('__none__')
428 else
428 else
429 custom[k] = '' if custom[k] == '__none__'
429 custom[k] = '' if custom[k] == '__none__'
430 end
430 end
431 end
431 end
432 end
432 end
433 attributes
433 attributes
434 end
434 end
435 end
435 end
@@ -1,124 +1,124
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 MembersController < ApplicationController
18 class MembersController < ApplicationController
19 model_object Member
19 model_object Member
20 before_filter :find_model_object, :except => [:index, :create, :autocomplete]
20 before_filter :find_model_object, :except => [:index, :create, :autocomplete]
21 before_filter :find_project_from_association, :except => [:index, :create, :autocomplete]
21 before_filter :find_project_from_association, :except => [:index, :create, :autocomplete]
22 before_filter :find_project_by_project_id, :only => [:index, :create, :autocomplete]
22 before_filter :find_project_by_project_id, :only => [:index, :create, :autocomplete]
23 before_filter :authorize
23 before_filter :authorize
24 accept_api_auth :index, :show, :create, :update, :destroy
24 accept_api_auth :index, :show, :create, :update, :destroy
25
25
26 def index
26 def index
27 @offset, @limit = api_offset_and_limit
27 @offset, @limit = api_offset_and_limit
28 @member_count = @project.member_principals.count
28 @member_count = @project.member_principals.count
29 @member_pages = Paginator.new self, @member_count, @limit, params['page']
29 @member_pages = Paginator.new @member_count, @limit, params['page']
30 @offset ||= @member_pages.current.offset
30 @offset ||= @member_pages.offset
31 @members = @project.member_principals.all(
31 @members = @project.member_principals.all(
32 :order => "#{Member.table_name}.id",
32 :order => "#{Member.table_name}.id",
33 :limit => @limit,
33 :limit => @limit,
34 :offset => @offset
34 :offset => @offset
35 )
35 )
36
36
37 respond_to do |format|
37 respond_to do |format|
38 format.html { head 406 }
38 format.html { head 406 }
39 format.api
39 format.api
40 end
40 end
41 end
41 end
42
42
43 def show
43 def show
44 respond_to do |format|
44 respond_to do |format|
45 format.html { head 406 }
45 format.html { head 406 }
46 format.api
46 format.api
47 end
47 end
48 end
48 end
49
49
50 def create
50 def create
51 members = []
51 members = []
52 if params[:membership]
52 if params[:membership]
53 if params[:membership][:user_ids]
53 if params[:membership][:user_ids]
54 attrs = params[:membership].dup
54 attrs = params[:membership].dup
55 user_ids = attrs.delete(:user_ids)
55 user_ids = attrs.delete(:user_ids)
56 user_ids.each do |user_id|
56 user_ids.each do |user_id|
57 members << Member.new(:role_ids => params[:membership][:role_ids], :user_id => user_id)
57 members << Member.new(:role_ids => params[:membership][:role_ids], :user_id => user_id)
58 end
58 end
59 else
59 else
60 members << Member.new(:role_ids => params[:membership][:role_ids], :user_id => params[:membership][:user_id])
60 members << Member.new(:role_ids => params[:membership][:role_ids], :user_id => params[:membership][:user_id])
61 end
61 end
62 @project.members << members
62 @project.members << members
63 end
63 end
64
64
65 respond_to do |format|
65 respond_to do |format|
66 format.html { redirect_to_settings_in_projects }
66 format.html { redirect_to_settings_in_projects }
67 format.js { @members = members }
67 format.js { @members = members }
68 format.api {
68 format.api {
69 @member = members.first
69 @member = members.first
70 if @member.valid?
70 if @member.valid?
71 render :action => 'show', :status => :created, :location => membership_url(@member)
71 render :action => 'show', :status => :created, :location => membership_url(@member)
72 else
72 else
73 render_validation_errors(@member)
73 render_validation_errors(@member)
74 end
74 end
75 }
75 }
76 end
76 end
77 end
77 end
78
78
79 def update
79 def update
80 if params[:membership]
80 if params[:membership]
81 @member.role_ids = params[:membership][:role_ids]
81 @member.role_ids = params[:membership][:role_ids]
82 end
82 end
83 saved = @member.save
83 saved = @member.save
84 respond_to do |format|
84 respond_to do |format|
85 format.html { redirect_to_settings_in_projects }
85 format.html { redirect_to_settings_in_projects }
86 format.js
86 format.js
87 format.api {
87 format.api {
88 if saved
88 if saved
89 render_api_ok
89 render_api_ok
90 else
90 else
91 render_validation_errors(@member)
91 render_validation_errors(@member)
92 end
92 end
93 }
93 }
94 end
94 end
95 end
95 end
96
96
97 def destroy
97 def destroy
98 if request.delete? && @member.deletable?
98 if request.delete? && @member.deletable?
99 @member.destroy
99 @member.destroy
100 end
100 end
101 respond_to do |format|
101 respond_to do |format|
102 format.html { redirect_to_settings_in_projects }
102 format.html { redirect_to_settings_in_projects }
103 format.js
103 format.js
104 format.api {
104 format.api {
105 if @member.destroyed?
105 if @member.destroyed?
106 render_api_ok
106 render_api_ok
107 else
107 else
108 head :unprocessable_entity
108 head :unprocessable_entity
109 end
109 end
110 }
110 }
111 end
111 end
112 end
112 end
113
113
114 def autocomplete
114 def autocomplete
115 @principals = Principal.active.not_member_of(@project).like(params[:q]).all(:limit => 100)
115 @principals = Principal.active.not_member_of(@project).like(params[:q]).all(:limit => 100)
116 render :layout => false
116 render :layout => false
117 end
117 end
118
118
119 private
119 private
120
120
121 def redirect_to_settings_in_projects
121 def redirect_to_settings_in_projects
122 redirect_to settings_project_path(@project, :tab => 'members')
122 redirect_to settings_project_path(@project, :tab => 'members')
123 end
123 end
124 end
124 end
@@ -1,141 +1,141
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 MessagesController < ApplicationController
18 class MessagesController < ApplicationController
19 menu_item :boards
19 menu_item :boards
20 default_search_scope :messages
20 default_search_scope :messages
21 before_filter :find_board, :only => [:new, :preview]
21 before_filter :find_board, :only => [:new, :preview]
22 before_filter :find_attachments, :only => [:preview]
22 before_filter :find_attachments, :only => [:preview]
23 before_filter :find_message, :except => [:new, :preview]
23 before_filter :find_message, :except => [:new, :preview]
24 before_filter :authorize, :except => [:preview, :edit, :destroy]
24 before_filter :authorize, :except => [:preview, :edit, :destroy]
25
25
26 helper :boards
26 helper :boards
27 helper :watchers
27 helper :watchers
28 helper :attachments
28 helper :attachments
29 include AttachmentsHelper
29 include AttachmentsHelper
30
30
31 REPLIES_PER_PAGE = 25 unless const_defined?(:REPLIES_PER_PAGE)
31 REPLIES_PER_PAGE = 25 unless const_defined?(:REPLIES_PER_PAGE)
32
32
33 # Show a topic and its replies
33 # Show a topic and its replies
34 def show
34 def show
35 page = params[:page]
35 page = params[:page]
36 # Find the page of the requested reply
36 # Find the page of the requested reply
37 if params[:r] && page.nil?
37 if params[:r] && page.nil?
38 offset = @topic.children.count(:conditions => ["#{Message.table_name}.id < ?", params[:r].to_i])
38 offset = @topic.children.count(:conditions => ["#{Message.table_name}.id < ?", params[:r].to_i])
39 page = 1 + offset / REPLIES_PER_PAGE
39 page = 1 + offset / REPLIES_PER_PAGE
40 end
40 end
41
41
42 @reply_count = @topic.children.count
42 @reply_count = @topic.children.count
43 @reply_pages = Paginator.new self, @reply_count, REPLIES_PER_PAGE, page
43 @reply_pages = Paginator.new @reply_count, REPLIES_PER_PAGE, page
44 @replies = @topic.children.
44 @replies = @topic.children.
45 includes(:author, :attachments, {:board => :project}).
45 includes(:author, :attachments, {:board => :project}).
46 reorder("#{Message.table_name}.created_on ASC").
46 reorder("#{Message.table_name}.created_on ASC").
47 limit(@reply_pages.items_per_page).
47 limit(@reply_pages.items_per_page).
48 offset(@reply_pages.current.offset).
48 offset(@reply_pages.offset).
49 all
49 all
50
50
51 @reply = Message.new(:subject => "RE: #{@message.subject}")
51 @reply = Message.new(:subject => "RE: #{@message.subject}")
52 render :action => "show", :layout => false if request.xhr?
52 render :action => "show", :layout => false if request.xhr?
53 end
53 end
54
54
55 # Create a new topic
55 # Create a new topic
56 def new
56 def new
57 @message = Message.new
57 @message = Message.new
58 @message.author = User.current
58 @message.author = User.current
59 @message.board = @board
59 @message.board = @board
60 @message.safe_attributes = params[:message]
60 @message.safe_attributes = params[:message]
61 if request.post?
61 if request.post?
62 @message.save_attachments(params[:attachments])
62 @message.save_attachments(params[:attachments])
63 if @message.save
63 if @message.save
64 call_hook(:controller_messages_new_after_save, { :params => params, :message => @message})
64 call_hook(:controller_messages_new_after_save, { :params => params, :message => @message})
65 render_attachment_warning_if_needed(@message)
65 render_attachment_warning_if_needed(@message)
66 redirect_to board_message_path(@board, @message)
66 redirect_to board_message_path(@board, @message)
67 end
67 end
68 end
68 end
69 end
69 end
70
70
71 # Reply to a topic
71 # Reply to a topic
72 def reply
72 def reply
73 @reply = Message.new
73 @reply = Message.new
74 @reply.author = User.current
74 @reply.author = User.current
75 @reply.board = @board
75 @reply.board = @board
76 @reply.safe_attributes = params[:reply]
76 @reply.safe_attributes = params[:reply]
77 @topic.children << @reply
77 @topic.children << @reply
78 if !@reply.new_record?
78 if !@reply.new_record?
79 call_hook(:controller_messages_reply_after_save, { :params => params, :message => @reply})
79 call_hook(:controller_messages_reply_after_save, { :params => params, :message => @reply})
80 attachments = Attachment.attach_files(@reply, params[:attachments])
80 attachments = Attachment.attach_files(@reply, params[:attachments])
81 render_attachment_warning_if_needed(@reply)
81 render_attachment_warning_if_needed(@reply)
82 end
82 end
83 redirect_to board_message_path(@board, @topic, :r => @reply)
83 redirect_to board_message_path(@board, @topic, :r => @reply)
84 end
84 end
85
85
86 # Edit a message
86 # Edit a message
87 def edit
87 def edit
88 (render_403; return false) unless @message.editable_by?(User.current)
88 (render_403; return false) unless @message.editable_by?(User.current)
89 @message.safe_attributes = params[:message]
89 @message.safe_attributes = params[:message]
90 if request.post? && @message.save
90 if request.post? && @message.save
91 attachments = Attachment.attach_files(@message, params[:attachments])
91 attachments = Attachment.attach_files(@message, params[:attachments])
92 render_attachment_warning_if_needed(@message)
92 render_attachment_warning_if_needed(@message)
93 flash[:notice] = l(:notice_successful_update)
93 flash[:notice] = l(:notice_successful_update)
94 @message.reload
94 @message.reload
95 redirect_to board_message_path(@message.board, @message.root, :r => (@message.parent_id && @message.id))
95 redirect_to board_message_path(@message.board, @message.root, :r => (@message.parent_id && @message.id))
96 end
96 end
97 end
97 end
98
98
99 # Delete a messages
99 # Delete a messages
100 def destroy
100 def destroy
101 (render_403; return false) unless @message.destroyable_by?(User.current)
101 (render_403; return false) unless @message.destroyable_by?(User.current)
102 r = @message.to_param
102 r = @message.to_param
103 @message.destroy
103 @message.destroy
104 if @message.parent
104 if @message.parent
105 redirect_to board_message_path(@board, @message.parent, :r => r)
105 redirect_to board_message_path(@board, @message.parent, :r => r)
106 else
106 else
107 redirect_to project_board_path(@project, @board)
107 redirect_to project_board_path(@project, @board)
108 end
108 end
109 end
109 end
110
110
111 def quote
111 def quote
112 @subject = @message.subject
112 @subject = @message.subject
113 @subject = "RE: #{@subject}" unless @subject.starts_with?('RE:')
113 @subject = "RE: #{@subject}" unless @subject.starts_with?('RE:')
114
114
115 @content = "#{ll(Setting.default_language, :text_user_wrote, @message.author)}\n> "
115 @content = "#{ll(Setting.default_language, :text_user_wrote, @message.author)}\n> "
116 @content << @message.content.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n"
116 @content << @message.content.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n"
117 end
117 end
118
118
119 def preview
119 def preview
120 message = @board.messages.find_by_id(params[:id])
120 message = @board.messages.find_by_id(params[:id])
121 @text = (params[:message] || params[:reply])[:content]
121 @text = (params[:message] || params[:reply])[:content]
122 @previewed = message
122 @previewed = message
123 render :partial => 'common/preview'
123 render :partial => 'common/preview'
124 end
124 end
125
125
126 private
126 private
127 def find_message
127 def find_message
128 find_board
128 find_board
129 @message = @board.messages.find(params[:id], :include => :parent)
129 @message = @board.messages.find(params[:id], :include => :parent)
130 @topic = @message.root
130 @topic = @message.root
131 rescue ActiveRecord::RecordNotFound
131 rescue ActiveRecord::RecordNotFound
132 render_404
132 render_404
133 end
133 end
134
134
135 def find_board
135 def find_board
136 @board = Board.find(params[:board_id], :include => :project)
136 @board = Board.find(params[:board_id], :include => :project)
137 @project = @board.project
137 @project = @board.project
138 rescue ActiveRecord::RecordNotFound
138 rescue ActiveRecord::RecordNotFound
139 render_404
139 render_404
140 end
140 end
141 end
141 end
@@ -1,111 +1,111
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 NewsController < ApplicationController
18 class NewsController < ApplicationController
19 default_search_scope :news
19 default_search_scope :news
20 model_object News
20 model_object News
21 before_filter :find_model_object, :except => [:new, :create, :index]
21 before_filter :find_model_object, :except => [:new, :create, :index]
22 before_filter :find_project_from_association, :except => [:new, :create, :index]
22 before_filter :find_project_from_association, :except => [:new, :create, :index]
23 before_filter :find_project_by_project_id, :only => [:new, :create]
23 before_filter :find_project_by_project_id, :only => [:new, :create]
24 before_filter :authorize, :except => [:index]
24 before_filter :authorize, :except => [:index]
25 before_filter :find_optional_project, :only => :index
25 before_filter :find_optional_project, :only => :index
26 accept_rss_auth :index
26 accept_rss_auth :index
27 accept_api_auth :index
27 accept_api_auth :index
28
28
29 helper :watchers
29 helper :watchers
30 helper :attachments
30 helper :attachments
31
31
32 def index
32 def index
33 case params[:format]
33 case params[:format]
34 when 'xml', 'json'
34 when 'xml', 'json'
35 @offset, @limit = api_offset_and_limit
35 @offset, @limit = api_offset_and_limit
36 else
36 else
37 @limit = 10
37 @limit = 10
38 end
38 end
39
39
40 scope = @project ? @project.news.visible : News.visible
40 scope = @project ? @project.news.visible : News.visible
41
41
42 @news_count = scope.count
42 @news_count = scope.count
43 @news_pages = Paginator.new self, @news_count, @limit, params['page']
43 @news_pages = Paginator.new @news_count, @limit, params['page']
44 @offset ||= @news_pages.current.offset
44 @offset ||= @news_pages.offset
45 @newss = scope.all(:include => [:author, :project],
45 @newss = scope.all(:include => [:author, :project],
46 :order => "#{News.table_name}.created_on DESC",
46 :order => "#{News.table_name}.created_on DESC",
47 :offset => @offset,
47 :offset => @offset,
48 :limit => @limit)
48 :limit => @limit)
49
49
50 respond_to do |format|
50 respond_to do |format|
51 format.html {
51 format.html {
52 @news = News.new # for adding news inline
52 @news = News.new # for adding news inline
53 render :layout => false if request.xhr?
53 render :layout => false if request.xhr?
54 }
54 }
55 format.api
55 format.api
56 format.atom { render_feed(@newss, :title => (@project ? @project.name : Setting.app_title) + ": #{l(:label_news_plural)}") }
56 format.atom { render_feed(@newss, :title => (@project ? @project.name : Setting.app_title) + ": #{l(:label_news_plural)}") }
57 end
57 end
58 end
58 end
59
59
60 def show
60 def show
61 @comments = @news.comments
61 @comments = @news.comments
62 @comments.reverse! if User.current.wants_comments_in_reverse_order?
62 @comments.reverse! if User.current.wants_comments_in_reverse_order?
63 end
63 end
64
64
65 def new
65 def new
66 @news = News.new(:project => @project, :author => User.current)
66 @news = News.new(:project => @project, :author => User.current)
67 end
67 end
68
68
69 def create
69 def create
70 @news = News.new(:project => @project, :author => User.current)
70 @news = News.new(:project => @project, :author => User.current)
71 @news.safe_attributes = params[:news]
71 @news.safe_attributes = params[:news]
72 @news.save_attachments(params[:attachments])
72 @news.save_attachments(params[:attachments])
73 if @news.save
73 if @news.save
74 render_attachment_warning_if_needed(@news)
74 render_attachment_warning_if_needed(@news)
75 flash[:notice] = l(:notice_successful_create)
75 flash[:notice] = l(:notice_successful_create)
76 redirect_to project_news_index_path(@project)
76 redirect_to project_news_index_path(@project)
77 else
77 else
78 render :action => 'new'
78 render :action => 'new'
79 end
79 end
80 end
80 end
81
81
82 def edit
82 def edit
83 end
83 end
84
84
85 def update
85 def update
86 @news.safe_attributes = params[:news]
86 @news.safe_attributes = params[:news]
87 @news.save_attachments(params[:attachments])
87 @news.save_attachments(params[:attachments])
88 if @news.save
88 if @news.save
89 render_attachment_warning_if_needed(@news)
89 render_attachment_warning_if_needed(@news)
90 flash[:notice] = l(:notice_successful_update)
90 flash[:notice] = l(:notice_successful_update)
91 redirect_to news_path(@news)
91 redirect_to news_path(@news)
92 else
92 else
93 render :action => 'edit'
93 render :action => 'edit'
94 end
94 end
95 end
95 end
96
96
97 def destroy
97 def destroy
98 @news.destroy
98 @news.destroy
99 redirect_to project_news_index_path(@project)
99 redirect_to project_news_index_path(@project)
100 end
100 end
101
101
102 private
102 private
103
103
104 def find_optional_project
104 def find_optional_project
105 return true unless params[:project_id]
105 return true unless params[:project_id]
106 @project = Project.find(params[:project_id])
106 @project = Project.find(params[:project_id])
107 authorize
107 authorize
108 rescue ActiveRecord::RecordNotFound
108 rescue ActiveRecord::RecordNotFound
109 render_404
109 render_404
110 end
110 end
111 end
111 end
@@ -1,106 +1,106
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 QueriesController < ApplicationController
18 class QueriesController < ApplicationController
19 menu_item :issues
19 menu_item :issues
20 before_filter :find_query, :except => [:new, :create, :index]
20 before_filter :find_query, :except => [:new, :create, :index]
21 before_filter :find_optional_project, :only => [:new, :create]
21 before_filter :find_optional_project, :only => [:new, :create]
22
22
23 accept_api_auth :index
23 accept_api_auth :index
24
24
25 include QueriesHelper
25 include QueriesHelper
26
26
27 def index
27 def index
28 case params[:format]
28 case params[:format]
29 when 'xml', 'json'
29 when 'xml', 'json'
30 @offset, @limit = api_offset_and_limit
30 @offset, @limit = api_offset_and_limit
31 else
31 else
32 @limit = per_page_option
32 @limit = per_page_option
33 end
33 end
34
34
35 @query_count = IssueQuery.visible.count
35 @query_count = IssueQuery.visible.count
36 @query_pages = Paginator.new self, @query_count, @limit, params['page']
36 @query_pages = Paginator.new @query_count, @limit, params['page']
37 @queries = IssueQuery.visible.all(:limit => @limit, :offset => @offset, :order => "#{Query.table_name}.name")
37 @queries = IssueQuery.visible.all(:limit => @limit, :offset => @offset, :order => "#{Query.table_name}.name")
38
38
39 respond_to do |format|
39 respond_to do |format|
40 format.api
40 format.api
41 end
41 end
42 end
42 end
43
43
44 def new
44 def new
45 @query = IssueQuery.new
45 @query = IssueQuery.new
46 @query.user = User.current
46 @query.user = User.current
47 @query.project = @project
47 @query.project = @project
48 @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
48 @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
49 @query.build_from_params(params)
49 @query.build_from_params(params)
50 end
50 end
51
51
52 def create
52 def create
53 @query = IssueQuery.new(params[:query])
53 @query = IssueQuery.new(params[:query])
54 @query.user = User.current
54 @query.user = User.current
55 @query.project = params[:query_is_for_all] ? nil : @project
55 @query.project = params[:query_is_for_all] ? nil : @project
56 @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
56 @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
57 @query.build_from_params(params)
57 @query.build_from_params(params)
58 @query.column_names = nil if params[:default_columns]
58 @query.column_names = nil if params[:default_columns]
59
59
60 if @query.save
60 if @query.save
61 flash[:notice] = l(:notice_successful_create)
61 flash[:notice] = l(:notice_successful_create)
62 redirect_to _project_issues_path(@project, :query_id => @query)
62 redirect_to _project_issues_path(@project, :query_id => @query)
63 else
63 else
64 render :action => 'new', :layout => !request.xhr?
64 render :action => 'new', :layout => !request.xhr?
65 end
65 end
66 end
66 end
67
67
68 def edit
68 def edit
69 end
69 end
70
70
71 def update
71 def update
72 @query.attributes = params[:query]
72 @query.attributes = params[:query]
73 @query.project = nil if params[:query_is_for_all]
73 @query.project = nil if params[:query_is_for_all]
74 @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
74 @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
75 @query.build_from_params(params)
75 @query.build_from_params(params)
76 @query.column_names = nil if params[:default_columns]
76 @query.column_names = nil if params[:default_columns]
77
77
78 if @query.save
78 if @query.save
79 flash[:notice] = l(:notice_successful_update)
79 flash[:notice] = l(:notice_successful_update)
80 redirect_to _project_issues_path(@project, :query_id => @query)
80 redirect_to _project_issues_path(@project, :query_id => @query)
81 else
81 else
82 render :action => 'edit'
82 render :action => 'edit'
83 end
83 end
84 end
84 end
85
85
86 def destroy
86 def destroy
87 @query.destroy
87 @query.destroy
88 redirect_to _project_issues_path(@project, :set_filter => 1)
88 redirect_to _project_issues_path(@project, :set_filter => 1)
89 end
89 end
90
90
91 private
91 private
92 def find_query
92 def find_query
93 @query = IssueQuery.find(params[:id])
93 @query = IssueQuery.find(params[:id])
94 @project = @query.project
94 @project = @query.project
95 render_403 unless @query.editable_by?(User.current)
95 render_403 unless @query.editable_by?(User.current)
96 rescue ActiveRecord::RecordNotFound
96 rescue ActiveRecord::RecordNotFound
97 render_404
97 render_404
98 end
98 end
99
99
100 def find_optional_project
100 def find_optional_project
101 @project = Project.find(params[:project_id]) if params[:project_id]
101 @project = Project.find(params[:project_id]) if params[:project_id]
102 render_403 unless User.current.allowed_to?(:save_queries, @project, :global => true)
102 render_403 unless User.current.allowed_to?(:save_queries, @project, :global => true)
103 rescue ActiveRecord::RecordNotFound
103 rescue ActiveRecord::RecordNotFound
104 render_404
104 render_404
105 end
105 end
106 end
106 end
@@ -1,434 +1,434
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 'SVG/Graph/Bar'
18 require 'SVG/Graph/Bar'
19 require 'SVG/Graph/BarHorizontal'
19 require 'SVG/Graph/BarHorizontal'
20 require 'digest/sha1'
20 require 'digest/sha1'
21 require 'redmine/scm/adapters/abstract_adapter'
21 require 'redmine/scm/adapters/abstract_adapter'
22
22
23 class ChangesetNotFound < Exception; end
23 class ChangesetNotFound < Exception; end
24 class InvalidRevisionParam < Exception; end
24 class InvalidRevisionParam < Exception; end
25
25
26 class RepositoriesController < ApplicationController
26 class RepositoriesController < ApplicationController
27 menu_item :repository
27 menu_item :repository
28 menu_item :settings, :only => [:new, :create, :edit, :update, :destroy, :committers]
28 menu_item :settings, :only => [:new, :create, :edit, :update, :destroy, :committers]
29 default_search_scope :changesets
29 default_search_scope :changesets
30
30
31 before_filter :find_project_by_project_id, :only => [:new, :create]
31 before_filter :find_project_by_project_id, :only => [:new, :create]
32 before_filter :find_repository, :only => [:edit, :update, :destroy, :committers]
32 before_filter :find_repository, :only => [:edit, :update, :destroy, :committers]
33 before_filter :find_project_repository, :except => [:new, :create, :edit, :update, :destroy, :committers]
33 before_filter :find_project_repository, :except => [:new, :create, :edit, :update, :destroy, :committers]
34 before_filter :find_changeset, :only => [:revision, :add_related_issue, :remove_related_issue]
34 before_filter :find_changeset, :only => [:revision, :add_related_issue, :remove_related_issue]
35 before_filter :authorize
35 before_filter :authorize
36 accept_rss_auth :revisions
36 accept_rss_auth :revisions
37
37
38 rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed
38 rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed
39
39
40 def new
40 def new
41 scm = params[:repository_scm] || (Redmine::Scm::Base.all & Setting.enabled_scm).first
41 scm = params[:repository_scm] || (Redmine::Scm::Base.all & Setting.enabled_scm).first
42 @repository = Repository.factory(scm)
42 @repository = Repository.factory(scm)
43 @repository.is_default = @project.repository.nil?
43 @repository.is_default = @project.repository.nil?
44 @repository.project = @project
44 @repository.project = @project
45 end
45 end
46
46
47 def create
47 def create
48 attrs = pickup_extra_info
48 attrs = pickup_extra_info
49 @repository = Repository.factory(params[:repository_scm])
49 @repository = Repository.factory(params[:repository_scm])
50 @repository.safe_attributes = params[:repository]
50 @repository.safe_attributes = params[:repository]
51 if attrs[:attrs_extra].keys.any?
51 if attrs[:attrs_extra].keys.any?
52 @repository.merge_extra_info(attrs[:attrs_extra])
52 @repository.merge_extra_info(attrs[:attrs_extra])
53 end
53 end
54 @repository.project = @project
54 @repository.project = @project
55 if request.post? && @repository.save
55 if request.post? && @repository.save
56 redirect_to settings_project_path(@project, :tab => 'repositories')
56 redirect_to settings_project_path(@project, :tab => 'repositories')
57 else
57 else
58 render :action => 'new'
58 render :action => 'new'
59 end
59 end
60 end
60 end
61
61
62 def edit
62 def edit
63 end
63 end
64
64
65 def update
65 def update
66 attrs = pickup_extra_info
66 attrs = pickup_extra_info
67 @repository.safe_attributes = attrs[:attrs]
67 @repository.safe_attributes = attrs[:attrs]
68 if attrs[:attrs_extra].keys.any?
68 if attrs[:attrs_extra].keys.any?
69 @repository.merge_extra_info(attrs[:attrs_extra])
69 @repository.merge_extra_info(attrs[:attrs_extra])
70 end
70 end
71 @repository.project = @project
71 @repository.project = @project
72 if request.put? && @repository.save
72 if request.put? && @repository.save
73 redirect_to settings_project_path(@project, :tab => 'repositories')
73 redirect_to settings_project_path(@project, :tab => 'repositories')
74 else
74 else
75 render :action => 'edit'
75 render :action => 'edit'
76 end
76 end
77 end
77 end
78
78
79 def pickup_extra_info
79 def pickup_extra_info
80 p = {}
80 p = {}
81 p_extra = {}
81 p_extra = {}
82 params[:repository].each do |k, v|
82 params[:repository].each do |k, v|
83 if k =~ /^extra_/
83 if k =~ /^extra_/
84 p_extra[k] = v
84 p_extra[k] = v
85 else
85 else
86 p[k] = v
86 p[k] = v
87 end
87 end
88 end
88 end
89 {:attrs => p, :attrs_extra => p_extra}
89 {:attrs => p, :attrs_extra => p_extra}
90 end
90 end
91 private :pickup_extra_info
91 private :pickup_extra_info
92
92
93 def committers
93 def committers
94 @committers = @repository.committers
94 @committers = @repository.committers
95 @users = @project.users
95 @users = @project.users
96 additional_user_ids = @committers.collect(&:last).collect(&:to_i) - @users.collect(&:id)
96 additional_user_ids = @committers.collect(&:last).collect(&:to_i) - @users.collect(&:id)
97 @users += User.find_all_by_id(additional_user_ids) unless additional_user_ids.empty?
97 @users += User.find_all_by_id(additional_user_ids) unless additional_user_ids.empty?
98 @users.compact!
98 @users.compact!
99 @users.sort!
99 @users.sort!
100 if request.post? && params[:committers].is_a?(Hash)
100 if request.post? && params[:committers].is_a?(Hash)
101 # Build a hash with repository usernames as keys and corresponding user ids as values
101 # Build a hash with repository usernames as keys and corresponding user ids as values
102 @repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h}
102 @repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h}
103 flash[:notice] = l(:notice_successful_update)
103 flash[:notice] = l(:notice_successful_update)
104 redirect_to settings_project_path(@project, :tab => 'repositories')
104 redirect_to settings_project_path(@project, :tab => 'repositories')
105 end
105 end
106 end
106 end
107
107
108 def destroy
108 def destroy
109 @repository.destroy if request.delete?
109 @repository.destroy if request.delete?
110 redirect_to settings_project_path(@project, :tab => 'repositories')
110 redirect_to settings_project_path(@project, :tab => 'repositories')
111 end
111 end
112
112
113 def show
113 def show
114 @repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty?
114 @repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty?
115
115
116 @entries = @repository.entries(@path, @rev)
116 @entries = @repository.entries(@path, @rev)
117 @changeset = @repository.find_changeset_by_name(@rev)
117 @changeset = @repository.find_changeset_by_name(@rev)
118 if request.xhr?
118 if request.xhr?
119 @entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
119 @entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
120 else
120 else
121 (show_error_not_found; return) unless @entries
121 (show_error_not_found; return) unless @entries
122 @changesets = @repository.latest_changesets(@path, @rev)
122 @changesets = @repository.latest_changesets(@path, @rev)
123 @properties = @repository.properties(@path, @rev)
123 @properties = @repository.properties(@path, @rev)
124 @repositories = @project.repositories
124 @repositories = @project.repositories
125 render :action => 'show'
125 render :action => 'show'
126 end
126 end
127 end
127 end
128
128
129 alias_method :browse, :show
129 alias_method :browse, :show
130
130
131 def changes
131 def changes
132 @entry = @repository.entry(@path, @rev)
132 @entry = @repository.entry(@path, @rev)
133 (show_error_not_found; return) unless @entry
133 (show_error_not_found; return) unless @entry
134 @changesets = @repository.latest_changesets(@path, @rev, Setting.repository_log_display_limit.to_i)
134 @changesets = @repository.latest_changesets(@path, @rev, Setting.repository_log_display_limit.to_i)
135 @properties = @repository.properties(@path, @rev)
135 @properties = @repository.properties(@path, @rev)
136 @changeset = @repository.find_changeset_by_name(@rev)
136 @changeset = @repository.find_changeset_by_name(@rev)
137 end
137 end
138
138
139 def revisions
139 def revisions
140 @changeset_count = @repository.changesets.count
140 @changeset_count = @repository.changesets.count
141 @changeset_pages = Paginator.new self, @changeset_count,
141 @changeset_pages = Paginator.new @changeset_count,
142 per_page_option,
142 per_page_option,
143 params['page']
143 params['page']
144 @changesets = @repository.changesets.
144 @changesets = @repository.changesets.
145 limit(@changeset_pages.items_per_page).
145 limit(@changeset_pages.items_per_page).
146 offset(@changeset_pages.current.offset).
146 offset(@changeset_pages.offset).
147 includes(:user, :repository, :parents).
147 includes(:user, :repository, :parents).
148 all
148 all
149
149
150 respond_to do |format|
150 respond_to do |format|
151 format.html { render :layout => false if request.xhr? }
151 format.html { render :layout => false if request.xhr? }
152 format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
152 format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
153 end
153 end
154 end
154 end
155
155
156 def raw
156 def raw
157 entry_and_raw(true)
157 entry_and_raw(true)
158 end
158 end
159
159
160 def entry
160 def entry
161 entry_and_raw(false)
161 entry_and_raw(false)
162 end
162 end
163
163
164 def entry_and_raw(is_raw)
164 def entry_and_raw(is_raw)
165 @entry = @repository.entry(@path, @rev)
165 @entry = @repository.entry(@path, @rev)
166 (show_error_not_found; return) unless @entry
166 (show_error_not_found; return) unless @entry
167
167
168 # If the entry is a dir, show the browser
168 # If the entry is a dir, show the browser
169 (show; return) if @entry.is_dir?
169 (show; return) if @entry.is_dir?
170
170
171 @content = @repository.cat(@path, @rev)
171 @content = @repository.cat(@path, @rev)
172 (show_error_not_found; return) unless @content
172 (show_error_not_found; return) unless @content
173 if is_raw ||
173 if is_raw ||
174 (@content.size && @content.size > Setting.file_max_size_displayed.to_i.kilobyte) ||
174 (@content.size && @content.size > Setting.file_max_size_displayed.to_i.kilobyte) ||
175 ! is_entry_text_data?(@content, @path)
175 ! is_entry_text_data?(@content, @path)
176 # Force the download
176 # Force the download
177 send_opt = { :filename => filename_for_content_disposition(@path.split('/').last) }
177 send_opt = { :filename => filename_for_content_disposition(@path.split('/').last) }
178 send_type = Redmine::MimeType.of(@path)
178 send_type = Redmine::MimeType.of(@path)
179 send_opt[:type] = send_type.to_s if send_type
179 send_opt[:type] = send_type.to_s if send_type
180 send_opt[:disposition] = (Redmine::MimeType.is_type?('image', @path) && !is_raw ? 'inline' : 'attachment')
180 send_opt[:disposition] = (Redmine::MimeType.is_type?('image', @path) && !is_raw ? 'inline' : 'attachment')
181 send_data @content, send_opt
181 send_data @content, send_opt
182 else
182 else
183 # Prevent empty lines when displaying a file with Windows style eol
183 # Prevent empty lines when displaying a file with Windows style eol
184 # TODO: UTF-16
184 # TODO: UTF-16
185 # Is this needs? AttachmentsController reads file simply.
185 # Is this needs? AttachmentsController reads file simply.
186 @content.gsub!("\r\n", "\n")
186 @content.gsub!("\r\n", "\n")
187 @changeset = @repository.find_changeset_by_name(@rev)
187 @changeset = @repository.find_changeset_by_name(@rev)
188 end
188 end
189 end
189 end
190 private :entry_and_raw
190 private :entry_and_raw
191
191
192 def is_entry_text_data?(ent, path)
192 def is_entry_text_data?(ent, path)
193 # UTF-16 contains "\x00".
193 # UTF-16 contains "\x00".
194 # It is very strict that file contains less than 30% of ascii symbols
194 # It is very strict that file contains less than 30% of ascii symbols
195 # in non Western Europe.
195 # in non Western Europe.
196 return true if Redmine::MimeType.is_type?('text', path)
196 return true if Redmine::MimeType.is_type?('text', path)
197 # Ruby 1.8.6 has a bug of integer divisions.
197 # Ruby 1.8.6 has a bug of integer divisions.
198 # http://apidock.com/ruby/v1_8_6_287/String/is_binary_data%3F
198 # http://apidock.com/ruby/v1_8_6_287/String/is_binary_data%3F
199 return false if ent.is_binary_data?
199 return false if ent.is_binary_data?
200 true
200 true
201 end
201 end
202 private :is_entry_text_data?
202 private :is_entry_text_data?
203
203
204 def annotate
204 def annotate
205 @entry = @repository.entry(@path, @rev)
205 @entry = @repository.entry(@path, @rev)
206 (show_error_not_found; return) unless @entry
206 (show_error_not_found; return) unless @entry
207
207
208 @annotate = @repository.scm.annotate(@path, @rev)
208 @annotate = @repository.scm.annotate(@path, @rev)
209 if @annotate.nil? || @annotate.empty?
209 if @annotate.nil? || @annotate.empty?
210 (render_error l(:error_scm_annotate); return)
210 (render_error l(:error_scm_annotate); return)
211 end
211 end
212 ann_buf_size = 0
212 ann_buf_size = 0
213 @annotate.lines.each do |buf|
213 @annotate.lines.each do |buf|
214 ann_buf_size += buf.size
214 ann_buf_size += buf.size
215 end
215 end
216 if ann_buf_size > Setting.file_max_size_displayed.to_i.kilobyte
216 if ann_buf_size > Setting.file_max_size_displayed.to_i.kilobyte
217 (render_error l(:error_scm_annotate_big_text_file); return)
217 (render_error l(:error_scm_annotate_big_text_file); return)
218 end
218 end
219 @changeset = @repository.find_changeset_by_name(@rev)
219 @changeset = @repository.find_changeset_by_name(@rev)
220 end
220 end
221
221
222 def revision
222 def revision
223 respond_to do |format|
223 respond_to do |format|
224 format.html
224 format.html
225 format.js {render :layout => false}
225 format.js {render :layout => false}
226 end
226 end
227 end
227 end
228
228
229 # Adds a related issue to a changeset
229 # Adds a related issue to a changeset
230 # POST /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues
230 # POST /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues
231 def add_related_issue
231 def add_related_issue
232 @issue = @changeset.find_referenced_issue_by_id(params[:issue_id])
232 @issue = @changeset.find_referenced_issue_by_id(params[:issue_id])
233 if @issue && (!@issue.visible? || @changeset.issues.include?(@issue))
233 if @issue && (!@issue.visible? || @changeset.issues.include?(@issue))
234 @issue = nil
234 @issue = nil
235 end
235 end
236
236
237 if @issue
237 if @issue
238 @changeset.issues << @issue
238 @changeset.issues << @issue
239 end
239 end
240 end
240 end
241
241
242 # Removes a related issue from a changeset
242 # Removes a related issue from a changeset
243 # DELETE /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues/:issue_id
243 # DELETE /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues/:issue_id
244 def remove_related_issue
244 def remove_related_issue
245 @issue = Issue.visible.find_by_id(params[:issue_id])
245 @issue = Issue.visible.find_by_id(params[:issue_id])
246 if @issue
246 if @issue
247 @changeset.issues.delete(@issue)
247 @changeset.issues.delete(@issue)
248 end
248 end
249 end
249 end
250
250
251 def diff
251 def diff
252 if params[:format] == 'diff'
252 if params[:format] == 'diff'
253 @diff = @repository.diff(@path, @rev, @rev_to)
253 @diff = @repository.diff(@path, @rev, @rev_to)
254 (show_error_not_found; return) unless @diff
254 (show_error_not_found; return) unless @diff
255 filename = "changeset_r#{@rev}"
255 filename = "changeset_r#{@rev}"
256 filename << "_r#{@rev_to}" if @rev_to
256 filename << "_r#{@rev_to}" if @rev_to
257 send_data @diff.join, :filename => "#{filename}.diff",
257 send_data @diff.join, :filename => "#{filename}.diff",
258 :type => 'text/x-patch',
258 :type => 'text/x-patch',
259 :disposition => 'attachment'
259 :disposition => 'attachment'
260 else
260 else
261 @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
261 @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
262 @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
262 @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
263
263
264 # Save diff type as user preference
264 # Save diff type as user preference
265 if User.current.logged? && @diff_type != User.current.pref[:diff_type]
265 if User.current.logged? && @diff_type != User.current.pref[:diff_type]
266 User.current.pref[:diff_type] = @diff_type
266 User.current.pref[:diff_type] = @diff_type
267 User.current.preference.save
267 User.current.preference.save
268 end
268 end
269 @cache_key = "repositories/diff/#{@repository.id}/" +
269 @cache_key = "repositories/diff/#{@repository.id}/" +
270 Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}-#{current_language}")
270 Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}-#{current_language}")
271 unless read_fragment(@cache_key)
271 unless read_fragment(@cache_key)
272 @diff = @repository.diff(@path, @rev, @rev_to)
272 @diff = @repository.diff(@path, @rev, @rev_to)
273 show_error_not_found unless @diff
273 show_error_not_found unless @diff
274 end
274 end
275
275
276 @changeset = @repository.find_changeset_by_name(@rev)
276 @changeset = @repository.find_changeset_by_name(@rev)
277 @changeset_to = @rev_to ? @repository.find_changeset_by_name(@rev_to) : nil
277 @changeset_to = @rev_to ? @repository.find_changeset_by_name(@rev_to) : nil
278 @diff_format_revisions = @repository.diff_format_revisions(@changeset, @changeset_to)
278 @diff_format_revisions = @repository.diff_format_revisions(@changeset, @changeset_to)
279 end
279 end
280 end
280 end
281
281
282 def stats
282 def stats
283 end
283 end
284
284
285 def graph
285 def graph
286 data = nil
286 data = nil
287 case params[:graph]
287 case params[:graph]
288 when "commits_per_month"
288 when "commits_per_month"
289 data = graph_commits_per_month(@repository)
289 data = graph_commits_per_month(@repository)
290 when "commits_per_author"
290 when "commits_per_author"
291 data = graph_commits_per_author(@repository)
291 data = graph_commits_per_author(@repository)
292 end
292 end
293 if data
293 if data
294 headers["Content-Type"] = "image/svg+xml"
294 headers["Content-Type"] = "image/svg+xml"
295 send_data(data, :type => "image/svg+xml", :disposition => "inline")
295 send_data(data, :type => "image/svg+xml", :disposition => "inline")
296 else
296 else
297 render_404
297 render_404
298 end
298 end
299 end
299 end
300
300
301 private
301 private
302
302
303 def find_repository
303 def find_repository
304 @repository = Repository.find(params[:id])
304 @repository = Repository.find(params[:id])
305 @project = @repository.project
305 @project = @repository.project
306 rescue ActiveRecord::RecordNotFound
306 rescue ActiveRecord::RecordNotFound
307 render_404
307 render_404
308 end
308 end
309
309
310 REV_PARAM_RE = %r{\A[a-f0-9]*\Z}i
310 REV_PARAM_RE = %r{\A[a-f0-9]*\Z}i
311
311
312 def find_project_repository
312 def find_project_repository
313 @project = Project.find(params[:id])
313 @project = Project.find(params[:id])
314 if params[:repository_id].present?
314 if params[:repository_id].present?
315 @repository = @project.repositories.find_by_identifier_param(params[:repository_id])
315 @repository = @project.repositories.find_by_identifier_param(params[:repository_id])
316 else
316 else
317 @repository = @project.repository
317 @repository = @project.repository
318 end
318 end
319 (render_404; return false) unless @repository
319 (render_404; return false) unless @repository
320 @path = params[:path].is_a?(Array) ? params[:path].join('/') : params[:path].to_s
320 @path = params[:path].is_a?(Array) ? params[:path].join('/') : params[:path].to_s
321 @rev = params[:rev].blank? ? @repository.default_branch : params[:rev].to_s.strip
321 @rev = params[:rev].blank? ? @repository.default_branch : params[:rev].to_s.strip
322 @rev_to = params[:rev_to]
322 @rev_to = params[:rev_to]
323
323
324 unless @rev.to_s.match(REV_PARAM_RE) && @rev_to.to_s.match(REV_PARAM_RE)
324 unless @rev.to_s.match(REV_PARAM_RE) && @rev_to.to_s.match(REV_PARAM_RE)
325 if @repository.branches.blank?
325 if @repository.branches.blank?
326 raise InvalidRevisionParam
326 raise InvalidRevisionParam
327 end
327 end
328 end
328 end
329 rescue ActiveRecord::RecordNotFound
329 rescue ActiveRecord::RecordNotFound
330 render_404
330 render_404
331 rescue InvalidRevisionParam
331 rescue InvalidRevisionParam
332 show_error_not_found
332 show_error_not_found
333 end
333 end
334
334
335 def find_changeset
335 def find_changeset
336 if @rev.present?
336 if @rev.present?
337 @changeset = @repository.find_changeset_by_name(@rev)
337 @changeset = @repository.find_changeset_by_name(@rev)
338 end
338 end
339 show_error_not_found unless @changeset
339 show_error_not_found unless @changeset
340 end
340 end
341
341
342 def show_error_not_found
342 def show_error_not_found
343 render_error :message => l(:error_scm_not_found), :status => 404
343 render_error :message => l(:error_scm_not_found), :status => 404
344 end
344 end
345
345
346 # Handler for Redmine::Scm::Adapters::CommandFailed exception
346 # Handler for Redmine::Scm::Adapters::CommandFailed exception
347 def show_error_command_failed(exception)
347 def show_error_command_failed(exception)
348 render_error l(:error_scm_command_failed, exception.message)
348 render_error l(:error_scm_command_failed, exception.message)
349 end
349 end
350
350
351 def graph_commits_per_month(repository)
351 def graph_commits_per_month(repository)
352 @date_to = Date.today
352 @date_to = Date.today
353 @date_from = @date_to << 11
353 @date_from = @date_to << 11
354 @date_from = Date.civil(@date_from.year, @date_from.month, 1)
354 @date_from = Date.civil(@date_from.year, @date_from.month, 1)
355 commits_by_day = Changeset.count(
355 commits_by_day = Changeset.count(
356 :all, :group => :commit_date,
356 :all, :group => :commit_date,
357 :conditions => ["repository_id = ? AND commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to])
357 :conditions => ["repository_id = ? AND commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to])
358 commits_by_month = [0] * 12
358 commits_by_month = [0] * 12
359 commits_by_day.each {|c| commits_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last }
359 commits_by_day.each {|c| commits_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last }
360
360
361 changes_by_day = Change.count(
361 changes_by_day = Change.count(
362 :all, :group => :commit_date, :include => :changeset,
362 :all, :group => :commit_date, :include => :changeset,
363 :conditions => ["#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to])
363 :conditions => ["#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to])
364 changes_by_month = [0] * 12
364 changes_by_month = [0] * 12
365 changes_by_day.each {|c| changes_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last }
365 changes_by_day.each {|c| changes_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last }
366
366
367 fields = []
367 fields = []
368 12.times {|m| fields << month_name(((Date.today.month - 1 - m) % 12) + 1)}
368 12.times {|m| fields << month_name(((Date.today.month - 1 - m) % 12) + 1)}
369
369
370 graph = SVG::Graph::Bar.new(
370 graph = SVG::Graph::Bar.new(
371 :height => 300,
371 :height => 300,
372 :width => 800,
372 :width => 800,
373 :fields => fields.reverse,
373 :fields => fields.reverse,
374 :stack => :side,
374 :stack => :side,
375 :scale_integers => true,
375 :scale_integers => true,
376 :step_x_labels => 2,
376 :step_x_labels => 2,
377 :show_data_values => false,
377 :show_data_values => false,
378 :graph_title => l(:label_commits_per_month),
378 :graph_title => l(:label_commits_per_month),
379 :show_graph_title => true
379 :show_graph_title => true
380 )
380 )
381
381
382 graph.add_data(
382 graph.add_data(
383 :data => commits_by_month[0..11].reverse,
383 :data => commits_by_month[0..11].reverse,
384 :title => l(:label_revision_plural)
384 :title => l(:label_revision_plural)
385 )
385 )
386
386
387 graph.add_data(
387 graph.add_data(
388 :data => changes_by_month[0..11].reverse,
388 :data => changes_by_month[0..11].reverse,
389 :title => l(:label_change_plural)
389 :title => l(:label_change_plural)
390 )
390 )
391
391
392 graph.burn
392 graph.burn
393 end
393 end
394
394
395 def graph_commits_per_author(repository)
395 def graph_commits_per_author(repository)
396 commits_by_author = Changeset.count(:all, :group => :committer, :conditions => ["repository_id = ?", repository.id])
396 commits_by_author = Changeset.count(:all, :group => :committer, :conditions => ["repository_id = ?", repository.id])
397 commits_by_author.to_a.sort! {|x, y| x.last <=> y.last}
397 commits_by_author.to_a.sort! {|x, y| x.last <=> y.last}
398
398
399 changes_by_author = Change.count(:all, :group => :committer, :include => :changeset, :conditions => ["#{Changeset.table_name}.repository_id = ?", repository.id])
399 changes_by_author = Change.count(:all, :group => :committer, :include => :changeset, :conditions => ["#{Changeset.table_name}.repository_id = ?", repository.id])
400 h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
400 h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
401
401
402 fields = commits_by_author.collect {|r| r.first}
402 fields = commits_by_author.collect {|r| r.first}
403 commits_data = commits_by_author.collect {|r| r.last}
403 commits_data = commits_by_author.collect {|r| r.last}
404 changes_data = commits_by_author.collect {|r| h[r.first] || 0}
404 changes_data = commits_by_author.collect {|r| h[r.first] || 0}
405
405
406 fields = fields + [""]*(10 - fields.length) if fields.length<10
406 fields = fields + [""]*(10 - fields.length) if fields.length<10
407 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
407 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
408 changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
408 changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
409
409
410 # Remove email adress in usernames
410 # Remove email adress in usernames
411 fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
411 fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
412
412
413 graph = SVG::Graph::BarHorizontal.new(
413 graph = SVG::Graph::BarHorizontal.new(
414 :height => 400,
414 :height => 400,
415 :width => 800,
415 :width => 800,
416 :fields => fields,
416 :fields => fields,
417 :stack => :side,
417 :stack => :side,
418 :scale_integers => true,
418 :scale_integers => true,
419 :show_data_values => false,
419 :show_data_values => false,
420 :rotate_y_labels => false,
420 :rotate_y_labels => false,
421 :graph_title => l(:label_commits_per_author),
421 :graph_title => l(:label_commits_per_author),
422 :show_graph_title => true
422 :show_graph_title => true
423 )
423 )
424 graph.add_data(
424 graph.add_data(
425 :data => commits_data,
425 :data => commits_data,
426 :title => l(:label_revision_plural)
426 :title => l(:label_revision_plural)
427 )
427 )
428 graph.add_data(
428 graph.add_data(
429 :data => changes_data,
429 :data => changes_data,
430 :title => l(:label_change_plural)
430 :title => l(:label_change_plural)
431 )
431 )
432 graph.burn
432 graph.burn
433 end
433 end
434 end
434 end
@@ -1,313 +1,313
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 TimelogController < ApplicationController
18 class TimelogController < ApplicationController
19 menu_item :issues
19 menu_item :issues
20
20
21 before_filter :find_project_for_new_time_entry, :only => [:create]
21 before_filter :find_project_for_new_time_entry, :only => [:create]
22 before_filter :find_time_entry, :only => [:show, :edit, :update]
22 before_filter :find_time_entry, :only => [:show, :edit, :update]
23 before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy]
23 before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy]
24 before_filter :authorize, :except => [:new, :index, :report]
24 before_filter :authorize, :except => [:new, :index, :report]
25
25
26 before_filter :find_optional_project, :only => [:index, :report]
26 before_filter :find_optional_project, :only => [:index, :report]
27 before_filter :find_optional_project_for_new_time_entry, :only => [:new]
27 before_filter :find_optional_project_for_new_time_entry, :only => [:new]
28 before_filter :authorize_global, :only => [:new, :index, :report]
28 before_filter :authorize_global, :only => [:new, :index, :report]
29
29
30 accept_rss_auth :index
30 accept_rss_auth :index
31 accept_api_auth :index, :show, :create, :update, :destroy
31 accept_api_auth :index, :show, :create, :update, :destroy
32
32
33 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
33 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
34
34
35 helper :sort
35 helper :sort
36 include SortHelper
36 include SortHelper
37 helper :issues
37 helper :issues
38 include TimelogHelper
38 include TimelogHelper
39 helper :custom_fields
39 helper :custom_fields
40 include CustomFieldsHelper
40 include CustomFieldsHelper
41 helper :queries
41 helper :queries
42
42
43 def index
43 def index
44 @query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_')
44 @query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_')
45 scope = time_entry_scope
45 scope = time_entry_scope
46
46
47 sort_init(@query.sort_criteria.empty? ? [['spent_on', 'desc']] : @query.sort_criteria)
47 sort_init(@query.sort_criteria.empty? ? [['spent_on', 'desc']] : @query.sort_criteria)
48 sort_update(@query.sortable_columns)
48 sort_update(@query.sortable_columns)
49
49
50 respond_to do |format|
50 respond_to do |format|
51 format.html {
51 format.html {
52 # Paginate results
52 # Paginate results
53 @entry_count = scope.count
53 @entry_count = scope.count
54 @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
54 @entry_pages = Paginator.new @entry_count, per_page_option, params['page']
55 @entries = scope.all(
55 @entries = scope.all(
56 :include => [:project, :activity, :user, {:issue => :tracker}],
56 :include => [:project, :activity, :user, {:issue => :tracker}],
57 :order => sort_clause,
57 :order => sort_clause,
58 :limit => @entry_pages.items_per_page,
58 :limit => @entry_pages.items_per_page,
59 :offset => @entry_pages.current.offset
59 :offset => @entry_pages.offset
60 )
60 )
61 @total_hours = scope.sum(:hours).to_f
61 @total_hours = scope.sum(:hours).to_f
62
62
63 render :layout => !request.xhr?
63 render :layout => !request.xhr?
64 }
64 }
65 format.api {
65 format.api {
66 @entry_count = scope.count
66 @entry_count = scope.count
67 @offset, @limit = api_offset_and_limit
67 @offset, @limit = api_offset_and_limit
68 @entries = scope.all(
68 @entries = scope.all(
69 :include => [:project, :activity, :user, {:issue => :tracker}],
69 :include => [:project, :activity, :user, {:issue => :tracker}],
70 :order => sort_clause,
70 :order => sort_clause,
71 :limit => @limit,
71 :limit => @limit,
72 :offset => @offset
72 :offset => @offset
73 )
73 )
74 }
74 }
75 format.atom {
75 format.atom {
76 entries = scope.all(
76 entries = scope.all(
77 :include => [:project, :activity, :user, {:issue => :tracker}],
77 :include => [:project, :activity, :user, {:issue => :tracker}],
78 :order => "#{TimeEntry.table_name}.created_on DESC",
78 :order => "#{TimeEntry.table_name}.created_on DESC",
79 :limit => Setting.feeds_limit.to_i
79 :limit => Setting.feeds_limit.to_i
80 )
80 )
81 render_feed(entries, :title => l(:label_spent_time))
81 render_feed(entries, :title => l(:label_spent_time))
82 }
82 }
83 format.csv {
83 format.csv {
84 # Export all entries
84 # Export all entries
85 @entries = scope.all(
85 @entries = scope.all(
86 :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
86 :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
87 :order => sort_clause
87 :order => sort_clause
88 )
88 )
89 send_data(entries_to_csv(@entries), :type => 'text/csv; header=present', :filename => 'timelog.csv')
89 send_data(entries_to_csv(@entries), :type => 'text/csv; header=present', :filename => 'timelog.csv')
90 }
90 }
91 end
91 end
92 end
92 end
93
93
94 def report
94 def report
95 @query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_')
95 @query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_')
96 scope = time_entry_scope
96 scope = time_entry_scope
97
97
98 @report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], scope)
98 @report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], scope)
99
99
100 respond_to do |format|
100 respond_to do |format|
101 format.html { render :layout => !request.xhr? }
101 format.html { render :layout => !request.xhr? }
102 format.csv { send_data(report_to_csv(@report), :type => 'text/csv; header=present', :filename => 'timelog.csv') }
102 format.csv { send_data(report_to_csv(@report), :type => 'text/csv; header=present', :filename => 'timelog.csv') }
103 end
103 end
104 end
104 end
105
105
106 def show
106 def show
107 respond_to do |format|
107 respond_to do |format|
108 # TODO: Implement html response
108 # TODO: Implement html response
109 format.html { render :nothing => true, :status => 406 }
109 format.html { render :nothing => true, :status => 406 }
110 format.api
110 format.api
111 end
111 end
112 end
112 end
113
113
114 def new
114 def new
115 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
115 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
116 @time_entry.safe_attributes = params[:time_entry]
116 @time_entry.safe_attributes = params[:time_entry]
117 end
117 end
118
118
119 def create
119 def create
120 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
120 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
121 @time_entry.safe_attributes = params[:time_entry]
121 @time_entry.safe_attributes = params[:time_entry]
122
122
123 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
123 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
124
124
125 if @time_entry.save
125 if @time_entry.save
126 respond_to do |format|
126 respond_to do |format|
127 format.html {
127 format.html {
128 flash[:notice] = l(:notice_successful_create)
128 flash[:notice] = l(:notice_successful_create)
129 if params[:continue]
129 if params[:continue]
130 if params[:project_id]
130 if params[:project_id]
131 options = {
131 options = {
132 :time_entry => {:issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id},
132 :time_entry => {:issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id},
133 :back_url => params[:back_url]
133 :back_url => params[:back_url]
134 }
134 }
135 if @time_entry.issue
135 if @time_entry.issue
136 redirect_to new_project_issue_time_entry_path(@time_entry.project, @time_entry.issue, options)
136 redirect_to new_project_issue_time_entry_path(@time_entry.project, @time_entry.issue, options)
137 else
137 else
138 redirect_to new_project_time_entry_path(@time_entry.project, options)
138 redirect_to new_project_time_entry_path(@time_entry.project, options)
139 end
139 end
140 else
140 else
141 options = {
141 options = {
142 :time_entry => {:project_id => @time_entry.project_id, :issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id},
142 :time_entry => {:project_id => @time_entry.project_id, :issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id},
143 :back_url => params[:back_url]
143 :back_url => params[:back_url]
144 }
144 }
145 redirect_to new_time_entry_path(options)
145 redirect_to new_time_entry_path(options)
146 end
146 end
147 else
147 else
148 redirect_back_or_default project_time_entries_path(@time_entry.project)
148 redirect_back_or_default project_time_entries_path(@time_entry.project)
149 end
149 end
150 }
150 }
151 format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) }
151 format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) }
152 end
152 end
153 else
153 else
154 respond_to do |format|
154 respond_to do |format|
155 format.html { render :action => 'new' }
155 format.html { render :action => 'new' }
156 format.api { render_validation_errors(@time_entry) }
156 format.api { render_validation_errors(@time_entry) }
157 end
157 end
158 end
158 end
159 end
159 end
160
160
161 def edit
161 def edit
162 @time_entry.safe_attributes = params[:time_entry]
162 @time_entry.safe_attributes = params[:time_entry]
163 end
163 end
164
164
165 def update
165 def update
166 @time_entry.safe_attributes = params[:time_entry]
166 @time_entry.safe_attributes = params[:time_entry]
167
167
168 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
168 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
169
169
170 if @time_entry.save
170 if @time_entry.save
171 respond_to do |format|
171 respond_to do |format|
172 format.html {
172 format.html {
173 flash[:notice] = l(:notice_successful_update)
173 flash[:notice] = l(:notice_successful_update)
174 redirect_back_or_default project_time_entries_path(@time_entry.project)
174 redirect_back_or_default project_time_entries_path(@time_entry.project)
175 }
175 }
176 format.api { render_api_ok }
176 format.api { render_api_ok }
177 end
177 end
178 else
178 else
179 respond_to do |format|
179 respond_to do |format|
180 format.html { render :action => 'edit' }
180 format.html { render :action => 'edit' }
181 format.api { render_validation_errors(@time_entry) }
181 format.api { render_validation_errors(@time_entry) }
182 end
182 end
183 end
183 end
184 end
184 end
185
185
186 def bulk_edit
186 def bulk_edit
187 @available_activities = TimeEntryActivity.shared.active
187 @available_activities = TimeEntryActivity.shared.active
188 @custom_fields = TimeEntry.first.available_custom_fields
188 @custom_fields = TimeEntry.first.available_custom_fields
189 end
189 end
190
190
191 def bulk_update
191 def bulk_update
192 attributes = parse_params_for_bulk_time_entry_attributes(params)
192 attributes = parse_params_for_bulk_time_entry_attributes(params)
193
193
194 unsaved_time_entry_ids = []
194 unsaved_time_entry_ids = []
195 @time_entries.each do |time_entry|
195 @time_entries.each do |time_entry|
196 time_entry.reload
196 time_entry.reload
197 time_entry.safe_attributes = attributes
197 time_entry.safe_attributes = attributes
198 call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry })
198 call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry })
199 unless time_entry.save
199 unless time_entry.save
200 # Keep unsaved time_entry ids to display them in flash error
200 # Keep unsaved time_entry ids to display them in flash error
201 unsaved_time_entry_ids << time_entry.id
201 unsaved_time_entry_ids << time_entry.id
202 end
202 end
203 end
203 end
204 set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids)
204 set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids)
205 redirect_back_or_default project_time_entries_path(@projects.first)
205 redirect_back_or_default project_time_entries_path(@projects.first)
206 end
206 end
207
207
208 def destroy
208 def destroy
209 destroyed = TimeEntry.transaction do
209 destroyed = TimeEntry.transaction do
210 @time_entries.each do |t|
210 @time_entries.each do |t|
211 unless t.destroy && t.destroyed?
211 unless t.destroy && t.destroyed?
212 raise ActiveRecord::Rollback
212 raise ActiveRecord::Rollback
213 end
213 end
214 end
214 end
215 end
215 end
216
216
217 respond_to do |format|
217 respond_to do |format|
218 format.html {
218 format.html {
219 if destroyed
219 if destroyed
220 flash[:notice] = l(:notice_successful_delete)
220 flash[:notice] = l(:notice_successful_delete)
221 else
221 else
222 flash[:error] = l(:notice_unable_delete_time_entry)
222 flash[:error] = l(:notice_unable_delete_time_entry)
223 end
223 end
224 redirect_back_or_default project_time_entries_path(@projects.first)
224 redirect_back_or_default project_time_entries_path(@projects.first)
225 }
225 }
226 format.api {
226 format.api {
227 if destroyed
227 if destroyed
228 render_api_ok
228 render_api_ok
229 else
229 else
230 render_validation_errors(@time_entries)
230 render_validation_errors(@time_entries)
231 end
231 end
232 }
232 }
233 end
233 end
234 end
234 end
235
235
236 private
236 private
237 def find_time_entry
237 def find_time_entry
238 @time_entry = TimeEntry.find(params[:id])
238 @time_entry = TimeEntry.find(params[:id])
239 unless @time_entry.editable_by?(User.current)
239 unless @time_entry.editable_by?(User.current)
240 render_403
240 render_403
241 return false
241 return false
242 end
242 end
243 @project = @time_entry.project
243 @project = @time_entry.project
244 rescue ActiveRecord::RecordNotFound
244 rescue ActiveRecord::RecordNotFound
245 render_404
245 render_404
246 end
246 end
247
247
248 def find_time_entries
248 def find_time_entries
249 @time_entries = TimeEntry.find_all_by_id(params[:id] || params[:ids])
249 @time_entries = TimeEntry.find_all_by_id(params[:id] || params[:ids])
250 raise ActiveRecord::RecordNotFound if @time_entries.empty?
250 raise ActiveRecord::RecordNotFound if @time_entries.empty?
251 @projects = @time_entries.collect(&:project).compact.uniq
251 @projects = @time_entries.collect(&:project).compact.uniq
252 @project = @projects.first if @projects.size == 1
252 @project = @projects.first if @projects.size == 1
253 rescue ActiveRecord::RecordNotFound
253 rescue ActiveRecord::RecordNotFound
254 render_404
254 render_404
255 end
255 end
256
256
257 def set_flash_from_bulk_time_entry_save(time_entries, unsaved_time_entry_ids)
257 def set_flash_from_bulk_time_entry_save(time_entries, unsaved_time_entry_ids)
258 if unsaved_time_entry_ids.empty?
258 if unsaved_time_entry_ids.empty?
259 flash[:notice] = l(:notice_successful_update) unless time_entries.empty?
259 flash[:notice] = l(:notice_successful_update) unless time_entries.empty?
260 else
260 else
261 flash[:error] = l(:notice_failed_to_save_time_entries,
261 flash[:error] = l(:notice_failed_to_save_time_entries,
262 :count => unsaved_time_entry_ids.size,
262 :count => unsaved_time_entry_ids.size,
263 :total => time_entries.size,
263 :total => time_entries.size,
264 :ids => '#' + unsaved_time_entry_ids.join(', #'))
264 :ids => '#' + unsaved_time_entry_ids.join(', #'))
265 end
265 end
266 end
266 end
267
267
268 def find_optional_project_for_new_time_entry
268 def find_optional_project_for_new_time_entry
269 if (project_id = (params[:project_id] || params[:time_entry] && params[:time_entry][:project_id])).present?
269 if (project_id = (params[:project_id] || params[:time_entry] && params[:time_entry][:project_id])).present?
270 @project = Project.find(project_id)
270 @project = Project.find(project_id)
271 end
271 end
272 if (issue_id = (params[:issue_id] || params[:time_entry] && params[:time_entry][:issue_id])).present?
272 if (issue_id = (params[:issue_id] || params[:time_entry] && params[:time_entry][:issue_id])).present?
273 @issue = Issue.find(issue_id)
273 @issue = Issue.find(issue_id)
274 @project ||= @issue.project
274 @project ||= @issue.project
275 end
275 end
276 rescue ActiveRecord::RecordNotFound
276 rescue ActiveRecord::RecordNotFound
277 render_404
277 render_404
278 end
278 end
279
279
280 def find_project_for_new_time_entry
280 def find_project_for_new_time_entry
281 find_optional_project_for_new_time_entry
281 find_optional_project_for_new_time_entry
282 if @project.nil?
282 if @project.nil?
283 render_404
283 render_404
284 end
284 end
285 end
285 end
286
286
287 def find_optional_project
287 def find_optional_project
288 if !params[:issue_id].blank?
288 if !params[:issue_id].blank?
289 @issue = Issue.find(params[:issue_id])
289 @issue = Issue.find(params[:issue_id])
290 @project = @issue.project
290 @project = @issue.project
291 elsif !params[:project_id].blank?
291 elsif !params[:project_id].blank?
292 @project = Project.find(params[:project_id])
292 @project = Project.find(params[:project_id])
293 end
293 end
294 end
294 end
295
295
296 # Returns the TimeEntry scope for index and report actions
296 # Returns the TimeEntry scope for index and report actions
297 def time_entry_scope
297 def time_entry_scope
298 scope = TimeEntry.visible.where(@query.statement)
298 scope = TimeEntry.visible.where(@query.statement)
299 if @issue
299 if @issue
300 scope = scope.on_issue(@issue)
300 scope = scope.on_issue(@issue)
301 elsif @project
301 elsif @project
302 scope = scope.on_project(@project, Setting.display_subprojects_issues?)
302 scope = scope.on_project(@project, Setting.display_subprojects_issues?)
303 end
303 end
304 scope
304 scope
305 end
305 end
306
306
307 def parse_params_for_bulk_time_entry_attributes(params)
307 def parse_params_for_bulk_time_entry_attributes(params)
308 attributes = (params[:time_entry] || {}).reject {|k,v| v.blank?}
308 attributes = (params[:time_entry] || {}).reject {|k,v| v.blank?}
309 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
309 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
310 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
310 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
311 attributes
311 attributes
312 end
312 end
313 end
313 end
@@ -1,212 +1,212
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 UsersController < ApplicationController
18 class UsersController < ApplicationController
19 layout 'admin'
19 layout 'admin'
20
20
21 before_filter :require_admin, :except => :show
21 before_filter :require_admin, :except => :show
22 before_filter :find_user, :only => [:show, :edit, :update, :destroy, :edit_membership, :destroy_membership]
22 before_filter :find_user, :only => [:show, :edit, :update, :destroy, :edit_membership, :destroy_membership]
23 accept_api_auth :index, :show, :create, :update, :destroy
23 accept_api_auth :index, :show, :create, :update, :destroy
24
24
25 helper :sort
25 helper :sort
26 include SortHelper
26 include SortHelper
27 helper :custom_fields
27 helper :custom_fields
28 include CustomFieldsHelper
28 include CustomFieldsHelper
29
29
30 def index
30 def index
31 sort_init 'login', 'asc'
31 sort_init 'login', 'asc'
32 sort_update %w(login firstname lastname mail admin created_on last_login_on)
32 sort_update %w(login firstname lastname mail admin created_on last_login_on)
33
33
34 case params[:format]
34 case params[:format]
35 when 'xml', 'json'
35 when 'xml', 'json'
36 @offset, @limit = api_offset_and_limit
36 @offset, @limit = api_offset_and_limit
37 else
37 else
38 @limit = per_page_option
38 @limit = per_page_option
39 end
39 end
40
40
41 @status = params[:status] || 1
41 @status = params[:status] || 1
42
42
43 scope = User.logged.status(@status)
43 scope = User.logged.status(@status)
44 scope = scope.like(params[:name]) if params[:name].present?
44 scope = scope.like(params[:name]) if params[:name].present?
45 scope = scope.in_group(params[:group_id]) if params[:group_id].present?
45 scope = scope.in_group(params[:group_id]) if params[:group_id].present?
46
46
47 @user_count = scope.count
47 @user_count = scope.count
48 @user_pages = Paginator.new self, @user_count, @limit, params['page']
48 @user_pages = Paginator.new @user_count, @limit, params['page']
49 @offset ||= @user_pages.current.offset
49 @offset ||= @user_pages.offset
50 @users = scope.order(sort_clause).limit(@limit).offset(@offset).all
50 @users = scope.order(sort_clause).limit(@limit).offset(@offset).all
51
51
52 respond_to do |format|
52 respond_to do |format|
53 format.html {
53 format.html {
54 @groups = Group.all.sort
54 @groups = Group.all.sort
55 render :layout => !request.xhr?
55 render :layout => !request.xhr?
56 }
56 }
57 format.api
57 format.api
58 end
58 end
59 end
59 end
60
60
61 def show
61 def show
62 # show projects based on current user visibility
62 # show projects based on current user visibility
63 @memberships = @user.memberships.all(:conditions => Project.visible_condition(User.current))
63 @memberships = @user.memberships.all(:conditions => Project.visible_condition(User.current))
64
64
65 events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10)
65 events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10)
66 @events_by_day = events.group_by(&:event_date)
66 @events_by_day = events.group_by(&:event_date)
67
67
68 unless User.current.admin?
68 unless User.current.admin?
69 if !@user.active? || (@user != User.current && @memberships.empty? && events.empty?)
69 if !@user.active? || (@user != User.current && @memberships.empty? && events.empty?)
70 render_404
70 render_404
71 return
71 return
72 end
72 end
73 end
73 end
74
74
75 respond_to do |format|
75 respond_to do |format|
76 format.html { render :layout => 'base' }
76 format.html { render :layout => 'base' }
77 format.api
77 format.api
78 end
78 end
79 end
79 end
80
80
81 def new
81 def new
82 @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
82 @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
83 @auth_sources = AuthSource.all
83 @auth_sources = AuthSource.all
84 end
84 end
85
85
86 def create
86 def create
87 @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
87 @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
88 @user.safe_attributes = params[:user]
88 @user.safe_attributes = params[:user]
89 @user.admin = params[:user][:admin] || false
89 @user.admin = params[:user][:admin] || false
90 @user.login = params[:user][:login]
90 @user.login = params[:user][:login]
91 @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id
91 @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id
92
92
93 if @user.save
93 if @user.save
94 @user.pref.attributes = params[:pref]
94 @user.pref.attributes = params[:pref]
95 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
95 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
96 @user.pref.save
96 @user.pref.save
97 @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
97 @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
98
98
99 Mailer.account_information(@user, params[:user][:password]).deliver if params[:send_information]
99 Mailer.account_information(@user, params[:user][:password]).deliver if params[:send_information]
100
100
101 respond_to do |format|
101 respond_to do |format|
102 format.html {
102 format.html {
103 flash[:notice] = l(:notice_user_successful_create, :id => view_context.link_to(@user.login, user_path(@user)))
103 flash[:notice] = l(:notice_user_successful_create, :id => view_context.link_to(@user.login, user_path(@user)))
104 if params[:continue]
104 if params[:continue]
105 redirect_to new_user_path
105 redirect_to new_user_path
106 else
106 else
107 redirect_to edit_user_path(@user)
107 redirect_to edit_user_path(@user)
108 end
108 end
109 }
109 }
110 format.api { render :action => 'show', :status => :created, :location => user_url(@user) }
110 format.api { render :action => 'show', :status => :created, :location => user_url(@user) }
111 end
111 end
112 else
112 else
113 @auth_sources = AuthSource.all
113 @auth_sources = AuthSource.all
114 # Clear password input
114 # Clear password input
115 @user.password = @user.password_confirmation = nil
115 @user.password = @user.password_confirmation = nil
116
116
117 respond_to do |format|
117 respond_to do |format|
118 format.html { render :action => 'new' }
118 format.html { render :action => 'new' }
119 format.api { render_validation_errors(@user) }
119 format.api { render_validation_errors(@user) }
120 end
120 end
121 end
121 end
122 end
122 end
123
123
124 def edit
124 def edit
125 @auth_sources = AuthSource.all
125 @auth_sources = AuthSource.all
126 @membership ||= Member.new
126 @membership ||= Member.new
127 end
127 end
128
128
129 def update
129 def update
130 @user.admin = params[:user][:admin] if params[:user][:admin]
130 @user.admin = params[:user][:admin] if params[:user][:admin]
131 @user.login = params[:user][:login] if params[:user][:login]
131 @user.login = params[:user][:login] if params[:user][:login]
132 if params[:user][:password].present? && (@user.auth_source_id.nil? || params[:user][:auth_source_id].blank?)
132 if params[:user][:password].present? && (@user.auth_source_id.nil? || params[:user][:auth_source_id].blank?)
133 @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation]
133 @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation]
134 end
134 end
135 @user.safe_attributes = params[:user]
135 @user.safe_attributes = params[:user]
136 # Was the account actived ? (do it before User#save clears the change)
136 # Was the account actived ? (do it before User#save clears the change)
137 was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE])
137 was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE])
138 # TODO: Similar to My#account
138 # TODO: Similar to My#account
139 @user.pref.attributes = params[:pref]
139 @user.pref.attributes = params[:pref]
140 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
140 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
141
141
142 if @user.save
142 if @user.save
143 @user.pref.save
143 @user.pref.save
144 @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
144 @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
145
145
146 if was_activated
146 if was_activated
147 Mailer.account_activated(@user).deliver
147 Mailer.account_activated(@user).deliver
148 elsif @user.active? && params[:send_information] && !params[:user][:password].blank? && @user.auth_source_id.nil?
148 elsif @user.active? && params[:send_information] && !params[:user][:password].blank? && @user.auth_source_id.nil?
149 Mailer.account_information(@user, params[:user][:password]).deliver
149 Mailer.account_information(@user, params[:user][:password]).deliver
150 end
150 end
151
151
152 respond_to do |format|
152 respond_to do |format|
153 format.html {
153 format.html {
154 flash[:notice] = l(:notice_successful_update)
154 flash[:notice] = l(:notice_successful_update)
155 redirect_to_referer_or edit_user_path(@user)
155 redirect_to_referer_or edit_user_path(@user)
156 }
156 }
157 format.api { render_api_ok }
157 format.api { render_api_ok }
158 end
158 end
159 else
159 else
160 @auth_sources = AuthSource.all
160 @auth_sources = AuthSource.all
161 @membership ||= Member.new
161 @membership ||= Member.new
162 # Clear password input
162 # Clear password input
163 @user.password = @user.password_confirmation = nil
163 @user.password = @user.password_confirmation = nil
164
164
165 respond_to do |format|
165 respond_to do |format|
166 format.html { render :action => :edit }
166 format.html { render :action => :edit }
167 format.api { render_validation_errors(@user) }
167 format.api { render_validation_errors(@user) }
168 end
168 end
169 end
169 end
170 end
170 end
171
171
172 def destroy
172 def destroy
173 @user.destroy
173 @user.destroy
174 respond_to do |format|
174 respond_to do |format|
175 format.html { redirect_back_or_default(users_path) }
175 format.html { redirect_back_or_default(users_path) }
176 format.api { render_api_ok }
176 format.api { render_api_ok }
177 end
177 end
178 end
178 end
179
179
180 def edit_membership
180 def edit_membership
181 @membership = Member.edit_membership(params[:membership_id], params[:membership], @user)
181 @membership = Member.edit_membership(params[:membership_id], params[:membership], @user)
182 @membership.save
182 @membership.save
183 respond_to do |format|
183 respond_to do |format|
184 format.html { redirect_to edit_user_path(@user, :tab => 'memberships') }
184 format.html { redirect_to edit_user_path(@user, :tab => 'memberships') }
185 format.js
185 format.js
186 end
186 end
187 end
187 end
188
188
189 def destroy_membership
189 def destroy_membership
190 @membership = Member.find(params[:membership_id])
190 @membership = Member.find(params[:membership_id])
191 if @membership.deletable?
191 if @membership.deletable?
192 @membership.destroy
192 @membership.destroy
193 end
193 end
194 respond_to do |format|
194 respond_to do |format|
195 format.html { redirect_to edit_user_path(@user, :tab => 'memberships') }
195 format.html { redirect_to edit_user_path(@user, :tab => 'memberships') }
196 format.js
196 format.js
197 end
197 end
198 end
198 end
199
199
200 private
200 private
201
201
202 def find_user
202 def find_user
203 if params[:id] == 'current'
203 if params[:id] == 'current'
204 require_login || return
204 require_login || return
205 @user = User.current
205 @user = User.current
206 else
206 else
207 @user = User.find(params[:id])
207 @user = User.find(params[:id])
208 end
208 end
209 rescue ActiveRecord::RecordNotFound
209 rescue ActiveRecord::RecordNotFound
210 render_404
210 render_404
211 end
211 end
212 end
212 end
@@ -1,356 +1,356
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 'diff'
18 require 'diff'
19
19
20 # The WikiController follows the Rails REST controller pattern but with
20 # The WikiController follows the Rails REST controller pattern but with
21 # a few differences
21 # a few differences
22 #
22 #
23 # * index - shows a list of WikiPages grouped by page or date
23 # * index - shows a list of WikiPages grouped by page or date
24 # * new - not used
24 # * new - not used
25 # * create - not used
25 # * create - not used
26 # * show - will also show the form for creating a new wiki page
26 # * show - will also show the form for creating a new wiki page
27 # * edit - used to edit an existing or new page
27 # * edit - used to edit an existing or new page
28 # * update - used to save a wiki page update to the database, including new pages
28 # * update - used to save a wiki page update to the database, including new pages
29 # * destroy - normal
29 # * destroy - normal
30 #
30 #
31 # Other member and collection methods are also used
31 # Other member and collection methods are also used
32 #
32 #
33 # TODO: still being worked on
33 # TODO: still being worked on
34 class WikiController < ApplicationController
34 class WikiController < ApplicationController
35 default_search_scope :wiki_pages
35 default_search_scope :wiki_pages
36 before_filter :find_wiki, :authorize
36 before_filter :find_wiki, :authorize
37 before_filter :find_existing_or_new_page, :only => [:show, :edit, :update]
37 before_filter :find_existing_or_new_page, :only => [:show, :edit, :update]
38 before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy, :destroy_version]
38 before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy, :destroy_version]
39 accept_api_auth :index, :show, :update, :destroy
39 accept_api_auth :index, :show, :update, :destroy
40 before_filter :find_attachments, :only => [:preview]
40 before_filter :find_attachments, :only => [:preview]
41
41
42 helper :attachments
42 helper :attachments
43 include AttachmentsHelper
43 include AttachmentsHelper
44 helper :watchers
44 helper :watchers
45 include Redmine::Export::PDF
45 include Redmine::Export::PDF
46
46
47 # List of pages, sorted alphabetically and by parent (hierarchy)
47 # List of pages, sorted alphabetically and by parent (hierarchy)
48 def index
48 def index
49 load_pages_for_index
49 load_pages_for_index
50
50
51 respond_to do |format|
51 respond_to do |format|
52 format.html {
52 format.html {
53 @pages_by_parent_id = @pages.group_by(&:parent_id)
53 @pages_by_parent_id = @pages.group_by(&:parent_id)
54 }
54 }
55 format.api
55 format.api
56 end
56 end
57 end
57 end
58
58
59 # List of page, by last update
59 # List of page, by last update
60 def date_index
60 def date_index
61 load_pages_for_index
61 load_pages_for_index
62 @pages_by_date = @pages.group_by {|p| p.updated_on.to_date}
62 @pages_by_date = @pages.group_by {|p| p.updated_on.to_date}
63 end
63 end
64
64
65 # display a page (in editing mode if it doesn't exist)
65 # display a page (in editing mode if it doesn't exist)
66 def show
66 def show
67 if @page.new_record?
67 if @page.new_record?
68 if User.current.allowed_to?(:edit_wiki_pages, @project) && editable? && !api_request?
68 if User.current.allowed_to?(:edit_wiki_pages, @project) && editable? && !api_request?
69 edit
69 edit
70 render :action => 'edit'
70 render :action => 'edit'
71 else
71 else
72 render_404
72 render_404
73 end
73 end
74 return
74 return
75 end
75 end
76 if params[:version] && !User.current.allowed_to?(:view_wiki_edits, @project)
76 if params[:version] && !User.current.allowed_to?(:view_wiki_edits, @project)
77 deny_access
77 deny_access
78 return
78 return
79 end
79 end
80 @content = @page.content_for_version(params[:version])
80 @content = @page.content_for_version(params[:version])
81 if User.current.allowed_to?(:export_wiki_pages, @project)
81 if User.current.allowed_to?(:export_wiki_pages, @project)
82 if params[:format] == 'pdf'
82 if params[:format] == 'pdf'
83 send_data(wiki_page_to_pdf(@page, @project), :type => 'application/pdf', :filename => "#{@page.title}.pdf")
83 send_data(wiki_page_to_pdf(@page, @project), :type => 'application/pdf', :filename => "#{@page.title}.pdf")
84 return
84 return
85 elsif params[:format] == 'html'
85 elsif params[:format] == 'html'
86 export = render_to_string :action => 'export', :layout => false
86 export = render_to_string :action => 'export', :layout => false
87 send_data(export, :type => 'text/html', :filename => "#{@page.title}.html")
87 send_data(export, :type => 'text/html', :filename => "#{@page.title}.html")
88 return
88 return
89 elsif params[:format] == 'txt'
89 elsif params[:format] == 'txt'
90 send_data(@content.text, :type => 'text/plain', :filename => "#{@page.title}.txt")
90 send_data(@content.text, :type => 'text/plain', :filename => "#{@page.title}.txt")
91 return
91 return
92 end
92 end
93 end
93 end
94 @editable = editable?
94 @editable = editable?
95 @sections_editable = @editable && User.current.allowed_to?(:edit_wiki_pages, @page.project) &&
95 @sections_editable = @editable && User.current.allowed_to?(:edit_wiki_pages, @page.project) &&
96 @content.current_version? &&
96 @content.current_version? &&
97 Redmine::WikiFormatting.supports_section_edit?
97 Redmine::WikiFormatting.supports_section_edit?
98
98
99 respond_to do |format|
99 respond_to do |format|
100 format.html
100 format.html
101 format.api
101 format.api
102 end
102 end
103 end
103 end
104
104
105 # edit an existing page or a new one
105 # edit an existing page or a new one
106 def edit
106 def edit
107 return render_403 unless editable?
107 return render_403 unless editable?
108 if @page.new_record?
108 if @page.new_record?
109 @page.content = WikiContent.new(:page => @page)
109 @page.content = WikiContent.new(:page => @page)
110 if params[:parent].present?
110 if params[:parent].present?
111 @page.parent = @page.wiki.find_page(params[:parent].to_s)
111 @page.parent = @page.wiki.find_page(params[:parent].to_s)
112 end
112 end
113 end
113 end
114
114
115 @content = @page.content_for_version(params[:version])
115 @content = @page.content_for_version(params[:version])
116 @content.text = initial_page_content(@page) if @content.text.blank?
116 @content.text = initial_page_content(@page) if @content.text.blank?
117 # don't keep previous comment
117 # don't keep previous comment
118 @content.comments = nil
118 @content.comments = nil
119
119
120 # To prevent StaleObjectError exception when reverting to a previous version
120 # To prevent StaleObjectError exception when reverting to a previous version
121 @content.version = @page.content.version
121 @content.version = @page.content.version
122
122
123 @text = @content.text
123 @text = @content.text
124 if params[:section].present? && Redmine::WikiFormatting.supports_section_edit?
124 if params[:section].present? && Redmine::WikiFormatting.supports_section_edit?
125 @section = params[:section].to_i
125 @section = params[:section].to_i
126 @text, @section_hash = Redmine::WikiFormatting.formatter.new(@text).get_section(@section)
126 @text, @section_hash = Redmine::WikiFormatting.formatter.new(@text).get_section(@section)
127 render_404 if @text.blank?
127 render_404 if @text.blank?
128 end
128 end
129 end
129 end
130
130
131 # Creates a new page or updates an existing one
131 # Creates a new page or updates an existing one
132 def update
132 def update
133 return render_403 unless editable?
133 return render_403 unless editable?
134 was_new_page = @page.new_record?
134 was_new_page = @page.new_record?
135 @page.content = WikiContent.new(:page => @page) if @page.new_record?
135 @page.content = WikiContent.new(:page => @page) if @page.new_record?
136 @page.safe_attributes = params[:wiki_page]
136 @page.safe_attributes = params[:wiki_page]
137
137
138 @content = @page.content
138 @content = @page.content
139 content_params = params[:content]
139 content_params = params[:content]
140 if content_params.nil? && params[:wiki_page].is_a?(Hash)
140 if content_params.nil? && params[:wiki_page].is_a?(Hash)
141 content_params = params[:wiki_page].slice(:text, :comments, :version)
141 content_params = params[:wiki_page].slice(:text, :comments, :version)
142 end
142 end
143 content_params ||= {}
143 content_params ||= {}
144
144
145 @content.comments = content_params[:comments]
145 @content.comments = content_params[:comments]
146 @text = content_params[:text]
146 @text = content_params[:text]
147 if params[:section].present? && Redmine::WikiFormatting.supports_section_edit?
147 if params[:section].present? && Redmine::WikiFormatting.supports_section_edit?
148 @section = params[:section].to_i
148 @section = params[:section].to_i
149 @section_hash = params[:section_hash]
149 @section_hash = params[:section_hash]
150 @content.text = Redmine::WikiFormatting.formatter.new(@content.text).update_section(params[:section].to_i, @text, @section_hash)
150 @content.text = Redmine::WikiFormatting.formatter.new(@content.text).update_section(params[:section].to_i, @text, @section_hash)
151 else
151 else
152 @content.version = content_params[:version] if content_params[:version]
152 @content.version = content_params[:version] if content_params[:version]
153 @content.text = @text
153 @content.text = @text
154 end
154 end
155 @content.author = User.current
155 @content.author = User.current
156
156
157 if @page.save_with_content
157 if @page.save_with_content
158 attachments = Attachment.attach_files(@page, params[:attachments])
158 attachments = Attachment.attach_files(@page, params[:attachments])
159 render_attachment_warning_if_needed(@page)
159 render_attachment_warning_if_needed(@page)
160 call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page})
160 call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page})
161
161
162 respond_to do |format|
162 respond_to do |format|
163 format.html { redirect_to project_wiki_page_path(@project, @page.title) }
163 format.html { redirect_to project_wiki_page_path(@project, @page.title) }
164 format.api {
164 format.api {
165 if was_new_page
165 if was_new_page
166 render :action => 'show', :status => :created, :location => project_wiki_page_path(@project, @page.title)
166 render :action => 'show', :status => :created, :location => project_wiki_page_path(@project, @page.title)
167 else
167 else
168 render_api_ok
168 render_api_ok
169 end
169 end
170 }
170 }
171 end
171 end
172 else
172 else
173 respond_to do |format|
173 respond_to do |format|
174 format.html { render :action => 'edit' }
174 format.html { render :action => 'edit' }
175 format.api { render_validation_errors(@content) }
175 format.api { render_validation_errors(@content) }
176 end
176 end
177 end
177 end
178
178
179 rescue ActiveRecord::StaleObjectError, Redmine::WikiFormatting::StaleSectionError
179 rescue ActiveRecord::StaleObjectError, Redmine::WikiFormatting::StaleSectionError
180 # Optimistic locking exception
180 # Optimistic locking exception
181 respond_to do |format|
181 respond_to do |format|
182 format.html {
182 format.html {
183 flash.now[:error] = l(:notice_locking_conflict)
183 flash.now[:error] = l(:notice_locking_conflict)
184 render :action => 'edit'
184 render :action => 'edit'
185 }
185 }
186 format.api { render_api_head :conflict }
186 format.api { render_api_head :conflict }
187 end
187 end
188 rescue ActiveRecord::RecordNotSaved
188 rescue ActiveRecord::RecordNotSaved
189 respond_to do |format|
189 respond_to do |format|
190 format.html { render :action => 'edit' }
190 format.html { render :action => 'edit' }
191 format.api { render_validation_errors(@content) }
191 format.api { render_validation_errors(@content) }
192 end
192 end
193 end
193 end
194
194
195 # rename a page
195 # rename a page
196 def rename
196 def rename
197 return render_403 unless editable?
197 return render_403 unless editable?
198 @page.redirect_existing_links = true
198 @page.redirect_existing_links = true
199 # used to display the *original* title if some AR validation errors occur
199 # used to display the *original* title if some AR validation errors occur
200 @original_title = @page.pretty_title
200 @original_title = @page.pretty_title
201 if request.post? && @page.update_attributes(params[:wiki_page])
201 if request.post? && @page.update_attributes(params[:wiki_page])
202 flash[:notice] = l(:notice_successful_update)
202 flash[:notice] = l(:notice_successful_update)
203 redirect_to project_wiki_page_path(@project, @page.title)
203 redirect_to project_wiki_page_path(@project, @page.title)
204 end
204 end
205 end
205 end
206
206
207 def protect
207 def protect
208 @page.update_attribute :protected, params[:protected]
208 @page.update_attribute :protected, params[:protected]
209 redirect_to project_wiki_page_path(@project, @page.title)
209 redirect_to project_wiki_page_path(@project, @page.title)
210 end
210 end
211
211
212 # show page history
212 # show page history
213 def history
213 def history
214 @version_count = @page.content.versions.count
214 @version_count = @page.content.versions.count
215 @version_pages = Paginator.new self, @version_count, per_page_option, params['page']
215 @version_pages = Paginator.new @version_count, per_page_option, params['page']
216 # don't load text
216 # don't load text
217 @versions = @page.content.versions.
217 @versions = @page.content.versions.
218 select("id, author_id, comments, updated_on, version").
218 select("id, author_id, comments, updated_on, version").
219 reorder('version DESC').
219 reorder('version DESC').
220 limit(@version_pages.items_per_page + 1).
220 limit(@version_pages.items_per_page + 1).
221 offset(@version_pages.current.offset).
221 offset(@version_pages.offset).
222 all
222 all
223
223
224 render :layout => false if request.xhr?
224 render :layout => false if request.xhr?
225 end
225 end
226
226
227 def diff
227 def diff
228 @diff = @page.diff(params[:version], params[:version_from])
228 @diff = @page.diff(params[:version], params[:version_from])
229 render_404 unless @diff
229 render_404 unless @diff
230 end
230 end
231
231
232 def annotate
232 def annotate
233 @annotate = @page.annotate(params[:version])
233 @annotate = @page.annotate(params[:version])
234 render_404 unless @annotate
234 render_404 unless @annotate
235 end
235 end
236
236
237 # Removes a wiki page and its history
237 # Removes a wiki page and its history
238 # Children can be either set as root pages, removed or reassigned to another parent page
238 # Children can be either set as root pages, removed or reassigned to another parent page
239 def destroy
239 def destroy
240 return render_403 unless editable?
240 return render_403 unless editable?
241
241
242 @descendants_count = @page.descendants.size
242 @descendants_count = @page.descendants.size
243 if @descendants_count > 0
243 if @descendants_count > 0
244 case params[:todo]
244 case params[:todo]
245 when 'nullify'
245 when 'nullify'
246 # Nothing to do
246 # Nothing to do
247 when 'destroy'
247 when 'destroy'
248 # Removes all its descendants
248 # Removes all its descendants
249 @page.descendants.each(&:destroy)
249 @page.descendants.each(&:destroy)
250 when 'reassign'
250 when 'reassign'
251 # Reassign children to another parent page
251 # Reassign children to another parent page
252 reassign_to = @wiki.pages.find_by_id(params[:reassign_to_id].to_i)
252 reassign_to = @wiki.pages.find_by_id(params[:reassign_to_id].to_i)
253 return unless reassign_to
253 return unless reassign_to
254 @page.children.each do |child|
254 @page.children.each do |child|
255 child.update_attribute(:parent, reassign_to)
255 child.update_attribute(:parent, reassign_to)
256 end
256 end
257 else
257 else
258 @reassignable_to = @wiki.pages - @page.self_and_descendants
258 @reassignable_to = @wiki.pages - @page.self_and_descendants
259 # display the destroy form if it's a user request
259 # display the destroy form if it's a user request
260 return unless api_request?
260 return unless api_request?
261 end
261 end
262 end
262 end
263 @page.destroy
263 @page.destroy
264 respond_to do |format|
264 respond_to do |format|
265 format.html { redirect_to project_wiki_index_path(@project) }
265 format.html { redirect_to project_wiki_index_path(@project) }
266 format.api { render_api_ok }
266 format.api { render_api_ok }
267 end
267 end
268 end
268 end
269
269
270 def destroy_version
270 def destroy_version
271 return render_403 unless editable?
271 return render_403 unless editable?
272
272
273 @content = @page.content_for_version(params[:version])
273 @content = @page.content_for_version(params[:version])
274 @content.destroy
274 @content.destroy
275 redirect_to_referer_or history_project_wiki_page_path(@project, @page.title)
275 redirect_to_referer_or history_project_wiki_page_path(@project, @page.title)
276 end
276 end
277
277
278 # Export wiki to a single pdf or html file
278 # Export wiki to a single pdf or html file
279 def export
279 def export
280 @pages = @wiki.pages.all(:order => 'title', :include => [:content, {:attachments => :author}])
280 @pages = @wiki.pages.all(:order => 'title', :include => [:content, {:attachments => :author}])
281 respond_to do |format|
281 respond_to do |format|
282 format.html {
282 format.html {
283 export = render_to_string :action => 'export_multiple', :layout => false
283 export = render_to_string :action => 'export_multiple', :layout => false
284 send_data(export, :type => 'text/html', :filename => "wiki.html")
284 send_data(export, :type => 'text/html', :filename => "wiki.html")
285 }
285 }
286 format.pdf {
286 format.pdf {
287 send_data(wiki_pages_to_pdf(@pages, @project), :type => 'application/pdf', :filename => "#{@project.identifier}.pdf")
287 send_data(wiki_pages_to_pdf(@pages, @project), :type => 'application/pdf', :filename => "#{@project.identifier}.pdf")
288 }
288 }
289 end
289 end
290 end
290 end
291
291
292 def preview
292 def preview
293 page = @wiki.find_page(params[:id])
293 page = @wiki.find_page(params[:id])
294 # page is nil when previewing a new page
294 # page is nil when previewing a new page
295 return render_403 unless page.nil? || editable?(page)
295 return render_403 unless page.nil? || editable?(page)
296 if page
296 if page
297 @attachments += page.attachments
297 @attachments += page.attachments
298 @previewed = page.content
298 @previewed = page.content
299 end
299 end
300 @text = params[:content][:text]
300 @text = params[:content][:text]
301 render :partial => 'common/preview'
301 render :partial => 'common/preview'
302 end
302 end
303
303
304 def add_attachment
304 def add_attachment
305 return render_403 unless editable?
305 return render_403 unless editable?
306 attachments = Attachment.attach_files(@page, params[:attachments])
306 attachments = Attachment.attach_files(@page, params[:attachments])
307 render_attachment_warning_if_needed(@page)
307 render_attachment_warning_if_needed(@page)
308 redirect_to :action => 'show', :id => @page.title, :project_id => @project
308 redirect_to :action => 'show', :id => @page.title, :project_id => @project
309 end
309 end
310
310
311 private
311 private
312
312
313 def find_wiki
313 def find_wiki
314 @project = Project.find(params[:project_id])
314 @project = Project.find(params[:project_id])
315 @wiki = @project.wiki
315 @wiki = @project.wiki
316 render_404 unless @wiki
316 render_404 unless @wiki
317 rescue ActiveRecord::RecordNotFound
317 rescue ActiveRecord::RecordNotFound
318 render_404
318 render_404
319 end
319 end
320
320
321 # Finds the requested page or a new page if it doesn't exist
321 # Finds the requested page or a new page if it doesn't exist
322 def find_existing_or_new_page
322 def find_existing_or_new_page
323 @page = @wiki.find_or_new_page(params[:id])
323 @page = @wiki.find_or_new_page(params[:id])
324 if @wiki.page_found_with_redirect?
324 if @wiki.page_found_with_redirect?
325 redirect_to params.update(:id => @page.title)
325 redirect_to params.update(:id => @page.title)
326 end
326 end
327 end
327 end
328
328
329 # Finds the requested page and returns a 404 error if it doesn't exist
329 # Finds the requested page and returns a 404 error if it doesn't exist
330 def find_existing_page
330 def find_existing_page
331 @page = @wiki.find_page(params[:id])
331 @page = @wiki.find_page(params[:id])
332 if @page.nil?
332 if @page.nil?
333 render_404
333 render_404
334 return
334 return
335 end
335 end
336 if @wiki.page_found_with_redirect?
336 if @wiki.page_found_with_redirect?
337 redirect_to params.update(:id => @page.title)
337 redirect_to params.update(:id => @page.title)
338 end
338 end
339 end
339 end
340
340
341 # Returns true if the current user is allowed to edit the page, otherwise false
341 # Returns true if the current user is allowed to edit the page, otherwise false
342 def editable?(page = @page)
342 def editable?(page = @page)
343 page.editable_by?(User.current)
343 page.editable_by?(User.current)
344 end
344 end
345
345
346 # Returns the default content of a new wiki page
346 # Returns the default content of a new wiki page
347 def initial_page_content(page)
347 def initial_page_content(page)
348 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
348 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
349 extend helper unless self.instance_of?(helper)
349 extend helper unless self.instance_of?(helper)
350 helper.instance_method(:initial_page_content).bind(self).call(page)
350 helper.instance_method(:initial_page_content).bind(self).call(page)
351 end
351 end
352
352
353 def load_pages_for_index
353 def load_pages_for_index
354 @pages = @wiki.pages.with_updated_on.reorder("#{WikiPage.table_name}.title").includes(:wiki => :project).includes(:parent).all
354 @pages = @wiki.pages.with_updated_on.reorder("#{WikiPage.table_name}.title").includes(:wiki => :project).includes(:parent).all
355 end
355 end
356 end
356 end
@@ -1,244 +1,244
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2012 Jean-Philippe Lang
4 # Copyright (C) 2006-2012 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 Redmine
20 module Redmine
21 module Pagination
21 module Pagination
22 class Paginator
22 class Paginator
23 attr_reader :item_count, :per_page, :page, :page_param
23 attr_reader :item_count, :per_page, :page, :page_param
24
24
25 def initialize(*args)
25 def initialize(*args)
26 if args.first.is_a?(ActionController::Base)
26 if args.first.is_a?(ActionController::Base)
27 args.shift
27 args.shift
28 ActiveSupport::Deprecation.warn "Paginator no longer takes a controller instance as the first argument. Remove it from #new arguments."
28 ActiveSupport::Deprecation.warn "Paginator no longer takes a controller instance as the first argument. Remove it from #new arguments."
29 end
29 end
30 item_count, per_page, page, page_param = *args
30 item_count, per_page, page, page_param = *args
31
31
32 @item_count = item_count
32 @item_count = item_count
33 @per_page = per_page
33 @per_page = per_page
34 page = (page || 1).to_i
34 page = (page || 1).to_i
35 if page < 1
35 if page < 1
36 page = 1
36 page = 1
37 end
37 end
38 @page = page
38 @page = page
39 @page_param = page_param || :page
39 @page_param = page_param || :page
40 end
40 end
41
41
42 def offset
42 def offset
43 (page - 1) * per_page
43 (page - 1) * per_page
44 end
44 end
45
45
46 def first_page
46 def first_page
47 if item_count > 0
47 if item_count > 0
48 1
48 1
49 end
49 end
50 end
50 end
51
51
52 def previous_page
52 def previous_page
53 if page > 1
53 if page > 1
54 page - 1
54 page - 1
55 end
55 end
56 end
56 end
57
57
58 def next_page
58 def next_page
59 if last_item < item_count
59 if last_item < item_count
60 page + 1
60 page + 1
61 end
61 end
62 end
62 end
63
63
64 def last_page
64 def last_page
65 if item_count > 0
65 if item_count > 0
66 (item_count - 1) / per_page + 1
66 (item_count - 1) / per_page + 1
67 end
67 end
68 end
68 end
69
69
70 def first_item
70 def first_item
71 item_count == 0 ? 0 : (offset + 1)
71 item_count == 0 ? 0 : (offset + 1)
72 end
72 end
73
73
74 def last_item
74 def last_item
75 l = first_item + per_page - 1
75 l = first_item + per_page - 1
76 l > item_count ? item_count : l
76 l > item_count ? item_count : l
77 end
77 end
78
78
79 def linked_pages
79 def linked_pages
80 pages = []
80 pages = []
81 if item_count > 0
81 if item_count > 0
82 pages += [first_page, page, last_page]
82 pages += [first_page, page, last_page]
83 pages += ((page-2)..(page+2)).to_a.select {|p| p > first_page && p < last_page}
83 pages += ((page-2)..(page+2)).to_a.select {|p| p > first_page && p < last_page}
84 end
84 end
85 pages = pages.compact.uniq.sort
85 pages = pages.compact.uniq.sort
86 if pages.size > 1
86 if pages.size > 1
87 pages
87 pages
88 else
88 else
89 []
89 []
90 end
90 end
91 end
91 end
92
92
93 def items_per_page
93 def items_per_page
94 ActiveSupport::Deprecation.warn "Paginator#items_per_page will be removed. Use #per_page instead."
94 ActiveSupport::Deprecation.warn "Paginator#items_per_page will be removed. Use #per_page instead."
95 per_page
95 per_page
96 end
96 end
97
97
98 def current
98 def current
99 ActiveSupport::Deprecation.warn "Paginator#current will be removed. Use .offset instead of .current.offset."
99 ActiveSupport::Deprecation.warn "Paginator#current will be removed. Use .offset instead of .current.offset."
100 self
100 self
101 end
101 end
102 end
102 end
103
103
104 # Paginates the given scope or model. Returns a Paginator instance and
104 # Paginates the given scope or model. Returns a Paginator instance and
105 # the collection of objects for the current page.
105 # the collection of objects for the current page.
106 #
106 #
107 # Options:
107 # Options:
108 # :parameter name of the page parameter
108 # :parameter name of the page parameter
109 #
109 #
110 # Examples:
110 # Examples:
111 # @user_pages, @users = paginate User.where(:status => 1)
111 # @user_pages, @users = paginate User.where(:status => 1)
112 #
112 #
113 def paginate(scope, options={})
113 def paginate(scope, options={})
114 options = options.dup
114 options = options.dup
115 finder_options = options.extract!(
115 finder_options = options.extract!(
116 :conditions,
116 :conditions,
117 :order,
117 :order,
118 :joins,
118 :joins,
119 :include,
119 :include,
120 :select
120 :select
121 )
121 )
122 if scope.is_a?(Symbol) || finder_options.values.compact.any?
122 if scope.is_a?(Symbol) || finder_options.values.compact.any?
123 return deprecated_paginate(scope, finder_options, options)
123 return deprecated_paginate(scope, finder_options, options)
124 end
124 end
125
125
126 paginator = paginator(scope.count, options)
126 paginator = paginator(scope.count, options)
127 collection = scope.limit(paginator.per_page).offset(paginator.offset).to_a
127 collection = scope.limit(paginator.per_page).offset(paginator.offset).to_a
128
128
129 return paginator, collection
129 return paginator, collection
130 end
130 end
131
131
132 def deprecated_paginate(arg, finder_options, options={})
132 def deprecated_paginate(arg, finder_options, options={})
133 ActiveSupport::Deprecation.warn "#paginate with a Symbol and/or find options is depreceted and will be removed. Use a scope instead."
133 ActiveSupport::Deprecation.warn "#paginate with a Symbol and/or find options is depreceted and will be removed. Use a scope instead."
134 klass = arg.is_a?(Symbol) ? arg.to_s.classify.constantize : arg
134 klass = arg.is_a?(Symbol) ? arg.to_s.classify.constantize : arg
135 scope = klass.scoped(finder_options)
135 scope = klass.scoped(finder_options)
136 paginate(scope, options)
136 paginate(scope, options)
137 end
137 end
138
138
139 def paginator(item_count, options={})
139 def paginator(item_count, options={})
140 options.assert_valid_keys :parameter, :per_page
140 options.assert_valid_keys :parameter, :per_page
141
141
142 page_param = options[:parameter] || :page
142 page_param = options[:parameter] || :page
143 page = (params[page_param] || 1).to_i
143 page = (params[page_param] || 1).to_i
144 per_page = options[:per_page] || per_page_option
144 per_page = options[:per_page] || per_page_option
145 Paginator.new(self, item_count, per_page, page, page_param)
145 Paginator.new(item_count, per_page, page, page_param)
146 end
146 end
147
147
148 module Helper
148 module Helper
149 include Redmine::I18n
149 include Redmine::I18n
150
150
151 # Renders the pagination links for the given paginator.
151 # Renders the pagination links for the given paginator.
152 #
152 #
153 # Options:
153 # Options:
154 # :per_page_links if set to false, the "Per page" links are not rendered
154 # :per_page_links if set to false, the "Per page" links are not rendered
155 #
155 #
156 def pagination_links_full(*args)
156 def pagination_links_full(*args)
157 pagination_links_each(*args) do |text, parameters, options|
157 pagination_links_each(*args) do |text, parameters, options|
158 if block_given?
158 if block_given?
159 yield text, parameters, options
159 yield text, parameters, options
160 else
160 else
161 link_to text, params.merge(parameters), options
161 link_to text, params.merge(parameters), options
162 end
162 end
163 end
163 end
164 end
164 end
165
165
166 # Yields the given block with the text and parameters
166 # Yields the given block with the text and parameters
167 # for each pagination link and returns a string that represents the links
167 # for each pagination link and returns a string that represents the links
168 def pagination_links_each(paginator, count=nil, options={}, &block)
168 def pagination_links_each(paginator, count=nil, options={}, &block)
169 options.assert_valid_keys :per_page_links
169 options.assert_valid_keys :per_page_links
170
170
171 per_page_links = options.delete(:per_page_links)
171 per_page_links = options.delete(:per_page_links)
172 per_page_links = false if count.nil?
172 per_page_links = false if count.nil?
173 page_param = paginator.page_param
173 page_param = paginator.page_param
174
174
175 html = ''
175 html = ''
176 if paginator.previous_page
176 if paginator.previous_page
177 # \xc2\xab(utf-8) = &#171;
177 # \xc2\xab(utf-8) = &#171;
178 text = "\xc2\xab " + l(:label_previous)
178 text = "\xc2\xab " + l(:label_previous)
179 html << yield(text, {page_param => paginator.previous_page}, :class => 'previous') + ' '
179 html << yield(text, {page_param => paginator.previous_page}, :class => 'previous') + ' '
180 end
180 end
181
181
182 previous = nil
182 previous = nil
183 paginator.linked_pages.each do |page|
183 paginator.linked_pages.each do |page|
184 if previous && previous != page - 1
184 if previous && previous != page - 1
185 html << content_tag('span', '...', :class => 'spacer') + ' '
185 html << content_tag('span', '...', :class => 'spacer') + ' '
186 end
186 end
187 if page == paginator.page
187 if page == paginator.page
188 html << content_tag('span', page.to_s, :class => 'current page')
188 html << content_tag('span', page.to_s, :class => 'current page')
189 else
189 else
190 html << yield(page.to_s, {page_param => page}, :class => 'page')
190 html << yield(page.to_s, {page_param => page}, :class => 'page')
191 end
191 end
192 html << ' '
192 html << ' '
193 previous = page
193 previous = page
194 end
194 end
195
195
196 if paginator.next_page
196 if paginator.next_page
197 # \xc2\xbb(utf-8) = &#187;
197 # \xc2\xbb(utf-8) = &#187;
198 text = l(:label_next) + " \xc2\xbb"
198 text = l(:label_next) + " \xc2\xbb"
199 html << yield(text, {page_param => paginator.next_page}, :class => 'next') + ' '
199 html << yield(text, {page_param => paginator.next_page}, :class => 'next') + ' '
200 end
200 end
201
201
202 html << content_tag('span', "(#{paginator.first_item}-#{paginator.last_item}/#{paginator.item_count})", :class => 'items') + ' '
202 html << content_tag('span', "(#{paginator.first_item}-#{paginator.last_item}/#{paginator.item_count})", :class => 'items') + ' '
203
203
204 if per_page_links != false && links = per_page_links(paginator, &block)
204 if per_page_links != false && links = per_page_links(paginator, &block)
205 html << content_tag('span', links.to_s, :class => 'per-page')
205 html << content_tag('span', links.to_s, :class => 'per-page')
206 end
206 end
207
207
208 html.html_safe
208 html.html_safe
209 end
209 end
210
210
211 # Renders the "Per page" links.
211 # Renders the "Per page" links.
212 def per_page_links(paginator, &block)
212 def per_page_links(paginator, &block)
213 values = per_page_options(paginator.per_page, paginator.item_count)
213 values = per_page_options(paginator.per_page, paginator.item_count)
214 if values.any?
214 if values.any?
215 links = values.collect do |n|
215 links = values.collect do |n|
216 if n == paginator.per_page
216 if n == paginator.per_page
217 content_tag('span', n.to_s)
217 content_tag('span', n.to_s)
218 else
218 else
219 yield(n, :per_page => n, paginator.page_param => nil)
219 yield(n, :per_page => n, paginator.page_param => nil)
220 end
220 end
221 end
221 end
222 l(:label_display_per_page, links.join(', ')).html_safe
222 l(:label_display_per_page, links.join(', ')).html_safe
223 end
223 end
224 end
224 end
225
225
226 def per_page_options(selected=nil, item_count=nil)
226 def per_page_options(selected=nil, item_count=nil)
227 options = Setting.per_page_options_array
227 options = Setting.per_page_options_array
228 if item_count && options.any?
228 if item_count && options.any?
229 if item_count > options.first
229 if item_count > options.first
230 max = options.detect {|value| value >= item_count} || item_count
230 max = options.detect {|value| value >= item_count} || item_count
231 else
231 else
232 max = item_count
232 max = item_count
233 end
233 end
234 options = options.select {|value| value <= max || value == selected}
234 options = options.select {|value| value <= max || value == selected}
235 end
235 end
236 if options.empty? || (options.size == 1 && options.first == selected)
236 if options.empty? || (options.size == 1 && options.first == selected)
237 []
237 []
238 else
238 else
239 options
239 options
240 end
240 end
241 end
241 end
242 end
242 end
243 end
243 end
244 end
244 end
General Comments 0
You need to be logged in to leave comments. Login now