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