##// END OF EJS Templates
Added per user custom queries....
Jean-Philippe Lang -
r563:1c44600c62dc
parent child
Show More
@@ -0,0 +1,29
1 <div class="contextual">
2 <% if loggedin? %>
3 <%= link_to l(:label_query_new), {:controller => 'queries', :action => 'new', :project_id => @project}, :class => 'icon icon-add' %>
4 <% end %>
5 </div>
6
7 <h2><%= l(:label_query_plural) %></h2>
8
9 <% if @queries.empty? %>
10 <p><i><%=l(:label_no_data)%></i></p>
11 <% else %>
12 <table class="list">
13 <% @queries.each do |query| %>
14 <tr class="<%= cycle('odd', 'even') %>">
15 <td>
16 <%= link_to query.name, :controller => 'projects', :action => 'list_issues', :id => @project, :query_id => query %>
17 </td>
18 <td align="right">
19 <small>
20 <% if query.editable_by?(@logged_in_user) %>
21 <%= link_to l(:button_edit), {:controller => 'queries', :action => 'edit', :id => query}, :class => 'icon icon-edit' %>
22 <%= link_to l(:button_delete), {:controller => 'queries', :action => 'destroy', :id => query}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
23 </small>
24 <% end %>
25 </td>
26 </tr>
27 <% end %>
28 </table>
29 <% end %>
@@ -0,0 +1,6
1 <h2><%= l(:label_query_new) %></h2>
2
3 <% form_tag({:action => 'new', :project_id => @query.project}) do %>
4 <%= render :partial => 'form', :locals => {:query => @query} %>
5 <%= submit_tag l(:button_save) %>
6 <% end %>
@@ -1,684 +1,667
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 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 'csv'
19 19
20 20 class ProjectsController < ApplicationController
21 21 layout 'base'
22 22 before_filter :find_project, :except => [ :index, :list, :add ]
23 23 before_filter :authorize, :except => [ :index, :list, :add, :archive, :unarchive, :destroy ]
24 24 before_filter :require_admin, :only => [ :add, :archive, :unarchive, :destroy ]
25 25
26 26 cache_sweeper :project_sweeper, :only => [ :add, :edit, :archive, :unarchive, :destroy ]
27 27 cache_sweeper :issue_sweeper, :only => [ :add_issue ]
28 28
29 29 helper :sort
30 30 include SortHelper
31 31 helper :custom_fields
32 32 include CustomFieldsHelper
33 33 helper :ifpdf
34 34 include IfpdfHelper
35 35 helper IssuesHelper
36 36 helper :queries
37 37 include QueriesHelper
38 38 helper :repositories
39 39 include RepositoriesHelper
40 40
41 41 def index
42 42 list
43 43 render :action => 'list' unless request.xhr?
44 44 end
45 45
46 46 # Lists public projects
47 47 def list
48 48 sort_init "#{Project.table_name}.name", "asc"
49 49 sort_update
50 50 @project_count = Project.count(:all, :conditions => Project.visible_by(logged_in_user))
51 51 @project_pages = Paginator.new self, @project_count,
52 52 15,
53 53 params['page']
54 54 @projects = Project.find :all, :order => sort_clause,
55 55 :conditions => Project.visible_by(logged_in_user),
56 56 :include => :parent,
57 57 :limit => @project_pages.items_per_page,
58 58 :offset => @project_pages.current.offset
59 59
60 60 render :action => "list", :layout => false if request.xhr?
61 61 end
62 62
63 63 # Add a new project
64 64 def add
65 65 @custom_fields = IssueCustomField.find(:all)
66 66 @root_projects = Project.find(:all, :conditions => "parent_id is null")
67 67 @project = Project.new(params[:project])
68 68 if request.get?
69 69 @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project) }
70 70 else
71 71 @project.custom_fields = CustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
72 72 @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) }
73 73 @project.custom_values = @custom_values
74 74 if params[:repository_enabled] && params[:repository_enabled] == "1"
75 75 @project.repository = Repository.factory(params[:repository_scm])
76 76 @project.repository.attributes = params[:repository]
77 77 end
78 78 if "1" == params[:wiki_enabled]
79 79 @project.wiki = Wiki.new
80 80 @project.wiki.attributes = params[:wiki]
81 81 end
82 82 if @project.save
83 83 flash[:notice] = l(:notice_successful_create)
84 84 redirect_to :controller => 'admin', :action => 'projects'
85 85 end
86 86 end
87 87 end
88 88
89 89 # Show @project
90 90 def show
91 91 @custom_values = @project.custom_values.find(:all, :include => :custom_field)
92 92 @members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
93 93 @subprojects = @project.active_children
94 94 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
95 95 @trackers = Tracker.find(:all, :order => 'position')
96 96 @open_issues_by_tracker = Issue.count(:group => :tracker, :joins => "INNER JOIN #{IssueStatus.table_name} ON #{IssueStatus.table_name}.id = #{Issue.table_name}.status_id", :conditions => ["project_id=? and #{IssueStatus.table_name}.is_closed=?", @project.id, false])
97 97 @total_issues_by_tracker = Issue.count(:group => :tracker, :conditions => ["project_id=?", @project.id])
98 98 end
99 99
100 100 def settings
101 101 @root_projects = Project::find(:all, :conditions => ["parent_id is null and id <> ?", @project.id])
102 102 @custom_fields = IssueCustomField.find(:all)
103 103 @issue_category ||= IssueCategory.new
104 104 @member ||= @project.members.new
105 105 @custom_values ||= ProjectCustomField.find(:all).collect { |x| @project.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) }
106 106 end
107 107
108 108 # Edit @project
109 109 def edit
110 110 if request.post?
111 111 @project.custom_fields = IssueCustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
112 112 if params[:custom_fields]
113 113 @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) }
114 114 @project.custom_values = @custom_values
115 115 end
116 116 if params[:repository_enabled]
117 117 case params[:repository_enabled]
118 118 when "0"
119 119 @project.repository = nil
120 120 when "1"
121 121 @project.repository ||= Repository.factory(params[:repository_scm])
122 122 @project.repository.update_attributes params[:repository] if @project.repository
123 123 end
124 124 end
125 125 if params[:wiki_enabled]
126 126 case params[:wiki_enabled]
127 127 when "0"
128 128 @project.wiki.destroy if @project.wiki
129 129 when "1"
130 130 @project.wiki ||= Wiki.new
131 131 @project.wiki.update_attributes params[:wiki]
132 132 end
133 133 end
134 134 @project.attributes = params[:project]
135 135 if @project.save
136 136 flash[:notice] = l(:notice_successful_update)
137 137 redirect_to :action => 'settings', :id => @project
138 138 else
139 139 settings
140 140 render :action => 'settings'
141 141 end
142 142 end
143 143 end
144 144
145 145 def archive
146 146 @project.archive if request.post? && @project.active?
147 147 redirect_to :controller => 'admin', :action => 'projects'
148 148 end
149 149
150 150 def unarchive
151 151 @project.unarchive if request.post? && !@project.active?
152 152 redirect_to :controller => 'admin', :action => 'projects'
153 153 end
154 154
155 155 # Delete @project
156 156 def destroy
157 157 @project_to_destroy = @project
158 158 if request.post? and params[:confirm]
159 159 @project_to_destroy.destroy
160 160 redirect_to :controller => 'admin', :action => 'projects'
161 161 end
162 162 # hide project in layout
163 163 @project = nil
164 164 end
165 165
166 166 # Add a new issue category to @project
167 167 def add_issue_category
168 168 if request.post?
169 169 @issue_category = @project.issue_categories.build(params[:issue_category])
170 170 if @issue_category.save
171 171 flash[:notice] = l(:notice_successful_create)
172 172 redirect_to :action => 'settings', :tab => 'categories', :id => @project
173 173 else
174 174 settings
175 175 render :action => 'settings'
176 176 end
177 177 end
178 178 end
179 179
180 180 # Add a new version to @project
181 181 def add_version
182 182 @version = @project.versions.build(params[:version])
183 183 if request.post? and @version.save
184 184 flash[:notice] = l(:notice_successful_create)
185 185 redirect_to :action => 'settings', :tab => 'versions', :id => @project
186 186 end
187 187 end
188 188
189 189 # Add a new member to @project
190 190 def add_member
191 191 @member = @project.members.build(params[:member])
192 192 if request.post? && @member.save
193 193 respond_to do |format|
194 194 format.html { redirect_to :action => 'settings', :tab => 'members', :id => @project }
195 195 format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'members'} }
196 196 end
197 197 else
198 198 settings
199 199 render :action => 'settings'
200 200 end
201 201 end
202 202
203 203 # Show members list of @project
204 204 def list_members
205 205 @members = @project.members.find(:all)
206 206 end
207 207
208 208 # Add a new document to @project
209 209 def add_document
210 210 @categories = Enumeration::get_values('DCAT')
211 211 @document = @project.documents.build(params[:document])
212 212 if request.post? and @document.save
213 213 # Save the attachments
214 214 params[:attachments].each { |a|
215 215 Attachment.create(:container => @document, :file => a, :author => logged_in_user) unless a.size == 0
216 216 } if params[:attachments] and params[:attachments].is_a? Array
217 217 flash[:notice] = l(:notice_successful_create)
218 218 Mailer.deliver_document_add(@document) if Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
219 219 redirect_to :action => 'list_documents', :id => @project
220 220 end
221 221 end
222 222
223 223 # Show documents list of @project
224 224 def list_documents
225 225 @documents = @project.documents.find :all, :include => :category
226 226 end
227 227
228 228 # Add a new issue to @project
229 229 def add_issue
230 230 @tracker = Tracker.find(params[:tracker_id])
231 231 @priorities = Enumeration::get_values('IPRI')
232 232
233 233 default_status = IssueStatus.default
234 234 unless default_status
235 235 flash.now[:notice] = 'No default issue status defined. Please check your configuration.'
236 236 render :nothing => true, :layout => true
237 237 return
238 238 end
239 239 @issue = Issue.new(:project => @project, :tracker => @tracker)
240 240 @issue.status = default_status
241 241 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker))if logged_in_user
242 242 if request.get?
243 243 @issue.start_date = Date.today
244 244 @custom_values = @project.custom_fields_for_issues(@tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue) }
245 245 else
246 246 @issue.attributes = params[:issue]
247 247
248 248 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
249 249 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
250 250
251 251 @issue.author_id = self.logged_in_user.id if self.logged_in_user
252 252 # Multiple file upload
253 253 @attachments = []
254 254 params[:attachments].each { |a|
255 255 @attachments << Attachment.new(:container => @issue, :file => a, :author => logged_in_user) unless a.size == 0
256 256 } if params[:attachments] and params[:attachments].is_a? Array
257 257 @custom_values = @project.custom_fields_for_issues(@tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
258 258 @issue.custom_values = @custom_values
259 259 if @issue.save
260 260 @attachments.each(&:save)
261 261 flash[:notice] = l(:notice_successful_create)
262 262 Mailer.deliver_issue_add(@issue) if Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
263 263 redirect_to :action => 'list_issues', :id => @project
264 264 end
265 265 end
266 266 end
267 267
268 268 # Show filtered/sorted issues list of @project
269 269 def list_issues
270 270 sort_init "#{Issue.table_name}.id", "desc"
271 271 sort_update
272 272
273 273 retrieve_query
274 274
275 275 @results_per_page_options = [ 15, 25, 50, 100 ]
276 276 if params[:per_page] and @results_per_page_options.include? params[:per_page].to_i
277 277 @results_per_page = params[:per_page].to_i
278 278 session[:results_per_page] = @results_per_page
279 279 else
280 280 @results_per_page = session[:results_per_page] || 25
281 281 end
282 282
283 283 if @query.valid?
284 284 @issue_count = Issue.count(:include => [:status, :project, :custom_values], :conditions => @query.statement)
285 285 @issue_pages = Paginator.new self, @issue_count, @results_per_page, params['page']
286 286 @issues = Issue.find :all, :order => sort_clause,
287 287 :include => [ :assigned_to, :status, :tracker, :project, :priority, :custom_values ],
288 288 :conditions => @query.statement,
289 289 :limit => @issue_pages.items_per_page,
290 290 :offset => @issue_pages.current.offset
291 end
292 @trackers = Tracker.find :all, :order => 'position'
291 end
293 292 render :layout => false if request.xhr?
294 293 end
295 294
296 295 # Export filtered/sorted issues list to CSV
297 296 def export_issues_csv
298 297 sort_init "#{Issue.table_name}.id", "desc"
299 298 sort_update
300 299
301 300 retrieve_query
302 301 render :action => 'list_issues' and return unless @query.valid?
303 302
304 303 @issues = Issue.find :all, :order => sort_clause,
305 304 :include => [ :assigned_to, :author, :status, :tracker, :priority, :project, {:custom_values => :custom_field} ],
306 305 :conditions => @query.statement,
307 306 :limit => Setting.issues_export_limit.to_i
308 307
309 308 ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
310 309 export = StringIO.new
311 310 CSV::Writer.generate(export, l(:general_csv_separator)) do |csv|
312 311 # csv header fields
313 312 headers = [ "#", l(:field_status),
314 313 l(:field_project),
315 314 l(:field_tracker),
316 315 l(:field_priority),
317 316 l(:field_subject),
318 317 l(:field_assigned_to),
319 318 l(:field_author),
320 319 l(:field_start_date),
321 320 l(:field_due_date),
322 321 l(:field_done_ratio),
323 322 l(:field_created_on),
324 323 l(:field_updated_on)
325 324 ]
326 325 for custom_field in @project.all_custom_fields
327 326 headers << custom_field.name
328 327 end
329 328 csv << headers.collect {|c| ic.iconv(c) }
330 329 # csv lines
331 330 @issues.each do |issue|
332 331 fields = [issue.id, issue.status.name,
333 332 issue.project.name,
334 333 issue.tracker.name,
335 334 issue.priority.name,
336 335 issue.subject,
337 336 (issue.assigned_to ? issue.assigned_to.name : ""),
338 337 issue.author.name,
339 338 issue.start_date ? l_date(issue.start_date) : nil,
340 339 issue.due_date ? l_date(issue.due_date) : nil,
341 340 issue.done_ratio,
342 341 l_datetime(issue.created_on),
343 342 l_datetime(issue.updated_on)
344 343 ]
345 344 for custom_field in @project.all_custom_fields
346 345 fields << (show_value issue.custom_value_for(custom_field))
347 346 end
348 347 csv << fields.collect {|c| ic.iconv(c.to_s) }
349 348 end
350 349 end
351 350 export.rewind
352 351 send_data(export.read, :type => 'text/csv; header=present', :filename => 'export.csv')
353 352 end
354 353
355 354 # Export filtered/sorted issues to PDF
356 355 def export_issues_pdf
357 356 sort_init "#{Issue.table_name}.id", "desc"
358 357 sort_update
359 358
360 359 retrieve_query
361 360 render :action => 'list_issues' and return unless @query.valid?
362 361
363 362 @issues = Issue.find :all, :order => sort_clause,
364 363 :include => [ :author, :status, :tracker, :priority, :project, :custom_values ],
365 364 :conditions => @query.statement,
366 365 :limit => Setting.issues_export_limit.to_i
367 366
368 367 @options_for_rfpdf ||= {}
369 368 @options_for_rfpdf[:file_name] = "export.pdf"
370 369 render :layout => false
371 370 end
372 371
373 372 def move_issues
374 373 @issues = @project.issues.find(params[:issue_ids]) if params[:issue_ids]
375 374 redirect_to :action => 'list_issues', :id => @project and return unless @issues
376 375 @projects = []
377 376 # find projects to which the user is allowed to move the issue
378 377 @logged_in_user.memberships.each {|m| @projects << m.project if Permission.allowed_to_role("projects/move_issues", m.role)}
379 378 # issue can be moved to any tracker
380 379 @trackers = Tracker.find(:all)
381 380 if request.post? and params[:new_project_id] and params[:new_tracker_id]
382 381 new_project = Project.find(params[:new_project_id])
383 382 new_tracker = Tracker.find(params[:new_tracker_id])
384 383 @issues.each { |i|
385 384 # project dependent properties
386 385 unless i.project_id == new_project.id
387 386 i.category = nil
388 387 i.fixed_version = nil
389 388 # delete issue relations
390 389 i.relations_from.clear
391 390 i.relations_to.clear
392 391 end
393 392 # move the issue
394 393 i.project = new_project
395 394 i.tracker = new_tracker
396 395 i.save
397 396 }
398 397 flash[:notice] = l(:notice_successful_update)
399 398 redirect_to :action => 'list_issues', :id => @project
400 399 end
401 400 end
402 401
403 def add_query
404 @query = Query.new(params[:query])
405 @query.project = @project
406 @query.user = logged_in_user
407
408 params[:fields].each do |field|
409 @query.add_filter(field, params[:operators][field], params[:values][field])
410 end if params[:fields]
411
412 if request.post? and @query.save
413 flash[:notice] = l(:notice_successful_create)
414 redirect_to :controller => 'reports', :action => 'issue_report', :id => @project
415 end
416 render :layout => false if request.xhr?
417 end
418
419 402 # Add a news to @project
420 403 def add_news
421 404 @news = News.new(:project => @project)
422 405 if request.post?
423 406 @news.attributes = params[:news]
424 407 @news.author_id = self.logged_in_user.id if self.logged_in_user
425 408 if @news.save
426 409 flash[:notice] = l(:notice_successful_create)
427 410 redirect_to :action => 'list_news', :id => @project
428 411 end
429 412 end
430 413 end
431 414
432 415 # Show news list of @project
433 416 def list_news
434 417 @news_pages, @news = paginate :news, :per_page => 10, :conditions => ["project_id=?", @project.id], :include => :author, :order => "#{News.table_name}.created_on DESC"
435 418 render :action => "list_news", :layout => false if request.xhr?
436 419 end
437 420
438 421 def add_file
439 422 if request.post?
440 423 @version = @project.versions.find_by_id(params[:version_id])
441 424 # Save the attachments
442 425 @attachments = []
443 426 params[:attachments].each { |file|
444 427 next unless file.size > 0
445 428 a = Attachment.create(:container => @version, :file => file, :author => logged_in_user)
446 429 @attachments << a unless a.new_record?
447 430 } if params[:attachments] and params[:attachments].is_a? Array
448 431 Mailer.deliver_attachments_add(@attachments) if !@attachments.empty? and Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
449 432 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
450 433 end
451 434 @versions = @project.versions.sort
452 435 end
453 436
454 437 def list_files
455 438 @versions = @project.versions.sort
456 439 end
457 440
458 441 # Show changelog for @project
459 442 def changelog
460 443 @trackers = Tracker.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
461 444 retrieve_selected_tracker_ids(@trackers)
462 445 @versions = @project.versions.sort
463 446 end
464 447
465 448 def roadmap
466 449 @trackers = Tracker.find(:all, :conditions => ["is_in_roadmap=?", true], :order => 'position')
467 450 retrieve_selected_tracker_ids(@trackers)
468 451 conditions = ("1" == params[:completed] ? nil : [ "#{Version.table_name}.effective_date > ? OR #{Version.table_name}.effective_date IS NULL", Date.today])
469 452 @versions = @project.versions.find(:all, :conditions => conditions).sort
470 453 end
471 454
472 455 def activity
473 456 if params[:year] and params[:year].to_i > 1900
474 457 @year = params[:year].to_i
475 458 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
476 459 @month = params[:month].to_i
477 460 end
478 461 end
479 462 @year ||= Date.today.year
480 463 @month ||= Date.today.month
481 464
482 465 @date_from = Date.civil(@year, @month, 1)
483 466 @date_to = @date_from >> 1
484 467
485 468 @events_by_day = {}
486 469
487 470 unless params[:show_issues] == "0"
488 471 @project.issues.find(:all, :include => [:author], :conditions => ["#{Issue.table_name}.created_on>=? and #{Issue.table_name}.created_on<=?", @date_from, @date_to] ).each { |i|
489 472 @events_by_day[i.created_on.to_date] ||= []
490 473 @events_by_day[i.created_on.to_date] << i
491 474 }
492 475 @show_issues = 1
493 476 end
494 477
495 478 unless params[:show_news] == "0"
496 479 @project.news.find(:all, :conditions => ["#{News.table_name}.created_on>=? and #{News.table_name}.created_on<=?", @date_from, @date_to], :include => :author ).each { |i|
497 480 @events_by_day[i.created_on.to_date] ||= []
498 481 @events_by_day[i.created_on.to_date] << i
499 482 }
500 483 @show_news = 1
501 484 end
502 485
503 486 unless params[:show_files] == "0"
504 487 Attachment.find(:all, :select => "#{Attachment.table_name}.*", :joins => "LEFT JOIN #{Version.table_name} ON #{Version.table_name}.id = #{Attachment.table_name}.container_id", :conditions => ["#{Attachment.table_name}.container_type='Version' and #{Version.table_name}.project_id=? and #{Attachment.table_name}.created_on>=? and #{Attachment.table_name}.created_on<=?", @project.id, @date_from, @date_to], :include => :author ).each { |i|
505 488 @events_by_day[i.created_on.to_date] ||= []
506 489 @events_by_day[i.created_on.to_date] << i
507 490 }
508 491 @show_files = 1
509 492 end
510 493
511 494 unless params[:show_documents] == "0"
512 495 @project.documents.find(:all, :conditions => ["#{Document.table_name}.created_on>=? and #{Document.table_name}.created_on<=?", @date_from, @date_to] ).each { |i|
513 496 @events_by_day[i.created_on.to_date] ||= []
514 497 @events_by_day[i.created_on.to_date] << i
515 498 }
516 499 Attachment.find(:all, :select => "attachments.*", :joins => "LEFT JOIN #{Document.table_name} ON #{Document.table_name}.id = #{Attachment.table_name}.container_id", :conditions => ["#{Attachment.table_name}.container_type='Document' and #{Document.table_name}.project_id=? and #{Attachment.table_name}.created_on>=? and #{Attachment.table_name}.created_on<=?", @project.id, @date_from, @date_to], :include => :author ).each { |i|
517 500 @events_by_day[i.created_on.to_date] ||= []
518 501 @events_by_day[i.created_on.to_date] << i
519 502 }
520 503 @show_documents = 1
521 504 end
522 505
523 506 unless @project.wiki.nil? || params[:show_wiki_edits] == "0"
524 507 select = "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " +
525 508 "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title"
526 509 joins = "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " +
527 510 "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id "
528 511 conditions = ["#{Wiki.table_name}.project_id = ? AND #{WikiContent.versioned_table_name}.updated_on BETWEEN ? AND ?",
529 512 @project.id, @date_from, @date_to]
530 513
531 514 WikiContent.versioned_class.find(:all, :select => select, :joins => joins, :conditions => conditions).each { |i|
532 515 # We provide this alias so all events can be treated in the same manner
533 516 def i.created_on
534 517 self.updated_on
535 518 end
536 519
537 520 @events_by_day[i.created_on.to_date] ||= []
538 521 @events_by_day[i.created_on.to_date] << i
539 522 }
540 523 @show_wiki_edits = 1
541 524 end
542 525
543 526 unless @project.repository.nil? || params[:show_changesets] == "0"
544 527 @project.repository.changesets.find(:all, :conditions => ["#{Changeset.table_name}.committed_on BETWEEN ? AND ?", @date_from, @date_to]).each { |i|
545 528 def i.created_on
546 529 self.committed_on
547 530 end
548 531 @events_by_day[i.created_on.to_date] ||= []
549 532 @events_by_day[i.created_on.to_date] << i
550 533 }
551 534 @show_changesets = 1
552 535 end
553 536
554 537 render :layout => false if request.xhr?
555 538 end
556 539
557 540 def calendar
558 541 @trackers = Tracker.find(:all, :order => 'position')
559 542 retrieve_selected_tracker_ids(@trackers)
560 543
561 544 if params[:year] and params[:year].to_i > 1900
562 545 @year = params[:year].to_i
563 546 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
564 547 @month = params[:month].to_i
565 548 end
566 549 end
567 550 @year ||= Date.today.year
568 551 @month ||= Date.today.month
569 552
570 553 @date_from = Date.civil(@year, @month, 1)
571 554 @date_to = (@date_from >> 1)-1
572 555 # start on monday
573 556 @date_from = @date_from - (@date_from.cwday-1)
574 557 # finish on sunday
575 558 @date_to = @date_to + (7-@date_to.cwday)
576 559
577 560 @events = []
578 561 @project.issues_with_subprojects(params[:with_subprojects]) do
579 562 @events += Issue.find(:all,
580 563 :include => [:tracker, :status, :assigned_to, :priority, :project],
581 564 :conditions => ["((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?)) and #{Issue.table_name}.tracker_id in (#{@selected_tracker_ids.join(',')})", @date_from, @date_to, @date_from, @date_to]
582 565 ) unless @selected_tracker_ids.empty?
583 566 end
584 567 @events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @date_from, @date_to])
585 568
586 569 @ending_events_by_days = @events.group_by {|event| event.due_date}
587 570 @starting_events_by_days = @events.group_by {|event| event.start_date}
588 571
589 572 render :layout => false if request.xhr?
590 573 end
591 574
592 575 def gantt
593 576 @trackers = Tracker.find(:all, :order => 'position')
594 577 retrieve_selected_tracker_ids(@trackers)
595 578
596 579 if params[:year] and params[:year].to_i >0
597 580 @year_from = params[:year].to_i
598 581 if params[:month] and params[:month].to_i >=1 and params[:month].to_i <= 12
599 582 @month_from = params[:month].to_i
600 583 else
601 584 @month_from = 1
602 585 end
603 586 else
604 587 @month_from ||= (Date.today << 1).month
605 588 @year_from ||= (Date.today << 1).year
606 589 end
607 590
608 591 @zoom = (params[:zoom].to_i > 0 and params[:zoom].to_i < 5) ? params[:zoom].to_i : 2
609 592 @months = (params[:months].to_i > 0 and params[:months].to_i < 25) ? params[:months].to_i : 6
610 593
611 594 @date_from = Date.civil(@year_from, @month_from, 1)
612 595 @date_to = (@date_from >> @months) - 1
613 596
614 597 @events = []
615 598 @project.issues_with_subprojects(params[:with_subprojects]) do
616 599 @events += Issue.find(:all,
617 600 :order => "start_date, due_date",
618 601 :include => [:tracker, :status, :assigned_to, :priority, :project],
619 602 :conditions => ["(((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null and #{Issue.table_name}.tracker_id in (#{@selected_tracker_ids.join(',')}))", @date_from, @date_to, @date_from, @date_to, @date_from, @date_to]
620 603 ) unless @selected_tracker_ids.empty?
621 604 end
622 605 @events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @date_from, @date_to])
623 606 @events.sort! {|x,y| x.start_date <=> y.start_date }
624 607
625 608 if params[:output]=='pdf'
626 609 @options_for_rfpdf ||= {}
627 610 @options_for_rfpdf[:file_name] = "gantt.pdf"
628 611 render :template => "projects/gantt.rfpdf", :layout => false
629 612 else
630 613 render :template => "projects/gantt.rhtml"
631 614 end
632 615 end
633 616
634 617 def feeds
635 618 @queries = @project.queries.find :all, :conditions => ["is_public=? or user_id=?", true, (logged_in_user ? logged_in_user.id : 0)]
636 619 @key = logged_in_user.get_or_create_rss_key.value if logged_in_user
637 620 end
638 621
639 622 private
640 623 # Find project of id params[:id]
641 624 # if not found, redirect to project list
642 625 # Used as a before_filter
643 626 def find_project
644 627 @project = Project.find(params[:id])
645 628 @html_title = @project.name
646 629 rescue ActiveRecord::RecordNotFound
647 630 render_404
648 631 end
649 632
650 633 def retrieve_selected_tracker_ids(selectable_trackers)
651 634 if ids = params[:tracker_ids]
652 635 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
653 636 else
654 637 @selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s }
655 638 end
656 639 end
657 640
658 641 # Retrieve query from session or build a new query
659 642 def retrieve_query
660 643 if params[:query_id]
661 644 @query = @project.queries.find(params[:query_id])
662 645 @query.executed_by = logged_in_user
663 646 session[:query] = @query
664 647 else
665 648 if params[:set_filter] or !session[:query] or session[:query].project_id != @project.id
666 649 # Give it a name, required to be valid
667 650 @query = Query.new(:name => "_", :executed_by => logged_in_user)
668 651 @query.project = @project
669 652 if params[:fields] and params[:fields].is_a? Array
670 653 params[:fields].each do |field|
671 654 @query.add_filter(field, params[:operators][field], params[:values][field])
672 655 end
673 656 else
674 657 @query.available_filters.keys.each do |field|
675 658 @query.add_short_filter(field, params[field]) if params[field]
676 659 end
677 660 end
678 661 session[:query] = @query
679 662 else
680 663 @query = session[:query]
681 664 end
682 665 end
683 666 end
684 667 end
@@ -1,52 +1,82
1 1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 layout 'base'
20 before_filter :require_login, :find_query
19 layout 'base'
20 before_filter :require_login, :except => :index
21 before_filter :find_project, :check_project_privacy
21 22
23 def index
24 @queries = @project.queries.find(:all,
25 :order => "name ASC",
26 :conditions => ["is_public = ? or user_id = ?", true, (logged_in_user ? logged_in_user.id : 0)])
27 end
28
29 def new
30 @query = Query.new(params[:query])
31 @query.project = @project
32 @query.user = logged_in_user
33 @query.executed_by = logged_in_user
34 @query.is_public = false unless logged_in_user.authorized_to(@project, 'projects/add_query')
35
36 params[:fields].each do |field|
37 @query.add_filter(field, params[:operators][field], params[:values][field])
38 end if params[:fields]
39
40 if request.post? and @query.save
41 flash[:notice] = l(:notice_successful_create)
42 redirect_to :controller => 'projects', :action => 'list_issues', :id => @project, :query_id => @query
43 return
44 end
45 render :layout => false if request.xhr?
46 end
47
22 48 def edit
23 49 if request.post?
24 50 @query.filters = {}
25 51 params[:fields].each do |field|
26 52 @query.add_filter(field, params[:operators][field], params[:values][field])
27 53 end if params[:fields]
28 54 @query.attributes = params[:query]
55 @query.is_public = false unless logged_in_user.authorized_to(@project, 'projects/add_query')
29 56
30 57 if @query.save
31 58 flash[:notice] = l(:notice_successful_update)
32 59 redirect_to :controller => 'projects', :action => 'list_issues', :id => @project, :query_id => @query
33 60 end
34 61 end
35 62 end
36 63
37 64 def destroy
38 65 @query.destroy if request.post?
39 redirect_to :controller => 'reports', :action => 'issue_report', :id => @project
66 redirect_to :controller => 'queries', :project_id => @project
40 67 end
41 68
42 69 private
43 def find_query
44 @query = Query.find(params[:id])
45 @query.executed_by = logged_in_user
46 @project = @query.project
47 # check if user is allowed to manage queries (same permission as add_query)
48 authorize('projects', 'add_query')
70 def find_project
71 if params[:id]
72 @query = Query.find(params[:id])
73 @query.executed_by = logged_in_user
74 @project = @query.project
75 render_403 unless @query.editable_by?(logged_in_user)
76 else
77 @project = Project.find(params[:project_id])
78 end
49 79 rescue ActiveRecord::RecordNotFound
50 80 render_404
51 81 end
52 82 end
@@ -1,214 +1,213
1 1 # redMine - project management software
2 2 # Copyright (C) 2006 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 ReportsController < ApplicationController
19 19 layout 'base'
20 20 before_filter :find_project, :authorize
21 21
22 22 def issue_report
23 23 @statuses = IssueStatus.find(:all, :order => 'position')
24 24
25 25 case params[:detail]
26 26 when "tracker"
27 27 @field = "tracker_id"
28 28 @rows = Tracker.find :all, :order => 'position'
29 29 @data = issues_by_tracker
30 30 @report_title = l(:field_tracker)
31 31 render :template => "reports/issue_report_details"
32 32 when "version"
33 33 @field = "fixed_version_id"
34 34 @rows = @project.versions.sort
35 35 @data = issues_by_version
36 36 @report_title = l(:field_version)
37 37 render :template => "reports/issue_report_details"
38 38 when "priority"
39 39 @field = "priority_id"
40 40 @rows = Enumeration::get_values('IPRI')
41 41 @data = issues_by_priority
42 42 @report_title = l(:field_priority)
43 43 render :template => "reports/issue_report_details"
44 44 when "category"
45 45 @field = "category_id"
46 46 @rows = @project.issue_categories
47 47 @data = issues_by_category
48 48 @report_title = l(:field_category)
49 49 render :template => "reports/issue_report_details"
50 50 when "author"
51 51 @field = "author_id"
52 52 @rows = @project.members.collect { |m| m.user }
53 53 @data = issues_by_author
54 54 @report_title = l(:field_author)
55 55 render :template => "reports/issue_report_details"
56 56 when "subproject"
57 57 @field = "project_id"
58 58 @rows = @project.active_children
59 59 @data = issues_by_subproject
60 60 @report_title = l(:field_subproject)
61 61 render :template => "reports/issue_report_details"
62 62 else
63 @queries = @project.queries.find :all, :conditions => ["is_public=? or user_id=?", true, (logged_in_user ? logged_in_user.id : 0)]
64 63 @trackers = Tracker.find(:all, :order => 'position')
65 64 @versions = @project.versions.sort
66 65 @priorities = Enumeration::get_values('IPRI')
67 66 @categories = @project.issue_categories
68 67 @authors = @project.members.collect { |m| m.user }
69 68 @subprojects = @project.active_children
70 69 issues_by_tracker
71 70 issues_by_version
72 71 issues_by_priority
73 72 issues_by_category
74 73 issues_by_author
75 74 issues_by_subproject
76 75 @total_hours = @project.time_entries.sum(:hours)
77 76 render :template => "reports/issue_report"
78 77 end
79 78 end
80 79
81 80 def delays
82 81 @trackers = Tracker.find(:all)
83 82 if request.get?
84 83 @selected_tracker_ids = @trackers.collect {|t| t.id.to_s }
85 84 else
86 85 @selected_tracker_ids = params[:tracker_ids].collect { |id| id.to_i.to_s } if params[:tracker_ids] and params[:tracker_ids].is_a? Array
87 86 end
88 87 @selected_tracker_ids ||= []
89 88 @raw =
90 89 ActiveRecord::Base.connection.select_all("SELECT datediff( a.created_on, b.created_on ) as delay, count(a.id) as total
91 90 FROM issue_histories a, issue_histories b, issues i
92 91 WHERE a.status_id =5
93 92 AND a.issue_id = b.issue_id
94 93 AND a.issue_id = i.id
95 94 AND i.tracker_id in (#{@selected_tracker_ids.join(',')})
96 95 AND b.id = (
97 96 SELECT min( c.id )
98 97 FROM issue_histories c
99 98 WHERE b.issue_id = c.issue_id )
100 99 GROUP BY delay") unless @selected_tracker_ids.empty?
101 100 @raw ||=[]
102 101
103 102 @x_from = 0
104 103 @x_to = 0
105 104 @y_from = 0
106 105 @y_to = 0
107 106 @sum_total = 0
108 107 @sum_delay = 0
109 108 @raw.each do |r|
110 109 @x_to = [r['delay'].to_i, @x_to].max
111 110 @y_to = [r['total'].to_i, @y_to].max
112 111 @sum_total = @sum_total + r['total'].to_i
113 112 @sum_delay = @sum_delay + r['total'].to_i * r['delay'].to_i
114 113 end
115 114 end
116 115
117 116 private
118 117 # Find project of id params[:id]
119 118 def find_project
120 119 @project = Project.find(params[:id])
121 120 rescue ActiveRecord::RecordNotFound
122 121 render_404
123 122 end
124 123
125 124 def issues_by_tracker
126 125 @issues_by_tracker ||=
127 126 ActiveRecord::Base.connection.select_all("select s.id as status_id,
128 127 s.is_closed as closed,
129 128 t.id as tracker_id,
130 129 count(i.id) as total
131 130 from
132 131 #{Issue.table_name} i, #{IssueStatus.table_name} s, #{Tracker.table_name} t
133 132 where
134 133 i.status_id=s.id
135 134 and i.tracker_id=t.id
136 135 and i.project_id=#{@project.id}
137 136 group by s.id, s.is_closed, t.id")
138 137 end
139 138
140 139 def issues_by_version
141 140 @issues_by_version ||=
142 141 ActiveRecord::Base.connection.select_all("select s.id as status_id,
143 142 s.is_closed as closed,
144 143 v.id as fixed_version_id,
145 144 count(i.id) as total
146 145 from
147 146 #{Issue.table_name} i, #{IssueStatus.table_name} s, #{Version.table_name} v
148 147 where
149 148 i.status_id=s.id
150 149 and i.fixed_version_id=v.id
151 150 and i.project_id=#{@project.id}
152 151 group by s.id, s.is_closed, v.id")
153 152 end
154 153
155 154 def issues_by_priority
156 155 @issues_by_priority ||=
157 156 ActiveRecord::Base.connection.select_all("select s.id as status_id,
158 157 s.is_closed as closed,
159 158 p.id as priority_id,
160 159 count(i.id) as total
161 160 from
162 161 #{Issue.table_name} i, #{IssueStatus.table_name} s, #{Enumeration.table_name} p
163 162 where
164 163 i.status_id=s.id
165 164 and i.priority_id=p.id
166 165 and i.project_id=#{@project.id}
167 166 group by s.id, s.is_closed, p.id")
168 167 end
169 168
170 169 def issues_by_category
171 170 @issues_by_category ||=
172 171 ActiveRecord::Base.connection.select_all("select s.id as status_id,
173 172 s.is_closed as closed,
174 173 c.id as category_id,
175 174 count(i.id) as total
176 175 from
177 176 #{Issue.table_name} i, #{IssueStatus.table_name} s, #{IssueCategory.table_name} c
178 177 where
179 178 i.status_id=s.id
180 179 and i.category_id=c.id
181 180 and i.project_id=#{@project.id}
182 181 group by s.id, s.is_closed, c.id")
183 182 end
184 183
185 184 def issues_by_author
186 185 @issues_by_author ||=
187 186 ActiveRecord::Base.connection.select_all("select s.id as status_id,
188 187 s.is_closed as closed,
189 188 a.id as author_id,
190 189 count(i.id) as total
191 190 from
192 191 #{Issue.table_name} i, #{IssueStatus.table_name} s, #{User.table_name} a
193 192 where
194 193 i.status_id=s.id
195 194 and i.author_id=a.id
196 195 and i.project_id=#{@project.id}
197 196 group by s.id, s.is_closed, a.id")
198 197 end
199 198
200 199 def issues_by_subproject
201 200 @issues_by_subproject ||=
202 201 ActiveRecord::Base.connection.select_all("select s.id as status_id,
203 202 s.is_closed as closed,
204 203 i.project_id as project_id,
205 204 count(i.id) as total
206 205 from
207 206 #{Issue.table_name} i, #{IssueStatus.table_name} s
208 207 where
209 208 i.status_id=s.id
210 209 and i.project_id IN (#{@project.active_children.collect{|p| p.id}.join(',')})
211 210 group by s.id, s.is_closed, i.project_id") if @project.active_children.any?
212 211 @issues_by_subproject ||= []
213 212 end
214 213 end
@@ -1,234 +1,239
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 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 Query < ActiveRecord::Base
19 19 belongs_to :project
20 20 belongs_to :user
21 21 serialize :filters
22 22
23 23 attr_protected :project, :user
24 24 attr_accessor :executed_by
25 25
26 26 validates_presence_of :name, :on => :save
27 27
28 28 @@operators = { "=" => :label_equals,
29 29 "!" => :label_not_equals,
30 30 "o" => :label_open_issues,
31 31 "c" => :label_closed_issues,
32 32 "!*" => :label_none,
33 33 "*" => :label_all,
34 34 "<t+" => :label_in_less_than,
35 35 ">t+" => :label_in_more_than,
36 36 "t+" => :label_in,
37 37 "t" => :label_today,
38 38 ">t-" => :label_less_than_ago,
39 39 "<t-" => :label_more_than_ago,
40 40 "t-" => :label_ago,
41 41 "~" => :label_contains,
42 42 "!~" => :label_not_contains }
43 43
44 44 cattr_reader :operators
45 45
46 46 @@operators_by_filter_type = { :list => [ "=", "!" ],
47 47 :list_status => [ "o", "=", "!", "c", "*" ],
48 48 :list_optional => [ "=", "!", "!*", "*" ],
49 49 :list_one_or_more => [ "*", "=" ],
50 50 :date => [ "<t+", ">t+", "t+", "t", ">t-", "<t-", "t-" ],
51 51 :date_past => [ ">t-", "<t-", "t-", "t" ],
52 52 :string => [ "=", "~", "!", "!~" ],
53 53 :text => [ "~", "!~" ] }
54 54
55 55 cattr_reader :operators_by_filter_type
56 56
57 57 def initialize(attributes = nil)
58 58 super attributes
59 59 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
60 self.is_public = true
61 60 end
62 61
63 62 def executed_by=(user)
64 63 @executed_by = user
65 64 set_language_if_valid(user.language) if user
66 65 end
67 66
68 67 def validate
69 68 filters.each_key do |field|
70 69 errors.add label_for(field), :activerecord_error_blank unless
71 70 # filter requires one or more values
72 71 (values_for(field) and !values_for(field).first.empty?) or
73 72 # filter doesn't require any value
74 73 ["o", "c", "!*", "*", "t"].include? operator_for(field)
75 74 end if filters
76 75 end
77 76
77 def editable_by?(user)
78 return false unless user
79 return true if !is_public && self.user_id == user.id
80 is_public && user.authorized_to(project, "projects/add_query")
81 end
82
78 83 def available_filters
79 84 return @available_filters if @available_filters
80 85 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
81 86 "tracker_id" => { :type => :list, :order => 2, :values => Tracker.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
82 87 "priority_id" => { :type => :list, :order => 3, :values => Enumeration.find(:all, :conditions => ['opt=?','IPRI']).collect{|s| [s.name, s.id.to_s] } },
83 88 "subject" => { :type => :text, :order => 8 },
84 89 "created_on" => { :type => :date_past, :order => 9 },
85 90 "updated_on" => { :type => :date_past, :order => 10 },
86 91 "start_date" => { :type => :date, :order => 11 },
87 92 "due_date" => { :type => :date, :order => 12 } }
88 93 unless project.nil?
89 94 # project specific filters
90 95 user_values = []
91 96 user_values << ["<< #{l(:label_me)} >>", "me"] if executed_by
92 97 user_values += @project.users.collect{|s| [s.name, s.id.to_s] }
93 98
94 99 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values }
95 100 @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values }
96 101 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
97 102 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.versions.sort.collect{|s| [s.name, s.id.to_s] } }
98 103 unless @project.active_children.empty?
99 104 @available_filters["subproject_id"] = { :type => :list_one_or_more, :order => 13, :values => @project.active_children.collect{|s| [s.name, s.id.to_s] } }
100 105 end
101 106 @project.all_custom_fields.select(&:is_filter?).each do |field|
102 107 case field.field_format
103 108 when "string", "int"
104 109 options = { :type => :string, :order => 20 }
105 110 when "text"
106 111 options = { :type => :text, :order => 20 }
107 112 when "list"
108 113 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
109 114 when "date"
110 115 options = { :type => :date, :order => 20 }
111 116 when "bool"
112 117 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
113 118 end
114 119 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
115 120 end
116 121 # remove category filter if no category defined
117 122 @available_filters.delete "category_id" if @available_filters["category_id"][:values].empty?
118 123 end
119 124 @available_filters
120 125 end
121 126
122 127 def add_filter(field, operator, values)
123 128 # values must be an array
124 129 return unless values and values.is_a? Array # and !values.first.empty?
125 130 # check if field is defined as an available filter
126 131 if available_filters.has_key? field
127 132 filter_options = available_filters[field]
128 133 # check if operator is allowed for that filter
129 134 #if @@operators_by_filter_type[filter_options[:type]].include? operator
130 135 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
131 136 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
132 137 #end
133 138 filters[field] = {:operator => operator, :values => values }
134 139 end
135 140 end
136 141
137 142 def add_short_filter(field, expression)
138 143 return unless expression
139 144 parms = expression.scan(/^(o|c|\!|\*)?(.*)$/).first
140 145 add_filter field, (parms[0] || "="), [parms[1] || ""]
141 146 end
142 147
143 148 def has_filter?(field)
144 149 filters and filters[field]
145 150 end
146 151
147 152 def operator_for(field)
148 153 has_filter?(field) ? filters[field][:operator] : nil
149 154 end
150 155
151 156 def values_for(field)
152 157 has_filter?(field) ? filters[field][:values] : nil
153 158 end
154 159
155 160 def label_for(field)
156 161 label = @available_filters[field][:name] if @available_filters.has_key?(field)
157 162 label ||= field.gsub(/\_id$/, "")
158 163 end
159 164
160 165 def statement
161 166 sql = "1=1"
162 167 if has_filter?("subproject_id")
163 168 subproject_ids = []
164 169 if operator_for("subproject_id") == "="
165 170 subproject_ids = values_for("subproject_id").each(&:to_i)
166 171 else
167 172 subproject_ids = project.active_children.collect{|p| p.id}
168 173 end
169 174 sql << " AND #{Issue.table_name}.project_id IN (%d,%s)" % [project.id, subproject_ids.join(",")] if project
170 175 else
171 176 sql << " AND #{Issue.table_name}.project_id=%d" % project.id if project
172 177 end
173 178 filters.each_key do |field|
174 179 next if field == "subproject_id"
175 180 v = values_for(field).clone
176 181 next unless v and !v.empty?
177 182
178 183 sql = sql + " AND " unless sql.empty?
179 184 sql << "("
180 185
181 186 if field =~ /^cf_(\d+)$/
182 187 # custom field
183 188 db_table = CustomValue.table_name
184 189 db_field = "value"
185 190 sql << "#{db_table}.custom_field_id = #{$1} AND "
186 191 else
187 192 # regular field
188 193 db_table = Issue.table_name
189 194 db_field = field
190 195 end
191 196
192 197 # "me" value subsitution
193 198 if %w(assigned_to_id author_id).include?(field)
194 199 v.push(executed_by ? executed_by.id.to_s : "0") if v.delete("me")
195 200 end
196 201
197 202 case operator_for field
198 203 when "="
199 204 sql = sql + "#{db_table}.#{db_field} IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
200 205 when "!"
201 206 sql = sql + "#{db_table}.#{db_field} NOT IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
202 207 when "!*"
203 208 sql = sql + "#{db_table}.#{db_field} IS NULL"
204 209 when "*"
205 210 sql = sql + "#{db_table}.#{db_field} IS NOT NULL"
206 211 when "o"
207 212 sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
208 213 when "c"
209 214 sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
210 215 when ">t-"
211 216 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today - v.first.to_i).to_time), connection.quoted_date((Date.today + 1).to_time)]
212 217 when "<t-"
213 218 sql = sql + "#{db_table}.#{db_field} <= '%s'" % connection.quoted_date((Date.today - v.first.to_i).to_time)
214 219 when "t-"
215 220 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today - v.first.to_i).to_time), connection.quoted_date((Date.today - v.first.to_i + 1).to_time)]
216 221 when ">t+"
217 222 sql = sql + "#{db_table}.#{db_field} >= '%s'" % connection.quoted_date((Date.today + v.first.to_i).to_time)
218 223 when "<t+"
219 224 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(Date.today.to_time), connection.quoted_date((Date.today + v.first.to_i + 1).to_time)]
220 225 when "t+"
221 226 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today + v.first.to_i).to_time), connection.quoted_date((Date.today + v.first.to_i + 1).to_time)]
222 227 when "t"
223 228 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(Date.today.to_time), connection.quoted_date((Date.today+1).to_time)]
224 229 when "~"
225 230 sql = sql + "#{db_table}.#{db_field} LIKE '%#{connection.quote_string(v.first)}%'"
226 231 when "!~"
227 232 sql = sql + "#{db_table}.#{db_field} NOT LIKE '%#{connection.quote_string(v.first)}%'"
228 233 end
229 234 sql << ")"
230 235
231 236 end if filters and valid?
232 237 sql
233 238 end
234 239 end
@@ -1,159 +1,166
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 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 "digest/sha1"
19 19
20 20 class User < ActiveRecord::Base
21 21 # Account statuses
22 22 STATUS_ACTIVE = 1
23 23 STATUS_REGISTERED = 2
24 24 STATUS_LOCKED = 3
25 25
26 26 has_many :memberships, :class_name => 'Member', :include => [ :project, :role ], :conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}", :order => "#{Project.table_name}.name", :dependent => :delete_all
27 27 has_many :projects, :through => :memberships
28 28 has_many :custom_values, :dependent => :delete_all, :as => :customized
29 29 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
30 30 has_one :rss_key, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
31 31 belongs_to :auth_source
32 32
33 33 attr_accessor :password, :password_confirmation
34 34 attr_accessor :last_before_login_on
35 35 # Prevents unauthorized assignments
36 36 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
37 37
38 38 validates_presence_of :login, :firstname, :lastname, :mail
39 39 validates_uniqueness_of :login, :mail
40 40 # Login must contain lettres, numbers, underscores only
41 41 validates_format_of :login, :with => /^[a-z0-9_\-@\.]+$/i
42 42 validates_length_of :login, :maximum => 30
43 43 validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-]*$/i
44 44 validates_length_of :firstname, :lastname, :maximum => 30
45 45 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
46 46 validates_length_of :mail, :maximum => 60
47 47 # Password length between 4 and 12
48 48 validates_length_of :password, :in => 4..12, :allow_nil => true
49 49 validates_confirmation_of :password, :allow_nil => true
50 50 validates_associated :custom_values, :on => :update
51 51
52 52 def before_save
53 53 # update hashed_password if password was set
54 54 self.hashed_password = User.hash_password(self.password) if self.password
55 55 end
56 56
57 57 def self.active
58 58 with_scope :find => { :conditions => [ "status = ?", STATUS_ACTIVE ] } do
59 59 yield
60 60 end
61 61 end
62 62
63 63 def self.find_active(*args)
64 64 active do
65 65 find(*args)
66 66 end
67 67 end
68 68
69 69 # Returns the user that matches provided login and password, or nil
70 70 def self.try_to_login(login, password)
71 71 user = find(:first, :conditions => ["login=?", login])
72 72 if user
73 73 # user is already in local database
74 74 return nil if !user.active?
75 75 if user.auth_source
76 76 # user has an external authentication method
77 77 return nil unless user.auth_source.authenticate(login, password)
78 78 else
79 79 # authentication with local password
80 80 return nil unless User.hash_password(password) == user.hashed_password
81 81 end
82 82 else
83 83 # user is not yet registered, try to authenticate with available sources
84 84 attrs = AuthSource.authenticate(login, password)
85 85 if attrs
86 86 onthefly = new(*attrs)
87 87 onthefly.login = login
88 88 onthefly.language = Setting.default_language
89 89 if onthefly.save
90 90 user = find(:first, :conditions => ["login=?", login])
91 91 logger.info("User '#{user.login}' created on the fly.") if logger
92 92 end
93 93 end
94 94 end
95 95 user.update_attribute(:last_login_on, Time.now) if user
96 96 user
97 97
98 98 rescue => text
99 99 raise text
100 100 end
101 101
102 102 # Return user's full name for display
103 103 def display_name
104 104 firstname + " " + lastname
105 105 end
106 106
107 107 def name
108 108 display_name
109 109 end
110 110
111 111 def active?
112 112 self.status == STATUS_ACTIVE
113 113 end
114 114
115 115 def registered?
116 116 self.status == STATUS_REGISTERED
117 117 end
118 118
119 119 def locked?
120 120 self.status == STATUS_LOCKED
121 121 end
122 122
123 123 def check_password?(clear_password)
124 124 User.hash_password(clear_password) == self.hashed_password
125 125 end
126 126
127 127 def role_for_project(project)
128 return nil unless project
128 129 member = memberships.detect {|m| m.project_id == project.id}
129 130 member ? member.role : nil
130 131 end
131 132
133 def authorized_to(project, action)
134 return true if self.admin?
135 role = role_for_project(project)
136 role && Permission.allowed_to_role(action, role)
137 end
138
132 139 def pref
133 140 self.preference ||= UserPreference.new(:user => self)
134 141 end
135 142
136 143 def get_or_create_rss_key
137 144 self.rss_key || Token.create(:user => self, :action => 'feeds')
138 145 end
139 146
140 147 def self.find_by_rss_key(key)
141 148 token = Token.find_by_value(key)
142 149 token && token.user.active? ? token.user : nil
143 150 end
144 151
145 152 def self.find_by_autologin_key(key)
146 153 token = Token.find_by_action_and_value('autologin', key)
147 154 token && (token.created_on > Setting.autologin.to_i.day.ago) && token.user.active? ? token.user : nil
148 155 end
149 156
150 157 def <=>(user)
151 158 lastname == user.lastname ? firstname <=> user.firstname : lastname <=> user.lastname
152 159 end
153 160
154 161 private
155 162 # Return password digest
156 163 def self.hash_password(clear_password)
157 164 Digest::SHA1.hexdigest(clear_password || "")
158 165 end
159 166 end
@@ -1,144 +1,155
1 1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
2 2 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
3 3 <head>
4 4 <title><%= Setting.app_title + (@html_title ? ": #{@html_title}" : "") %></title>
5 5 <meta http-equiv="content-type" content="text/html; charset=utf-8" />
6 6 <meta name="description" content="redMine" />
7 7 <meta name="keywords" content="issue,bug,tracker" />
8 8 <!--[if IE]>
9 9 <style type="text/css">
10 10 body {behavior: url(<%= stylesheet_path "csshover.htc" %>);}
11 11 </style>
12 12 <![endif]-->
13 13 <%= stylesheet_link_tag "application" %>
14 14 <%= stylesheet_link_tag "print", :media => "print" %>
15 15 <%= javascript_include_tag :defaults %>
16 16 <%= javascript_include_tag 'menu' %>
17 17 <%= stylesheet_link_tag 'jstoolbar' %>
18 18 <!-- page specific tags --><%= yield :header_tags %>
19 19 </head>
20 20
21 21 <body>
22 22 <div id="container" >
23 23
24 24 <div id="header">
25 25 <div style="float: left;">
26 26 <h1><%= Setting.app_title %></h1>
27 27 <h2><%= Setting.app_subtitle %></h2>
28 28 </div>
29 29 <div style="float: right; padding-right: 1em; padding-top: 0.2em;">
30 30 <% if loggedin? %><small><%=l(:label_logged_as)%> <strong><%= @logged_in_user.login %></strong> -</small><% end %>
31 31 <small><%= toggle_link l(:label_search), 'quick-search-form', :focus => 'quick-search-input' %></small>
32 32 <% form_tag({:controller => 'search', :action => 'index', :id => @project}, :method => :get, :id => 'quick-search-form', :style => "display:none;" ) do %>
33 33 <%= text_field_tag 'q', @question, :size => 15, :class => 'small', :id => 'quick-search-input' %>
34 34 <% end %>
35 35 </div>
36 36 </div>
37 37
38 38 <div id="navigation">
39 39 <ul>
40 40 <li><%= link_to l(:label_home), { :controller => 'welcome' }, :class => "icon icon-home" %></li>
41 41 <li><%= link_to l(:label_my_page), { :controller => 'my', :action => 'page'}, :class => "icon icon-mypage" %></li>
42 42
43 43 <% if loggedin? and @logged_in_user.memberships.any? %>
44 44 <li class="submenu"><%= link_to l(:label_project_plural), { :controller => 'projects' }, :class => "icon icon-projects", :onmouseover => "buttonMouseover(event, 'menuAllProjects');" %></li>
45 45 <% else %>
46 46 <li><%= link_to l(:label_project_plural), { :controller => 'projects' }, :class => "icon icon-projects" %></li>
47 47 <% end %>
48 48
49 49 <% unless @project.nil? || @project.id.nil? %>
50 50 <li class="submenu"><%= link_to @project.name, { :controller => 'projects', :action => 'show', :id => @project }, :class => "icon icon-projects", :onmouseover => "buttonMouseover(event, 'menuProject');" %></li>
51 51 <% end %>
52 52
53 53 <% if loggedin? %>
54 54 <li><%= link_to l(:label_my_account), { :controller => 'my', :action => 'account' }, :class => "icon icon-user" %></li>
55 55 <% end %>
56 56
57 57 <% if admin_loggedin? %>
58 58 <li class="submenu"><%= link_to l(:label_administration), { :controller => 'admin' }, :class => "icon icon-admin", :onmouseover => "buttonMouseover(event, 'menuAdmin');" %></li>
59 59 <% end %>
60 60
61 61 <li class="right"><%= link_to l(:label_help), { :controller => 'help', :ctrl => params[:controller], :page => params[:action] }, :onclick => "window.open(this.href); return false;", :class => "icon icon-help" %></li>
62 62
63 63 <% if loggedin? %>
64 64 <li class="right"><%= link_to l(:label_logout), { :controller => 'account', :action => 'logout' }, :class => "icon icon-user" %></li>
65 65 <% else %>
66 66 <li class="right"><%= link_to l(:label_login), { :controller => 'account', :action => 'login' }, :class => "icon icon-user" %></li>
67 67 <% end %>
68 68 </ul>
69 69 </div>
70 70
71 71 <% if admin_loggedin? %>
72 72 <%= render :partial => 'admin/menu' %>
73 73 <% end %>
74 74
75 75 <% unless @project.nil? || @project.id.nil? %>
76 76 <div id="menuProject" class="menu" onmouseover="menuMouseover(event)">
77 77 <%= link_to l(:label_calendar), {:controller => 'projects', :action => 'calendar', :id => @project }, :class => "menuItem" %>
78 78 <%= link_to l(:label_gantt), {:controller => 'projects', :action => 'gantt', :id => @project }, :class => "menuItem" %>
79 79 <%= link_to l(:label_issue_plural), {:controller => 'projects', :action => 'list_issues', :id => @project }, :class => "menuItem" %>
80 <% if @project && authorize_for('projects', 'add_issue') %>
81 <a class="menuItem" href="#" onmouseover="menuItemMouseover(event,'menuNewIssue');" onclick="this.blur(); return false;"><span class="menuItemText"><%= l(:label_issue_new) %></span><span class="menuItemArrow">&#9654;</span></a>
82 <% end %>
80 83 <%= link_to l(:label_report_plural), {:controller => 'reports', :action => 'issue_report', :id => @project }, :class => "menuItem" %>
81 84 <%= link_to l(:label_activity), {:controller => 'projects', :action => 'activity', :id => @project }, :class => "menuItem" %>
82 85 <%= link_to l(:label_news_plural), {:controller => 'projects', :action => 'list_news', :id => @project }, :class => "menuItem" %>
83 86 <%= link_to l(:label_change_log), {:controller => 'projects', :action => 'changelog', :id => @project }, :class => "menuItem" %>
84 87 <%= link_to l(:label_roadmap), {:controller => 'projects', :action => 'roadmap', :id => @project }, :class => "menuItem" %>
85 88 <%= link_to l(:label_document_plural), {:controller => 'projects', :action => 'list_documents', :id => @project }, :class => "menuItem" %>
86 89 <%= link_to l(:label_wiki), {:controller => 'wiki', :id => @project, :page => nil }, :class => "menuItem" if @project.wiki and !@project.wiki.new_record? %>
87 90 <%= link_to l(:label_board_plural), {:controller => 'boards', :project_id => @project }, :class => "menuItem" unless @project.boards.empty? %>
88 91 <%= link_to l(:label_attachment_plural), {:controller => 'projects', :action => 'list_files', :id => @project }, :class => "menuItem" %>
89 92 <%= link_to l(:label_search), {:controller => 'search', :action => 'index', :id => @project }, :class => "menuItem" %>
90 93 <%= link_to l(:label_repository), {:controller => 'repositories', :action => 'show', :id => @project}, :class => "menuItem" if @project.repository and !@project.repository.new_record? %>
91 94 <%= link_to_if_authorized l(:label_settings), {:controller => 'projects', :action => 'settings', :id => @project }, :class => "menuItem" %>
92 95 </div>
93 96 <% end %>
97
98 <% if @project && authorize_for('projects', 'add_issue') %>
99 <div id="menuNewIssue" class="menu" onmouseover="menuMouseover(event)">
100 <% Tracker.find(:all, :order => 'position').each do |tracker| %>
101 <%= link_to tracker.name, {:controller => 'projects', :action => 'add_issue', :id => @project, :tracker_id => tracker}, :class => "menuItem" %>
102 <% end %>
103 </div>
104 <% end %>
94 105
95 106 <% if loggedin? and @logged_in_user.memberships.any? %>
96 107 <div id="menuAllProjects" class="menu" onmouseover="menuMouseover(event)">
97 108 <%= link_to l(:label_project_all), {:controller => 'projects' }, :class => "menuItem" %>
98 109 <% @logged_in_user.memberships.find(:all, :limit => 20).each do |membership| %>
99 110 <%= link_to membership.project.name, {:controller => 'projects',:action => 'show', :id => membership.project }, :class => "menuItem" %>
100 111 <% end %>
101 112 </div>
102 113 <% end %>
103 114
104 115 <div id="subcontent">
105 116
106 117 <% unless @project.nil? || @project.id.nil? %>
107 118 <h2><%= @project.name %></h2>
108 119 <ul class="menublock">
109 120 <li><%= link_to l(:label_overview), :controller => 'projects', :action => 'show', :id => @project %></li>
110 121 <li><%= link_to l(:label_calendar), :controller => 'projects', :action => 'calendar', :id => @project %></li>
111 122 <li><%= link_to l(:label_gantt), :controller => 'projects', :action => 'gantt', :id => @project %></li>
112 123 <li><%= link_to l(:label_issue_plural), :controller => 'projects', :action => 'list_issues', :id => @project %></li>
113 124 <li><%= link_to l(:label_report_plural), :controller => 'reports', :action => 'issue_report', :id => @project %></li>
114 125 <li><%= link_to l(:label_activity), :controller => 'projects', :action => 'activity', :id => @project %></li>
115 126 <li><%= link_to l(:label_news_plural), :controller => 'projects', :action => 'list_news', :id => @project %></li>
116 127 <li><%= link_to l(:label_change_log), :controller => 'projects', :action => 'changelog', :id => @project %></li>
117 128 <li><%= link_to l(:label_roadmap), :controller => 'projects', :action => 'roadmap', :id => @project %></li>
118 129 <li><%= link_to l(:label_document_plural), :controller => 'projects', :action => 'list_documents', :id => @project %></li>
119 130 <%= content_tag("li", link_to(l(:label_wiki), :controller => 'wiki', :id => @project, :page => nil)) if @project.wiki and !@project.wiki.new_record? %>
120 131 <%= content_tag("li", link_to(l(:label_board_plural), :controller => 'boards', :project_id => @project)) unless @project.boards.empty? %>
121 132 <li><%= link_to l(:label_attachment_plural), :controller => 'projects', :action => 'list_files', :id => @project %></li>
122 133 <li><%= link_to l(:label_search), :controller => 'search', :action => 'index', :id => @project %></li>
123 134 <%= content_tag("li", link_to(l(:label_repository), :controller => 'repositories', :action => 'show', :id => @project)) if @project.repository and !@project.repository.new_record? %>
124 135 <li><%= link_to_if_authorized l(:label_settings), :controller => 'projects', :action => 'settings', :id => @project %></li>
125 136 </ul>
126 137 <% end %>
127 138 </div>
128 139
129 140 <div id="content">
130 141 <% if flash[:notice] %><p style="color: green"><%= flash[:notice] %></p><% end %>
131 142 <%= yield %>
132 143 </div>
133 144
134 145 <div id="ajax-indicator" style="display:none;">
135 146 <span><%= l(:label_loading) %></span>
136 147 </div>
137 148
138 149 <div id="footer">
139 150 <p><a href="http://redmine.rubyforge.org/">redMine</a> <small><%= Redmine::VERSION %> &copy 2006-2007 Jean-Philippe Lang</small></p>
140 151 </div>
141 152
142 153 </div>
143 154 </body>
144 155 </html> No newline at end of file
@@ -1,88 +1,84
1 1 <% if @query.new_record? %>
2 2 <div class="contextual">
3 <%= render :partial => 'issues/add_shortcut', :locals => {:trackers => @trackers } %>
3 <%= link_to l(:label_query_plural), :controller => 'queries', :project_id => @project %>
4 4 </div>
5 5 <h2><%=l(:label_issue_plural)%></h2>
6 6
7 7 <% form_tag({:action => 'list_issues'}, :id => 'query_form') do %>
8 8 <%= render :partial => 'queries/filters', :locals => {:query => @query} %>
9 9 <% end %>
10 10 <div class="contextual">
11 11 <%= link_to_remote l(:button_apply),
12 12 { :url => { :controller => 'projects', :action => 'list_issues', :id => @project, :set_filter => 1 },
13 13 :update => "content",
14 14 :with => "Form.serialize('query_form')"
15 15 }, :class => 'icon icon-edit' %>
16 16
17 17 <%= link_to_remote l(:button_clear),
18 18 { :url => {:controller => 'projects', :action => 'list_issues', :id => @project, :set_filter => 1},
19 19 :update => "content",
20 20 }, :class => 'icon icon-reload' %>
21 21
22 <% if authorize_for('projects', 'add_query') %>
22 <% if loggedin? %>
23 23 <%= link_to_remote l(:button_save),
24 { :url => { :controller => 'projects', :action => "add_query", :id => @project },
24 { :url => { :controller => 'queries', :action => 'new', :project_id => @project },
25 25 :method => 'get',
26 26 :update => "content",
27 27 :with => "Form.serialize('query_form')"
28 28 }, :class => 'icon icon-save' %>
29 29 <% end %>
30 30 </div>
31 31 <br />
32 32 <% else %>
33 33 <div class="contextual">
34 <%= render :partial => 'issues/add_shortcut', :locals => {:trackers => @trackers } %>
35 <%= link_to l(:button_clear), {:controller => 'projects', :action => 'list_issues', :id => @project, :set_filter => 1}, :class => 'icon icon-reload' %>
36 <% if authorize_for('projects', 'add_query') %>
37 <%= link_to l(:button_edit), {:controller => 'queries', :action => 'edit', :id => @query}, :class => 'icon icon-edit' %>
38 <%= link_to l(:button_delete), {:controller => 'queries', :action => 'destroy', :id => @query}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
39 <% end %>
34 <%= link_to l(:label_query_plural), {:controller => 'queries', :project_id => @project} %> |
35 <%= link_to l(:label_issue_view_all), {:controller => 'projects', :action => 'list_issues', :id => @project, :set_filter => 1} %>
40 36 </div>
41 37 <h2><%= @query.name %></h2>
42 38 <% end %>
43 39 <%= error_messages_for 'query' %>
44 40 <% if @query.valid? %>
45 41 <% if @issues.empty? %>
46 42 <p><i><%= l(:label_no_data) %></i></p>
47 43 <% else %>
48 44 &nbsp;
49 45 <% form_tag({:controller => 'projects', :action => 'move_issues', :id => @project}, :id => 'issues_form' ) do %>
50 46 <table class="list">
51 47 <thead><tr>
52 48 <th></th>
53 49 <%= sort_header_tag("#{Issue.table_name}.id", :caption => '#') %>
54 50 <%= sort_header_tag("#{Issue.table_name}.tracker_id", :caption => l(:field_tracker)) %>
55 51 <%= sort_header_tag("#{IssueStatus.table_name}.name", :caption => l(:field_status)) %>
56 52 <%= sort_header_tag("#{Issue.table_name}.priority_id", :caption => l(:field_priority)) %>
57 53 <th><%=l(:field_subject)%></th>
58 54 <%= sort_header_tag("#{User.table_name}.lastname", :caption => l(:field_assigned_to)) %>
59 55 <%= sort_header_tag("#{Issue.table_name}.updated_on", :caption => l(:field_updated_on)) %>
60 56 </tr></thead>
61 57 <tbody>
62 58 <% for issue in @issues %>
63 59 <tr class="<%= cycle("odd", "even") %>">
64 60 <th style="width:15px;"><%= check_box_tag "issue_ids[]", issue.id, false, :id => "issue_#{issue.id}" %></th>
65 61 <td align="center"><%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %></td>
66 62 <td align="center"><%= issue.tracker.name %></td>
67 63 <td><div class="square" style="background:#<%= issue.status.html_color %>;"></div> <%= issue.status.name %></td>
68 64 <td align="center"><%= issue.priority.name %></td>
69 65 <td><%= "#{issue.project.name} - " unless @project && @project == issue.project %><%= link_to h(issue.subject), :controller => 'issues', :action => 'show', :id => issue %></td>
70 66 <td align="center"><%= issue.assigned_to.name if issue.assigned_to %></td>
71 67 <td align="center"><%= format_time(issue.updated_on) %></td>
72 68 </tr>
73 69 <% end %>
74 70 </tbody>
75 71 </table>
76 72 <div class="contextual">
77 73 <%= l(:label_export_to) %>
78 74 <%= link_to 'CSV', {:action => 'export_issues_csv', :id => @project}, :class => 'icon icon-csv' %>,
79 75 <%= link_to 'PDF', {:action => 'export_issues_pdf', :id => @project}, :class => 'icon icon-pdf' %>
80 76 </div>
81 77 <p><%= submit_tag l(:button_move), :class => "button-small" %>
82 78 &nbsp;
83 79 <%= pagination_links_full @issue_pages %>
84 80 [ <%= @issue_pages.current.first_item %> - <%= @issue_pages.current.last_item %> / <%= @issue_count %> ]
85 81 </p>
86 82 <% end %>
87 83 <% end %>
88 84 <% end %> No newline at end of file
@@ -1,62 +1,59
1 1 <div class="contextual">
2 2 <%= link_to l(:label_feed_plural), {:action => 'feeds', :id => @project}, :class => 'icon icon-feed' %>
3 3 </div>
4 4
5 5 <h2><%=l(:label_overview)%></h2>
6 6
7 7 <div class="splitcontentleft">
8 8 <%= textilizable @project.description %>
9 9 <ul>
10 10 <% unless @project.homepage.blank? %><li><%=l(:field_homepage)%>: <%= auto_link @project.homepage %></li><% end %>
11 11 <li><%=l(:field_created_on)%>: <%= format_date(@project.created_on) %></li>
12 12 <% unless @project.parent.nil? %>
13 13 <li><%=l(:field_parent)%>: <%= link_to @project.parent.name, :controller => 'projects', :action => 'show', :id => @project.parent %></li>
14 14 <% end %>
15 15 <% for custom_value in @custom_values %>
16 16 <% if !custom_value.value.empty? %>
17 17 <li><%= custom_value.custom_field.name%>: <%=h show_value(custom_value) %></li>
18 18 <% end %>
19 19 <% end %>
20 20 </ul>
21 21
22 22 <div class="box">
23 <div class="contextual">
24 <%= render :partial => 'issues/add_shortcut', :locals => {:trackers => @trackers } %>
25 </div>
26 23 <h3 class="icon22 icon22-tracker"><%=l(:label_issue_tracking)%></h3>
27 24 <ul>
28 25 <% for tracker in @trackers %>
29 26 <li><%= link_to tracker.name, :controller => 'projects', :action => 'list_issues', :id => @project,
30 27 :set_filter => 1,
31 28 "tracker_id" => tracker.id %>:
32 29 <%= @open_issues_by_tracker[tracker] || 0 %> <%= lwr(:label_open_issues, @open_issues_by_tracker[tracker] || 0) %>
33 30 <%= l(:label_on) %> <%= @total_issues_by_tracker[tracker] || 0 %></li>
34 31 <% end %>
35 32 </ul>
36 33 <p class="textcenter"><small><%= link_to l(:label_issue_view_all), :controller => 'projects', :action => 'list_issues', :id => @project, :set_filter => 1 %></small></p>
37 34 </div>
38 35 </div>
39 36
40 37 <div class="splitcontentright">
41 38 <div class="box">
42 39 <h3 class="icon22 icon22-users"><%=l(:label_member_plural)%></h3>
43 40 <% @members_by_role.keys.sort.each do |role| %>
44 41 <%= role.name %>: <%= @members_by_role[role].collect(&:user).sort.collect{|u| link_to_user u}.join(", ") %><br />
45 42 <% end %>
46 43 </div>
47 44
48 45 <% if @subprojects.any? %>
49 46 <div class="box">
50 47 <h3 class="icon22 icon22-projects"><%=l(:label_subproject_plural)%></h3>
51 48 <%= @subprojects.collect{|p| link_to(p.name, :action => 'show', :id => p)}.join(", ") %>
52 49 </div>
53 50 <% end %>
54 51
55 52 <% if @news.any? %>
56 53 <div class="box">
57 54 <h3><%=l(:label_news_latest)%></h3>
58 55 <%= render :partial => 'news/news', :collection => @news %>
59 56 <p class="textcenter"><small><%= link_to l(:label_news_view_all), :controller => 'projects', :action => 'list_news', :id => @project %></small></p>
60 57 </div>
61 58 <% end %>
62 59 </div> No newline at end of file
@@ -1,100 +1,100
1 1 <script type="text/javascript">
2 2 //<![CDATA[
3 3 function add_filter() {
4 4 select = $('add_filter_select');
5 5 field = select.value
6 6 Element.show('tr_' + field);
7 7 check_box = $('cb_' + field);
8 8 check_box.checked = true;
9 9 toggle_filter(field);
10 10 select.selectedIndex = 0;
11 11
12 12 for (i=0; i<select.options.length; i++) {
13 13 if (select.options[i].value == field) {
14 14 select.options[i].disabled = true;
15 15 }
16 16 }
17 17 }
18 18
19 19 function toggle_filter(field) {
20 20 check_box = $('cb_' + field);
21 21
22 22 if (check_box.checked) {
23 23 Element.show("operators_" + field);
24 24 toggle_operator(field);
25 25 } else {
26 26 Element.hide("operators_" + field);
27 27 Element.hide("div_values_" + field);
28 28 }
29 29 }
30 30
31 31 function toggle_operator(field) {
32 32 operator = $("operators_" + field);
33 33 switch (operator.value) {
34 34 case "!*":
35 35 case "*":
36 36 case "t":
37 37 case "o":
38 38 case "c":
39 39 Element.hide("div_values_" + field);
40 40 break;
41 41 default:
42 42 Element.show("div_values_" + field);
43 43 break;
44 44 }
45 45 }
46 46
47 47 function toggle_multi_select(field) {
48 48 select = $('values_' + field);
49 49 if (select.multiple == true) {
50 50 select.multiple = false;
51 51 } else {
52 52 select.multiple = true;
53 53 }
54 54 }
55 55 //]]>
56 56 </script>
57 57
58 <fieldset style="margin:0;"><legend><%= l(:label_filter_plural) %></legend>
58 <fieldset><legend><%= l(:label_filter_plural) %></legend>
59 59 <table width="100%">
60 60 <tr>
61 61 <td>
62 62 <table style="padding:0;">
63 63 <% query.available_filters.sort{|a,b| a[1][:order]<=>b[1][:order]}.each do |filter| %>
64 64 <% field = filter[0]
65 65 options = filter[1] %>
66 66 <tr <%= 'style="display:none;"' unless query.has_filter?(field) %> id="tr_<%= field %>">
67 67 <td valign="top" style="width:200px;">
68 68 <%= check_box_tag 'fields[]', field, query.has_filter?(field), :onclick => "toggle_filter('#{field}');", :id => "cb_#{field}" %>
69 69 <label for="cb_<%= field %>"><%= filter[1][:name] || l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) %></label>
70 70 </td>
71 71 <td valign="top" style="width:150px;">
72 72 <%= select_tag "operators[#{field}]", options_for_select(operators_for_select(options[:type]), query.operator_for(field)), :id => "operators_#{field}", :onchange => "toggle_operator('#{field}');", :class => "select-small", :style => "vertical-align: top;" %>
73 73 </td>
74 74 <td valign="top">
75 75 <div id="div_values_<%= field %>" style="display:none;">
76 76 <% case options[:type]
77 77 when :list, :list_optional, :list_status, :list_one_or_more %>
78 78 <select <%= "multiple=true" if query.values_for(field) and query.values_for(field).length > 1 %> name="values[<%= field %>][]" id="values_<%= field %>" class="select-small" style="vertical-align: top;">
79 79 <%= options_for_select options[:values], query.values_for(field) %>
80 80 </select>
81 81 <%= link_to_function image_tag('expand.png'), "toggle_multi_select('#{field}');" %>
82 82 <% when :date, :date_past %>
83 83 <%= text_field_tag "values[#{field}][]", query.values_for(field), :id => "values_#{field}", :size => 3, :class => "select-small" %> <%= l(:label_day_plural) %>
84 84 <% when :string, :text %>
85 85 <%= text_field_tag "values[#{field}][]", query.values_for(field), :id => "values_#{field}", :size => 30, :class => "select-small" %>
86 86 <% end %>
87 87 </div>
88 88 <script type="text/javascript">toggle_filter('<%= field %>');</script>
89 89 </td>
90 90 </tr>
91 91 <% end %>
92 92 </table>
93 93 </td>
94 94 <td align="right" valign="top">
95 95 <%= l(:label_filter_add) %>:
96 96 <%= select_tag 'add_filter_select', options_for_select([["",""]] + query.available_filters.sort{|a,b| a[1][:order]<=>b[1][:order]}.collect{|field| [ field[1][:name] || l(("field_"+field[0].to_s.gsub(/\_id$/, "")).to_sym), field[0]] unless query.has_filter?(field[0])}.compact), :onchange => "add_filter();", :class => "select-small" %>
97 97 </td>
98 98 </tr>
99 99 </table>
100 100 </fieldset> No newline at end of file
@@ -1,12 +1,15
1 1 <%= error_messages_for 'query' %>
2 2
3 <!--[form:query]-->
4 3 <div class="box">
5 4 <div class="tabular">
6 5 <p><label for="query_name"><%=l(:field_name)%></label>
7 6 <%= text_field 'query', 'name', :size => 80 %></p>
7
8 <% if authorize_for('projects', 'add_query') %>
9 <p><label for="query_is_public"><%=l(:field_is_public)%></label>
10 <%= check_box 'query', 'is_public' %></p>
11 <% end %>
8 12 </div>
9 13
10 14 <%= render :partial => 'queries/filters', :locals => {:query => query}%>
11 15 </div>
12 <!--[eoform:query]--> No newline at end of file
@@ -1,6 +1,6
1 1 <h2><%= l(:label_query) %></h2>
2 2
3 3 <% form_tag({:action => 'edit', :id => @query}) do %>
4 4 <%= render :partial => 'form', :locals => {:query => @query} %>
5 5 <%= submit_tag l(:button_save) %>
6 <% end %> No newline at end of file
6 <% end %>
@@ -1,51 +1,34
1 1 <h2><%=l(:label_report_plural)%></h2>
2 2
3 <div class="splitcontentleft">
4 <div class="contextual">
5 <%= link_to_if_authorized l(:label_query_new), {:controller => 'projects', :action => 'add_query', :id => @project}, :class => 'icon icon-add' %>
6 </div>
7 <h3><%= l(:label_query_plural) %></h3>
8
9 <% if @queries.empty? %><p><i><%=l(:label_no_data)%></i></p><% end %>
10 <ul>
11 <% @queries.each do |query| %>
12 <li><%= link_to query.name, :controller => 'projects', :action => 'list_issues', :id => @project, :query_id => query %></li>
13 <% end %>
14 </ul>
15 </div>
16 <div class="splitcontentright">
17 3 <% if @total_hours %>
18 4 <h3 class="textright"><%= l(:label_spent_time) %>:
19 5 <%= link_to(lwr(:label_f_hour, @total_hours), {:controller => 'timelog', :action => 'details', :project_id => @project}, :class => 'icon icon-time') %>
20 6 </h3>
21 7 <% end %>
22 </div>
23
24 <div class="clear"></div>
25 8
26 9 <div class="splitcontentleft">
27 10 <h3><%=l(:field_tracker)%>&nbsp;&nbsp;<%= link_to image_tag('zoom_in.png'), :detail => 'tracker' %></h3>
28 11 <%= render :partial => 'simple', :locals => { :data => @issues_by_tracker, :field_name => "tracker_id", :rows => @trackers } %>
29 12 <br />
30 13 <h3><%=l(:field_version)%>&nbsp;&nbsp;<%= link_to image_tag('zoom_in.png'), :detail => 'version' %></h3>
31 14 <%= render :partial => 'simple', :locals => { :data => @issues_by_version, :field_name => "fixed_version_id", :rows => @versions } %>
32 15 <br />
33 16 <h3><%=l(:field_author)%>&nbsp;&nbsp;<%= link_to image_tag('zoom_in.png'), :detail => 'author' %></h3>
34 17 <%= render :partial => 'simple', :locals => { :data => @issues_by_author, :field_name => "author_id", :rows => @authors } %>
35 18 <br />
36 19 </div>
37 20
38 21 <div class="splitcontentright">
39 22 <h3><%=l(:field_priority)%>&nbsp;&nbsp;<%= link_to image_tag('zoom_in.png'), :detail => 'priority' %></h3>
40 23 <%= render :partial => 'simple', :locals => { :data => @issues_by_priority, :field_name => "priority_id", :rows => @priorities } %>
41 24 <br />
42 25 <% if @project.children.any? %>
43 26 <h3><%=l(:field_subproject)%>&nbsp;&nbsp;<%= link_to image_tag('zoom_in.png'), :detail => 'subproject' %></h3>
44 27 <%= render :partial => 'simple', :locals => { :data => @issues_by_subproject, :field_name => "project_id", :rows => @subprojects } %>
45 28 <br />
46 29 <% end %>
47 30 <h3><%=l(:field_category)%>&nbsp;&nbsp;<%= link_to image_tag('zoom_in.png'), :detail => 'category' %></h3>
48 31 <%= render :partial => 'simple', :locals => { :data => @issues_by_category, :field_name => "category_id", :rows => @categories } %>
49 32 <br />
50 33 </div>
51 34
1 NO CONTENT: modified file, binary diff hidden
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now