diff --git a/app/controllers/activities_controller.rb b/app/controllers/activities_controller.rb new file mode 100644 index 000000000..ae6a67369 --- /dev/null +++ b/app/controllers/activities_controller.rb @@ -0,0 +1,59 @@ +class ActivitiesController < ApplicationController + menu_item :activity + before_filter :find_optional_project + accept_key_auth :index + + def index + @days = Setting.activity_days_default.to_i + + if params[:from] + begin; @date_to = params[:from].to_date + 1; rescue; end + end + + @date_to ||= Date.today + 1 + @date_from = @date_to - @days + @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1') + @author = (params[:user_id].blank? ? nil : User.active.find(params[:user_id])) + + @activity = Redmine::Activity::Fetcher.new(User.current, :project => @project, + :with_subprojects => @with_subprojects, + :author => @author) + @activity.scope_select {|t| !params["show_#{t}"].nil?} + @activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty? + + events = @activity.events(@date_from, @date_to) + + if events.empty? || stale?(:etag => [events.first, User.current]) + respond_to do |format| + format.html { + @events_by_day = events.group_by(&:event_date) + render :layout => false if request.xhr? + } + format.atom { + title = l(:label_activity) + if @author + title = @author.name + elsif @activity.scope.size == 1 + title = l("label_#{@activity.scope.first.singularize}_plural") + end + render_feed(events, :title => "#{@project || Setting.app_title}: #{title}") + } + end + end + + rescue ActiveRecord::RecordNotFound + render_404 + end + + private + + # TODO: refactor, duplicated in projects_controller + def find_optional_project + return true unless params[:id] + @project = Project.find(params[:id]) + authorize + rescue ActiveRecord::RecordNotFound + render_404 + end + +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index c00b6ba42..725bde788 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -201,7 +201,23 @@ class ApplicationController < ActionController::Base def self.model_object(model) write_inheritable_attribute('model_object', model) end - + + # Filter for bulk issue operations + def find_issues + @issues = Issue.find_all_by_id(params[:id] || params[:ids]) + raise ActiveRecord::RecordNotFound if @issues.empty? + projects = @issues.collect(&:project).compact.uniq + if projects.size == 1 + @project = projects.first + else + # TODO: let users bulk edit/move/destroy issues from different projects + render_error 'Can not bulk edit/move/destroy issues from different projects' + return false + end + rescue ActiveRecord::RecordNotFound + render_404 + end + # make sure that the user is a member of the project (or admin) if project is private # used as a before_filter for actions that do not require any particular permission on the project def check_project_privacy @@ -242,7 +258,7 @@ class ApplicationController < ActionController::Base def render_403 @project = nil respond_to do |format| - format.html { render :template => "common/403", :layout => (request.xhr? ? false : 'base'), :status => 403 } + format.html { render :template => "common/403", :layout => use_layout, :status => 403 } format.atom { head 403 } format.xml { head 403 } format.js { head 403 } @@ -253,7 +269,7 @@ class ApplicationController < ActionController::Base def render_404 respond_to do |format| - format.html { render :template => "common/404", :layout => !request.xhr?, :status => 404 } + format.html { render :template => "common/404", :layout => use_layout, :status => 404 } format.atom { head 404 } format.xml { head 404 } format.js { head 404 } @@ -266,7 +282,7 @@ class ApplicationController < ActionController::Base respond_to do |format| format.html { flash.now[:error] = msg - render :text => '', :layout => !request.xhr?, :status => 500 + render :text => '', :layout => use_layout, :status => 500 } format.atom { head 500 } format.xml { head 500 } @@ -274,6 +290,13 @@ class ApplicationController < ActionController::Base format.json { head 500 } end end + + # Picks which layout to use based on the request + # + # @return [boolean, string] name of the layout to use or false for no layout + def use_layout + request.xhr? ? false : 'base' + end def invalid_authenticity_token if api_request? @@ -349,6 +372,21 @@ class ApplicationController < ActionController::Base flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present? end + # Sets the `flash` notice or error based the number of issues that did not save + # + # @param [Array, Issue] issues all of the saved and unsaved Issues + # @param [Array, Integer] unsaved_issue_ids the issue ids that were not saved + def set_flash_from_bulk_issue_save(issues, unsaved_issue_ids) + if unsaved_issue_ids.empty? + flash[:notice] = l(:notice_successful_update) unless issues.empty? + else + flash[:error] = l(:notice_failed_to_save_issues, + :count => unsaved_issue_ids.size, + :total => issues.size, + :ids => '#' + unsaved_issue_ids.join(', #')) + end + end + # Rescues an invalid query statement. Just in case... def query_statement_invalid(exception) logger.error "Query::StatementInvalid: #{exception.message}" if logger diff --git a/app/controllers/auto_completes_controller.rb b/app/controllers/auto_completes_controller.rb new file mode 100644 index 000000000..1438106f6 --- /dev/null +++ b/app/controllers/auto_completes_controller.rb @@ -0,0 +1,25 @@ +class AutoCompletesController < ApplicationController + before_filter :find_project + + def issues + @issues = [] + q = params[:q].to_s + if q.match(/^\d+$/) + @issues << @project.issues.visible.find_by_id(q.to_i) + end + unless q.blank? + @issues += @project.issues.visible.find(:all, :conditions => ["LOWER(#{Issue.table_name}.subject) LIKE ?", "%#{q.downcase}%"], :limit => 10) + end + render :layout => false + end + + private + + def find_project + project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id] + @project = Project.find(project_id) + rescue ActiveRecord::RecordNotFound + render_404 + end + +end diff --git a/app/controllers/boards_controller.rb b/app/controllers/boards_controller.rb index 541fefada..fa82218de 100644 --- a/app/controllers/boards_controller.rb +++ b/app/controllers/boards_controller.rb @@ -18,6 +18,7 @@ class BoardsController < ApplicationController default_search_scope :messages before_filter :find_project, :find_board_if_available, :authorize + accept_key_auth :index, :show helper :messages include MessagesHelper diff --git a/app/controllers/calendars_controller.rb b/app/controllers/calendars_controller.rb index f2af58086..febacd075 100644 --- a/app/controllers/calendars_controller.rb +++ b/app/controllers/calendars_controller.rb @@ -1,4 +1,5 @@ class CalendarsController < ApplicationController + menu_item :calendar before_filter :find_optional_project rescue_from Query::StatementInvalid, :with => :query_statement_invalid @@ -31,8 +32,11 @@ class CalendarsController < ApplicationController @calendar.events = events end - render :layout => false if request.xhr? + render :action => 'show', :layout => false if request.xhr? end + def update + show + end end diff --git a/app/controllers/context_menus_controller.rb b/app/controllers/context_menus_controller.rb new file mode 100644 index 000000000..442f85b37 --- /dev/null +++ b/app/controllers/context_menus_controller.rb @@ -0,0 +1,33 @@ +class ContextMenusController < ApplicationController + helper :watchers + + def issues + @issues = Issue.find_all_by_id(params[:ids], :include => :project) + if (@issues.size == 1) + @issue = @issues.first + @allowed_statuses = @issue.new_statuses_allowed_to(User.current) + end + projects = @issues.collect(&:project).compact.uniq + @project = projects.first if projects.size == 1 + + @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)), + :log_time => (@project && User.current.allowed_to?(:log_time, @project)), + :update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))), + :move => (@project && User.current.allowed_to?(:move_issues, @project)), + :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)), + :delete => (@project && User.current.allowed_to?(:delete_issues, @project)) + } + if @project + @assignables = @project.assignable_users + @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to) + @trackers = @project.trackers + end + + @priorities = IssuePriority.all.reverse + @statuses = IssueStatus.find(:all, :order => 'position') + @back = back_url + + render :layout => false + end + +end diff --git a/app/controllers/gantts_controller.rb b/app/controllers/gantts_controller.rb index e762c19c8..6a6071e86 100644 --- a/app/controllers/gantts_controller.rb +++ b/app/controllers/gantts_controller.rb @@ -1,4 +1,5 @@ class GanttsController < ApplicationController + menu_item :gantt before_filter :find_optional_project rescue_from Query::StatementInvalid, :with => :query_statement_invalid @@ -42,4 +43,8 @@ class GanttsController < ApplicationController end end + def update + show + end + end diff --git a/app/controllers/issue_moves_controller.rb b/app/controllers/issue_moves_controller.rb new file mode 100644 index 000000000..6ac46a1ee --- /dev/null +++ b/app/controllers/issue_moves_controller.rb @@ -0,0 +1,65 @@ +class IssueMovesController < ApplicationController + default_search_scope :issues + before_filter :find_issues + before_filter :authorize + + def new + prepare_for_issue_move + render :layout => false if request.xhr? + end + + def create + prepare_for_issue_move + + if request.post? + new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id]) + unsaved_issue_ids = [] + moved_issues = [] + @issues.each do |issue| + issue.reload + issue.init_journal(User.current) + call_hook(:controller_issues_move_before_save, { :params => params, :issue => issue, :target_project => @target_project, :copy => !!@copy }) + if r = issue.move_to_project(@target_project, new_tracker, {:copy => @copy, :attributes => extract_changed_attributes_for_move(params)}) + moved_issues << r + else + unsaved_issue_ids << issue.id + end + end + set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids) + + if params[:follow] + if @issues.size == 1 && moved_issues.size == 1 + redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first + else + redirect_to :controller => 'issues', :action => 'index', :project_id => (@target_project || @project) + end + else + redirect_to :controller => 'issues', :action => 'index', :project_id => @project + end + return + end + end + + private + + def prepare_for_issue_move + @issues.sort! + @copy = params[:copy_options] && params[:copy_options][:copy] + @allowed_projects = Issue.allowed_target_projects_on_move + @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id] + @target_project ||= @project + @trackers = @target_project.trackers + @available_statuses = Workflow.available_statuses(@project) + end + + def extract_changed_attributes_for_move(params) + changed_attributes = {} + [:assigned_to_id, :status_id, :start_date, :due_date].each do |valid_attribute| + unless params[valid_attribute].blank? + changed_attributes[valid_attribute] = (params[valid_attribute] == 'none' ? nil : params[valid_attribute]) + end + end + changed_attributes + end + +end diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index 46c61ea0f..0364e307c 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -19,14 +19,14 @@ class IssuesController < ApplicationController menu_item :new_issue, :only => [:new, :create] default_search_scope :issues - before_filter :find_issue, :only => [:show, :edit, :update, :reply] - before_filter :find_issues, :only => [:bulk_edit, :move, :perform_move, :destroy] - before_filter :find_project, :only => [:new, :create, :update_form, :preview, :auto_complete] - before_filter :authorize, :except => [:index, :changes, :preview, :context_menu] - before_filter :find_optional_project, :only => [:index, :changes] + before_filter :find_issue, :only => [:show, :edit, :update] + before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :move, :perform_move, :destroy] + before_filter :find_project, :only => [:new, :create] + before_filter :authorize, :except => [:index] + before_filter :find_optional_project, :only => [:index] before_filter :check_for_default_issue_status, :only => [:new, :create] before_filter :build_new_issue_from_params, :only => [:new, :create] - accept_key_auth :index, :show, :changes + accept_key_auth :index, :show rescue_from Query::StatementInvalid, :with => :query_statement_invalid @@ -54,6 +54,7 @@ class IssuesController < ApplicationController :render => { :nothing => true, :status => :method_not_allowed } verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed } + verify :method => :post, :only => :bulk_update, :render => {:nothing => true, :status => :method_not_allowed } verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed } def index @@ -95,21 +96,6 @@ class IssuesController < ApplicationController render_404 end - def changes - retrieve_query - sort_init 'id', 'desc' - sort_update(@query.sortable_columns) - - if @query.valid? - @journals = @query.journals(:order => "#{Journal.table_name}.created_on DESC", - :limit => 25) - end - @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name) - render :layout => false, :content_type => 'application/atom+xml' - rescue ActiveRecord::RecordNotFound - render_404 - end - def show @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC") @journals.each_with_index {|j,i| j.indice = i+1} @@ -124,7 +110,7 @@ class IssuesController < ApplicationController format.html { render :template => 'issues/show.rhtml' } format.xml { render :layout => false } format.json { render :text => @issue.to_json, :layout => false } - format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' } + format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' } format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") } end end @@ -132,7 +118,10 @@ class IssuesController < ApplicationController # Add a new issue # The new issue will be created from an existing one if copy_from parameter is given def new - render :action => 'new', :layout => !request.xhr? + respond_to do |format| + format.html { render :action => 'new', :layout => !request.xhr? } + format.js { render :partial => 'attributes' } + end end def create @@ -200,92 +189,30 @@ class IssuesController < ApplicationController end end - def reply - journal = Journal.find(params[:journal_id]) if params[:journal_id] - if journal - user = journal.user - text = journal.notes - else - user = @issue.author - text = @issue.description - end - # Replaces pre blocks with [...] - text = text.to_s.strip.gsub(%r{
((.|\s)*?)}m, '[...]') - content = "#{ll(Setting.default_language, :text_user_wrote, user)}\n> " - content << text.gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n" - - render(:update) { |page| - page.<< "$('notes').value = \"#{escape_javascript content}\";" - page.show 'update' - page << "Form.Element.focus('notes');" - page << "Element.scrollTo('update');" - page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;" - } - end - # Bulk edit a set of issues def bulk_edit @issues.sort! - if request.post? - attributes = (params[:issue] || {}).reject {|k,v| v.blank?} - attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'} - attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values] - - unsaved_issue_ids = [] - @issues.each do |issue| - issue.reload - journal = issue.init_journal(User.current, params[:notes]) - issue.safe_attributes = attributes - call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue }) - unless issue.save - # Keep unsaved issue ids to display them in flash error - unsaved_issue_ids << issue.id - end - end - set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids) - redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project}) - return - end @available_statuses = Workflow.available_statuses(@project) @custom_fields = @project.all_issue_custom_fields end - def move - prepare_for_issue_move - render :layout => false if request.xhr? - end + def bulk_update + @issues.sort! + attributes = parse_params_for_bulk_issue_attributes(params) - # TODO: more descriptive name? move to separate controller like IssueMovesController? - def perform_move - prepare_for_issue_move - - if request.post? - new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id]) - unsaved_issue_ids = [] - moved_issues = [] - @issues.each do |issue| - issue.reload - issue.init_journal(User.current) - call_hook(:controller_issues_move_before_save, { :params => params, :issue => issue, :target_project => @target_project, :copy => !!@copy }) - if r = issue.move_to_project(@target_project, new_tracker, {:copy => @copy, :attributes => extract_changed_attributes_for_move(params)}) - moved_issues << r - else - unsaved_issue_ids << issue.id - end + unsaved_issue_ids = [] + @issues.each do |issue| + issue.reload + journal = issue.init_journal(User.current, params[:notes]) + issue.safe_attributes = attributes + call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue }) + unless issue.save + # Keep unsaved issue ids to display them in flash error + unsaved_issue_ids << issue.id end - set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids) - - if params[:follow] - if @issues.size == 1 && moved_issues.size == 1 - redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first - else - redirect_to :controller => 'issues', :action => 'index', :project_id => (@target_project || @project) - end - else - redirect_to :controller => 'issues', :action => 'index', :project_id => @project - end - return end + set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids) + redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project}) end def destroy @@ -318,77 +245,7 @@ class IssuesController < ApplicationController format.json { head :ok } end end - - def context_menu - @issues = Issue.find_all_by_id(params[:ids], :include => :project) - if (@issues.size == 1) - @issue = @issues.first - @allowed_statuses = @issue.new_statuses_allowed_to(User.current) - end - projects = @issues.collect(&:project).compact.uniq - @project = projects.first if projects.size == 1 - @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)), - :log_time => (@project && User.current.allowed_to?(:log_time, @project)), - :update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))), - :move => (@project && User.current.allowed_to?(:move_issues, @project)), - :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)), - :delete => (@project && User.current.allowed_to?(:delete_issues, @project)) - } - if @project - @assignables = @project.assignable_users - @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to) - @trackers = @project.trackers - end - - @priorities = IssuePriority.all.reverse - @statuses = IssueStatus.find(:all, :order => 'position') - @back = back_url - - render :layout => false - end - - def update_form - if params[:id].blank? - @issue = Issue.new - @issue.project = @project - else - @issue = @project.issues.visible.find(params[:id]) - end - @issue.attributes = params[:issue] - @allowed_statuses = ([@issue.status] + @issue.status.find_new_statuses_allowed_to(User.current.roles_for_project(@project), @issue.tracker)).uniq - @priorities = IssuePriority.all - - render :partial => 'attributes' - end - - def preview - @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank? - if @issue - @attachements = @issue.attachments - @description = params[:issue] && params[:issue][:description] - if @description && @description.gsub(/(\r?\n|\n\r?)/, "\n") == @issue.description.to_s.gsub(/(\r?\n|\n\r?)/, "\n") - @description = nil - end - @notes = params[:notes] - else - @description = (params[:issue] ? params[:issue][:description] : nil) - end - render :layout => false - end - - def auto_complete - @issues = [] - q = params[:q].to_s - if q.match(/^\d+$/) - @issues << @project.issues.visible.find_by_id(q.to_i) - end - unless q.blank? - @issues += @project.issues.visible.find(:all, :conditions => ["LOWER(#{Issue.table_name}.subject) LIKE ?", "%#{q.downcase}%"], :limit => 10) - end - render :layout => false - end - private def find_issue @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category]) @@ -397,22 +254,6 @@ private render_404 end - # Filter for bulk operations - def find_issues - @issues = Issue.find_all_by_id(params[:id] || params[:ids]) - raise ActiveRecord::RecordNotFound if @issues.empty? - projects = @issues.collect(&:project).compact.uniq - if projects.size == 1 - @project = projects.first - else - # TODO: let users bulk edit/move/destroy issues from different projects - render_error 'Can not bulk edit/move/destroy issues from different projects' - return false - end - rescue ActiveRecord::RecordNotFound - render_404 - end - def find_project project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id] @project = Project.find(project_id) @@ -429,7 +270,7 @@ private @edit_allowed = User.current.allowed_to?(:edit_issues, @project) @time_entry = TimeEntry.new - @notes = params[:notes] + @notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil) @issue.init_journal(User.current, @notes) # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue] @@ -443,8 +284,14 @@ private # TODO: Refactor, lots of extra code in here def build_new_issue_from_params - @issue = Issue.new - @issue.copy_from(params[:copy_from]) if params[:copy_from] + if params[:id].blank? + @issue = Issue.new + @issue.copy_from(params[:copy_from]) if params[:copy_from] + @issue.project = @project + else + @issue = @project.issues.visible.find(params[:id]) + end + @issue.project = @project # Tracker must be set before custom field values @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first) @@ -462,27 +309,6 @@ private @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true) end - def prepare_for_issue_move - @issues.sort! - @copy = params[:copy_options] && params[:copy_options][:copy] - @allowed_projects = Issue.allowed_target_projects_on_move - @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id] - @target_project ||= @project - @trackers = @target_project.trackers - @available_statuses = Workflow.available_statuses(@project) - end - - def set_flash_from_bulk_issue_save(issues, unsaved_issue_ids) - if unsaved_issue_ids.empty? - flash[:notice] = l(:notice_successful_update) unless issues.empty? - else - flash[:error] = l(:notice_failed_to_save_issues, - :count => unsaved_issue_ids.size, - :total => issues.size, - :ids => '#' + unsaved_issue_ids.join(', #')) - end - end - def check_for_default_issue_status if IssueStatus.default.nil? render_error l(:error_no_default_issue_status) @@ -490,13 +316,10 @@ private end end - def extract_changed_attributes_for_move(params) - changed_attributes = {} - [:assigned_to_id, :status_id, :start_date, :due_date].each do |valid_attribute| - unless params[valid_attribute].blank? - changed_attributes[valid_attribute] = (params[valid_attribute] == 'none' ? nil : params[valid_attribute]) - end - end - changed_attributes + def parse_params_for_bulk_issue_attributes(params) + attributes = (params[:issue] || {}).reject {|k,v| v.blank?} + attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'} + attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values] + attributes end end diff --git a/app/controllers/journals_controller.rb b/app/controllers/journals_controller.rb index e9fe9099d..a3b1abde4 100644 --- a/app/controllers/journals_controller.rb +++ b/app/controllers/journals_controller.rb @@ -16,7 +16,54 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class JournalsController < ApplicationController - before_filter :find_journal + before_filter :find_journal, :only => [:edit] + before_filter :find_issue, :only => [:new] + before_filter :find_optional_project, :only => [:index] + accept_key_auth :index + + helper :issues + helper :queries + include QueriesHelper + helper :sort + include SortHelper + + def index + retrieve_query + sort_init 'id', 'desc' + sort_update(@query.sortable_columns) + + if @query.valid? + @journals = @query.journals(:order => "#{Journal.table_name}.created_on DESC", + :limit => 25) + end + @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name) + render :layout => false, :content_type => 'application/atom+xml' + rescue ActiveRecord::RecordNotFound + render_404 + end + + def new + journal = Journal.find(params[:journal_id]) if params[:journal_id] + if journal + user = journal.user + text = journal.notes + else + user = @issue.author + text = @issue.description + end + # Replaces pre blocks with [...] + text = text.to_s.strip.gsub(%r{
((.|\s)*?)}m, '[...]') + content = "#{ll(Setting.default_language, :text_user_wrote, user)}\n> " + content << text.gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n" + + render(:update) { |page| + page.<< "$('notes').value = \"#{escape_javascript content}\";" + page.show 'update' + page << "Form.Element.focus('notes');" + page << "Element.scrollTo('update');" + page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;" + } + end def edit if request.post? @@ -38,4 +85,12 @@ private rescue ActiveRecord::RecordNotFound render_404 end + + # TODO: duplicated in IssuesController + def find_issue + @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category]) + @project = @issue.project + rescue ActiveRecord::RecordNotFound + render_404 + end end diff --git a/app/controllers/previews_controller.rb b/app/controllers/previews_controller.rb new file mode 100644 index 000000000..e1c644653 --- /dev/null +++ b/app/controllers/previews_controller.rb @@ -0,0 +1,28 @@ +class PreviewsController < ApplicationController + before_filter :find_project + + def issue + @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank? + if @issue + @attachements = @issue.attachments + @description = params[:issue] && params[:issue][:description] + if @description && @description.gsub(/(\r?\n|\n\r?)/, "\n") == @issue.description.to_s.gsub(/(\r?\n|\n\r?)/, "\n") + @description = nil + end + @notes = params[:notes] + else + @description = (params[:issue] ? params[:issue][:description] : nil) + end + render :layout => false + end + + private + + def find_project + project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id] + @project = Project.find(project_id) + rescue ActiveRecord::RecordNotFound + render_404 + end + +end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 44071d214..845ab3c15 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -17,17 +17,15 @@ class ProjectsController < ApplicationController menu_item :overview - menu_item :activity, :only => :activity menu_item :roadmap, :only => :roadmap menu_item :files, :only => [:list_files, :add_file] menu_item :settings, :only => :settings - before_filter :find_project, :except => [ :index, :list, :add, :copy, :activity ] - before_filter :find_optional_project, :only => :activity - before_filter :authorize, :except => [ :index, :list, :add, :copy, :archive, :unarchive, :destroy, :activity ] + before_filter :find_project, :except => [ :index, :list, :add, :copy ] + before_filter :authorize, :except => [ :index, :list, :add, :copy, :archive, :unarchive, :destroy] before_filter :authorize_global, :only => :add before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ] - accept_key_auth :activity, :index + accept_key_auth :index after_filter :only => [:add, :edit, :archive, :unarchive, :destroy] do |controller| if controller.request.post? @@ -289,72 +287,6 @@ class ProjectsController < ApplicationController render :layout => !request.xhr? end - def roadmap - @trackers = @project.trackers.find(:all, :order => 'position') - retrieve_selected_tracker_ids(@trackers, @trackers.select {|t| t.is_in_roadmap?}) - @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1') - project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id] - - @versions = @project.shared_versions || [] - @versions += @project.rolled_up_versions.visible if @with_subprojects - @versions = @versions.uniq.sort - @versions.reject! {|version| version.closed? || version.completed? } unless params[:completed] - - @issues_by_version = {} - unless @selected_tracker_ids.empty? - @versions.each do |version| - issues = version.fixed_issues.visible.find(:all, - :include => [:project, :status, :tracker, :priority], - :conditions => {:tracker_id => @selected_tracker_ids, :project_id => project_ids}, - :order => "#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id") - @issues_by_version[version] = issues - end - end - @versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?} - end - - def activity - @days = Setting.activity_days_default.to_i - - if params[:from] - begin; @date_to = params[:from].to_date + 1; rescue; end - end - - @date_to ||= Date.today + 1 - @date_from = @date_to - @days - @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1') - @author = (params[:user_id].blank? ? nil : User.active.find(params[:user_id])) - - @activity = Redmine::Activity::Fetcher.new(User.current, :project => @project, - :with_subprojects => @with_subprojects, - :author => @author) - @activity.scope_select {|t| !params["show_#{t}"].nil?} - @activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty? - - events = @activity.events(@date_from, @date_to) - - if events.empty? || stale?(:etag => [events.first, User.current]) - respond_to do |format| - format.html { - @events_by_day = events.group_by(&:event_date) - render :layout => false if request.xhr? - } - format.atom { - title = l(:label_activity) - if @author - title = @author.name - elsif @activity.scope.size == 1 - title = l("label_#{@activity.scope.first.singularize}_plural") - end - render_feed(events, :title => "#{@project || Setting.app_title}: #{title}") - } - end - end - - rescue ActiveRecord::RecordNotFound - render_404 - end - private def find_optional_project return true unless params[:id] @@ -364,14 +296,6 @@ private render_404 end - def retrieve_selected_tracker_ids(selectable_trackers, default_trackers=nil) - if ids = params[:tracker_ids] - @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s } - else - @selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s } - end - end - # Validates parent_id param according to user's permissions # TODO: move it to Project model in a validation that depends on User.current def validate_parent_id diff --git a/app/controllers/versions_controller.rb b/app/controllers/versions_controller.rb index 46b4778d4..dd01da95b 100644 --- a/app/controllers/versions_controller.rb +++ b/app/controllers/versions_controller.rb @@ -18,13 +18,37 @@ class VersionsController < ApplicationController menu_item :roadmap model_object Version - before_filter :find_model_object, :except => [:new, :close_completed] - before_filter :find_project_from_association, :except => [:new, :close_completed] - before_filter :find_project, :only => [:new, :close_completed] + before_filter :find_model_object, :except => [:index, :new, :close_completed] + before_filter :find_project_from_association, :except => [:index, :new, :close_completed] + before_filter :find_project, :only => [:index, :new, :close_completed] before_filter :authorize helper :custom_fields helper :projects + + def index + @trackers = @project.trackers.find(:all, :order => 'position') + retrieve_selected_tracker_ids(@trackers, @trackers.select {|t| t.is_in_roadmap?}) + @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1') + project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id] + + @versions = @project.shared_versions || [] + @versions += @project.rolled_up_versions.visible if @with_subprojects + @versions = @versions.uniq.sort + @versions.reject! {|version| version.closed? || version.completed? } unless params[:completed] + + @issues_by_version = {} + unless @selected_tracker_ids.empty? + @versions.each do |version| + issues = version.fixed_issues.visible.find(:all, + :include => [:project, :status, :tracker, :priority], + :conditions => {:tracker_id => @selected_tracker_ids, :project_id => project_ids}, + :order => "#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id") + @issues_by_version[version] = issues + end + end + @versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?} + end def show @issues = @version.fixed_issues.visible.find(:all, @@ -105,4 +129,13 @@ private rescue ActiveRecord::RecordNotFound render_404 end + + def retrieve_selected_tracker_ids(selectable_trackers, default_trackers=nil) + if ids = params[:tracker_ids] + @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s } + else + @selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s } + end + end + end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 2c334b7f0..19dd654db 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -737,6 +737,11 @@ module ApplicationHelper javascript_include_tag('context_menu') + stylesheet_link_tag('context_menu') end + if l(:direction) == 'rtl' + content_for :header_tags do + stylesheet_link_tag('context_menu_rtl') + end + end @context_menu_included = true end javascript_tag "new ContextMenu('#{ url_for(url) }')" diff --git a/app/helpers/calendars_helper.rb b/app/helpers/calendars_helper.rb new file mode 100644 index 000000000..08e665dcd --- /dev/null +++ b/app/helpers/calendars_helper.rb @@ -0,0 +1,45 @@ +module CalendarsHelper + def link_to_previous_month(year, month, options={}) + target_year, target_month = if month == 1 + [year - 1, 12] + else + [year, month - 1] + end + + name = if target_month == 12 + "#{month_name(target_month)} #{target_year}" + else + "#{month_name(target_month)}" + end + + link_to_month(('« ' + name), target_year, target_month, options) + end + + def link_to_next_month(year, month, options={}) + target_year, target_month = if month == 12 + [year + 1, 1] + else + [year, month + 1] + end + + name = if target_month == 1 + "#{month_name(target_month)} #{target_year}" + else + "#{month_name(target_month)}" + end + + link_to_month((name + ' »'), target_year, target_month, options) + end + + def link_to_month(link_name, year, month, options={}) + project_id = options[:project].present? ? options[:project].to_param : nil + + link_target = calendar_path(:year => year, :month => month, :project_id => project_id) + + link_to_remote(link_name, + {:update => "content", :url => link_target, :method => :put}, + {:href => link_target}) + + end + +end diff --git a/app/helpers/issue_moves_helper.rb b/app/helpers/issue_moves_helper.rb new file mode 100644 index 000000000..b58b4ce5a --- /dev/null +++ b/app/helpers/issue_moves_helper.rb @@ -0,0 +1,2 @@ +module IssueMovesHelper +end diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 60798fedf..617822986 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -30,12 +30,14 @@ module IssuesHelper end def render_issue_tooltip(issue) + @cached_label_status ||= l(:field_status) @cached_label_start_date ||= l(:field_start_date) @cached_label_due_date ||= l(:field_due_date) @cached_label_assigned_to ||= l(:field_assigned_to) @cached_label_priority ||= l(:field_priority) link_to_issue(issue) + "