##// END OF EJS Templates
Replaces find(:all) calls....
Jean-Philippe Lang -
r10687:5b21efd4a432
parent child
Show More
@@ -1,78 +1,78
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 CustomFieldsController < ApplicationController
18 class CustomFieldsController < ApplicationController
19 layout 'admin'
19 layout 'admin'
20
20
21 before_filter :require_admin
21 before_filter :require_admin
22 before_filter :build_new_custom_field, :only => [:new, :create]
22 before_filter :build_new_custom_field, :only => [:new, :create]
23 before_filter :find_custom_field, :only => [:edit, :update, :destroy]
23 before_filter :find_custom_field, :only => [:edit, :update, :destroy]
24
24
25 def index
25 def index
26 @custom_fields_by_type = CustomField.find(:all).group_by {|f| f.class.name }
26 @custom_fields_by_type = CustomField.all.group_by {|f| f.class.name }
27 @tab = params[:tab] || 'IssueCustomField'
27 @tab = params[:tab] || 'IssueCustomField'
28 end
28 end
29
29
30 def new
30 def new
31 end
31 end
32
32
33 def create
33 def create
34 if request.post? and @custom_field.save
34 if request.post? and @custom_field.save
35 flash[:notice] = l(:notice_successful_create)
35 flash[:notice] = l(:notice_successful_create)
36 call_hook(:controller_custom_fields_new_after_save, :params => params, :custom_field => @custom_field)
36 call_hook(:controller_custom_fields_new_after_save, :params => params, :custom_field => @custom_field)
37 redirect_to :action => 'index', :tab => @custom_field.class.name
37 redirect_to :action => 'index', :tab => @custom_field.class.name
38 else
38 else
39 render :action => 'new'
39 render :action => 'new'
40 end
40 end
41 end
41 end
42
42
43 def edit
43 def edit
44 end
44 end
45
45
46 def update
46 def update
47 if request.put? and @custom_field.update_attributes(params[:custom_field])
47 if request.put? and @custom_field.update_attributes(params[:custom_field])
48 flash[:notice] = l(:notice_successful_update)
48 flash[:notice] = l(:notice_successful_update)
49 call_hook(:controller_custom_fields_edit_after_save, :params => params, :custom_field => @custom_field)
49 call_hook(:controller_custom_fields_edit_after_save, :params => params, :custom_field => @custom_field)
50 redirect_to :action => 'index', :tab => @custom_field.class.name
50 redirect_to :action => 'index', :tab => @custom_field.class.name
51 else
51 else
52 render :action => 'edit'
52 render :action => 'edit'
53 end
53 end
54 end
54 end
55
55
56 def destroy
56 def destroy
57 @custom_field.destroy
57 @custom_field.destroy
58 redirect_to :action => 'index', :tab => @custom_field.class.name
58 redirect_to :action => 'index', :tab => @custom_field.class.name
59 rescue
59 rescue
60 flash[:error] = l(:error_can_not_delete_custom_field)
60 flash[:error] = l(:error_can_not_delete_custom_field)
61 redirect_to :action => 'index'
61 redirect_to :action => 'index'
62 end
62 end
63
63
64 private
64 private
65
65
66 def build_new_custom_field
66 def build_new_custom_field
67 @custom_field = CustomField.new_subclass_instance(params[:type], params[:custom_field])
67 @custom_field = CustomField.new_subclass_instance(params[:type], params[:custom_field])
68 if @custom_field.nil?
68 if @custom_field.nil?
69 render_404
69 render_404
70 end
70 end
71 end
71 end
72
72
73 def find_custom_field
73 def find_custom_field
74 @custom_field = CustomField.find(params[:id])
74 @custom_field = CustomField.find(params[:id])
75 rescue ActiveRecord::RecordNotFound
75 rescue ActiveRecord::RecordNotFound
76 render_404
76 render_404
77 end
77 end
78 end
78 end
@@ -1,94 +1,94
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 DocumentsController < ApplicationController
18 class DocumentsController < ApplicationController
19 default_search_scope :documents
19 default_search_scope :documents
20 model_object Document
20 model_object Document
21 before_filter :find_project_by_project_id, :only => [:index, :new, :create]
21 before_filter :find_project_by_project_id, :only => [:index, :new, :create]
22 before_filter :find_model_object, :except => [:index, :new, :create]
22 before_filter :find_model_object, :except => [:index, :new, :create]
23 before_filter :find_project_from_association, :except => [:index, :new, :create]
23 before_filter :find_project_from_association, :except => [:index, :new, :create]
24 before_filter :authorize
24 before_filter :authorize
25
25
26 helper :attachments
26 helper :attachments
27
27
28 def index
28 def index
29 @sort_by = %w(category date title author).include?(params[:sort_by]) ? params[:sort_by] : 'category'
29 @sort_by = %w(category date title author).include?(params[:sort_by]) ? params[:sort_by] : 'category'
30 documents = @project.documents.find :all, :include => [:attachments, :category]
30 documents = @project.documents.find :all, :include => [:attachments, :category]
31 case @sort_by
31 case @sort_by
32 when 'date'
32 when 'date'
33 @grouped = documents.group_by {|d| d.updated_on.to_date }
33 @grouped = documents.group_by {|d| d.updated_on.to_date }
34 when 'title'
34 when 'title'
35 @grouped = documents.group_by {|d| d.title.first.upcase}
35 @grouped = documents.group_by {|d| d.title.first.upcase}
36 when 'author'
36 when 'author'
37 @grouped = documents.select{|d| d.attachments.any?}.group_by {|d| d.attachments.last.author}
37 @grouped = documents.select{|d| d.attachments.any?}.group_by {|d| d.attachments.last.author}
38 else
38 else
39 @grouped = documents.group_by(&:category)
39 @grouped = documents.group_by(&:category)
40 end
40 end
41 @document = @project.documents.build
41 @document = @project.documents.build
42 render :layout => false if request.xhr?
42 render :layout => false if request.xhr?
43 end
43 end
44
44
45 def show
45 def show
46 @attachments = @document.attachments.find(:all, :order => "created_on DESC")
46 @attachments = @document.attachments.all
47 end
47 end
48
48
49 def new
49 def new
50 @document = @project.documents.build
50 @document = @project.documents.build
51 @document.safe_attributes = params[:document]
51 @document.safe_attributes = params[:document]
52 end
52 end
53
53
54 def create
54 def create
55 @document = @project.documents.build
55 @document = @project.documents.build
56 @document.safe_attributes = params[:document]
56 @document.safe_attributes = params[:document]
57 @document.save_attachments(params[:attachments])
57 @document.save_attachments(params[:attachments])
58 if @document.save
58 if @document.save
59 render_attachment_warning_if_needed(@document)
59 render_attachment_warning_if_needed(@document)
60 flash[:notice] = l(:notice_successful_create)
60 flash[:notice] = l(:notice_successful_create)
61 redirect_to :action => 'index', :project_id => @project
61 redirect_to :action => 'index', :project_id => @project
62 else
62 else
63 render :action => 'new'
63 render :action => 'new'
64 end
64 end
65 end
65 end
66
66
67 def edit
67 def edit
68 end
68 end
69
69
70 def update
70 def update
71 @document.safe_attributes = params[:document]
71 @document.safe_attributes = params[:document]
72 if request.put? and @document.save
72 if request.put? and @document.save
73 flash[:notice] = l(:notice_successful_update)
73 flash[:notice] = l(:notice_successful_update)
74 redirect_to :action => 'show', :id => @document
74 redirect_to :action => 'show', :id => @document
75 else
75 else
76 render :action => 'edit'
76 render :action => 'edit'
77 end
77 end
78 end
78 end
79
79
80 def destroy
80 def destroy
81 @document.destroy if request.delete?
81 @document.destroy if request.delete?
82 redirect_to :controller => 'documents', :action => 'index', :project_id => @project
82 redirect_to :controller => 'documents', :action => 'index', :project_id => @project
83 end
83 end
84
84
85 def add_attachment
85 def add_attachment
86 attachments = Attachment.attach_files(@document, params[:attachments])
86 attachments = Attachment.attach_files(@document, params[:attachments])
87 render_attachment_warning_if_needed(@document)
87 render_attachment_warning_if_needed(@document)
88
88
89 if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added')
89 if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added')
90 Mailer.attachments_added(attachments[:files]).deliver
90 Mailer.attachments_added(attachments[:files]).deliver
91 end
91 end
92 redirect_to :action => 'show', :id => @document
92 redirect_to :action => 'show', :id => @document
93 end
93 end
94 end
94 end
@@ -1,139 +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_message, :except => [:new, :preview]
22 before_filter :find_message, :except => [:new, :preview]
23 before_filter :authorize, :except => [:preview, :edit, :destroy]
23 before_filter :authorize, :except => [:preview, :edit, :destroy]
24
24
25 helper :boards
25 helper :boards
26 helper :watchers
26 helper :watchers
27 helper :attachments
27 helper :attachments
28 include AttachmentsHelper
28 include AttachmentsHelper
29
29
30 REPLIES_PER_PAGE = 25 unless const_defined?(:REPLIES_PER_PAGE)
30 REPLIES_PER_PAGE = 25 unless const_defined?(:REPLIES_PER_PAGE)
31
31
32 # Show a topic and its replies
32 # Show a topic and its replies
33 def show
33 def show
34 page = params[:page]
34 page = params[:page]
35 # Find the page of the requested reply
35 # Find the page of the requested reply
36 if params[:r] && page.nil?
36 if params[:r] && page.nil?
37 offset = @topic.children.count(:conditions => ["#{Message.table_name}.id < ?", params[:r].to_i])
37 offset = @topic.children.count(:conditions => ["#{Message.table_name}.id < ?", params[:r].to_i])
38 page = 1 + offset / REPLIES_PER_PAGE
38 page = 1 + offset / REPLIES_PER_PAGE
39 end
39 end
40
40
41 @reply_count = @topic.children.count
41 @reply_count = @topic.children.count
42 @reply_pages = Paginator.new self, @reply_count, REPLIES_PER_PAGE, page
42 @reply_pages = Paginator.new self, @reply_count, REPLIES_PER_PAGE, page
43 @replies = @topic.children.find(:all, :include => [:author, :attachments, {:board => :project}],
43 @replies = @topic.children.
44 :order => "#{Message.table_name}.created_on ASC",
44 includes(:author, :attachments, {:board => :project}).
45 :limit => @reply_pages.items_per_page,
45 reorder("#{Message.table_name}.created_on ASC").
46 :offset => @reply_pages.current.offset)
46 limit(@reply_pages.items_per_page).
47 offset(@reply_pages.current.offset).
48 all
47
49
48 @reply = Message.new(:subject => "RE: #{@message.subject}")
50 @reply = Message.new(:subject => "RE: #{@message.subject}")
49 render :action => "show", :layout => false if request.xhr?
51 render :action => "show", :layout => false if request.xhr?
50 end
52 end
51
53
52 # Create a new topic
54 # Create a new topic
53 def new
55 def new
54 @message = Message.new
56 @message = Message.new
55 @message.author = User.current
57 @message.author = User.current
56 @message.board = @board
58 @message.board = @board
57 @message.safe_attributes = params[:message]
59 @message.safe_attributes = params[:message]
58 if request.post?
60 if request.post?
59 @message.save_attachments(params[:attachments])
61 @message.save_attachments(params[:attachments])
60 if @message.save
62 if @message.save
61 call_hook(:controller_messages_new_after_save, { :params => params, :message => @message})
63 call_hook(:controller_messages_new_after_save, { :params => params, :message => @message})
62 render_attachment_warning_if_needed(@message)
64 render_attachment_warning_if_needed(@message)
63 redirect_to board_message_path(@board, @message)
65 redirect_to board_message_path(@board, @message)
64 end
66 end
65 end
67 end
66 end
68 end
67
69
68 # Reply to a topic
70 # Reply to a topic
69 def reply
71 def reply
70 @reply = Message.new
72 @reply = Message.new
71 @reply.author = User.current
73 @reply.author = User.current
72 @reply.board = @board
74 @reply.board = @board
73 @reply.safe_attributes = params[:reply]
75 @reply.safe_attributes = params[:reply]
74 @topic.children << @reply
76 @topic.children << @reply
75 if !@reply.new_record?
77 if !@reply.new_record?
76 call_hook(:controller_messages_reply_after_save, { :params => params, :message => @reply})
78 call_hook(:controller_messages_reply_after_save, { :params => params, :message => @reply})
77 attachments = Attachment.attach_files(@reply, params[:attachments])
79 attachments = Attachment.attach_files(@reply, params[:attachments])
78 render_attachment_warning_if_needed(@reply)
80 render_attachment_warning_if_needed(@reply)
79 end
81 end
80 redirect_to board_message_path(@board, @topic, :r => @reply)
82 redirect_to board_message_path(@board, @topic, :r => @reply)
81 end
83 end
82
84
83 # Edit a message
85 # Edit a message
84 def edit
86 def edit
85 (render_403; return false) unless @message.editable_by?(User.current)
87 (render_403; return false) unless @message.editable_by?(User.current)
86 @message.safe_attributes = params[:message]
88 @message.safe_attributes = params[:message]
87 if request.post? && @message.save
89 if request.post? && @message.save
88 attachments = Attachment.attach_files(@message, params[:attachments])
90 attachments = Attachment.attach_files(@message, params[:attachments])
89 render_attachment_warning_if_needed(@message)
91 render_attachment_warning_if_needed(@message)
90 flash[:notice] = l(:notice_successful_update)
92 flash[:notice] = l(:notice_successful_update)
91 @message.reload
93 @message.reload
92 redirect_to board_message_path(@message.board, @message.root, :r => (@message.parent_id && @message.id))
94 redirect_to board_message_path(@message.board, @message.root, :r => (@message.parent_id && @message.id))
93 end
95 end
94 end
96 end
95
97
96 # Delete a messages
98 # Delete a messages
97 def destroy
99 def destroy
98 (render_403; return false) unless @message.destroyable_by?(User.current)
100 (render_403; return false) unless @message.destroyable_by?(User.current)
99 r = @message.to_param
101 r = @message.to_param
100 @message.destroy
102 @message.destroy
101 if @message.parent
103 if @message.parent
102 redirect_to board_message_path(@board, @message.parent, :r => r)
104 redirect_to board_message_path(@board, @message.parent, :r => r)
103 else
105 else
104 redirect_to project_board_path(@project, @board)
106 redirect_to project_board_path(@project, @board)
105 end
107 end
106 end
108 end
107
109
108 def quote
110 def quote
109 @subject = @message.subject
111 @subject = @message.subject
110 @subject = "RE: #{@subject}" unless @subject.starts_with?('RE:')
112 @subject = "RE: #{@subject}" unless @subject.starts_with?('RE:')
111
113
112 @content = "#{ll(Setting.default_language, :text_user_wrote, @message.author)}\n> "
114 @content = "#{ll(Setting.default_language, :text_user_wrote, @message.author)}\n> "
113 @content << @message.content.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n"
115 @content << @message.content.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n"
114 end
116 end
115
117
116 def preview
118 def preview
117 message = @board.messages.find_by_id(params[:id])
119 message = @board.messages.find_by_id(params[:id])
118 @attachements = message.attachments if message
120 @attachements = message.attachments if message
119 @text = (params[:message] || params[:reply])[:content]
121 @text = (params[:message] || params[:reply])[:content]
120 @previewed = message
122 @previewed = message
121 render :partial => 'common/preview'
123 render :partial => 'common/preview'
122 end
124 end
123
125
124 private
126 private
125 def find_message
127 def find_message
126 find_board
128 find_board
127 @message = @board.messages.find(params[:id], :include => :parent)
129 @message = @board.messages.find(params[:id], :include => :parent)
128 @topic = @message.root
130 @topic = @message.root
129 rescue ActiveRecord::RecordNotFound
131 rescue ActiveRecord::RecordNotFound
130 render_404
132 render_404
131 end
133 end
132
134
133 def find_board
135 def find_board
134 @board = Board.find(params[:board_id], :include => :project)
136 @board = Board.find(params[:board_id], :include => :project)
135 @project = @board.project
137 @project = @board.project
136 rescue ActiveRecord::RecordNotFound
138 rescue ActiveRecord::RecordNotFound
137 render_404
139 render_404
138 end
140 end
139 end
141 end
@@ -1,261 +1,260
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 ProjectsController < ApplicationController
18 class ProjectsController < ApplicationController
19 menu_item :overview
19 menu_item :overview
20 menu_item :roadmap, :only => :roadmap
20 menu_item :roadmap, :only => :roadmap
21 menu_item :settings, :only => :settings
21 menu_item :settings, :only => :settings
22
22
23 before_filter :find_project, :except => [ :index, :list, :new, :create, :copy ]
23 before_filter :find_project, :except => [ :index, :list, :new, :create, :copy ]
24 before_filter :authorize, :except => [ :index, :list, :new, :create, :copy, :archive, :unarchive, :destroy]
24 before_filter :authorize, :except => [ :index, :list, :new, :create, :copy, :archive, :unarchive, :destroy]
25 before_filter :authorize_global, :only => [:new, :create]
25 before_filter :authorize_global, :only => [:new, :create]
26 before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ]
26 before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ]
27 accept_rss_auth :index
27 accept_rss_auth :index
28 accept_api_auth :index, :show, :create, :update, :destroy
28 accept_api_auth :index, :show, :create, :update, :destroy
29
29
30 after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller|
30 after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller|
31 if controller.request.post?
31 if controller.request.post?
32 controller.send :expire_action, :controller => 'welcome', :action => 'robots'
32 controller.send :expire_action, :controller => 'welcome', :action => 'robots'
33 end
33 end
34 end
34 end
35
35
36 helper :sort
36 helper :sort
37 include SortHelper
37 include SortHelper
38 helper :custom_fields
38 helper :custom_fields
39 include CustomFieldsHelper
39 include CustomFieldsHelper
40 helper :issues
40 helper :issues
41 helper :queries
41 helper :queries
42 include QueriesHelper
42 include QueriesHelper
43 helper :repositories
43 helper :repositories
44 include RepositoriesHelper
44 include RepositoriesHelper
45 include ProjectsHelper
45 include ProjectsHelper
46
46
47 # Lists visible projects
47 # Lists visible projects
48 def index
48 def index
49 respond_to do |format|
49 respond_to do |format|
50 format.html {
50 format.html {
51 scope = Project
51 scope = Project
52 unless params[:closed]
52 unless params[:closed]
53 scope = scope.active
53 scope = scope.active
54 end
54 end
55 @projects = scope.visible.order('lft').all
55 @projects = scope.visible.order('lft').all
56 }
56 }
57 format.api {
57 format.api {
58 @offset, @limit = api_offset_and_limit
58 @offset, @limit = api_offset_and_limit
59 @project_count = Project.visible.count
59 @project_count = Project.visible.count
60 @projects = Project.visible.all(:offset => @offset, :limit => @limit, :order => 'lft')
60 @projects = Project.visible.offset(@offset).limit(@limit).order('lft').all
61 }
61 }
62 format.atom {
62 format.atom {
63 projects = Project.visible.find(:all, :order => 'created_on DESC',
63 projects = Project.visible.order('created_on DESC').limit(Setting.feeds_limit.to_i).all
64 :limit => Setting.feeds_limit.to_i)
65 render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
64 render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
66 }
65 }
67 end
66 end
68 end
67 end
69
68
70 def new
69 def new
71 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
70 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
72 @trackers = Tracker.sorted.all
71 @trackers = Tracker.sorted.all
73 @project = Project.new
72 @project = Project.new
74 @project.safe_attributes = params[:project]
73 @project.safe_attributes = params[:project]
75 end
74 end
76
75
77 def create
76 def create
78 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
77 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
79 @trackers = Tracker.sorted.all
78 @trackers = Tracker.sorted.all
80 @project = Project.new
79 @project = Project.new
81 @project.safe_attributes = params[:project]
80 @project.safe_attributes = params[:project]
82
81
83 if validate_parent_id && @project.save
82 if validate_parent_id && @project.save
84 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
83 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
85 # Add current user as a project member if he is not admin
84 # Add current user as a project member if he is not admin
86 unless User.current.admin?
85 unless User.current.admin?
87 r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
86 r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
88 m = Member.new(:user => User.current, :roles => [r])
87 m = Member.new(:user => User.current, :roles => [r])
89 @project.members << m
88 @project.members << m
90 end
89 end
91 respond_to do |format|
90 respond_to do |format|
92 format.html {
91 format.html {
93 flash[:notice] = l(:notice_successful_create)
92 flash[:notice] = l(:notice_successful_create)
94 redirect_to(params[:continue] ?
93 redirect_to(params[:continue] ?
95 {:controller => 'projects', :action => 'new', :project => {:parent_id => @project.parent_id}.reject {|k,v| v.nil?}} :
94 {:controller => 'projects', :action => 'new', :project => {:parent_id => @project.parent_id}.reject {|k,v| v.nil?}} :
96 {:controller => 'projects', :action => 'settings', :id => @project}
95 {:controller => 'projects', :action => 'settings', :id => @project}
97 )
96 )
98 }
97 }
99 format.api { render :action => 'show', :status => :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) }
98 format.api { render :action => 'show', :status => :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) }
100 end
99 end
101 else
100 else
102 respond_to do |format|
101 respond_to do |format|
103 format.html { render :action => 'new' }
102 format.html { render :action => 'new' }
104 format.api { render_validation_errors(@project) }
103 format.api { render_validation_errors(@project) }
105 end
104 end
106 end
105 end
107
106
108 end
107 end
109
108
110 def copy
109 def copy
111 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
110 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
112 @trackers = Tracker.sorted.all
111 @trackers = Tracker.sorted.all
113 @source_project = Project.find(params[:id])
112 @source_project = Project.find(params[:id])
114 if request.get?
113 if request.get?
115 @project = Project.copy_from(@source_project)
114 @project = Project.copy_from(@source_project)
116 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
115 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
117 else
116 else
118 Mailer.with_deliveries(params[:notifications] == '1') do
117 Mailer.with_deliveries(params[:notifications] == '1') do
119 @project = Project.new
118 @project = Project.new
120 @project.safe_attributes = params[:project]
119 @project.safe_attributes = params[:project]
121 if validate_parent_id && @project.copy(@source_project, :only => params[:only])
120 if validate_parent_id && @project.copy(@source_project, :only => params[:only])
122 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
121 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
123 flash[:notice] = l(:notice_successful_create)
122 flash[:notice] = l(:notice_successful_create)
124 redirect_to :controller => 'projects', :action => 'settings', :id => @project
123 redirect_to :controller => 'projects', :action => 'settings', :id => @project
125 elsif !@project.new_record?
124 elsif !@project.new_record?
126 # Project was created
125 # Project was created
127 # But some objects were not copied due to validation failures
126 # But some objects were not copied due to validation failures
128 # (eg. issues from disabled trackers)
127 # (eg. issues from disabled trackers)
129 # TODO: inform about that
128 # TODO: inform about that
130 redirect_to :controller => 'projects', :action => 'settings', :id => @project
129 redirect_to :controller => 'projects', :action => 'settings', :id => @project
131 end
130 end
132 end
131 end
133 end
132 end
134 rescue ActiveRecord::RecordNotFound
133 rescue ActiveRecord::RecordNotFound
135 # source_project not found
134 # source_project not found
136 render_404
135 render_404
137 end
136 end
138
137
139 # Show @project
138 # Show @project
140 def show
139 def show
141 if params[:jump]
140 if params[:jump]
142 # try to redirect to the requested menu item
141 # try to redirect to the requested menu item
143 redirect_to_project_menu_item(@project, params[:jump]) && return
142 redirect_to_project_menu_item(@project, params[:jump]) && return
144 end
143 end
145
144
146 @users_by_role = @project.users_by_role
145 @users_by_role = @project.users_by_role
147 @subprojects = @project.children.visible.all
146 @subprojects = @project.children.visible.all
148 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
147 @news = @project.news.limit(5).includes(:author, :project).reorder("#{News.table_name}.created_on DESC").all
149 @trackers = @project.rolled_up_trackers
148 @trackers = @project.rolled_up_trackers
150
149
151 cond = @project.project_condition(Setting.display_subprojects_issues?)
150 cond = @project.project_condition(Setting.display_subprojects_issues?)
152
151
153 @open_issues_by_tracker = Issue.visible.open.where(cond).count(:group => :tracker)
152 @open_issues_by_tracker = Issue.visible.open.where(cond).count(:group => :tracker)
154 @total_issues_by_tracker = Issue.visible.where(cond).count(:group => :tracker)
153 @total_issues_by_tracker = Issue.visible.where(cond).count(:group => :tracker)
155
154
156 if User.current.allowed_to?(:view_time_entries, @project)
155 if User.current.allowed_to?(:view_time_entries, @project)
157 @total_hours = TimeEntry.visible.sum(:hours, :include => :project, :conditions => cond).to_f
156 @total_hours = TimeEntry.visible.sum(:hours, :include => :project, :conditions => cond).to_f
158 end
157 end
159
158
160 @key = User.current.rss_key
159 @key = User.current.rss_key
161
160
162 respond_to do |format|
161 respond_to do |format|
163 format.html
162 format.html
164 format.api
163 format.api
165 end
164 end
166 end
165 end
167
166
168 def settings
167 def settings
169 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
168 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
170 @issue_category ||= IssueCategory.new
169 @issue_category ||= IssueCategory.new
171 @member ||= @project.members.new
170 @member ||= @project.members.new
172 @trackers = Tracker.sorted.all
171 @trackers = Tracker.sorted.all
173 @wiki ||= @project.wiki
172 @wiki ||= @project.wiki
174 end
173 end
175
174
176 def edit
175 def edit
177 end
176 end
178
177
179 def update
178 def update
180 @project.safe_attributes = params[:project]
179 @project.safe_attributes = params[:project]
181 if validate_parent_id && @project.save
180 if validate_parent_id && @project.save
182 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
181 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
183 respond_to do |format|
182 respond_to do |format|
184 format.html {
183 format.html {
185 flash[:notice] = l(:notice_successful_update)
184 flash[:notice] = l(:notice_successful_update)
186 redirect_to :action => 'settings', :id => @project
185 redirect_to :action => 'settings', :id => @project
187 }
186 }
188 format.api { render_api_ok }
187 format.api { render_api_ok }
189 end
188 end
190 else
189 else
191 respond_to do |format|
190 respond_to do |format|
192 format.html {
191 format.html {
193 settings
192 settings
194 render :action => 'settings'
193 render :action => 'settings'
195 }
194 }
196 format.api { render_validation_errors(@project) }
195 format.api { render_validation_errors(@project) }
197 end
196 end
198 end
197 end
199 end
198 end
200
199
201 def modules
200 def modules
202 @project.enabled_module_names = params[:enabled_module_names]
201 @project.enabled_module_names = params[:enabled_module_names]
203 flash[:notice] = l(:notice_successful_update)
202 flash[:notice] = l(:notice_successful_update)
204 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
203 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
205 end
204 end
206
205
207 def archive
206 def archive
208 if request.post?
207 if request.post?
209 unless @project.archive
208 unless @project.archive
210 flash[:error] = l(:error_can_not_archive_project)
209 flash[:error] = l(:error_can_not_archive_project)
211 end
210 end
212 end
211 end
213 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
212 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
214 end
213 end
215
214
216 def unarchive
215 def unarchive
217 @project.unarchive if request.post? && !@project.active?
216 @project.unarchive if request.post? && !@project.active?
218 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
217 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
219 end
218 end
220
219
221 def close
220 def close
222 @project.close
221 @project.close
223 redirect_to project_path(@project)
222 redirect_to project_path(@project)
224 end
223 end
225
224
226 def reopen
225 def reopen
227 @project.reopen
226 @project.reopen
228 redirect_to project_path(@project)
227 redirect_to project_path(@project)
229 end
228 end
230
229
231 # Delete @project
230 # Delete @project
232 def destroy
231 def destroy
233 @project_to_destroy = @project
232 @project_to_destroy = @project
234 if api_request? || params[:confirm]
233 if api_request? || params[:confirm]
235 @project_to_destroy.destroy
234 @project_to_destroy.destroy
236 respond_to do |format|
235 respond_to do |format|
237 format.html { redirect_to :controller => 'admin', :action => 'projects' }
236 format.html { redirect_to :controller => 'admin', :action => 'projects' }
238 format.api { render_api_ok }
237 format.api { render_api_ok }
239 end
238 end
240 end
239 end
241 # hide project in layout
240 # hide project in layout
242 @project = nil
241 @project = nil
243 end
242 end
244
243
245 private
244 private
246
245
247 # Validates parent_id param according to user's permissions
246 # Validates parent_id param according to user's permissions
248 # TODO: move it to Project model in a validation that depends on User.current
247 # TODO: move it to Project model in a validation that depends on User.current
249 def validate_parent_id
248 def validate_parent_id
250 return true if User.current.admin?
249 return true if User.current.admin?
251 parent_id = params[:project] && params[:project][:parent_id]
250 parent_id = params[:project] && params[:project][:parent_id]
252 if parent_id || @project.new_record?
251 if parent_id || @project.new_record?
253 parent = parent_id.blank? ? nil : Project.find_by_id(parent_id.to_i)
252 parent = parent_id.blank? ? nil : Project.find_by_id(parent_id.to_i)
254 unless @project.allowed_parents.include?(parent)
253 unless @project.allowed_parents.include?(parent)
255 @project.errors.add :parent_id, :invalid
254 @project.errors.add :parent_id, :invalid
256 return false
255 return false
257 end
256 end
258 end
257 end
259 true
258 true
260 end
259 end
261 end
260 end
@@ -1,95 +1,95
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 ReportsController < ApplicationController
18 class ReportsController < ApplicationController
19 menu_item :issues
19 menu_item :issues
20 before_filter :find_project, :authorize, :find_issue_statuses
20 before_filter :find_project, :authorize, :find_issue_statuses
21
21
22 def issue_report
22 def issue_report
23 @trackers = @project.trackers
23 @trackers = @project.trackers
24 @versions = @project.shared_versions.sort
24 @versions = @project.shared_versions.sort
25 @priorities = IssuePriority.all.reverse
25 @priorities = IssuePriority.all.reverse
26 @categories = @project.issue_categories
26 @categories = @project.issue_categories
27 @assignees = (Setting.issue_group_assignment? ? @project.principals : @project.users).sort
27 @assignees = (Setting.issue_group_assignment? ? @project.principals : @project.users).sort
28 @authors = @project.users.sort
28 @authors = @project.users.sort
29 @subprojects = @project.descendants.visible
29 @subprojects = @project.descendants.visible
30
30
31 @issues_by_tracker = Issue.by_tracker(@project)
31 @issues_by_tracker = Issue.by_tracker(@project)
32 @issues_by_version = Issue.by_version(@project)
32 @issues_by_version = Issue.by_version(@project)
33 @issues_by_priority = Issue.by_priority(@project)
33 @issues_by_priority = Issue.by_priority(@project)
34 @issues_by_category = Issue.by_category(@project)
34 @issues_by_category = Issue.by_category(@project)
35 @issues_by_assigned_to = Issue.by_assigned_to(@project)
35 @issues_by_assigned_to = Issue.by_assigned_to(@project)
36 @issues_by_author = Issue.by_author(@project)
36 @issues_by_author = Issue.by_author(@project)
37 @issues_by_subproject = Issue.by_subproject(@project) || []
37 @issues_by_subproject = Issue.by_subproject(@project) || []
38
38
39 render :template => "reports/issue_report"
39 render :template => "reports/issue_report"
40 end
40 end
41
41
42 def issue_report_details
42 def issue_report_details
43 case params[:detail]
43 case params[:detail]
44 when "tracker"
44 when "tracker"
45 @field = "tracker_id"
45 @field = "tracker_id"
46 @rows = @project.trackers
46 @rows = @project.trackers
47 @data = Issue.by_tracker(@project)
47 @data = Issue.by_tracker(@project)
48 @report_title = l(:field_tracker)
48 @report_title = l(:field_tracker)
49 when "version"
49 when "version"
50 @field = "fixed_version_id"
50 @field = "fixed_version_id"
51 @rows = @project.shared_versions.sort
51 @rows = @project.shared_versions.sort
52 @data = Issue.by_version(@project)
52 @data = Issue.by_version(@project)
53 @report_title = l(:field_version)
53 @report_title = l(:field_version)
54 when "priority"
54 when "priority"
55 @field = "priority_id"
55 @field = "priority_id"
56 @rows = IssuePriority.all.reverse
56 @rows = IssuePriority.all.reverse
57 @data = Issue.by_priority(@project)
57 @data = Issue.by_priority(@project)
58 @report_title = l(:field_priority)
58 @report_title = l(:field_priority)
59 when "category"
59 when "category"
60 @field = "category_id"
60 @field = "category_id"
61 @rows = @project.issue_categories
61 @rows = @project.issue_categories
62 @data = Issue.by_category(@project)
62 @data = Issue.by_category(@project)
63 @report_title = l(:field_category)
63 @report_title = l(:field_category)
64 when "assigned_to"
64 when "assigned_to"
65 @field = "assigned_to_id"
65 @field = "assigned_to_id"
66 @rows = (Setting.issue_group_assignment? ? @project.principals : @project.users).sort
66 @rows = (Setting.issue_group_assignment? ? @project.principals : @project.users).sort
67 @data = Issue.by_assigned_to(@project)
67 @data = Issue.by_assigned_to(@project)
68 @report_title = l(:field_assigned_to)
68 @report_title = l(:field_assigned_to)
69 when "author"
69 when "author"
70 @field = "author_id"
70 @field = "author_id"
71 @rows = @project.users.sort
71 @rows = @project.users.sort
72 @data = Issue.by_author(@project)
72 @data = Issue.by_author(@project)
73 @report_title = l(:field_author)
73 @report_title = l(:field_author)
74 when "subproject"
74 when "subproject"
75 @field = "project_id"
75 @field = "project_id"
76 @rows = @project.descendants.visible
76 @rows = @project.descendants.visible
77 @data = Issue.by_subproject(@project) || []
77 @data = Issue.by_subproject(@project) || []
78 @report_title = l(:field_subproject)
78 @report_title = l(:field_subproject)
79 end
79 end
80
80
81 respond_to do |format|
81 respond_to do |format|
82 if @field
82 if @field
83 format.html {}
83 format.html {}
84 else
84 else
85 format.html { redirect_to :action => 'issue_report', :id => @project }
85 format.html { redirect_to :action => 'issue_report', :id => @project }
86 end
86 end
87 end
87 end
88 end
88 end
89
89
90 private
90 private
91
91
92 def find_issue_statuses
92 def find_issue_statuses
93 @statuses = IssueStatus.find(:all, :order => 'position')
93 @statuses = IssueStatus.sorted.all
94 end
94 end
95 end
95 end
@@ -1,433 +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 self, @changeset_count,
142 per_page_option,
142 per_page_option,
143 params['page']
143 params['page']
144 @changesets = @repository.changesets.find(:all,
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.current.offset).
147 :include => [:user, :repository, :parents])
147 includes(:user, :repository, :parents).
148 all
148
149
149 respond_to do |format|
150 respond_to do |format|
150 format.html { render :layout => false if request.xhr? }
151 format.html { render :layout => false if request.xhr? }
151 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)}") }
152 end
153 end
153 end
154 end
154
155
155 def raw
156 def raw
156 entry_and_raw(true)
157 entry_and_raw(true)
157 end
158 end
158
159
159 def entry
160 def entry
160 entry_and_raw(false)
161 entry_and_raw(false)
161 end
162 end
162
163
163 def entry_and_raw(is_raw)
164 def entry_and_raw(is_raw)
164 @entry = @repository.entry(@path, @rev)
165 @entry = @repository.entry(@path, @rev)
165 (show_error_not_found; return) unless @entry
166 (show_error_not_found; return) unless @entry
166
167
167 # If the entry is a dir, show the browser
168 # If the entry is a dir, show the browser
168 (show; return) if @entry.is_dir?
169 (show; return) if @entry.is_dir?
169
170
170 @content = @repository.cat(@path, @rev)
171 @content = @repository.cat(@path, @rev)
171 (show_error_not_found; return) unless @content
172 (show_error_not_found; return) unless @content
172 if is_raw ||
173 if is_raw ||
173 (@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) ||
174 ! is_entry_text_data?(@content, @path)
175 ! is_entry_text_data?(@content, @path)
175 # Force the download
176 # Force the download
176 send_opt = { :filename => filename_for_content_disposition(@path.split('/').last) }
177 send_opt = { :filename => filename_for_content_disposition(@path.split('/').last) }
177 send_type = Redmine::MimeType.of(@path)
178 send_type = Redmine::MimeType.of(@path)
178 send_opt[:type] = send_type.to_s if send_type
179 send_opt[:type] = send_type.to_s if send_type
179 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')
180 send_data @content, send_opt
181 send_data @content, send_opt
181 else
182 else
182 # Prevent empty lines when displaying a file with Windows style eol
183 # Prevent empty lines when displaying a file with Windows style eol
183 # TODO: UTF-16
184 # TODO: UTF-16
184 # Is this needs? AttachmentsController reads file simply.
185 # Is this needs? AttachmentsController reads file simply.
185 @content.gsub!("\r\n", "\n")
186 @content.gsub!("\r\n", "\n")
186 @changeset = @repository.find_changeset_by_name(@rev)
187 @changeset = @repository.find_changeset_by_name(@rev)
187 end
188 end
188 end
189 end
189 private :entry_and_raw
190 private :entry_and_raw
190
191
191 def is_entry_text_data?(ent, path)
192 def is_entry_text_data?(ent, path)
192 # UTF-16 contains "\x00".
193 # UTF-16 contains "\x00".
193 # 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
194 # in non Western Europe.
195 # in non Western Europe.
195 return true if Redmine::MimeType.is_type?('text', path)
196 return true if Redmine::MimeType.is_type?('text', path)
196 # Ruby 1.8.6 has a bug of integer divisions.
197 # Ruby 1.8.6 has a bug of integer divisions.
197 # 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
198 return false if ent.is_binary_data?
199 return false if ent.is_binary_data?
199 true
200 true
200 end
201 end
201 private :is_entry_text_data?
202 private :is_entry_text_data?
202
203
203 def annotate
204 def annotate
204 @entry = @repository.entry(@path, @rev)
205 @entry = @repository.entry(@path, @rev)
205 (show_error_not_found; return) unless @entry
206 (show_error_not_found; return) unless @entry
206
207
207 @annotate = @repository.scm.annotate(@path, @rev)
208 @annotate = @repository.scm.annotate(@path, @rev)
208 if @annotate.nil? || @annotate.empty?
209 if @annotate.nil? || @annotate.empty?
209 (render_error l(:error_scm_annotate); return)
210 (render_error l(:error_scm_annotate); return)
210 end
211 end
211 ann_buf_size = 0
212 ann_buf_size = 0
212 @annotate.lines.each do |buf|
213 @annotate.lines.each do |buf|
213 ann_buf_size += buf.size
214 ann_buf_size += buf.size
214 end
215 end
215 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
216 (render_error l(:error_scm_annotate_big_text_file); return)
217 (render_error l(:error_scm_annotate_big_text_file); return)
217 end
218 end
218 @changeset = @repository.find_changeset_by_name(@rev)
219 @changeset = @repository.find_changeset_by_name(@rev)
219 end
220 end
220
221
221 def revision
222 def revision
222 respond_to do |format|
223 respond_to do |format|
223 format.html
224 format.html
224 format.js {render :layout => false}
225 format.js {render :layout => false}
225 end
226 end
226 end
227 end
227
228
228 # Adds a related issue to a changeset
229 # Adds a related issue to a changeset
229 # POST /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues
230 # POST /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues
230 def add_related_issue
231 def add_related_issue
231 @issue = @changeset.find_referenced_issue_by_id(params[:issue_id])
232 @issue = @changeset.find_referenced_issue_by_id(params[:issue_id])
232 if @issue && (!@issue.visible? || @changeset.issues.include?(@issue))
233 if @issue && (!@issue.visible? || @changeset.issues.include?(@issue))
233 @issue = nil
234 @issue = nil
234 end
235 end
235
236
236 if @issue
237 if @issue
237 @changeset.issues << @issue
238 @changeset.issues << @issue
238 end
239 end
239 end
240 end
240
241
241 # Removes a related issue from a changeset
242 # Removes a related issue from a changeset
242 # 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
243 def remove_related_issue
244 def remove_related_issue
244 @issue = Issue.visible.find_by_id(params[:issue_id])
245 @issue = Issue.visible.find_by_id(params[:issue_id])
245 if @issue
246 if @issue
246 @changeset.issues.delete(@issue)
247 @changeset.issues.delete(@issue)
247 end
248 end
248 end
249 end
249
250
250 def diff
251 def diff
251 if params[:format] == 'diff'
252 if params[:format] == 'diff'
252 @diff = @repository.diff(@path, @rev, @rev_to)
253 @diff = @repository.diff(@path, @rev, @rev_to)
253 (show_error_not_found; return) unless @diff
254 (show_error_not_found; return) unless @diff
254 filename = "changeset_r#{@rev}"
255 filename = "changeset_r#{@rev}"
255 filename << "_r#{@rev_to}" if @rev_to
256 filename << "_r#{@rev_to}" if @rev_to
256 send_data @diff.join, :filename => "#{filename}.diff",
257 send_data @diff.join, :filename => "#{filename}.diff",
257 :type => 'text/x-patch',
258 :type => 'text/x-patch',
258 :disposition => 'attachment'
259 :disposition => 'attachment'
259 else
260 else
260 @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
261 @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
261 @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
262 @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
262
263
263 # Save diff type as user preference
264 # Save diff type as user preference
264 if User.current.logged? && @diff_type != User.current.pref[:diff_type]
265 if User.current.logged? && @diff_type != User.current.pref[:diff_type]
265 User.current.pref[:diff_type] = @diff_type
266 User.current.pref[:diff_type] = @diff_type
266 User.current.preference.save
267 User.current.preference.save
267 end
268 end
268 @cache_key = "repositories/diff/#{@repository.id}/" +
269 @cache_key = "repositories/diff/#{@repository.id}/" +
269 Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}-#{current_language}")
270 Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}-#{current_language}")
270 unless read_fragment(@cache_key)
271 unless read_fragment(@cache_key)
271 @diff = @repository.diff(@path, @rev, @rev_to)
272 @diff = @repository.diff(@path, @rev, @rev_to)
272 show_error_not_found unless @diff
273 show_error_not_found unless @diff
273 end
274 end
274
275
275 @changeset = @repository.find_changeset_by_name(@rev)
276 @changeset = @repository.find_changeset_by_name(@rev)
276 @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
277 @diff_format_revisions = @repository.diff_format_revisions(@changeset, @changeset_to)
278 @diff_format_revisions = @repository.diff_format_revisions(@changeset, @changeset_to)
278 end
279 end
279 end
280 end
280
281
281 def stats
282 def stats
282 end
283 end
283
284
284 def graph
285 def graph
285 data = nil
286 data = nil
286 case params[:graph]
287 case params[:graph]
287 when "commits_per_month"
288 when "commits_per_month"
288 data = graph_commits_per_month(@repository)
289 data = graph_commits_per_month(@repository)
289 when "commits_per_author"
290 when "commits_per_author"
290 data = graph_commits_per_author(@repository)
291 data = graph_commits_per_author(@repository)
291 end
292 end
292 if data
293 if data
293 headers["Content-Type"] = "image/svg+xml"
294 headers["Content-Type"] = "image/svg+xml"
294 send_data(data, :type => "image/svg+xml", :disposition => "inline")
295 send_data(data, :type => "image/svg+xml", :disposition => "inline")
295 else
296 else
296 render_404
297 render_404
297 end
298 end
298 end
299 end
299
300
300 private
301 private
301
302
302 def find_repository
303 def find_repository
303 @repository = Repository.find(params[:id])
304 @repository = Repository.find(params[:id])
304 @project = @repository.project
305 @project = @repository.project
305 rescue ActiveRecord::RecordNotFound
306 rescue ActiveRecord::RecordNotFound
306 render_404
307 render_404
307 end
308 end
308
309
309 REV_PARAM_RE = %r{\A[a-f0-9]*\Z}i
310 REV_PARAM_RE = %r{\A[a-f0-9]*\Z}i
310
311
311 def find_project_repository
312 def find_project_repository
312 @project = Project.find(params[:id])
313 @project = Project.find(params[:id])
313 if params[:repository_id].present?
314 if params[:repository_id].present?
314 @repository = @project.repositories.find_by_identifier_param(params[:repository_id])
315 @repository = @project.repositories.find_by_identifier_param(params[:repository_id])
315 else
316 else
316 @repository = @project.repository
317 @repository = @project.repository
317 end
318 end
318 (render_404; return false) unless @repository
319 (render_404; return false) unless @repository
319 @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
320 @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
321 @rev_to = params[:rev_to]
322 @rev_to = params[:rev_to]
322
323
323 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)
324 if @repository.branches.blank?
325 if @repository.branches.blank?
325 raise InvalidRevisionParam
326 raise InvalidRevisionParam
326 end
327 end
327 end
328 end
328 rescue ActiveRecord::RecordNotFound
329 rescue ActiveRecord::RecordNotFound
329 render_404
330 render_404
330 rescue InvalidRevisionParam
331 rescue InvalidRevisionParam
331 show_error_not_found
332 show_error_not_found
332 end
333 end
333
334
334 def find_changeset
335 def find_changeset
335 if @rev.present?
336 if @rev.present?
336 @changeset = @repository.find_changeset_by_name(@rev)
337 @changeset = @repository.find_changeset_by_name(@rev)
337 end
338 end
338 show_error_not_found unless @changeset
339 show_error_not_found unless @changeset
339 end
340 end
340
341
341 def show_error_not_found
342 def show_error_not_found
342 render_error :message => l(:error_scm_not_found), :status => 404
343 render_error :message => l(:error_scm_not_found), :status => 404
343 end
344 end
344
345
345 # Handler for Redmine::Scm::Adapters::CommandFailed exception
346 # Handler for Redmine::Scm::Adapters::CommandFailed exception
346 def show_error_command_failed(exception)
347 def show_error_command_failed(exception)
347 render_error l(:error_scm_command_failed, exception.message)
348 render_error l(:error_scm_command_failed, exception.message)
348 end
349 end
349
350
350 def graph_commits_per_month(repository)
351 def graph_commits_per_month(repository)
351 @date_to = Date.today
352 @date_to = Date.today
352 @date_from = @date_to << 11
353 @date_from = @date_to << 11
353 @date_from = Date.civil(@date_from.year, @date_from.month, 1)
354 @date_from = Date.civil(@date_from.year, @date_from.month, 1)
354 commits_by_day = Changeset.count(
355 commits_by_day = Changeset.count(
355 :all, :group => :commit_date,
356 :all, :group => :commit_date,
356 :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])
357 commits_by_month = [0] * 12
358 commits_by_month = [0] * 12
358 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 }
359
360
360 changes_by_day = Change.count(
361 changes_by_day = Change.count(
361 :all, :group => :commit_date, :include => :changeset,
362 :all, :group => :commit_date, :include => :changeset,
362 :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])
363 changes_by_month = [0] * 12
364 changes_by_month = [0] * 12
364 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 }
365
366
366 fields = []
367 fields = []
367 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)}
368
369
369 graph = SVG::Graph::Bar.new(
370 graph = SVG::Graph::Bar.new(
370 :height => 300,
371 :height => 300,
371 :width => 800,
372 :width => 800,
372 :fields => fields.reverse,
373 :fields => fields.reverse,
373 :stack => :side,
374 :stack => :side,
374 :scale_integers => true,
375 :scale_integers => true,
375 :step_x_labels => 2,
376 :step_x_labels => 2,
376 :show_data_values => false,
377 :show_data_values => false,
377 :graph_title => l(:label_commits_per_month),
378 :graph_title => l(:label_commits_per_month),
378 :show_graph_title => true
379 :show_graph_title => true
379 )
380 )
380
381
381 graph.add_data(
382 graph.add_data(
382 :data => commits_by_month[0..11].reverse,
383 :data => commits_by_month[0..11].reverse,
383 :title => l(:label_revision_plural)
384 :title => l(:label_revision_plural)
384 )
385 )
385
386
386 graph.add_data(
387 graph.add_data(
387 :data => changes_by_month[0..11].reverse,
388 :data => changes_by_month[0..11].reverse,
388 :title => l(:label_change_plural)
389 :title => l(:label_change_plural)
389 )
390 )
390
391
391 graph.burn
392 graph.burn
392 end
393 end
393
394
394 def graph_commits_per_author(repository)
395 def graph_commits_per_author(repository)
395 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])
396 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}
397
398
398 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])
399 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}
400
401
401 fields = commits_by_author.collect {|r| r.first}
402 fields = commits_by_author.collect {|r| r.first}
402 commits_data = commits_by_author.collect {|r| r.last}
403 commits_data = commits_by_author.collect {|r| r.last}
403 changes_data = commits_by_author.collect {|r| h[r.first] || 0}
404 changes_data = commits_by_author.collect {|r| h[r.first] || 0}
404
405
405 fields = fields + [""]*(10 - fields.length) if fields.length<10
406 fields = fields + [""]*(10 - fields.length) if fields.length<10
406 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
407 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
408
409
409 # Remove email adress in usernames
410 # Remove email adress in usernames
410 fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
411 fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
411
412
412 graph = SVG::Graph::BarHorizontal.new(
413 graph = SVG::Graph::BarHorizontal.new(
413 :height => 400,
414 :height => 400,
414 :width => 800,
415 :width => 800,
415 :fields => fields,
416 :fields => fields,
416 :stack => :side,
417 :stack => :side,
417 :scale_integers => true,
418 :scale_integers => true,
418 :show_data_values => false,
419 :show_data_values => false,
419 :rotate_y_labels => false,
420 :rotate_y_labels => false,
420 :graph_title => l(:label_commits_per_author),
421 :graph_title => l(:label_commits_per_author),
421 :show_graph_title => true
422 :show_graph_title => true
422 )
423 )
423 graph.add_data(
424 graph.add_data(
424 :data => commits_data,
425 :data => commits_data,
425 :title => l(:label_revision_plural)
426 :title => l(:label_revision_plural)
426 )
427 )
427 graph.add_data(
428 graph.add_data(
428 :data => changes_data,
429 :data => changes_data,
429 :title => l(:label_change_plural)
430 :title => l(:label_change_plural)
430 )
431 )
431 graph.burn
432 graph.burn
432 end
433 end
433 end
434 end
@@ -1,101 +1,101
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 TrackersController < ApplicationController
18 class TrackersController < ApplicationController
19 layout 'admin'
19 layout 'admin'
20
20
21 before_filter :require_admin, :except => :index
21 before_filter :require_admin, :except => :index
22 before_filter :require_admin_or_api_request, :only => :index
22 before_filter :require_admin_or_api_request, :only => :index
23 accept_api_auth :index
23 accept_api_auth :index
24
24
25 def index
25 def index
26 respond_to do |format|
26 respond_to do |format|
27 format.html {
27 format.html {
28 @tracker_pages, @trackers = paginate :trackers, :per_page => 10, :order => 'position'
28 @tracker_pages, @trackers = paginate :trackers, :per_page => 10, :order => 'position'
29 render :action => "index", :layout => false if request.xhr?
29 render :action => "index", :layout => false if request.xhr?
30 }
30 }
31 format.api {
31 format.api {
32 @trackers = Tracker.sorted.all
32 @trackers = Tracker.sorted.all
33 }
33 }
34 end
34 end
35 end
35 end
36
36
37 def new
37 def new
38 @tracker ||= Tracker.new(params[:tracker])
38 @tracker ||= Tracker.new(params[:tracker])
39 @trackers = Tracker.find :all, :order => 'position'
39 @trackers = Tracker.find :all, :order => 'position'
40 @projects = Project.find(:all)
40 @projects = Project.all
41 end
41 end
42
42
43 def create
43 def create
44 @tracker = Tracker.new(params[:tracker])
44 @tracker = Tracker.new(params[:tracker])
45 if request.post? and @tracker.save
45 if request.post? and @tracker.save
46 # workflow copy
46 # workflow copy
47 if !params[:copy_workflow_from].blank? && (copy_from = Tracker.find_by_id(params[:copy_workflow_from]))
47 if !params[:copy_workflow_from].blank? && (copy_from = Tracker.find_by_id(params[:copy_workflow_from]))
48 @tracker.workflow_rules.copy(copy_from)
48 @tracker.workflow_rules.copy(copy_from)
49 end
49 end
50 flash[:notice] = l(:notice_successful_create)
50 flash[:notice] = l(:notice_successful_create)
51 redirect_to :action => 'index'
51 redirect_to :action => 'index'
52 return
52 return
53 end
53 end
54 new
54 new
55 render :action => 'new'
55 render :action => 'new'
56 end
56 end
57
57
58 def edit
58 def edit
59 @tracker ||= Tracker.find(params[:id])
59 @tracker ||= Tracker.find(params[:id])
60 @projects = Project.find(:all)
60 @projects = Project.all
61 end
61 end
62
62
63 def update
63 def update
64 @tracker = Tracker.find(params[:id])
64 @tracker = Tracker.find(params[:id])
65 if request.put? and @tracker.update_attributes(params[:tracker])
65 if request.put? and @tracker.update_attributes(params[:tracker])
66 flash[:notice] = l(:notice_successful_update)
66 flash[:notice] = l(:notice_successful_update)
67 redirect_to :action => 'index'
67 redirect_to :action => 'index'
68 return
68 return
69 end
69 end
70 edit
70 edit
71 render :action => 'edit'
71 render :action => 'edit'
72 end
72 end
73
73
74 def destroy
74 def destroy
75 @tracker = Tracker.find(params[:id])
75 @tracker = Tracker.find(params[:id])
76 unless @tracker.issues.empty?
76 unless @tracker.issues.empty?
77 flash[:error] = l(:error_can_not_delete_tracker)
77 flash[:error] = l(:error_can_not_delete_tracker)
78 else
78 else
79 @tracker.destroy
79 @tracker.destroy
80 end
80 end
81 redirect_to :action => 'index'
81 redirect_to :action => 'index'
82 end
82 end
83
83
84 def fields
84 def fields
85 if request.post? && params[:trackers]
85 if request.post? && params[:trackers]
86 params[:trackers].each do |tracker_id, tracker_params|
86 params[:trackers].each do |tracker_id, tracker_params|
87 tracker = Tracker.find_by_id(tracker_id)
87 tracker = Tracker.find_by_id(tracker_id)
88 if tracker
88 if tracker
89 tracker.core_fields = tracker_params[:core_fields]
89 tracker.core_fields = tracker_params[:core_fields]
90 tracker.custom_field_ids = tracker_params[:custom_field_ids]
90 tracker.custom_field_ids = tracker_params[:custom_field_ids]
91 tracker.save
91 tracker.save
92 end
92 end
93 end
93 end
94 flash[:notice] = l(:notice_successful_update)
94 flash[:notice] = l(:notice_successful_update)
95 redirect_to :action => 'fields'
95 redirect_to :action => 'fields'
96 return
96 return
97 end
97 end
98 @trackers = Tracker.sorted.all
98 @trackers = Tracker.sorted.all
99 @custom_fields = IssueCustomField.all.sort
99 @custom_fields = IssueCustomField.all.sort
100 end
100 end
101 end
101 end
@@ -1,214 +1,214
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 self, @user_count, @limit, params['page']
49 @offset ||= @user_pages.current.offset
49 @offset ||= @user_pages.current.offset
50 @users = scope.find :all,
50 @users = scope.find :all,
51 :order => sort_clause,
51 :order => sort_clause,
52 :limit => @limit,
52 :limit => @limit,
53 :offset => @offset
53 :offset => @offset
54
54
55 respond_to do |format|
55 respond_to do |format|
56 format.html {
56 format.html {
57 @groups = Group.all.sort
57 @groups = Group.all.sort
58 render :layout => !request.xhr?
58 render :layout => !request.xhr?
59 }
59 }
60 format.api
60 format.api
61 end
61 end
62 end
62 end
63
63
64 def show
64 def show
65 # show projects based on current user visibility
65 # show projects based on current user visibility
66 @memberships = @user.memberships.all(:conditions => Project.visible_condition(User.current))
66 @memberships = @user.memberships.all(:conditions => Project.visible_condition(User.current))
67
67
68 events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10)
68 events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10)
69 @events_by_day = events.group_by(&:event_date)
69 @events_by_day = events.group_by(&:event_date)
70
70
71 unless User.current.admin?
71 unless User.current.admin?
72 if !@user.active? || (@user != User.current && @memberships.empty? && events.empty?)
72 if !@user.active? || (@user != User.current && @memberships.empty? && events.empty?)
73 render_404
73 render_404
74 return
74 return
75 end
75 end
76 end
76 end
77
77
78 respond_to do |format|
78 respond_to do |format|
79 format.html { render :layout => 'base' }
79 format.html { render :layout => 'base' }
80 format.api
80 format.api
81 end
81 end
82 end
82 end
83
83
84 def new
84 def new
85 @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
85 @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
86 @auth_sources = AuthSource.find(:all)
86 @auth_sources = AuthSource.all
87 end
87 end
88
88
89 def create
89 def create
90 @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
90 @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
91 @user.safe_attributes = params[:user]
91 @user.safe_attributes = params[:user]
92 @user.admin = params[:user][:admin] || false
92 @user.admin = params[:user][:admin] || false
93 @user.login = params[:user][:login]
93 @user.login = params[:user][:login]
94 @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id
94 @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id
95
95
96 if @user.save
96 if @user.save
97 @user.pref.attributes = params[:pref]
97 @user.pref.attributes = params[:pref]
98 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
98 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
99 @user.pref.save
99 @user.pref.save
100 @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
100 @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
101
101
102 Mailer.account_information(@user, params[:user][:password]).deliver if params[:send_information]
102 Mailer.account_information(@user, params[:user][:password]).deliver if params[:send_information]
103
103
104 respond_to do |format|
104 respond_to do |format|
105 format.html {
105 format.html {
106 flash[:notice] = l(:notice_user_successful_create, :id => view_context.link_to(@user.login, user_path(@user)))
106 flash[:notice] = l(:notice_user_successful_create, :id => view_context.link_to(@user.login, user_path(@user)))
107 redirect_to(params[:continue] ?
107 redirect_to(params[:continue] ?
108 {:controller => 'users', :action => 'new'} :
108 {:controller => 'users', :action => 'new'} :
109 {:controller => 'users', :action => 'edit', :id => @user}
109 {:controller => 'users', :action => 'edit', :id => @user}
110 )
110 )
111 }
111 }
112 format.api { render :action => 'show', :status => :created, :location => user_url(@user) }
112 format.api { render :action => 'show', :status => :created, :location => user_url(@user) }
113 end
113 end
114 else
114 else
115 @auth_sources = AuthSource.find(:all)
115 @auth_sources = AuthSource.all
116 # Clear password input
116 # Clear password input
117 @user.password = @user.password_confirmation = nil
117 @user.password = @user.password_confirmation = nil
118
118
119 respond_to do |format|
119 respond_to do |format|
120 format.html { render :action => 'new' }
120 format.html { render :action => 'new' }
121 format.api { render_validation_errors(@user) }
121 format.api { render_validation_errors(@user) }
122 end
122 end
123 end
123 end
124 end
124 end
125
125
126 def edit
126 def edit
127 @auth_sources = AuthSource.find(:all)
127 @auth_sources = AuthSource.all
128 @membership ||= Member.new
128 @membership ||= Member.new
129 end
129 end
130
130
131 def update
131 def update
132 @user.admin = params[:user][:admin] if params[:user][:admin]
132 @user.admin = params[:user][:admin] if params[:user][:admin]
133 @user.login = params[:user][:login] if params[:user][:login]
133 @user.login = params[:user][:login] if params[:user][:login]
134 if params[:user][:password].present? && (@user.auth_source_id.nil? || params[:user][:auth_source_id].blank?)
134 if params[:user][:password].present? && (@user.auth_source_id.nil? || params[:user][:auth_source_id].blank?)
135 @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation]
135 @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation]
136 end
136 end
137 @user.safe_attributes = params[:user]
137 @user.safe_attributes = params[:user]
138 # Was the account actived ? (do it before User#save clears the change)
138 # Was the account actived ? (do it before User#save clears the change)
139 was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE])
139 was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE])
140 # TODO: Similar to My#account
140 # TODO: Similar to My#account
141 @user.pref.attributes = params[:pref]
141 @user.pref.attributes = params[:pref]
142 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
142 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
143
143
144 if @user.save
144 if @user.save
145 @user.pref.save
145 @user.pref.save
146 @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
146 @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
147
147
148 if was_activated
148 if was_activated
149 Mailer.account_activated(@user).deliver
149 Mailer.account_activated(@user).deliver
150 elsif @user.active? && params[:send_information] && !params[:user][:password].blank? && @user.auth_source_id.nil?
150 elsif @user.active? && params[:send_information] && !params[:user][:password].blank? && @user.auth_source_id.nil?
151 Mailer.account_information(@user, params[:user][:password]).deliver
151 Mailer.account_information(@user, params[:user][:password]).deliver
152 end
152 end
153
153
154 respond_to do |format|
154 respond_to do |format|
155 format.html {
155 format.html {
156 flash[:notice] = l(:notice_successful_update)
156 flash[:notice] = l(:notice_successful_update)
157 redirect_to_referer_or edit_user_path(@user)
157 redirect_to_referer_or edit_user_path(@user)
158 }
158 }
159 format.api { render_api_ok }
159 format.api { render_api_ok }
160 end
160 end
161 else
161 else
162 @auth_sources = AuthSource.find(:all)
162 @auth_sources = AuthSource.all
163 @membership ||= Member.new
163 @membership ||= Member.new
164 # Clear password input
164 # Clear password input
165 @user.password = @user.password_confirmation = nil
165 @user.password = @user.password_confirmation = nil
166
166
167 respond_to do |format|
167 respond_to do |format|
168 format.html { render :action => :edit }
168 format.html { render :action => :edit }
169 format.api { render_validation_errors(@user) }
169 format.api { render_validation_errors(@user) }
170 end
170 end
171 end
171 end
172 end
172 end
173
173
174 def destroy
174 def destroy
175 @user.destroy
175 @user.destroy
176 respond_to do |format|
176 respond_to do |format|
177 format.html { redirect_back_or_default(users_url) }
177 format.html { redirect_back_or_default(users_url) }
178 format.api { render_api_ok }
178 format.api { render_api_ok }
179 end
179 end
180 end
180 end
181
181
182 def edit_membership
182 def edit_membership
183 @membership = Member.edit_membership(params[:membership_id], params[:membership], @user)
183 @membership = Member.edit_membership(params[:membership_id], params[:membership], @user)
184 @membership.save
184 @membership.save
185 respond_to do |format|
185 respond_to do |format|
186 format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
186 format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
187 format.js
187 format.js
188 end
188 end
189 end
189 end
190
190
191 def destroy_membership
191 def destroy_membership
192 @membership = Member.find(params[:membership_id])
192 @membership = Member.find(params[:membership_id])
193 if @membership.deletable?
193 if @membership.deletable?
194 @membership.destroy
194 @membership.destroy
195 end
195 end
196 respond_to do |format|
196 respond_to do |format|
197 format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
197 format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
198 format.js
198 format.js
199 end
199 end
200 end
200 end
201
201
202 private
202 private
203
203
204 def find_user
204 def find_user
205 if params[:id] == 'current'
205 if params[:id] == 'current'
206 require_login || return
206 require_login || return
207 @user = User.current
207 @user = User.current
208 else
208 else
209 @user = User.find(params[:id])
209 @user = User.find(params[:id])
210 end
210 end
211 rescue ActiveRecord::RecordNotFound
211 rescue ActiveRecord::RecordNotFound
212 render_404
212 render_404
213 end
213 end
214 end
214 end
@@ -1,181 +1,182
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 VersionsController < ApplicationController
18 class VersionsController < ApplicationController
19 menu_item :roadmap
19 menu_item :roadmap
20 model_object Version
20 model_object Version
21 before_filter :find_model_object, :except => [:index, :new, :create, :close_completed]
21 before_filter :find_model_object, :except => [:index, :new, :create, :close_completed]
22 before_filter :find_project_from_association, :except => [:index, :new, :create, :close_completed]
22 before_filter :find_project_from_association, :except => [:index, :new, :create, :close_completed]
23 before_filter :find_project_by_project_id, :only => [:index, :new, :create, :close_completed]
23 before_filter :find_project_by_project_id, :only => [:index, :new, :create, :close_completed]
24 before_filter :authorize
24 before_filter :authorize
25
25
26 accept_api_auth :index, :show, :create, :update, :destroy
26 accept_api_auth :index, :show, :create, :update, :destroy
27
27
28 helper :custom_fields
28 helper :custom_fields
29 helper :projects
29 helper :projects
30
30
31 def index
31 def index
32 respond_to do |format|
32 respond_to do |format|
33 format.html {
33 format.html {
34 @trackers = @project.trackers.find(:all, :order => 'position')
34 @trackers = @project.trackers.sorted.all
35 retrieve_selected_tracker_ids(@trackers, @trackers.select {|t| t.is_in_roadmap?})
35 retrieve_selected_tracker_ids(@trackers, @trackers.select {|t| t.is_in_roadmap?})
36 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
36 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
37 project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id]
37 project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id]
38
38
39 @versions = @project.shared_versions || []
39 @versions = @project.shared_versions || []
40 @versions += @project.rolled_up_versions.visible if @with_subprojects
40 @versions += @project.rolled_up_versions.visible if @with_subprojects
41 @versions = @versions.uniq.sort
41 @versions = @versions.uniq.sort
42 unless params[:completed]
42 unless params[:completed]
43 @completed_versions = @versions.select {|version| version.closed? || version.completed? }
43 @completed_versions = @versions.select {|version| version.closed? || version.completed? }
44 @versions -= @completed_versions
44 @versions -= @completed_versions
45 end
45 end
46
46
47 @issues_by_version = {}
47 @issues_by_version = {}
48 if @selected_tracker_ids.any? && @versions.any?
48 if @selected_tracker_ids.any? && @versions.any?
49 issues = Issue.visible.all(
49 issues = Issue.visible.all(
50 :include => [:project, :status, :tracker, :priority, :fixed_version],
50 :include => [:project, :status, :tracker, :priority, :fixed_version],
51 :conditions => {:tracker_id => @selected_tracker_ids, :project_id => project_ids, :fixed_version_id => @versions.map(&:id)},
51 :conditions => {:tracker_id => @selected_tracker_ids, :project_id => project_ids, :fixed_version_id => @versions.map(&:id)},
52 :order => "#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id"
52 :order => "#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id"
53 )
53 )
54 @issues_by_version = issues.group_by(&:fixed_version)
54 @issues_by_version = issues.group_by(&:fixed_version)
55 end
55 end
56 @versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?}
56 @versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?}
57 }
57 }
58 format.api {
58 format.api {
59 @versions = @project.shared_versions.all
59 @versions = @project.shared_versions.all
60 }
60 }
61 end
61 end
62 end
62 end
63
63
64 def show
64 def show
65 respond_to do |format|
65 respond_to do |format|
66 format.html {
66 format.html {
67 @issues = @version.fixed_issues.visible.find(:all,
67 @issues = @version.fixed_issues.visible.
68 :include => [:status, :tracker, :priority],
68 includes(:status, :tracker, :priority).
69 :order => "#{Tracker.table_name}.position, #{Issue.table_name}.id")
69 reorder("#{Tracker.table_name}.position, #{Issue.table_name}.id").
70 all
70 }
71 }
71 format.api
72 format.api
72 end
73 end
73 end
74 end
74
75
75 def new
76 def new
76 @version = @project.versions.build
77 @version = @project.versions.build
77 @version.safe_attributes = params[:version]
78 @version.safe_attributes = params[:version]
78
79
79 respond_to do |format|
80 respond_to do |format|
80 format.html
81 format.html
81 format.js
82 format.js
82 end
83 end
83 end
84 end
84
85
85 def create
86 def create
86 @version = @project.versions.build
87 @version = @project.versions.build
87 if params[:version]
88 if params[:version]
88 attributes = params[:version].dup
89 attributes = params[:version].dup
89 attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing'])
90 attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing'])
90 @version.safe_attributes = attributes
91 @version.safe_attributes = attributes
91 end
92 end
92
93
93 if request.post?
94 if request.post?
94 if @version.save
95 if @version.save
95 respond_to do |format|
96 respond_to do |format|
96 format.html do
97 format.html do
97 flash[:notice] = l(:notice_successful_create)
98 flash[:notice] = l(:notice_successful_create)
98 redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
99 redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
99 end
100 end
100 format.js
101 format.js
101 format.api do
102 format.api do
102 render :action => 'show', :status => :created, :location => version_url(@version)
103 render :action => 'show', :status => :created, :location => version_url(@version)
103 end
104 end
104 end
105 end
105 else
106 else
106 respond_to do |format|
107 respond_to do |format|
107 format.html { render :action => 'new' }
108 format.html { render :action => 'new' }
108 format.js { render :action => 'new' }
109 format.js { render :action => 'new' }
109 format.api { render_validation_errors(@version) }
110 format.api { render_validation_errors(@version) }
110 end
111 end
111 end
112 end
112 end
113 end
113 end
114 end
114
115
115 def edit
116 def edit
116 end
117 end
117
118
118 def update
119 def update
119 if request.put? && params[:version]
120 if request.put? && params[:version]
120 attributes = params[:version].dup
121 attributes = params[:version].dup
121 attributes.delete('sharing') unless @version.allowed_sharings.include?(attributes['sharing'])
122 attributes.delete('sharing') unless @version.allowed_sharings.include?(attributes['sharing'])
122 @version.safe_attributes = attributes
123 @version.safe_attributes = attributes
123 if @version.save
124 if @version.save
124 respond_to do |format|
125 respond_to do |format|
125 format.html {
126 format.html {
126 flash[:notice] = l(:notice_successful_update)
127 flash[:notice] = l(:notice_successful_update)
127 redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
128 redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
128 }
129 }
129 format.api { render_api_ok }
130 format.api { render_api_ok }
130 end
131 end
131 else
132 else
132 respond_to do |format|
133 respond_to do |format|
133 format.html { render :action => 'edit' }
134 format.html { render :action => 'edit' }
134 format.api { render_validation_errors(@version) }
135 format.api { render_validation_errors(@version) }
135 end
136 end
136 end
137 end
137 end
138 end
138 end
139 end
139
140
140 def close_completed
141 def close_completed
141 if request.put?
142 if request.put?
142 @project.close_completed_versions
143 @project.close_completed_versions
143 end
144 end
144 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
145 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
145 end
146 end
146
147
147 def destroy
148 def destroy
148 if @version.fixed_issues.empty?
149 if @version.fixed_issues.empty?
149 @version.destroy
150 @version.destroy
150 respond_to do |format|
151 respond_to do |format|
151 format.html { redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project }
152 format.html { redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project }
152 format.api { render_api_ok }
153 format.api { render_api_ok }
153 end
154 end
154 else
155 else
155 respond_to do |format|
156 respond_to do |format|
156 format.html {
157 format.html {
157 flash[:error] = l(:notice_unable_delete_version)
158 flash[:error] = l(:notice_unable_delete_version)
158 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
159 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
159 }
160 }
160 format.api { head :unprocessable_entity }
161 format.api { head :unprocessable_entity }
161 end
162 end
162 end
163 end
163 end
164 end
164
165
165 def status_by
166 def status_by
166 respond_to do |format|
167 respond_to do |format|
167 format.html { render :action => 'show' }
168 format.html { render :action => 'show' }
168 format.js
169 format.js
169 end
170 end
170 end
171 end
171
172
172 private
173 private
173
174
174 def retrieve_selected_tracker_ids(selectable_trackers, default_trackers=nil)
175 def retrieve_selected_tracker_ids(selectable_trackers, default_trackers=nil)
175 if ids = params[:tracker_ids]
176 if ids = params[:tracker_ids]
176 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
177 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
177 else
178 else
178 @selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s }
179 @selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s }
179 end
180 end
180 end
181 end
181 end
182 end
@@ -1,95 +1,95
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 WatchersController < ApplicationController
18 class WatchersController < ApplicationController
19 before_filter :find_project
19 before_filter :find_project
20 before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch]
20 before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch]
21 before_filter :authorize, :only => [:new, :destroy]
21 before_filter :authorize, :only => [:new, :destroy]
22
22
23 def watch
23 def watch
24 if @watched.respond_to?(:visible?) && !@watched.visible?(User.current)
24 if @watched.respond_to?(:visible?) && !@watched.visible?(User.current)
25 render_403
25 render_403
26 else
26 else
27 set_watcher(User.current, true)
27 set_watcher(User.current, true)
28 end
28 end
29 end
29 end
30
30
31 def unwatch
31 def unwatch
32 set_watcher(User.current, false)
32 set_watcher(User.current, false)
33 end
33 end
34
34
35 def new
35 def new
36 end
36 end
37
37
38 def create
38 def create
39 if params[:watcher].is_a?(Hash) && request.post?
39 if params[:watcher].is_a?(Hash) && request.post?
40 user_ids = params[:watcher][:user_ids] || [params[:watcher][:user_id]]
40 user_ids = params[:watcher][:user_ids] || [params[:watcher][:user_id]]
41 user_ids.each do |user_id|
41 user_ids.each do |user_id|
42 Watcher.create(:watchable => @watched, :user_id => user_id)
42 Watcher.create(:watchable => @watched, :user_id => user_id)
43 end
43 end
44 end
44 end
45 respond_to do |format|
45 respond_to do |format|
46 format.html { redirect_to_referer_or {render :text => 'Watcher added.', :layout => true}}
46 format.html { redirect_to_referer_or {render :text => 'Watcher added.', :layout => true}}
47 format.js
47 format.js
48 end
48 end
49 end
49 end
50
50
51 def append
51 def append
52 if params[:watcher].is_a?(Hash)
52 if params[:watcher].is_a?(Hash)
53 user_ids = params[:watcher][:user_ids] || [params[:watcher][:user_id]]
53 user_ids = params[:watcher][:user_ids] || [params[:watcher][:user_id]]
54 @users = User.active.find_all_by_id(user_ids)
54 @users = User.active.find_all_by_id(user_ids)
55 end
55 end
56 end
56 end
57
57
58 def destroy
58 def destroy
59 @watched.set_watcher(User.find(params[:user_id]), false) if request.post?
59 @watched.set_watcher(User.find(params[:user_id]), false) if request.post?
60 respond_to do |format|
60 respond_to do |format|
61 format.html { redirect_to :back }
61 format.html { redirect_to :back }
62 format.js
62 format.js
63 end
63 end
64 end
64 end
65
65
66 def autocomplete_for_user
66 def autocomplete_for_user
67 @users = User.active.like(params[:q]).find(:all, :limit => 100)
67 @users = User.active.like(params[:q]).limit(100).all
68 if @watched
68 if @watched
69 @users -= @watched.watcher_users
69 @users -= @watched.watcher_users
70 end
70 end
71 render :layout => false
71 render :layout => false
72 end
72 end
73
73
74 private
74 private
75 def find_project
75 def find_project
76 if params[:object_type] && params[:object_id]
76 if params[:object_type] && params[:object_id]
77 klass = Object.const_get(params[:object_type].camelcase)
77 klass = Object.const_get(params[:object_type].camelcase)
78 return false unless klass.respond_to?('watched_by')
78 return false unless klass.respond_to?('watched_by')
79 @watched = klass.find(params[:object_id])
79 @watched = klass.find(params[:object_id])
80 @project = @watched.project
80 @project = @watched.project
81 elsif params[:project_id]
81 elsif params[:project_id]
82 @project = Project.visible.find_by_param(params[:project_id])
82 @project = Project.visible.find_by_param(params[:project_id])
83 end
83 end
84 rescue
84 rescue
85 render_404
85 render_404
86 end
86 end
87
87
88 def set_watcher(user, watching)
88 def set_watcher(user, watching)
89 @watched.set_watcher(user, watching)
89 @watched.set_watcher(user, watching)
90 respond_to do |format|
90 respond_to do |format|
91 format.html { redirect_to_referer_or {render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true}}
91 format.html { redirect_to_referer_or {render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true}}
92 format.js { render :partial => 'set_watcher', :locals => {:user => user, :watched => @watched} }
92 format.js { render :partial => 'set_watcher', :locals => {:user => user, :watched => @watched} }
93 end
93 end
94 end
94 end
95 end
95 end
General Comments 0
You need to be logged in to leave comments. Login now