##// END OF EJS Templates
Resourcified trackers....
Jean-Philippe Lang -
r7768:6577f37fc3c3
parent child
Show More
@@ -1,73 +1,84
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 verify :method => :post, :only => :destroy, :redirect_to => { :action => :index }
26
27 def index
25 def index
28 respond_to do |format|
26 respond_to do |format|
29 format.html {
27 format.html {
30 @tracker_pages, @trackers = paginate :trackers, :per_page => 10, :order => 'position'
28 @tracker_pages, @trackers = paginate :trackers, :per_page => 10, :order => 'position'
31 render :action => "index", :layout => false if request.xhr?
29 render :action => "index", :layout => false if request.xhr?
32 }
30 }
33 format.api {
31 format.api {
34 @trackers = Tracker.all
32 @trackers = Tracker.all
35 }
33 }
36 end
34 end
37 end
35 end
38
36
39 def new
37 def new
38 @tracker ||= Tracker.new(params[:tracker])
39 @trackers = Tracker.find :all, :order => 'position'
40 @projects = Project.find(:all)
41 end
42
43 def create
40 @tracker = Tracker.new(params[:tracker])
44 @tracker = Tracker.new(params[:tracker])
41 if request.post? and @tracker.save
45 if request.post? and @tracker.save
42 # workflow copy
46 # workflow copy
43 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]))
44 @tracker.workflows.copy(copy_from)
48 @tracker.workflows.copy(copy_from)
45 end
49 end
46 flash[:notice] = l(:notice_successful_create)
50 flash[:notice] = l(:notice_successful_create)
47 redirect_to :action => 'index'
51 redirect_to :action => 'index'
48 return
52 return
49 end
53 end
50 @trackers = Tracker.find :all, :order => 'position'
54 new
51 @projects = Project.find(:all)
55 render :action => 'new'
52 end
56 end
53
57
54 def edit
58 def edit
59 @tracker ||= Tracker.find(params[:id])
60 @projects = Project.find(:all)
61 end
62
63 def update
55 @tracker = Tracker.find(params[:id])
64 @tracker = Tracker.find(params[:id])
56 if request.post? and @tracker.update_attributes(params[:tracker])
65 if request.put? and @tracker.update_attributes(params[:tracker])
57 flash[:notice] = l(:notice_successful_update)
66 flash[:notice] = l(:notice_successful_update)
58 redirect_to :action => 'index'
67 redirect_to :action => 'index'
59 return
68 return
60 end
69 end
61 @projects = Project.find(:all)
70 edit
71 render :action => 'edit'
62 end
72 end
63
73
74 verify :method => :delete, :only => :destroy, :redirect_to => { :action => :index }
64 def destroy
75 def destroy
65 @tracker = Tracker.find(params[:id])
76 @tracker = Tracker.find(params[:id])
66 unless @tracker.issues.empty?
77 unless @tracker.issues.empty?
67 flash[:error] = l(:error_can_not_delete_tracker)
78 flash[:error] = l(:error_can_not_delete_tracker)
68 else
79 else
69 @tracker.destroy
80 @tracker.destroy
70 end
81 end
71 redirect_to :action => 'index'
82 redirect_to :action => 'index'
72 end
83 end
73 end
84 end
@@ -1,1052 +1,1052
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2011 Jean-Philippe Lang
4 # Copyright (C) 2006-2011 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 require 'forwardable'
20 require 'forwardable'
21 require 'cgi'
21 require 'cgi'
22
22
23 module ApplicationHelper
23 module ApplicationHelper
24 include Redmine::WikiFormatting::Macros::Definitions
24 include Redmine::WikiFormatting::Macros::Definitions
25 include Redmine::I18n
25 include Redmine::I18n
26 include GravatarHelper::PublicMethods
26 include GravatarHelper::PublicMethods
27
27
28 extend Forwardable
28 extend Forwardable
29 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
29 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
30
30
31 # Return true if user is authorized for controller/action, otherwise false
31 # Return true if user is authorized for controller/action, otherwise false
32 def authorize_for(controller, action)
32 def authorize_for(controller, action)
33 User.current.allowed_to?({:controller => controller, :action => action}, @project)
33 User.current.allowed_to?({:controller => controller, :action => action}, @project)
34 end
34 end
35
35
36 # Display a link if user is authorized
36 # Display a link if user is authorized
37 #
37 #
38 # @param [String] name Anchor text (passed to link_to)
38 # @param [String] name Anchor text (passed to link_to)
39 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
39 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
40 # @param [optional, Hash] html_options Options passed to link_to
40 # @param [optional, Hash] html_options Options passed to link_to
41 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
41 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
42 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
42 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
43 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
43 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
44 end
44 end
45
45
46 # Display a link to remote if user is authorized
46 # Display a link to remote if user is authorized
47 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
47 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
48 url = options[:url] || {}
48 url = options[:url] || {}
49 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
49 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
50 end
50 end
51
51
52 # Displays a link to user's account page if active
52 # Displays a link to user's account page if active
53 def link_to_user(user, options={})
53 def link_to_user(user, options={})
54 if user.is_a?(User)
54 if user.is_a?(User)
55 name = h(user.name(options[:format]))
55 name = h(user.name(options[:format]))
56 if user.active?
56 if user.active?
57 link_to name, :controller => 'users', :action => 'show', :id => user
57 link_to name, :controller => 'users', :action => 'show', :id => user
58 else
58 else
59 name
59 name
60 end
60 end
61 else
61 else
62 h(user.to_s)
62 h(user.to_s)
63 end
63 end
64 end
64 end
65
65
66 # Displays a link to +issue+ with its subject.
66 # Displays a link to +issue+ with its subject.
67 # Examples:
67 # Examples:
68 #
68 #
69 # link_to_issue(issue) # => Defect #6: This is the subject
69 # link_to_issue(issue) # => Defect #6: This is the subject
70 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
70 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
71 # link_to_issue(issue, :subject => false) # => Defect #6
71 # link_to_issue(issue, :subject => false) # => Defect #6
72 # link_to_issue(issue, :project => true) # => Foo - Defect #6
72 # link_to_issue(issue, :project => true) # => Foo - Defect #6
73 #
73 #
74 def link_to_issue(issue, options={})
74 def link_to_issue(issue, options={})
75 title = nil
75 title = nil
76 subject = nil
76 subject = nil
77 if options[:subject] == false
77 if options[:subject] == false
78 title = truncate(issue.subject, :length => 60)
78 title = truncate(issue.subject, :length => 60)
79 else
79 else
80 subject = issue.subject
80 subject = issue.subject
81 if options[:truncate]
81 if options[:truncate]
82 subject = truncate(subject, :length => options[:truncate])
82 subject = truncate(subject, :length => options[:truncate])
83 end
83 end
84 end
84 end
85 s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
85 s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
86 :class => issue.css_classes,
86 :class => issue.css_classes,
87 :title => title
87 :title => title
88 s << ": #{h subject}" if subject
88 s << ": #{h subject}" if subject
89 s = "#{h issue.project} - " + s if options[:project]
89 s = "#{h issue.project} - " + s if options[:project]
90 s
90 s
91 end
91 end
92
92
93 # Generates a link to an attachment.
93 # Generates a link to an attachment.
94 # Options:
94 # Options:
95 # * :text - Link text (default to attachment filename)
95 # * :text - Link text (default to attachment filename)
96 # * :download - Force download (default: false)
96 # * :download - Force download (default: false)
97 def link_to_attachment(attachment, options={})
97 def link_to_attachment(attachment, options={})
98 text = options.delete(:text) || attachment.filename
98 text = options.delete(:text) || attachment.filename
99 action = options.delete(:download) ? 'download' : 'show'
99 action = options.delete(:download) ? 'download' : 'show'
100 link_to(h(text),
100 link_to(h(text),
101 {:controller => 'attachments', :action => action,
101 {:controller => 'attachments', :action => action,
102 :id => attachment, :filename => attachment.filename },
102 :id => attachment, :filename => attachment.filename },
103 options)
103 options)
104 end
104 end
105
105
106 # Generates a link to a SCM revision
106 # Generates a link to a SCM revision
107 # Options:
107 # Options:
108 # * :text - Link text (default to the formatted revision)
108 # * :text - Link text (default to the formatted revision)
109 def link_to_revision(revision, project, options={})
109 def link_to_revision(revision, project, options={})
110 text = options.delete(:text) || format_revision(revision)
110 text = options.delete(:text) || format_revision(revision)
111 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
111 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
112
112
113 link_to(h(text), {:controller => 'repositories', :action => 'revision', :id => project, :rev => rev},
113 link_to(h(text), {:controller => 'repositories', :action => 'revision', :id => project, :rev => rev},
114 :title => l(:label_revision_id, format_revision(revision)))
114 :title => l(:label_revision_id, format_revision(revision)))
115 end
115 end
116
116
117 # Generates a link to a message
117 # Generates a link to a message
118 def link_to_message(message, options={}, html_options = nil)
118 def link_to_message(message, options={}, html_options = nil)
119 link_to(
119 link_to(
120 h(truncate(message.subject, :length => 60)),
120 h(truncate(message.subject, :length => 60)),
121 { :controller => 'messages', :action => 'show',
121 { :controller => 'messages', :action => 'show',
122 :board_id => message.board_id,
122 :board_id => message.board_id,
123 :id => message.root,
123 :id => message.root,
124 :r => (message.parent_id && message.id),
124 :r => (message.parent_id && message.id),
125 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
125 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
126 }.merge(options),
126 }.merge(options),
127 html_options
127 html_options
128 )
128 )
129 end
129 end
130
130
131 # Generates a link to a project if active
131 # Generates a link to a project if active
132 # Examples:
132 # Examples:
133 #
133 #
134 # link_to_project(project) # => link to the specified project overview
134 # link_to_project(project) # => link to the specified project overview
135 # link_to_project(project, :action=>'settings') # => link to project settings
135 # link_to_project(project, :action=>'settings') # => link to project settings
136 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
136 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
137 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
137 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
138 #
138 #
139 def link_to_project(project, options={}, html_options = nil)
139 def link_to_project(project, options={}, html_options = nil)
140 if project.active?
140 if project.active?
141 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
141 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
142 link_to(h(project), url, html_options)
142 link_to(h(project), url, html_options)
143 else
143 else
144 h(project)
144 h(project)
145 end
145 end
146 end
146 end
147
147
148 def toggle_link(name, id, options={})
148 def toggle_link(name, id, options={})
149 onclick = "Element.toggle('#{id}'); "
149 onclick = "Element.toggle('#{id}'); "
150 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
150 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
151 onclick << "return false;"
151 onclick << "return false;"
152 link_to(name, "#", :onclick => onclick)
152 link_to(name, "#", :onclick => onclick)
153 end
153 end
154
154
155 def image_to_function(name, function, html_options = {})
155 def image_to_function(name, function, html_options = {})
156 html_options.symbolize_keys!
156 html_options.symbolize_keys!
157 tag(:input, html_options.merge({
157 tag(:input, html_options.merge({
158 :type => "image", :src => image_path(name),
158 :type => "image", :src => image_path(name),
159 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
159 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
160 }))
160 }))
161 end
161 end
162
162
163 def prompt_to_remote(name, text, param, url, html_options = {})
163 def prompt_to_remote(name, text, param, url, html_options = {})
164 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
164 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
165 link_to name, {}, html_options
165 link_to name, {}, html_options
166 end
166 end
167
167
168 def format_activity_title(text)
168 def format_activity_title(text)
169 h(truncate_single_line(text, :length => 100))
169 h(truncate_single_line(text, :length => 100))
170 end
170 end
171
171
172 def format_activity_day(date)
172 def format_activity_day(date)
173 date == Date.today ? l(:label_today).titleize : format_date(date)
173 date == Date.today ? l(:label_today).titleize : format_date(date)
174 end
174 end
175
175
176 def format_activity_description(text)
176 def format_activity_description(text)
177 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
177 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
178 end
178 end
179
179
180 def format_version_name(version)
180 def format_version_name(version)
181 if version.project == @project
181 if version.project == @project
182 h(version)
182 h(version)
183 else
183 else
184 h("#{version.project} - #{version}")
184 h("#{version.project} - #{version}")
185 end
185 end
186 end
186 end
187
187
188 def due_date_distance_in_words(date)
188 def due_date_distance_in_words(date)
189 if date
189 if date
190 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
190 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
191 end
191 end
192 end
192 end
193
193
194 def render_page_hierarchy(pages, node=nil, options={})
194 def render_page_hierarchy(pages, node=nil, options={})
195 content = ''
195 content = ''
196 if pages[node]
196 if pages[node]
197 content << "<ul class=\"pages-hierarchy\">\n"
197 content << "<ul class=\"pages-hierarchy\">\n"
198 pages[node].each do |page|
198 pages[node].each do |page|
199 content << "<li>"
199 content << "<li>"
200 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
200 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
201 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
201 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
202 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
202 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
203 content << "</li>\n"
203 content << "</li>\n"
204 end
204 end
205 content << "</ul>\n"
205 content << "</ul>\n"
206 end
206 end
207 content.html_safe
207 content.html_safe
208 end
208 end
209
209
210 # Renders flash messages
210 # Renders flash messages
211 def render_flash_messages
211 def render_flash_messages
212 s = ''
212 s = ''
213 flash.each do |k,v|
213 flash.each do |k,v|
214 s << content_tag('div', v, :class => "flash #{k}")
214 s << content_tag('div', v, :class => "flash #{k}")
215 end
215 end
216 s.html_safe
216 s.html_safe
217 end
217 end
218
218
219 # Renders tabs and their content
219 # Renders tabs and their content
220 def render_tabs(tabs)
220 def render_tabs(tabs)
221 if tabs.any?
221 if tabs.any?
222 render :partial => 'common/tabs', :locals => {:tabs => tabs}
222 render :partial => 'common/tabs', :locals => {:tabs => tabs}
223 else
223 else
224 content_tag 'p', l(:label_no_data), :class => "nodata"
224 content_tag 'p', l(:label_no_data), :class => "nodata"
225 end
225 end
226 end
226 end
227
227
228 # Renders the project quick-jump box
228 # Renders the project quick-jump box
229 def render_project_jump_box
229 def render_project_jump_box
230 return unless User.current.logged?
230 return unless User.current.logged?
231 projects = User.current.memberships.collect(&:project).compact.uniq
231 projects = User.current.memberships.collect(&:project).compact.uniq
232 if projects.any?
232 if projects.any?
233 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
233 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
234 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
234 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
235 '<option value="" disabled="disabled">---</option>'
235 '<option value="" disabled="disabled">---</option>'
236 s << project_tree_options_for_select(projects, :selected => @project) do |p|
236 s << project_tree_options_for_select(projects, :selected => @project) do |p|
237 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
237 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
238 end
238 end
239 s << '</select>'
239 s << '</select>'
240 s.html_safe
240 s.html_safe
241 end
241 end
242 end
242 end
243
243
244 def project_tree_options_for_select(projects, options = {})
244 def project_tree_options_for_select(projects, options = {})
245 s = ''
245 s = ''
246 project_tree(projects) do |project, level|
246 project_tree(projects) do |project, level|
247 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
247 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
248 tag_options = {:value => project.id}
248 tag_options = {:value => project.id}
249 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
249 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
250 tag_options[:selected] = 'selected'
250 tag_options[:selected] = 'selected'
251 else
251 else
252 tag_options[:selected] = nil
252 tag_options[:selected] = nil
253 end
253 end
254 tag_options.merge!(yield(project)) if block_given?
254 tag_options.merge!(yield(project)) if block_given?
255 s << content_tag('option', name_prefix + h(project), tag_options)
255 s << content_tag('option', name_prefix + h(project), tag_options)
256 end
256 end
257 s.html_safe
257 s.html_safe
258 end
258 end
259
259
260 # Yields the given block for each project with its level in the tree
260 # Yields the given block for each project with its level in the tree
261 #
261 #
262 # Wrapper for Project#project_tree
262 # Wrapper for Project#project_tree
263 def project_tree(projects, &block)
263 def project_tree(projects, &block)
264 Project.project_tree(projects, &block)
264 Project.project_tree(projects, &block)
265 end
265 end
266
266
267 def project_nested_ul(projects, &block)
267 def project_nested_ul(projects, &block)
268 s = ''
268 s = ''
269 if projects.any?
269 if projects.any?
270 ancestors = []
270 ancestors = []
271 projects.sort_by(&:lft).each do |project|
271 projects.sort_by(&:lft).each do |project|
272 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
272 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
273 s << "<ul>\n"
273 s << "<ul>\n"
274 else
274 else
275 ancestors.pop
275 ancestors.pop
276 s << "</li>"
276 s << "</li>"
277 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
277 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
278 ancestors.pop
278 ancestors.pop
279 s << "</ul></li>\n"
279 s << "</ul></li>\n"
280 end
280 end
281 end
281 end
282 s << "<li>"
282 s << "<li>"
283 s << yield(project).to_s
283 s << yield(project).to_s
284 ancestors << project
284 ancestors << project
285 end
285 end
286 s << ("</li></ul>\n" * ancestors.size)
286 s << ("</li></ul>\n" * ancestors.size)
287 end
287 end
288 s.html_safe
288 s.html_safe
289 end
289 end
290
290
291 def principals_check_box_tags(name, principals)
291 def principals_check_box_tags(name, principals)
292 s = ''
292 s = ''
293 principals.sort.each do |principal|
293 principals.sort.each do |principal|
294 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
294 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
295 end
295 end
296 s.html_safe
296 s.html_safe
297 end
297 end
298
298
299 # Returns a string for users/groups option tags
299 # Returns a string for users/groups option tags
300 def principals_options_for_select(collection, selected=nil)
300 def principals_options_for_select(collection, selected=nil)
301 s = ''
301 s = ''
302 groups = ''
302 groups = ''
303 collection.sort.each do |element|
303 collection.sort.each do |element|
304 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
304 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
305 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
305 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
306 end
306 end
307 unless groups.empty?
307 unless groups.empty?
308 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
308 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
309 end
309 end
310 s
310 s
311 end
311 end
312
312
313 # Truncates and returns the string as a single line
313 # Truncates and returns the string as a single line
314 def truncate_single_line(string, *args)
314 def truncate_single_line(string, *args)
315 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
315 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
316 end
316 end
317
317
318 # Truncates at line break after 250 characters or options[:length]
318 # Truncates at line break after 250 characters or options[:length]
319 def truncate_lines(string, options={})
319 def truncate_lines(string, options={})
320 length = options[:length] || 250
320 length = options[:length] || 250
321 if string.to_s =~ /\A(.{#{length}}.*?)$/m
321 if string.to_s =~ /\A(.{#{length}}.*?)$/m
322 "#{$1}..."
322 "#{$1}..."
323 else
323 else
324 string
324 string
325 end
325 end
326 end
326 end
327
327
328 def html_hours(text)
328 def html_hours(text)
329 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
329 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
330 end
330 end
331
331
332 def authoring(created, author, options={})
332 def authoring(created, author, options={})
333 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
333 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
334 end
334 end
335
335
336 def time_tag(time)
336 def time_tag(time)
337 text = distance_of_time_in_words(Time.now, time)
337 text = distance_of_time_in_words(Time.now, time)
338 if @project
338 if @project
339 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
339 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
340 else
340 else
341 content_tag('acronym', text, :title => format_time(time))
341 content_tag('acronym', text, :title => format_time(time))
342 end
342 end
343 end
343 end
344
344
345 def syntax_highlight(name, content)
345 def syntax_highlight(name, content)
346 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
346 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
347 end
347 end
348
348
349 def to_path_param(path)
349 def to_path_param(path)
350 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
350 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
351 end
351 end
352
352
353 def pagination_links_full(paginator, count=nil, options={})
353 def pagination_links_full(paginator, count=nil, options={})
354 page_param = options.delete(:page_param) || :page
354 page_param = options.delete(:page_param) || :page
355 per_page_links = options.delete(:per_page_links)
355 per_page_links = options.delete(:per_page_links)
356 url_param = params.dup
356 url_param = params.dup
357
357
358 html = ''
358 html = ''
359 if paginator.current.previous
359 if paginator.current.previous
360 # \xc2\xab(utf-8) = &#171;
360 # \xc2\xab(utf-8) = &#171;
361 html << link_to_content_update(
361 html << link_to_content_update(
362 "\xc2\xab " + l(:label_previous),
362 "\xc2\xab " + l(:label_previous),
363 url_param.merge(page_param => paginator.current.previous)) + ' '
363 url_param.merge(page_param => paginator.current.previous)) + ' '
364 end
364 end
365
365
366 html << (pagination_links_each(paginator, options) do |n|
366 html << (pagination_links_each(paginator, options) do |n|
367 link_to_content_update(n.to_s, url_param.merge(page_param => n))
367 link_to_content_update(n.to_s, url_param.merge(page_param => n))
368 end || '')
368 end || '')
369
369
370 if paginator.current.next
370 if paginator.current.next
371 # \xc2\xbb(utf-8) = &#187;
371 # \xc2\xbb(utf-8) = &#187;
372 html << ' ' + link_to_content_update(
372 html << ' ' + link_to_content_update(
373 (l(:label_next) + " \xc2\xbb"),
373 (l(:label_next) + " \xc2\xbb"),
374 url_param.merge(page_param => paginator.current.next))
374 url_param.merge(page_param => paginator.current.next))
375 end
375 end
376
376
377 unless count.nil?
377 unless count.nil?
378 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
378 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
379 if per_page_links != false && links = per_page_links(paginator.items_per_page)
379 if per_page_links != false && links = per_page_links(paginator.items_per_page)
380 html << " | #{links}"
380 html << " | #{links}"
381 end
381 end
382 end
382 end
383
383
384 html.html_safe
384 html.html_safe
385 end
385 end
386
386
387 def per_page_links(selected=nil)
387 def per_page_links(selected=nil)
388 links = Setting.per_page_options_array.collect do |n|
388 links = Setting.per_page_options_array.collect do |n|
389 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
389 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
390 end
390 end
391 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
391 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
392 end
392 end
393
393
394 def reorder_links(name, url)
394 def reorder_links(name, url, method = :post)
395 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
395 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
396 url.merge({"#{name}[move_to]" => 'highest'}),
396 url.merge({"#{name}[move_to]" => 'highest'}),
397 :method => :post, :title => l(:label_sort_highest)) +
397 :method => method, :title => l(:label_sort_highest)) +
398 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
398 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
399 url.merge({"#{name}[move_to]" => 'higher'}),
399 url.merge({"#{name}[move_to]" => 'higher'}),
400 :method => :post, :title => l(:label_sort_higher)) +
400 :method => method, :title => l(:label_sort_higher)) +
401 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
401 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
402 url.merge({"#{name}[move_to]" => 'lower'}),
402 url.merge({"#{name}[move_to]" => 'lower'}),
403 :method => :post, :title => l(:label_sort_lower)) +
403 :method => method, :title => l(:label_sort_lower)) +
404 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
404 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
405 url.merge({"#{name}[move_to]" => 'lowest'}),
405 url.merge({"#{name}[move_to]" => 'lowest'}),
406 :method => :post, :title => l(:label_sort_lowest))
406 :method => method, :title => l(:label_sort_lowest))
407 end
407 end
408
408
409 def breadcrumb(*args)
409 def breadcrumb(*args)
410 elements = args.flatten
410 elements = args.flatten
411 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
411 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
412 end
412 end
413
413
414 def other_formats_links(&block)
414 def other_formats_links(&block)
415 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
415 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
416 yield Redmine::Views::OtherFormatsBuilder.new(self)
416 yield Redmine::Views::OtherFormatsBuilder.new(self)
417 concat('</p>'.html_safe)
417 concat('</p>'.html_safe)
418 end
418 end
419
419
420 def page_header_title
420 def page_header_title
421 if @project.nil? || @project.new_record?
421 if @project.nil? || @project.new_record?
422 h(Setting.app_title)
422 h(Setting.app_title)
423 else
423 else
424 b = []
424 b = []
425 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
425 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
426 if ancestors.any?
426 if ancestors.any?
427 root = ancestors.shift
427 root = ancestors.shift
428 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
428 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
429 if ancestors.size > 2
429 if ancestors.size > 2
430 b << "\xe2\x80\xa6"
430 b << "\xe2\x80\xa6"
431 ancestors = ancestors[-2, 2]
431 ancestors = ancestors[-2, 2]
432 end
432 end
433 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
433 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
434 end
434 end
435 b << h(@project)
435 b << h(@project)
436 b.join(" \xc2\xbb ").html_safe
436 b.join(" \xc2\xbb ").html_safe
437 end
437 end
438 end
438 end
439
439
440 def html_title(*args)
440 def html_title(*args)
441 if args.empty?
441 if args.empty?
442 title = @html_title || []
442 title = @html_title || []
443 title << @project.name if @project
443 title << @project.name if @project
444 title << Setting.app_title unless Setting.app_title == title.last
444 title << Setting.app_title unless Setting.app_title == title.last
445 title.select {|t| !t.blank? }.join(' - ')
445 title.select {|t| !t.blank? }.join(' - ')
446 else
446 else
447 @html_title ||= []
447 @html_title ||= []
448 @html_title += args
448 @html_title += args
449 end
449 end
450 end
450 end
451
451
452 # Returns the theme, controller name, and action as css classes for the
452 # Returns the theme, controller name, and action as css classes for the
453 # HTML body.
453 # HTML body.
454 def body_css_classes
454 def body_css_classes
455 css = []
455 css = []
456 if theme = Redmine::Themes.theme(Setting.ui_theme)
456 if theme = Redmine::Themes.theme(Setting.ui_theme)
457 css << 'theme-' + theme.name
457 css << 'theme-' + theme.name
458 end
458 end
459
459
460 css << 'controller-' + params[:controller]
460 css << 'controller-' + params[:controller]
461 css << 'action-' + params[:action]
461 css << 'action-' + params[:action]
462 css.join(' ')
462 css.join(' ')
463 end
463 end
464
464
465 def accesskey(s)
465 def accesskey(s)
466 Redmine::AccessKeys.key_for s
466 Redmine::AccessKeys.key_for s
467 end
467 end
468
468
469 # Formats text according to system settings.
469 # Formats text according to system settings.
470 # 2 ways to call this method:
470 # 2 ways to call this method:
471 # * with a String: textilizable(text, options)
471 # * with a String: textilizable(text, options)
472 # * with an object and one of its attribute: textilizable(issue, :description, options)
472 # * with an object and one of its attribute: textilizable(issue, :description, options)
473 def textilizable(*args)
473 def textilizable(*args)
474 options = args.last.is_a?(Hash) ? args.pop : {}
474 options = args.last.is_a?(Hash) ? args.pop : {}
475 case args.size
475 case args.size
476 when 1
476 when 1
477 obj = options[:object]
477 obj = options[:object]
478 text = args.shift
478 text = args.shift
479 when 2
479 when 2
480 obj = args.shift
480 obj = args.shift
481 attr = args.shift
481 attr = args.shift
482 text = obj.send(attr).to_s
482 text = obj.send(attr).to_s
483 else
483 else
484 raise ArgumentError, 'invalid arguments to textilizable'
484 raise ArgumentError, 'invalid arguments to textilizable'
485 end
485 end
486 return '' if text.blank?
486 return '' if text.blank?
487 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
487 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
488 only_path = options.delete(:only_path) == false ? false : true
488 only_path = options.delete(:only_path) == false ? false : true
489
489
490 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
490 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
491
491
492 @parsed_headings = []
492 @parsed_headings = []
493 @current_section = 0 if options[:edit_section_links]
493 @current_section = 0 if options[:edit_section_links]
494 text = parse_non_pre_blocks(text) do |text|
494 text = parse_non_pre_blocks(text) do |text|
495 [:parse_sections, :parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros, :parse_headings].each do |method_name|
495 [:parse_sections, :parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros, :parse_headings].each do |method_name|
496 send method_name, text, project, obj, attr, only_path, options
496 send method_name, text, project, obj, attr, only_path, options
497 end
497 end
498 end
498 end
499
499
500 if @parsed_headings.any?
500 if @parsed_headings.any?
501 replace_toc(text, @parsed_headings)
501 replace_toc(text, @parsed_headings)
502 end
502 end
503
503
504 text
504 text
505 end
505 end
506
506
507 def parse_non_pre_blocks(text)
507 def parse_non_pre_blocks(text)
508 s = StringScanner.new(text)
508 s = StringScanner.new(text)
509 tags = []
509 tags = []
510 parsed = ''
510 parsed = ''
511 while !s.eos?
511 while !s.eos?
512 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
512 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
513 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
513 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
514 if tags.empty?
514 if tags.empty?
515 yield text
515 yield text
516 end
516 end
517 parsed << text
517 parsed << text
518 if tag
518 if tag
519 if closing
519 if closing
520 if tags.last == tag.downcase
520 if tags.last == tag.downcase
521 tags.pop
521 tags.pop
522 end
522 end
523 else
523 else
524 tags << tag.downcase
524 tags << tag.downcase
525 end
525 end
526 parsed << full_tag
526 parsed << full_tag
527 end
527 end
528 end
528 end
529 # Close any non closing tags
529 # Close any non closing tags
530 while tag = tags.pop
530 while tag = tags.pop
531 parsed << "</#{tag}>"
531 parsed << "</#{tag}>"
532 end
532 end
533 parsed.html_safe
533 parsed.html_safe
534 end
534 end
535
535
536 def parse_inline_attachments(text, project, obj, attr, only_path, options)
536 def parse_inline_attachments(text, project, obj, attr, only_path, options)
537 # when using an image link, try to use an attachment, if possible
537 # when using an image link, try to use an attachment, if possible
538 if options[:attachments] || (obj && obj.respond_to?(:attachments))
538 if options[:attachments] || (obj && obj.respond_to?(:attachments))
539 attachments = nil
539 attachments = nil
540 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
540 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
541 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
541 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
542 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
542 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
543 # search for the picture in attachments
543 # search for the picture in attachments
544 if found = attachments.detect { |att| att.filename.downcase == filename }
544 if found = attachments.detect { |att| att.filename.downcase == filename }
545 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
545 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
546 desc = found.description.to_s.gsub('"', '')
546 desc = found.description.to_s.gsub('"', '')
547 if !desc.blank? && alttext.blank?
547 if !desc.blank? && alttext.blank?
548 alt = " title=\"#{desc}\" alt=\"#{desc}\""
548 alt = " title=\"#{desc}\" alt=\"#{desc}\""
549 end
549 end
550 "src=\"#{image_url}\"#{alt}".html_safe
550 "src=\"#{image_url}\"#{alt}".html_safe
551 else
551 else
552 m.html_safe
552 m.html_safe
553 end
553 end
554 end
554 end
555 end
555 end
556 end
556 end
557
557
558 # Wiki links
558 # Wiki links
559 #
559 #
560 # Examples:
560 # Examples:
561 # [[mypage]]
561 # [[mypage]]
562 # [[mypage|mytext]]
562 # [[mypage|mytext]]
563 # wiki links can refer other project wikis, using project name or identifier:
563 # wiki links can refer other project wikis, using project name or identifier:
564 # [[project:]] -> wiki starting page
564 # [[project:]] -> wiki starting page
565 # [[project:|mytext]]
565 # [[project:|mytext]]
566 # [[project:mypage]]
566 # [[project:mypage]]
567 # [[project:mypage|mytext]]
567 # [[project:mypage|mytext]]
568 def parse_wiki_links(text, project, obj, attr, only_path, options)
568 def parse_wiki_links(text, project, obj, attr, only_path, options)
569 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
569 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
570 link_project = project
570 link_project = project
571 esc, all, page, title = $1, $2, $3, $5
571 esc, all, page, title = $1, $2, $3, $5
572 if esc.nil?
572 if esc.nil?
573 if page =~ /^([^\:]+)\:(.*)$/
573 if page =~ /^([^\:]+)\:(.*)$/
574 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
574 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
575 page = $2
575 page = $2
576 title ||= $1 if page.blank?
576 title ||= $1 if page.blank?
577 end
577 end
578
578
579 if link_project && link_project.wiki
579 if link_project && link_project.wiki
580 # extract anchor
580 # extract anchor
581 anchor = nil
581 anchor = nil
582 if page =~ /^(.+?)\#(.+)$/
582 if page =~ /^(.+?)\#(.+)$/
583 page, anchor = $1, $2
583 page, anchor = $1, $2
584 end
584 end
585 anchor = sanitize_anchor_name(anchor) if anchor.present?
585 anchor = sanitize_anchor_name(anchor) if anchor.present?
586 # check if page exists
586 # check if page exists
587 wiki_page = link_project.wiki.find_page(page)
587 wiki_page = link_project.wiki.find_page(page)
588 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
588 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
589 "##{anchor}"
589 "##{anchor}"
590 else
590 else
591 case options[:wiki_links]
591 case options[:wiki_links]
592 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
592 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
593 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
593 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
594 else
594 else
595 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
595 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
596 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor)
596 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor)
597 end
597 end
598 end
598 end
599 link_to(title || h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
599 link_to(title || h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
600 else
600 else
601 # project or wiki doesn't exist
601 # project or wiki doesn't exist
602 all.html_safe
602 all.html_safe
603 end
603 end
604 else
604 else
605 all.html_safe
605 all.html_safe
606 end
606 end
607 end
607 end
608 end
608 end
609
609
610 # Redmine links
610 # Redmine links
611 #
611 #
612 # Examples:
612 # Examples:
613 # Issues:
613 # Issues:
614 # #52 -> Link to issue #52
614 # #52 -> Link to issue #52
615 # Changesets:
615 # Changesets:
616 # r52 -> Link to revision 52
616 # r52 -> Link to revision 52
617 # commit:a85130f -> Link to scmid starting with a85130f
617 # commit:a85130f -> Link to scmid starting with a85130f
618 # Documents:
618 # Documents:
619 # document#17 -> Link to document with id 17
619 # document#17 -> Link to document with id 17
620 # document:Greetings -> Link to the document with title "Greetings"
620 # document:Greetings -> Link to the document with title "Greetings"
621 # document:"Some document" -> Link to the document with title "Some document"
621 # document:"Some document" -> Link to the document with title "Some document"
622 # Versions:
622 # Versions:
623 # version#3 -> Link to version with id 3
623 # version#3 -> Link to version with id 3
624 # version:1.0.0 -> Link to version named "1.0.0"
624 # version:1.0.0 -> Link to version named "1.0.0"
625 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
625 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
626 # Attachments:
626 # Attachments:
627 # attachment:file.zip -> Link to the attachment of the current object named file.zip
627 # attachment:file.zip -> Link to the attachment of the current object named file.zip
628 # Source files:
628 # Source files:
629 # source:some/file -> Link to the file located at /some/file in the project's repository
629 # source:some/file -> Link to the file located at /some/file in the project's repository
630 # source:some/file@52 -> Link to the file's revision 52
630 # source:some/file@52 -> Link to the file's revision 52
631 # source:some/file#L120 -> Link to line 120 of the file
631 # source:some/file#L120 -> Link to line 120 of the file
632 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
632 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
633 # export:some/file -> Force the download of the file
633 # export:some/file -> Force the download of the file
634 # Forum messages:
634 # Forum messages:
635 # message#1218 -> Link to message with id 1218
635 # message#1218 -> Link to message with id 1218
636 #
636 #
637 # Links can refer other objects from other projects, using project identifier:
637 # Links can refer other objects from other projects, using project identifier:
638 # identifier:r52
638 # identifier:r52
639 # identifier:document:"Some document"
639 # identifier:document:"Some document"
640 # identifier:version:1.0.0
640 # identifier:version:1.0.0
641 # identifier:source:some/file
641 # identifier:source:some/file
642 def parse_redmine_links(text, project, obj, attr, only_path, options)
642 def parse_redmine_links(text, project, obj, attr, only_path, options)
643 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|forum|news|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
643 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|forum|news|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
644 leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10
644 leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10
645 link = nil
645 link = nil
646 if project_identifier
646 if project_identifier
647 project = Project.visible.find_by_identifier(project_identifier)
647 project = Project.visible.find_by_identifier(project_identifier)
648 end
648 end
649 if esc.nil?
649 if esc.nil?
650 if prefix.nil? && sep == 'r'
650 if prefix.nil? && sep == 'r'
651 # project.changesets.visible raises an SQL error because of a double join on repositories
651 # project.changesets.visible raises an SQL error because of a double join on repositories
652 if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier))
652 if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier))
653 link = link_to(h("#{project_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
653 link = link_to(h("#{project_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
654 :class => 'changeset',
654 :class => 'changeset',
655 :title => truncate_single_line(changeset.comments, :length => 100))
655 :title => truncate_single_line(changeset.comments, :length => 100))
656 end
656 end
657 elsif sep == '#'
657 elsif sep == '#'
658 oid = identifier.to_i
658 oid = identifier.to_i
659 case prefix
659 case prefix
660 when nil
660 when nil
661 if issue = Issue.visible.find_by_id(oid, :include => :status)
661 if issue = Issue.visible.find_by_id(oid, :include => :status)
662 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
662 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
663 :class => issue.css_classes,
663 :class => issue.css_classes,
664 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
664 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
665 end
665 end
666 when 'document'
666 when 'document'
667 if document = Document.visible.find_by_id(oid)
667 if document = Document.visible.find_by_id(oid)
668 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
668 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
669 :class => 'document'
669 :class => 'document'
670 end
670 end
671 when 'version'
671 when 'version'
672 if version = Version.visible.find_by_id(oid)
672 if version = Version.visible.find_by_id(oid)
673 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
673 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
674 :class => 'version'
674 :class => 'version'
675 end
675 end
676 when 'message'
676 when 'message'
677 if message = Message.visible.find_by_id(oid, :include => :parent)
677 if message = Message.visible.find_by_id(oid, :include => :parent)
678 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
678 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
679 end
679 end
680 when 'forum'
680 when 'forum'
681 if board = Board.visible.find_by_id(oid)
681 if board = Board.visible.find_by_id(oid)
682 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
682 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
683 :class => 'board'
683 :class => 'board'
684 end
684 end
685 when 'news'
685 when 'news'
686 if news = News.visible.find_by_id(oid)
686 if news = News.visible.find_by_id(oid)
687 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
687 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
688 :class => 'news'
688 :class => 'news'
689 end
689 end
690 when 'project'
690 when 'project'
691 if p = Project.visible.find_by_id(oid)
691 if p = Project.visible.find_by_id(oid)
692 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
692 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
693 end
693 end
694 end
694 end
695 elsif sep == ':'
695 elsif sep == ':'
696 # removes the double quotes if any
696 # removes the double quotes if any
697 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
697 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
698 case prefix
698 case prefix
699 when 'document'
699 when 'document'
700 if project && document = project.documents.visible.find_by_title(name)
700 if project && document = project.documents.visible.find_by_title(name)
701 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
701 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
702 :class => 'document'
702 :class => 'document'
703 end
703 end
704 when 'version'
704 when 'version'
705 if project && version = project.versions.visible.find_by_name(name)
705 if project && version = project.versions.visible.find_by_name(name)
706 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
706 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
707 :class => 'version'
707 :class => 'version'
708 end
708 end
709 when 'forum'
709 when 'forum'
710 if project && board = project.boards.visible.find_by_name(name)
710 if project && board = project.boards.visible.find_by_name(name)
711 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
711 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
712 :class => 'board'
712 :class => 'board'
713 end
713 end
714 when 'news'
714 when 'news'
715 if project && news = project.news.visible.find_by_title(name)
715 if project && news = project.news.visible.find_by_title(name)
716 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
716 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
717 :class => 'news'
717 :class => 'news'
718 end
718 end
719 when 'commit'
719 when 'commit'
720 if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"]))
720 if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"]))
721 link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier},
721 link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier},
722 :class => 'changeset',
722 :class => 'changeset',
723 :title => truncate_single_line(h(changeset.comments), :length => 100)
723 :title => truncate_single_line(h(changeset.comments), :length => 100)
724 end
724 end
725 when 'source', 'export'
725 when 'source', 'export'
726 if project && project.repository && User.current.allowed_to?(:browse_repository, project)
726 if project && project.repository && User.current.allowed_to?(:browse_repository, project)
727 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
727 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
728 path, rev, anchor = $1, $3, $5
728 path, rev, anchor = $1, $3, $5
729 link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
729 link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
730 :path => to_path_param(path),
730 :path => to_path_param(path),
731 :rev => rev,
731 :rev => rev,
732 :anchor => anchor,
732 :anchor => anchor,
733 :format => (prefix == 'export' ? 'raw' : nil)},
733 :format => (prefix == 'export' ? 'raw' : nil)},
734 :class => (prefix == 'export' ? 'source download' : 'source')
734 :class => (prefix == 'export' ? 'source download' : 'source')
735 end
735 end
736 when 'attachment'
736 when 'attachment'
737 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
737 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
738 if attachments && attachment = attachments.detect {|a| a.filename == name }
738 if attachments && attachment = attachments.detect {|a| a.filename == name }
739 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
739 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
740 :class => 'attachment'
740 :class => 'attachment'
741 end
741 end
742 when 'project'
742 when 'project'
743 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
743 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
744 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
744 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
745 end
745 end
746 end
746 end
747 end
747 end
748 end
748 end
749 (leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")).html_safe
749 (leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")).html_safe
750 end
750 end
751 end
751 end
752
752
753 HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE)
753 HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE)
754
754
755 def parse_sections(text, project, obj, attr, only_path, options)
755 def parse_sections(text, project, obj, attr, only_path, options)
756 return unless options[:edit_section_links]
756 return unless options[:edit_section_links]
757 text.gsub!(HEADING_RE) do
757 text.gsub!(HEADING_RE) do
758 @current_section += 1
758 @current_section += 1
759 if @current_section > 1
759 if @current_section > 1
760 content_tag('div',
760 content_tag('div',
761 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
761 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
762 :class => 'contextual',
762 :class => 'contextual',
763 :title => l(:button_edit_section)) + $1
763 :title => l(:button_edit_section)) + $1
764 else
764 else
765 $1
765 $1
766 end
766 end
767 end
767 end
768 end
768 end
769
769
770 # Headings and TOC
770 # Headings and TOC
771 # Adds ids and links to headings unless options[:headings] is set to false
771 # Adds ids and links to headings unless options[:headings] is set to false
772 def parse_headings(text, project, obj, attr, only_path, options)
772 def parse_headings(text, project, obj, attr, only_path, options)
773 return if options[:headings] == false
773 return if options[:headings] == false
774
774
775 text.gsub!(HEADING_RE) do
775 text.gsub!(HEADING_RE) do
776 level, attrs, content = $2.to_i, $3, $4
776 level, attrs, content = $2.to_i, $3, $4
777 item = strip_tags(content).strip
777 item = strip_tags(content).strip
778 anchor = sanitize_anchor_name(item)
778 anchor = sanitize_anchor_name(item)
779 # used for single-file wiki export
779 # used for single-file wiki export
780 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
780 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
781 @parsed_headings << [level, anchor, item]
781 @parsed_headings << [level, anchor, item]
782 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
782 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
783 end
783 end
784 end
784 end
785
785
786 MACROS_RE = /
786 MACROS_RE = /
787 (!)? # escaping
787 (!)? # escaping
788 (
788 (
789 \{\{ # opening tag
789 \{\{ # opening tag
790 ([\w]+) # macro name
790 ([\w]+) # macro name
791 (\(([^\}]*)\))? # optional arguments
791 (\(([^\}]*)\))? # optional arguments
792 \}\} # closing tag
792 \}\} # closing tag
793 )
793 )
794 /x unless const_defined?(:MACROS_RE)
794 /x unless const_defined?(:MACROS_RE)
795
795
796 # Macros substitution
796 # Macros substitution
797 def parse_macros(text, project, obj, attr, only_path, options)
797 def parse_macros(text, project, obj, attr, only_path, options)
798 text.gsub!(MACROS_RE) do
798 text.gsub!(MACROS_RE) do
799 esc, all, macro = $1, $2, $3.downcase
799 esc, all, macro = $1, $2, $3.downcase
800 args = ($5 || '').split(',').each(&:strip)
800 args = ($5 || '').split(',').each(&:strip)
801 if esc.nil?
801 if esc.nil?
802 begin
802 begin
803 exec_macro(macro, obj, args)
803 exec_macro(macro, obj, args)
804 rescue => e
804 rescue => e
805 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
805 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
806 end || all
806 end || all
807 else
807 else
808 all
808 all
809 end
809 end
810 end
810 end
811 end
811 end
812
812
813 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
813 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
814
814
815 # Renders the TOC with given headings
815 # Renders the TOC with given headings
816 def replace_toc(text, headings)
816 def replace_toc(text, headings)
817 text.gsub!(TOC_RE) do
817 text.gsub!(TOC_RE) do
818 if headings.empty?
818 if headings.empty?
819 ''
819 ''
820 else
820 else
821 div_class = 'toc'
821 div_class = 'toc'
822 div_class << ' right' if $1 == '>'
822 div_class << ' right' if $1 == '>'
823 div_class << ' left' if $1 == '<'
823 div_class << ' left' if $1 == '<'
824 out = "<ul class=\"#{div_class}\"><li>"
824 out = "<ul class=\"#{div_class}\"><li>"
825 root = headings.map(&:first).min
825 root = headings.map(&:first).min
826 current = root
826 current = root
827 started = false
827 started = false
828 headings.each do |level, anchor, item|
828 headings.each do |level, anchor, item|
829 if level > current
829 if level > current
830 out << '<ul><li>' * (level - current)
830 out << '<ul><li>' * (level - current)
831 elsif level < current
831 elsif level < current
832 out << "</li></ul>\n" * (current - level) + "</li><li>"
832 out << "</li></ul>\n" * (current - level) + "</li><li>"
833 elsif started
833 elsif started
834 out << '</li><li>'
834 out << '</li><li>'
835 end
835 end
836 out << "<a href=\"##{anchor}\">#{item}</a>"
836 out << "<a href=\"##{anchor}\">#{item}</a>"
837 current = level
837 current = level
838 started = true
838 started = true
839 end
839 end
840 out << '</li></ul>' * (current - root)
840 out << '</li></ul>' * (current - root)
841 out << '</li></ul>'
841 out << '</li></ul>'
842 end
842 end
843 end
843 end
844 end
844 end
845
845
846 # Same as Rails' simple_format helper without using paragraphs
846 # Same as Rails' simple_format helper without using paragraphs
847 def simple_format_without_paragraph(text)
847 def simple_format_without_paragraph(text)
848 text.to_s.
848 text.to_s.
849 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
849 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
850 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
850 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
851 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
851 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
852 html_safe
852 html_safe
853 end
853 end
854
854
855 def lang_options_for_select(blank=true)
855 def lang_options_for_select(blank=true)
856 (blank ? [["(auto)", ""]] : []) +
856 (blank ? [["(auto)", ""]] : []) +
857 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
857 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
858 end
858 end
859
859
860 def label_tag_for(name, option_tags = nil, options = {})
860 def label_tag_for(name, option_tags = nil, options = {})
861 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
861 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
862 content_tag("label", label_text)
862 content_tag("label", label_text)
863 end
863 end
864
864
865 def labelled_tabular_form_for(name, object, options, &proc)
865 def labelled_tabular_form_for(name, object, options, &proc)
866 options[:html] ||= {}
866 options[:html] ||= {}
867 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
867 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
868 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
868 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
869 end
869 end
870
870
871 def back_url_hidden_field_tag
871 def back_url_hidden_field_tag
872 back_url = params[:back_url] || request.env['HTTP_REFERER']
872 back_url = params[:back_url] || request.env['HTTP_REFERER']
873 back_url = CGI.unescape(back_url.to_s)
873 back_url = CGI.unescape(back_url.to_s)
874 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
874 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
875 end
875 end
876
876
877 def check_all_links(form_name)
877 def check_all_links(form_name)
878 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
878 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
879 " | ".html_safe +
879 " | ".html_safe +
880 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
880 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
881 end
881 end
882
882
883 def progress_bar(pcts, options={})
883 def progress_bar(pcts, options={})
884 pcts = [pcts, pcts] unless pcts.is_a?(Array)
884 pcts = [pcts, pcts] unless pcts.is_a?(Array)
885 pcts = pcts.collect(&:round)
885 pcts = pcts.collect(&:round)
886 pcts[1] = pcts[1] - pcts[0]
886 pcts[1] = pcts[1] - pcts[0]
887 pcts << (100 - pcts[1] - pcts[0])
887 pcts << (100 - pcts[1] - pcts[0])
888 width = options[:width] || '100px;'
888 width = options[:width] || '100px;'
889 legend = options[:legend] || ''
889 legend = options[:legend] || ''
890 content_tag('table',
890 content_tag('table',
891 content_tag('tr',
891 content_tag('tr',
892 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
892 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
893 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
893 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
894 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
894 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
895 ), :class => 'progress', :style => "width: #{width};").html_safe +
895 ), :class => 'progress', :style => "width: #{width};").html_safe +
896 content_tag('p', legend, :class => 'pourcent').html_safe
896 content_tag('p', legend, :class => 'pourcent').html_safe
897 end
897 end
898
898
899 def checked_image(checked=true)
899 def checked_image(checked=true)
900 if checked
900 if checked
901 image_tag 'toggle_check.png'
901 image_tag 'toggle_check.png'
902 end
902 end
903 end
903 end
904
904
905 def context_menu(url)
905 def context_menu(url)
906 unless @context_menu_included
906 unless @context_menu_included
907 content_for :header_tags do
907 content_for :header_tags do
908 javascript_include_tag('context_menu') +
908 javascript_include_tag('context_menu') +
909 stylesheet_link_tag('context_menu')
909 stylesheet_link_tag('context_menu')
910 end
910 end
911 if l(:direction) == 'rtl'
911 if l(:direction) == 'rtl'
912 content_for :header_tags do
912 content_for :header_tags do
913 stylesheet_link_tag('context_menu_rtl')
913 stylesheet_link_tag('context_menu_rtl')
914 end
914 end
915 end
915 end
916 @context_menu_included = true
916 @context_menu_included = true
917 end
917 end
918 javascript_tag "new ContextMenu('#{ url_for(url) }')"
918 javascript_tag "new ContextMenu('#{ url_for(url) }')"
919 end
919 end
920
920
921 def context_menu_link(name, url, options={})
921 def context_menu_link(name, url, options={})
922 options[:class] ||= ''
922 options[:class] ||= ''
923 if options.delete(:selected)
923 if options.delete(:selected)
924 options[:class] << ' icon-checked disabled'
924 options[:class] << ' icon-checked disabled'
925 options[:disabled] = true
925 options[:disabled] = true
926 end
926 end
927 if options.delete(:disabled)
927 if options.delete(:disabled)
928 options.delete(:method)
928 options.delete(:method)
929 options.delete(:confirm)
929 options.delete(:confirm)
930 options.delete(:onclick)
930 options.delete(:onclick)
931 options[:class] << ' disabled'
931 options[:class] << ' disabled'
932 url = '#'
932 url = '#'
933 end
933 end
934 link_to h(name), url, options
934 link_to h(name), url, options
935 end
935 end
936
936
937 def calendar_for(field_id)
937 def calendar_for(field_id)
938 include_calendar_headers_tags
938 include_calendar_headers_tags
939 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
939 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
940 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
940 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
941 end
941 end
942
942
943 def include_calendar_headers_tags
943 def include_calendar_headers_tags
944 unless @calendar_headers_tags_included
944 unless @calendar_headers_tags_included
945 @calendar_headers_tags_included = true
945 @calendar_headers_tags_included = true
946 content_for :header_tags do
946 content_for :header_tags do
947 start_of_week = case Setting.start_of_week.to_i
947 start_of_week = case Setting.start_of_week.to_i
948 when 1
948 when 1
949 'Calendar._FD = 1;' # Monday
949 'Calendar._FD = 1;' # Monday
950 when 7
950 when 7
951 'Calendar._FD = 0;' # Sunday
951 'Calendar._FD = 0;' # Sunday
952 when 6
952 when 6
953 'Calendar._FD = 6;' # Saturday
953 'Calendar._FD = 6;' # Saturday
954 else
954 else
955 '' # use language
955 '' # use language
956 end
956 end
957
957
958 javascript_include_tag('calendar/calendar') +
958 javascript_include_tag('calendar/calendar') +
959 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
959 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
960 javascript_tag(start_of_week) +
960 javascript_tag(start_of_week) +
961 javascript_include_tag('calendar/calendar-setup') +
961 javascript_include_tag('calendar/calendar-setup') +
962 stylesheet_link_tag('calendar')
962 stylesheet_link_tag('calendar')
963 end
963 end
964 end
964 end
965 end
965 end
966
966
967 def content_for(name, content = nil, &block)
967 def content_for(name, content = nil, &block)
968 @has_content ||= {}
968 @has_content ||= {}
969 @has_content[name] = true
969 @has_content[name] = true
970 super(name, content, &block)
970 super(name, content, &block)
971 end
971 end
972
972
973 def has_content?(name)
973 def has_content?(name)
974 (@has_content && @has_content[name]) || false
974 (@has_content && @has_content[name]) || false
975 end
975 end
976
976
977 def email_delivery_enabled?
977 def email_delivery_enabled?
978 !!ActionMailer::Base.perform_deliveries
978 !!ActionMailer::Base.perform_deliveries
979 end
979 end
980
980
981 # Returns the avatar image tag for the given +user+ if avatars are enabled
981 # Returns the avatar image tag for the given +user+ if avatars are enabled
982 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
982 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
983 def avatar(user, options = { })
983 def avatar(user, options = { })
984 if Setting.gravatar_enabled?
984 if Setting.gravatar_enabled?
985 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
985 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
986 email = nil
986 email = nil
987 if user.respond_to?(:mail)
987 if user.respond_to?(:mail)
988 email = user.mail
988 email = user.mail
989 elsif user.to_s =~ %r{<(.+?)>}
989 elsif user.to_s =~ %r{<(.+?)>}
990 email = $1
990 email = $1
991 end
991 end
992 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
992 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
993 else
993 else
994 ''
994 ''
995 end
995 end
996 end
996 end
997
997
998 def sanitize_anchor_name(anchor)
998 def sanitize_anchor_name(anchor)
999 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
999 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1000 end
1000 end
1001
1001
1002 # Returns the javascript tags that are included in the html layout head
1002 # Returns the javascript tags that are included in the html layout head
1003 def javascript_heads
1003 def javascript_heads
1004 tags = javascript_include_tag(:defaults)
1004 tags = javascript_include_tag(:defaults)
1005 unless User.current.pref.warn_on_leaving_unsaved == '0'
1005 unless User.current.pref.warn_on_leaving_unsaved == '0'
1006 tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
1006 tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
1007 end
1007 end
1008 tags
1008 tags
1009 end
1009 end
1010
1010
1011 def favicon
1011 def favicon
1012 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
1012 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
1013 end
1013 end
1014
1014
1015 def robot_exclusion_tag
1015 def robot_exclusion_tag
1016 '<meta name="robots" content="noindex,follow,noarchive" />'
1016 '<meta name="robots" content="noindex,follow,noarchive" />'
1017 end
1017 end
1018
1018
1019 # Returns true if arg is expected in the API response
1019 # Returns true if arg is expected in the API response
1020 def include_in_api_response?(arg)
1020 def include_in_api_response?(arg)
1021 unless @included_in_api_response
1021 unless @included_in_api_response
1022 param = params[:include]
1022 param = params[:include]
1023 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1023 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1024 @included_in_api_response.collect!(&:strip)
1024 @included_in_api_response.collect!(&:strip)
1025 end
1025 end
1026 @included_in_api_response.include?(arg.to_s)
1026 @included_in_api_response.include?(arg.to_s)
1027 end
1027 end
1028
1028
1029 # Returns options or nil if nometa param or X-Redmine-Nometa header
1029 # Returns options or nil if nometa param or X-Redmine-Nometa header
1030 # was set in the request
1030 # was set in the request
1031 def api_meta(options)
1031 def api_meta(options)
1032 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1032 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1033 # compatibility mode for activeresource clients that raise
1033 # compatibility mode for activeresource clients that raise
1034 # an error when unserializing an array with attributes
1034 # an error when unserializing an array with attributes
1035 nil
1035 nil
1036 else
1036 else
1037 options
1037 options
1038 end
1038 end
1039 end
1039 end
1040
1040
1041 private
1041 private
1042
1042
1043 def wiki_helper
1043 def wiki_helper
1044 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1044 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1045 extend helper
1045 extend helper
1046 return self
1046 return self
1047 end
1047 end
1048
1048
1049 def link_to_content_update(text, url_params = {}, html_options = {})
1049 def link_to_content_update(text, url_params = {}, html_options = {})
1050 link_to(text, url_params, html_options)
1050 link_to(text, url_params, html_options)
1051 end
1051 end
1052 end
1052 end
@@ -1,5 +1,5
1 <h2><%= link_to l(:label_tracker_plural), :controller => 'trackers', :action => 'index' %> &#187; <%=h @tracker %></h2>
1 <h2><%= link_to l(:label_tracker_plural), trackers_path %> &#187; <%=h @tracker %></h2>
2
2
3 <% form_for :tracker, @tracker, :url => { :action => 'edit' }, :builder => TabularFormBuilder do |f| %>
3 <% form_for @tracker, :builder => TabularFormBuilder do |f| %>
4 <%= render :partial => 'form', :locals => { :f => f } %>
4 <%= render :partial => 'form', :locals => { :f => f } %>
5 <% end %>
5 <% end %>
@@ -1,33 +1,33
1 <div class="contextual">
1 <div class="contextual">
2 <%= link_to l(:label_tracker_new), {:action => 'new'}, :class => 'icon icon-add' %>
2 <%= link_to l(:label_tracker_new), new_tracker_path, :class => 'icon icon-add' %>
3 </div>
3 </div>
4
4
5 <h2><%=l(:label_tracker_plural)%></h2>
5 <h2><%=l(:label_tracker_plural)%></h2>
6
6
7 <table class="list">
7 <table class="list">
8 <thead><tr>
8 <thead><tr>
9 <th><%=l(:label_tracker)%></th>
9 <th><%=l(:label_tracker)%></th>
10 <th></th>
10 <th></th>
11 <th><%=l(:button_sort)%></th>
11 <th><%=l(:button_sort)%></th>
12 <th></th>
12 <th></th>
13 </tr></thead>
13 </tr></thead>
14 <tbody>
14 <tbody>
15 <% for tracker in @trackers %>
15 <% for tracker in @trackers %>
16 <tr class="<%= cycle("odd", "even") %>">
16 <tr class="<%= cycle("odd", "even") %>">
17 <td><%= link_to h(tracker.name), :action => 'edit', :id => tracker %></td>
17 <td><%= link_to h(tracker.name), edit_tracker_path(tracker) %></td>
18 <td align="center"><% unless tracker.workflows.count > 0 %><span class="icon icon-warning"><%= l(:text_tracker_no_workflow) %> (<%= link_to l(:button_edit), {:controller => 'workflows', :action => 'edit', :tracker_id => tracker} %>)</span><% end %></td>
18 <td align="center"><% unless tracker.workflows.count > 0 %><span class="icon icon-warning"><%= l(:text_tracker_no_workflow) %> (<%= link_to l(:button_edit), {:controller => 'workflows', :action => 'edit', :tracker_id => tracker} %>)</span><% end %></td>
19 <td align="center" style="width:15%;"><%= reorder_links('tracker', {:action => 'edit', :id => tracker}) %></td>
19 <td align="center" style="width:15%;"><%= reorder_links('tracker', {:action => 'update', :id => tracker}, :put) %></td>
20 <td class="buttons">
20 <td class="buttons">
21 <%= link_to(l(:button_delete), { :action => 'destroy', :id => tracker },
21 <%= link_to(l(:button_delete), tracker_path(tracker),
22 :method => :post,
22 :method => :delete,
23 :confirm => l(:text_are_you_sure),
23 :confirm => l(:text_are_you_sure),
24 :class => 'icon icon-del') %>
24 :class => 'icon icon-del') %>
25 </td>
25 </td>
26 </tr>
26 </tr>
27 <% end %>
27 <% end %>
28 </tbody>
28 </tbody>
29 </table>
29 </table>
30
30
31 <p class="pagination"><%= pagination_links_full @tracker_pages %></p>
31 <p class="pagination"><%= pagination_links_full @tracker_pages %></p>
32
32
33 <% html_title(l(:label_tracker_plural)) -%>
33 <% html_title(l(:label_tracker_plural)) -%>
@@ -1,5 +1,5
1 <h2><%= link_to l(:label_tracker_plural), :controller => 'trackers', :action => 'index' %> &#187; <%=l(:label_tracker_new)%></h2>
1 <h2><%= link_to l(:label_tracker_plural), trackers_path %> &#187; <%=l(:label_tracker_new)%></h2>
2
2
3 <% form_for :tracker, @tracker, :url => { :action => 'new' }, :builder => TabularFormBuilder do |f| %>
3 <% form_for @tracker, :builder => TabularFormBuilder do |f| %>
4 <%= render :partial => 'form', :locals => { :f => f } %>
4 <%= render :partial => 'form', :locals => { :f => f } %>
5 <% end %>
5 <% end %>
@@ -1,255 +1,255
1 ActionController::Routing::Routes.draw do |map|
1 ActionController::Routing::Routes.draw do |map|
2 # Add your own custom routes here.
2 # Add your own custom routes here.
3 # The priority is based upon order of creation: first created -> highest priority.
3 # The priority is based upon order of creation: first created -> highest priority.
4
4
5 # Here's a sample route:
5 # Here's a sample route:
6 # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
6 # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
7 # Keep in mind you can assign values other than :controller and :action
7 # Keep in mind you can assign values other than :controller and :action
8
8
9 map.home '', :controller => 'welcome'
9 map.home '', :controller => 'welcome'
10
10
11 map.signin 'login', :controller => 'account', :action => 'login'
11 map.signin 'login', :controller => 'account', :action => 'login'
12 map.signout 'logout', :controller => 'account', :action => 'logout'
12 map.signout 'logout', :controller => 'account', :action => 'logout'
13
13
14 map.connect 'roles/workflow/:id/:role_id/:tracker_id', :controller => 'roles', :action => 'workflow'
14 map.connect 'roles/workflow/:id/:role_id/:tracker_id', :controller => 'roles', :action => 'workflow'
15 map.connect 'help/:ctrl/:page', :controller => 'help'
15 map.connect 'help/:ctrl/:page', :controller => 'help'
16
16
17 map.with_options :controller => 'time_entry_reports', :action => 'report',:conditions => {:method => :get} do |time_report|
17 map.with_options :controller => 'time_entry_reports', :action => 'report',:conditions => {:method => :get} do |time_report|
18 time_report.connect 'projects/:project_id/issues/:issue_id/time_entries/report'
18 time_report.connect 'projects/:project_id/issues/:issue_id/time_entries/report'
19 time_report.connect 'projects/:project_id/issues/:issue_id/time_entries/report.:format'
19 time_report.connect 'projects/:project_id/issues/:issue_id/time_entries/report.:format'
20 time_report.connect 'projects/:project_id/time_entries/report'
20 time_report.connect 'projects/:project_id/time_entries/report'
21 time_report.connect 'projects/:project_id/time_entries/report.:format'
21 time_report.connect 'projects/:project_id/time_entries/report.:format'
22 time_report.connect 'time_entries/report'
22 time_report.connect 'time_entries/report'
23 time_report.connect 'time_entries/report.:format'
23 time_report.connect 'time_entries/report.:format'
24 end
24 end
25
25
26 map.bulk_edit_time_entry 'time_entries/bulk_edit',
26 map.bulk_edit_time_entry 'time_entries/bulk_edit',
27 :controller => 'timelog', :action => 'bulk_edit', :conditions => { :method => :get }
27 :controller => 'timelog', :action => 'bulk_edit', :conditions => { :method => :get }
28 map.bulk_update_time_entry 'time_entries/bulk_edit',
28 map.bulk_update_time_entry 'time_entries/bulk_edit',
29 :controller => 'timelog', :action => 'bulk_update', :conditions => { :method => :post }
29 :controller => 'timelog', :action => 'bulk_update', :conditions => { :method => :post }
30 map.time_entries_context_menu '/time_entries/context_menu',
30 map.time_entries_context_menu '/time_entries/context_menu',
31 :controller => 'context_menus', :action => 'time_entries'
31 :controller => 'context_menus', :action => 'time_entries'
32 # TODO: wasteful since this is also nested under issues, projects, and projects/issues
32 # TODO: wasteful since this is also nested under issues, projects, and projects/issues
33 map.resources :time_entries, :controller => 'timelog'
33 map.resources :time_entries, :controller => 'timelog'
34
34
35 map.connect 'projects/:id/wiki', :controller => 'wikis', :action => 'edit', :conditions => {:method => :post}
35 map.connect 'projects/:id/wiki', :controller => 'wikis', :action => 'edit', :conditions => {:method => :post}
36 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :get}
36 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :get}
37 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :post}
37 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :post}
38
38
39 map.with_options :controller => 'messages' do |messages_routes|
39 map.with_options :controller => 'messages' do |messages_routes|
40 messages_routes.with_options :conditions => {:method => :get} do |messages_views|
40 messages_routes.with_options :conditions => {:method => :get} do |messages_views|
41 messages_views.connect 'boards/:board_id/topics/new', :action => 'new'
41 messages_views.connect 'boards/:board_id/topics/new', :action => 'new'
42 messages_views.connect 'boards/:board_id/topics/:id', :action => 'show'
42 messages_views.connect 'boards/:board_id/topics/:id', :action => 'show'
43 messages_views.connect 'boards/:board_id/topics/:id/edit', :action => 'edit'
43 messages_views.connect 'boards/:board_id/topics/:id/edit', :action => 'edit'
44 end
44 end
45 messages_routes.with_options :conditions => {:method => :post} do |messages_actions|
45 messages_routes.with_options :conditions => {:method => :post} do |messages_actions|
46 messages_actions.connect 'boards/:board_id/topics/new', :action => 'new'
46 messages_actions.connect 'boards/:board_id/topics/new', :action => 'new'
47 messages_actions.connect 'boards/:board_id/topics/:id/replies', :action => 'reply'
47 messages_actions.connect 'boards/:board_id/topics/:id/replies', :action => 'reply'
48 messages_actions.connect 'boards/:board_id/topics/:id/:action', :action => /edit|destroy/
48 messages_actions.connect 'boards/:board_id/topics/:id/:action', :action => /edit|destroy/
49 end
49 end
50 end
50 end
51
51
52 map.with_options :controller => 'boards' do |board_routes|
52 map.with_options :controller => 'boards' do |board_routes|
53 board_routes.with_options :conditions => {:method => :get} do |board_views|
53 board_routes.with_options :conditions => {:method => :get} do |board_views|
54 board_views.connect 'projects/:project_id/boards', :action => 'index'
54 board_views.connect 'projects/:project_id/boards', :action => 'index'
55 board_views.connect 'projects/:project_id/boards/new', :action => 'new'
55 board_views.connect 'projects/:project_id/boards/new', :action => 'new'
56 board_views.connect 'projects/:project_id/boards/:id', :action => 'show'
56 board_views.connect 'projects/:project_id/boards/:id', :action => 'show'
57 board_views.connect 'projects/:project_id/boards/:id.:format', :action => 'show'
57 board_views.connect 'projects/:project_id/boards/:id.:format', :action => 'show'
58 board_views.connect 'projects/:project_id/boards/:id/edit', :action => 'edit'
58 board_views.connect 'projects/:project_id/boards/:id/edit', :action => 'edit'
59 end
59 end
60 board_routes.with_options :conditions => {:method => :post} do |board_actions|
60 board_routes.with_options :conditions => {:method => :post} do |board_actions|
61 board_actions.connect 'projects/:project_id/boards', :action => 'new'
61 board_actions.connect 'projects/:project_id/boards', :action => 'new'
62 board_actions.connect 'projects/:project_id/boards/:id/:action', :action => /edit|destroy/
62 board_actions.connect 'projects/:project_id/boards/:id/:action', :action => /edit|destroy/
63 end
63 end
64 end
64 end
65
65
66 map.with_options :controller => 'documents' do |document_routes|
66 map.with_options :controller => 'documents' do |document_routes|
67 document_routes.with_options :conditions => {:method => :get} do |document_views|
67 document_routes.with_options :conditions => {:method => :get} do |document_views|
68 document_views.connect 'projects/:project_id/documents', :action => 'index'
68 document_views.connect 'projects/:project_id/documents', :action => 'index'
69 document_views.connect 'projects/:project_id/documents/new', :action => 'new'
69 document_views.connect 'projects/:project_id/documents/new', :action => 'new'
70 document_views.connect 'documents/:id', :action => 'show'
70 document_views.connect 'documents/:id', :action => 'show'
71 document_views.connect 'documents/:id/edit', :action => 'edit'
71 document_views.connect 'documents/:id/edit', :action => 'edit'
72 end
72 end
73 document_routes.with_options :conditions => {:method => :post} do |document_actions|
73 document_routes.with_options :conditions => {:method => :post} do |document_actions|
74 document_actions.connect 'projects/:project_id/documents', :action => 'new'
74 document_actions.connect 'projects/:project_id/documents', :action => 'new'
75 document_actions.connect 'documents/:id/:action', :action => /destroy|edit/
75 document_actions.connect 'documents/:id/:action', :action => /destroy|edit/
76 end
76 end
77 end
77 end
78
78
79 map.resources :issue_moves, :only => [:new, :create], :path_prefix => '/issues', :as => 'move'
79 map.resources :issue_moves, :only => [:new, :create], :path_prefix => '/issues', :as => 'move'
80 map.resources :queries, :except => [:show]
80 map.resources :queries, :except => [:show]
81
81
82 # Misc issue routes. TODO: move into resources
82 # Misc issue routes. TODO: move into resources
83 map.auto_complete_issues '/issues/auto_complete', :controller => 'auto_completes', :action => 'issues'
83 map.auto_complete_issues '/issues/auto_complete', :controller => 'auto_completes', :action => 'issues'
84 map.preview_issue '/issues/preview/:id', :controller => 'previews', :action => 'issue' # TODO: would look nicer as /issues/:id/preview
84 map.preview_issue '/issues/preview/:id', :controller => 'previews', :action => 'issue' # TODO: would look nicer as /issues/:id/preview
85 map.issues_context_menu '/issues/context_menu', :controller => 'context_menus', :action => 'issues'
85 map.issues_context_menu '/issues/context_menu', :controller => 'context_menus', :action => 'issues'
86 map.issue_changes '/issues/changes', :controller => 'journals', :action => 'index'
86 map.issue_changes '/issues/changes', :controller => 'journals', :action => 'index'
87 map.bulk_edit_issue 'issues/bulk_edit', :controller => 'issues', :action => 'bulk_edit', :conditions => { :method => :get }
87 map.bulk_edit_issue 'issues/bulk_edit', :controller => 'issues', :action => 'bulk_edit', :conditions => { :method => :get }
88 map.bulk_update_issue 'issues/bulk_edit', :controller => 'issues', :action => 'bulk_update', :conditions => { :method => :post }
88 map.bulk_update_issue 'issues/bulk_edit', :controller => 'issues', :action => 'bulk_update', :conditions => { :method => :post }
89 map.quoted_issue '/issues/:id/quoted', :controller => 'journals', :action => 'new', :id => /\d+/, :conditions => { :method => :post }
89 map.quoted_issue '/issues/:id/quoted', :controller => 'journals', :action => 'new', :id => /\d+/, :conditions => { :method => :post }
90 map.connect '/issues/:id/destroy', :controller => 'issues', :action => 'destroy', :conditions => { :method => :post } # legacy
90 map.connect '/issues/:id/destroy', :controller => 'issues', :action => 'destroy', :conditions => { :method => :post } # legacy
91
91
92 map.with_options :controller => 'gantts', :action => 'show' do |gantts_routes|
92 map.with_options :controller => 'gantts', :action => 'show' do |gantts_routes|
93 gantts_routes.connect '/projects/:project_id/issues/gantt'
93 gantts_routes.connect '/projects/:project_id/issues/gantt'
94 gantts_routes.connect '/projects/:project_id/issues/gantt.:format'
94 gantts_routes.connect '/projects/:project_id/issues/gantt.:format'
95 gantts_routes.connect '/issues/gantt.:format'
95 gantts_routes.connect '/issues/gantt.:format'
96 end
96 end
97
97
98 map.with_options :controller => 'calendars', :action => 'show' do |calendars_routes|
98 map.with_options :controller => 'calendars', :action => 'show' do |calendars_routes|
99 calendars_routes.connect '/projects/:project_id/issues/calendar'
99 calendars_routes.connect '/projects/:project_id/issues/calendar'
100 calendars_routes.connect '/issues/calendar'
100 calendars_routes.connect '/issues/calendar'
101 end
101 end
102
102
103 map.with_options :controller => 'reports', :conditions => {:method => :get} do |reports|
103 map.with_options :controller => 'reports', :conditions => {:method => :get} do |reports|
104 reports.connect 'projects/:id/issues/report', :action => 'issue_report'
104 reports.connect 'projects/:id/issues/report', :action => 'issue_report'
105 reports.connect 'projects/:id/issues/report/:detail', :action => 'issue_report_details'
105 reports.connect 'projects/:id/issues/report/:detail', :action => 'issue_report_details'
106 end
106 end
107
107
108 # Following two routes conflict with the resources because #index allows POST
108 # Following two routes conflict with the resources because #index allows POST
109 map.connect '/issues', :controller => 'issues', :action => 'index', :conditions => { :method => :post }
109 map.connect '/issues', :controller => 'issues', :action => 'index', :conditions => { :method => :post }
110 map.connect '/issues/create', :controller => 'issues', :action => 'index', :conditions => { :method => :post }
110 map.connect '/issues/create', :controller => 'issues', :action => 'index', :conditions => { :method => :post }
111
111
112 map.resources :issues, :member => { :edit => :post }, :collection => {} do |issues|
112 map.resources :issues, :member => { :edit => :post }, :collection => {} do |issues|
113 issues.resources :time_entries, :controller => 'timelog'
113 issues.resources :time_entries, :controller => 'timelog'
114 issues.resources :relations, :shallow => true, :controller => 'issue_relations', :only => [:index, :show, :create, :destroy]
114 issues.resources :relations, :shallow => true, :controller => 'issue_relations', :only => [:index, :show, :create, :destroy]
115 end
115 end
116
116
117 map.resources :issues, :path_prefix => '/projects/:project_id', :collection => { :create => :post } do |issues|
117 map.resources :issues, :path_prefix => '/projects/:project_id', :collection => { :create => :post } do |issues|
118 issues.resources :time_entries, :controller => 'timelog'
118 issues.resources :time_entries, :controller => 'timelog'
119 end
119 end
120
120
121 map.connect 'projects/:id/members/new', :controller => 'members', :action => 'new'
121 map.connect 'projects/:id/members/new', :controller => 'members', :action => 'new'
122
122
123 map.with_options :controller => 'users' do |users|
123 map.with_options :controller => 'users' do |users|
124 users.connect 'users/:id/edit/:tab', :action => 'edit', :tab => nil, :conditions => {:method => :get}
124 users.connect 'users/:id/edit/:tab', :action => 'edit', :tab => nil, :conditions => {:method => :get}
125
125
126 users.with_options :conditions => {:method => :post} do |user_actions|
126 users.with_options :conditions => {:method => :post} do |user_actions|
127 user_actions.connect 'users/:id/memberships', :action => 'edit_membership'
127 user_actions.connect 'users/:id/memberships', :action => 'edit_membership'
128 user_actions.connect 'users/:id/memberships/:membership_id', :action => 'edit_membership'
128 user_actions.connect 'users/:id/memberships/:membership_id', :action => 'edit_membership'
129 user_actions.connect 'users/:id/memberships/:membership_id/destroy', :action => 'destroy_membership'
129 user_actions.connect 'users/:id/memberships/:membership_id/destroy', :action => 'destroy_membership'
130 end
130 end
131 end
131 end
132
132
133 map.resources :users, :member => {
133 map.resources :users, :member => {
134 :edit_membership => :post,
134 :edit_membership => :post,
135 :destroy_membership => :post
135 :destroy_membership => :post
136 }
136 }
137
137
138 # For nice "roadmap" in the url for the index action
138 # For nice "roadmap" in the url for the index action
139 map.connect 'projects/:project_id/roadmap', :controller => 'versions', :action => 'index'
139 map.connect 'projects/:project_id/roadmap', :controller => 'versions', :action => 'index'
140
140
141 map.all_news 'news', :controller => 'news', :action => 'index'
141 map.all_news 'news', :controller => 'news', :action => 'index'
142 map.formatted_all_news 'news.:format', :controller => 'news', :action => 'index'
142 map.formatted_all_news 'news.:format', :controller => 'news', :action => 'index'
143 map.preview_news '/news/preview', :controller => 'previews', :action => 'news'
143 map.preview_news '/news/preview', :controller => 'previews', :action => 'news'
144 map.connect 'news/:id/comments', :controller => 'comments', :action => 'create', :conditions => {:method => :post}
144 map.connect 'news/:id/comments', :controller => 'comments', :action => 'create', :conditions => {:method => :post}
145 map.connect 'news/:id/comments/:comment_id', :controller => 'comments', :action => 'destroy', :conditions => {:method => :delete}
145 map.connect 'news/:id/comments/:comment_id', :controller => 'comments', :action => 'destroy', :conditions => {:method => :delete}
146
146
147 map.resources :projects, :member => {
147 map.resources :projects, :member => {
148 :copy => [:get, :post],
148 :copy => [:get, :post],
149 :settings => :get,
149 :settings => :get,
150 :modules => :post,
150 :modules => :post,
151 :archive => :post,
151 :archive => :post,
152 :unarchive => :post
152 :unarchive => :post
153 } do |project|
153 } do |project|
154 project.resource :project_enumerations, :as => 'enumerations', :only => [:update, :destroy]
154 project.resource :project_enumerations, :as => 'enumerations', :only => [:update, :destroy]
155 project.resources :files, :only => [:index, :new, :create]
155 project.resources :files, :only => [:index, :new, :create]
156 project.resources :versions, :shallow => true, :collection => {:close_completed => :put}, :member => {:status_by => :post}
156 project.resources :versions, :shallow => true, :collection => {:close_completed => :put}, :member => {:status_by => :post}
157 project.resources :news, :shallow => true
157 project.resources :news, :shallow => true
158 project.resources :time_entries, :controller => 'timelog', :path_prefix => 'projects/:project_id'
158 project.resources :time_entries, :controller => 'timelog', :path_prefix => 'projects/:project_id'
159 project.resources :queries, :only => [:new, :create]
159 project.resources :queries, :only => [:new, :create]
160 project.resources :issue_categories, :shallow => true
160 project.resources :issue_categories, :shallow => true
161
161
162 project.wiki_start_page 'wiki', :controller => 'wiki', :action => 'show', :conditions => {:method => :get}
162 project.wiki_start_page 'wiki', :controller => 'wiki', :action => 'show', :conditions => {:method => :get}
163 project.wiki_index 'wiki/index', :controller => 'wiki', :action => 'index', :conditions => {:method => :get}
163 project.wiki_index 'wiki/index', :controller => 'wiki', :action => 'index', :conditions => {:method => :get}
164 project.wiki_diff 'wiki/:id/diff/:version', :controller => 'wiki', :action => 'diff', :version => nil
164 project.wiki_diff 'wiki/:id/diff/:version', :controller => 'wiki', :action => 'diff', :version => nil
165 project.wiki_diff 'wiki/:id/diff/:version/vs/:version_from', :controller => 'wiki', :action => 'diff'
165 project.wiki_diff 'wiki/:id/diff/:version/vs/:version_from', :controller => 'wiki', :action => 'diff'
166 project.wiki_annotate 'wiki/:id/annotate/:version', :controller => 'wiki', :action => 'annotate'
166 project.wiki_annotate 'wiki/:id/annotate/:version', :controller => 'wiki', :action => 'annotate'
167 project.resources :wiki, :except => [:new, :create], :member => {
167 project.resources :wiki, :except => [:new, :create], :member => {
168 :rename => [:get, :post],
168 :rename => [:get, :post],
169 :history => :get,
169 :history => :get,
170 :preview => :any,
170 :preview => :any,
171 :protect => :post,
171 :protect => :post,
172 :add_attachment => :post
172 :add_attachment => :post
173 }, :collection => {
173 }, :collection => {
174 :export => :get,
174 :export => :get,
175 :date_index => :get
175 :date_index => :get
176 }
176 }
177
177
178 end
178 end
179
179
180 # Destroy uses a get request to prompt the user before the actual DELETE request
180 # Destroy uses a get request to prompt the user before the actual DELETE request
181 map.project_destroy_confirm 'projects/:id/destroy', :controller => 'projects', :action => 'destroy', :conditions => {:method => :get}
181 map.project_destroy_confirm 'projects/:id/destroy', :controller => 'projects', :action => 'destroy', :conditions => {:method => :get}
182
182
183 # TODO: port to be part of the resources route(s)
183 # TODO: port to be part of the resources route(s)
184 map.with_options :controller => 'projects' do |project_mapper|
184 map.with_options :controller => 'projects' do |project_mapper|
185 project_mapper.with_options :conditions => {:method => :get} do |project_views|
185 project_mapper.with_options :conditions => {:method => :get} do |project_views|
186 project_views.connect 'projects/:id/settings/:tab', :controller => 'projects', :action => 'settings'
186 project_views.connect 'projects/:id/settings/:tab', :controller => 'projects', :action => 'settings'
187 project_views.connect 'projects/:project_id/issues/:copy_from/copy', :controller => 'issues', :action => 'new'
187 project_views.connect 'projects/:project_id/issues/:copy_from/copy', :controller => 'issues', :action => 'new'
188 end
188 end
189 end
189 end
190
190
191 map.with_options :controller => 'activities', :action => 'index', :conditions => {:method => :get} do |activity|
191 map.with_options :controller => 'activities', :action => 'index', :conditions => {:method => :get} do |activity|
192 activity.connect 'projects/:id/activity'
192 activity.connect 'projects/:id/activity'
193 activity.connect 'projects/:id/activity.:format'
193 activity.connect 'projects/:id/activity.:format'
194 activity.connect 'activity', :id => nil
194 activity.connect 'activity', :id => nil
195 activity.connect 'activity.:format', :id => nil
195 activity.connect 'activity.:format', :id => nil
196 end
196 end
197
197
198 map.with_options :controller => 'repositories' do |repositories|
198 map.with_options :controller => 'repositories' do |repositories|
199 repositories.with_options :conditions => {:method => :get} do |repository_views|
199 repositories.with_options :conditions => {:method => :get} do |repository_views|
200 repository_views.connect 'projects/:id/repository', :action => 'show'
200 repository_views.connect 'projects/:id/repository', :action => 'show'
201 repository_views.connect 'projects/:id/repository/edit', :action => 'edit'
201 repository_views.connect 'projects/:id/repository/edit', :action => 'edit'
202 repository_views.connect 'projects/:id/repository/statistics', :action => 'stats'
202 repository_views.connect 'projects/:id/repository/statistics', :action => 'stats'
203 repository_views.connect 'projects/:id/repository/revisions', :action => 'revisions'
203 repository_views.connect 'projects/:id/repository/revisions', :action => 'revisions'
204 repository_views.connect 'projects/:id/repository/revisions.:format', :action => 'revisions'
204 repository_views.connect 'projects/:id/repository/revisions.:format', :action => 'revisions'
205 repository_views.connect 'projects/:id/repository/revisions/:rev', :action => 'revision'
205 repository_views.connect 'projects/:id/repository/revisions/:rev', :action => 'revision'
206 repository_views.connect 'projects/:id/repository/revisions/:rev/diff', :action => 'diff'
206 repository_views.connect 'projects/:id/repository/revisions/:rev/diff', :action => 'diff'
207 repository_views.connect 'projects/:id/repository/revisions/:rev/diff.:format', :action => 'diff'
207 repository_views.connect 'projects/:id/repository/revisions/:rev/diff.:format', :action => 'diff'
208 repository_views.connect 'projects/:id/repository/revisions/:rev/raw/*path', :action => 'entry', :format => 'raw', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
208 repository_views.connect 'projects/:id/repository/revisions/:rev/raw/*path', :action => 'entry', :format => 'raw', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
209 repository_views.connect 'projects/:id/repository/revisions/:rev/:action/*path', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
209 repository_views.connect 'projects/:id/repository/revisions/:rev/:action/*path', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
210 repository_views.connect 'projects/:id/repository/raw/*path', :action => 'entry', :format => 'raw'
210 repository_views.connect 'projects/:id/repository/raw/*path', :action => 'entry', :format => 'raw'
211 # TODO: why the following route is required?
211 # TODO: why the following route is required?
212 repository_views.connect 'projects/:id/repository/entry/*path', :action => 'entry'
212 repository_views.connect 'projects/:id/repository/entry/*path', :action => 'entry'
213 repository_views.connect 'projects/:id/repository/:action/*path'
213 repository_views.connect 'projects/:id/repository/:action/*path'
214 end
214 end
215
215
216 repositories.connect 'projects/:id/repository/:action', :conditions => {:method => :post}
216 repositories.connect 'projects/:id/repository/:action', :conditions => {:method => :post}
217 end
217 end
218
218
219 map.connect 'attachments/:id', :controller => 'attachments', :action => 'show', :id => /\d+/
219 map.connect 'attachments/:id', :controller => 'attachments', :action => 'show', :id => /\d+/
220 map.connect 'attachments/:id.:format', :controller => 'attachments', :action => 'show', :id => /\d+/
220 map.connect 'attachments/:id.:format', :controller => 'attachments', :action => 'show', :id => /\d+/
221 map.connect 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/
221 map.connect 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/
222 map.connect 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/
222 map.connect 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/
223
223
224 map.resources :groups
224 map.resources :groups
225 map.resources :trackers, :except => :show
225
226
226 #left old routes at the bottom for backwards compat
227 #left old routes at the bottom for backwards compat
227 map.connect 'trackers.:format', :controller => 'trackers', :action => 'index'
228 map.connect 'issue_statuses.:format', :controller => 'issue_statuses', :action => 'index'
228 map.connect 'issue_statuses.:format', :controller => 'issue_statuses', :action => 'index'
229 map.connect 'projects/:project_id/issues/:action', :controller => 'issues'
229 map.connect 'projects/:project_id/issues/:action', :controller => 'issues'
230 map.connect 'projects/:project_id/documents/:action', :controller => 'documents'
230 map.connect 'projects/:project_id/documents/:action', :controller => 'documents'
231 map.connect 'projects/:project_id/boards/:action/:id', :controller => 'boards'
231 map.connect 'projects/:project_id/boards/:action/:id', :controller => 'boards'
232 map.connect 'boards/:board_id/topics/:action/:id', :controller => 'messages'
232 map.connect 'boards/:board_id/topics/:action/:id', :controller => 'messages'
233 map.connect 'wiki/:id/:page/:action', :page => nil, :controller => 'wiki'
233 map.connect 'wiki/:id/:page/:action', :page => nil, :controller => 'wiki'
234 map.connect 'projects/:project_id/news/:action', :controller => 'news'
234 map.connect 'projects/:project_id/news/:action', :controller => 'news'
235 map.connect 'projects/:project_id/timelog/:action/:id', :controller => 'timelog', :project_id => /.+/
235 map.connect 'projects/:project_id/timelog/:action/:id', :controller => 'timelog', :project_id => /.+/
236 map.with_options :controller => 'repositories' do |omap|
236 map.with_options :controller => 'repositories' do |omap|
237 omap.repositories_show 'repositories/browse/:id/*path', :action => 'browse'
237 omap.repositories_show 'repositories/browse/:id/*path', :action => 'browse'
238 omap.repositories_changes 'repositories/changes/:id/*path', :action => 'changes'
238 omap.repositories_changes 'repositories/changes/:id/*path', :action => 'changes'
239 omap.repositories_diff 'repositories/diff/:id/*path', :action => 'diff'
239 omap.repositories_diff 'repositories/diff/:id/*path', :action => 'diff'
240 omap.repositories_entry 'repositories/entry/:id/*path', :action => 'entry'
240 omap.repositories_entry 'repositories/entry/:id/*path', :action => 'entry'
241 omap.repositories_entry 'repositories/annotate/:id/*path', :action => 'annotate'
241 omap.repositories_entry 'repositories/annotate/:id/*path', :action => 'annotate'
242 omap.connect 'repositories/revision/:id/:rev', :action => 'revision'
242 omap.connect 'repositories/revision/:id/:rev', :action => 'revision'
243 end
243 end
244
244
245 map.with_options :controller => 'sys' do |sys|
245 map.with_options :controller => 'sys' do |sys|
246 sys.connect 'sys/projects.:format', :action => 'projects', :conditions => {:method => :get}
246 sys.connect 'sys/projects.:format', :action => 'projects', :conditions => {:method => :get}
247 sys.connect 'sys/projects/:id/repository.:format', :action => 'create_project_repository', :conditions => {:method => :post}
247 sys.connect 'sys/projects/:id/repository.:format', :action => 'create_project_repository', :conditions => {:method => :post}
248 end
248 end
249
249
250 # Install the default route as the lowest priority.
250 # Install the default route as the lowest priority.
251 map.connect ':controller/:action/:id'
251 map.connect ':controller/:action/:id'
252 map.connect 'robots.txt', :controller => 'welcome', :action => 'robots'
252 map.connect 'robots.txt', :controller => 'welcome', :action => 'robots'
253 # Used for OpenID
253 # Used for OpenID
254 map.root :controller => 'account', :action => 'login'
254 map.root :controller => 'account', :action => 'login'
255 end
255 end
@@ -1,132 +1,145
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19 require 'trackers_controller'
19 require 'trackers_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class TrackersController; def rescue_action(e) raise e end; end
22 class TrackersController; def rescue_action(e) raise e end; end
23
23
24 class TrackersControllerTest < ActionController::TestCase
24 class TrackersControllerTest < ActionController::TestCase
25 fixtures :trackers, :projects, :projects_trackers, :users, :issues, :custom_fields
25 fixtures :trackers, :projects, :projects_trackers, :users, :issues, :custom_fields
26
26
27 def setup
27 def setup
28 @controller = TrackersController.new
28 @controller = TrackersController.new
29 @request = ActionController::TestRequest.new
29 @request = ActionController::TestRequest.new
30 @response = ActionController::TestResponse.new
30 @response = ActionController::TestResponse.new
31 User.current = nil
31 User.current = nil
32 @request.session[:user_id] = 1 # admin
32 @request.session[:user_id] = 1 # admin
33 end
33 end
34
34
35 def test_index
35 def test_index
36 get :index
36 get :index
37 assert_response :success
37 assert_response :success
38 assert_template 'index'
38 assert_template 'index'
39 end
39 end
40
40
41 def test_index_by_anonymous_should_redirect_to_login_form
41 def test_index_by_anonymous_should_redirect_to_login_form
42 @request.session[:user_id] = nil
42 @request.session[:user_id] = nil
43 get :index
43 get :index
44 assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Ftrackers'
44 assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Ftrackers'
45 end
45 end
46
46
47 def test_index_by_user_should_respond_with_406
47 def test_index_by_user_should_respond_with_406
48 @request.session[:user_id] = 2
48 @request.session[:user_id] = 2
49 get :index
49 get :index
50 assert_response 406
50 assert_response 406
51 end
51 end
52
52
53 def test_get_new
53 def test_new
54 get :new
54 get :new
55 assert_response :success
55 assert_response :success
56 assert_template 'new'
56 assert_template 'new'
57 end
57 end
58
58
59 def test_post_new
59 def test_create
60 post :new, :tracker => { :name => 'New tracker', :project_ids => ['1', '', ''], :custom_field_ids => ['1', '6', ''] }
60 assert_difference 'Tracker.count' do
61 post :create, :tracker => { :name => 'New tracker', :project_ids => ['1', '', ''], :custom_field_ids => ['1', '6', ''] }
62 end
61 assert_redirected_to :action => 'index'
63 assert_redirected_to :action => 'index'
62 tracker = Tracker.find_by_name('New tracker')
64 tracker = Tracker.first(:order => 'id DESC')
65 assert_equal 'New tracker', tracker.name
63 assert_equal [1], tracker.project_ids.sort
66 assert_equal [1], tracker.project_ids.sort
64 assert_equal [1, 6], tracker.custom_field_ids
67 assert_equal [1, 6], tracker.custom_field_ids
65 assert_equal 0, tracker.workflows.count
68 assert_equal 0, tracker.workflows.count
66 end
69 end
67
70
68 def test_post_new_with_workflow_copy
71 def test_create_new_with_workflow_copy
69 post :new, :tracker => { :name => 'New tracker' }, :copy_workflow_from => 1
72 assert_difference 'Tracker.count' do
73 post :create, :tracker => { :name => 'New tracker' }, :copy_workflow_from => 1
74 end
70 assert_redirected_to :action => 'index'
75 assert_redirected_to :action => 'index'
71 tracker = Tracker.find_by_name('New tracker')
76 tracker = Tracker.find_by_name('New tracker')
72 assert_equal 0, tracker.projects.count
77 assert_equal 0, tracker.projects.count
73 assert_equal Tracker.find(1).workflows.count, tracker.workflows.count
78 assert_equal Tracker.find(1).workflows.count, tracker.workflows.count
74 end
79 end
75
80
76 def test_get_edit
81 def test_create_new_failure
82 assert_no_difference 'Tracker.count' do
83 post :create, :tracker => { :name => '', :project_ids => ['1', '', ''], :custom_field_ids => ['1', '6', ''] }
84 end
85 assert_response :success
86 assert_template 'new'
87 end
88
89 def test_edit
77 Tracker.find(1).project_ids = [1, 3]
90 Tracker.find(1).project_ids = [1, 3]
78
91
79 get :edit, :id => 1
92 get :edit, :id => 1
80 assert_response :success
93 assert_response :success
81 assert_template 'edit'
94 assert_template 'edit'
82
95
83 assert_tag :input, :attributes => { :name => 'tracker[project_ids][]',
96 assert_tag :input, :attributes => { :name => 'tracker[project_ids][]',
84 :value => '1',
97 :value => '1',
85 :checked => 'checked' }
98 :checked => 'checked' }
86
99
87 assert_tag :input, :attributes => { :name => 'tracker[project_ids][]',
100 assert_tag :input, :attributes => { :name => 'tracker[project_ids][]',
88 :value => '2',
101 :value => '2',
89 :checked => nil }
102 :checked => nil }
90
103
91 assert_tag :input, :attributes => { :name => 'tracker[project_ids][]',
104 assert_tag :input, :attributes => { :name => 'tracker[project_ids][]',
92 :value => '',
105 :value => '',
93 :type => 'hidden'}
106 :type => 'hidden'}
94 end
107 end
95
108
96 def test_post_edit
109 def test_update
97 post :edit, :id => 1, :tracker => { :name => 'Renamed',
110 put :update, :id => 1, :tracker => { :name => 'Renamed',
98 :project_ids => ['1', '2', ''] }
111 :project_ids => ['1', '2', ''] }
99 assert_redirected_to :action => 'index'
112 assert_redirected_to :action => 'index'
100 assert_equal [1, 2], Tracker.find(1).project_ids.sort
113 assert_equal [1, 2], Tracker.find(1).project_ids.sort
101 end
114 end
102
115
103 def test_post_edit_without_projects
116 def test_update_without_projects
104 post :edit, :id => 1, :tracker => { :name => 'Renamed',
117 put :update, :id => 1, :tracker => { :name => 'Renamed',
105 :project_ids => [''] }
118 :project_ids => [''] }
106 assert_redirected_to :action => 'index'
119 assert_redirected_to :action => 'index'
107 assert Tracker.find(1).project_ids.empty?
120 assert Tracker.find(1).project_ids.empty?
108 end
121 end
109
122
110 def test_move_lower
123 def test_move_lower
111 tracker = Tracker.find_by_position(1)
124 tracker = Tracker.find_by_position(1)
112 post :edit, :id => 1, :tracker => { :move_to => 'lower' }
125 put :update, :id => 1, :tracker => { :move_to => 'lower' }
113 assert_equal 2, tracker.reload.position
126 assert_equal 2, tracker.reload.position
114 end
127 end
115
128
116 def test_destroy
129 def test_destroy
117 tracker = Tracker.create!(:name => 'Destroyable')
130 tracker = Tracker.create!(:name => 'Destroyable')
118 assert_difference 'Tracker.count', -1 do
131 assert_difference 'Tracker.count', -1 do
119 post :destroy, :id => tracker.id
132 delete :destroy, :id => tracker.id
120 end
133 end
121 assert_redirected_to :action => 'index'
134 assert_redirected_to :action => 'index'
122 assert_nil flash[:error]
135 assert_nil flash[:error]
123 end
136 end
124
137
125 def test_destroy_tracker_in_use
138 def test_destroy_tracker_in_use
126 assert_no_difference 'Tracker.count' do
139 assert_no_difference 'Tracker.count' do
127 post :destroy, :id => 1
140 delete :destroy, :id => 1
128 end
141 end
129 assert_redirected_to :action => 'index'
142 assert_redirected_to :action => 'index'
130 assert_not_nil flash[:error]
143 assert_not_nil flash[:error]
131 end
144 end
132 end
145 end
General Comments 0
You need to be logged in to leave comments. Login now