65 changes: 43 additions & 22 deletions app/controllers/account_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -19,8 +19,10 @@ class AccountController < ApplicationController
helper :custom_fields
include CustomFieldsHelper

self.main_menu = false

# prevents login action to be filtered by check_if_login_required application scope filter
skip_before_filter :check_if_login_required, :check_password_change
skip_before_action :check_if_login_required, :check_password_change

# Overrides ApplicationController#verify_authenticity_token to disable
# token verification on openid callbacks
Expand All @@ -32,15 +34,15 @@ def verify_authenticity_token

# Login request and validation
def login
if request.get?
if request.post?
authenticate_user
else
if User.current.logged?
redirect_back_or_default home_url, :referer => true
end
else
authenticate_user
end
rescue AuthSourceException => e
logger.error "An error occured when authenticating #{params[:username]}: #{e.message}"
logger.error "An error occurred when authenticating #{params[:username]}: #{e.message}"
render_error :message => e.message
end

Expand All @@ -58,24 +60,38 @@ def logout
# Lets user choose a new password
def lost_password
(redirect_to(home_url); return) unless Setting.lost_password?
if params[:token]
@token = Token.find_token("recovery", params[:token].to_s)
if prt = (params[:token] || session[:password_recovery_token])
@token = Token.find_token("recovery", prt.to_s)
if @token.nil? || @token.expired?
redirect_to home_url
return
end

# redirect to remove the token query parameter from the URL and add it to the session
if request.query_parameters[:token].present?
session[:password_recovery_token] = @token.value
redirect_to lost_password_url
return
end

@user = @token.user
unless @user && @user.active?
redirect_to home_url
return
end
if request.post?
@user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
if @user.save
@token.destroy
flash[:notice] = l(:notice_account_password_updated)
redirect_to signin_path
return
if @user.must_change_passwd? && @user.check_password?(params[:new_password])
flash.now[:error] = l(:notice_new_password_must_be_different)
else
@user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
@user.must_change_passwd = false
if @user.save
@token.destroy
Mailer.password_updated(@user)
flash[:notice] = l(:notice_account_password_updated)
redirect_to signin_path
return
end
end
end
render :template => "account/password_recovery"
Expand All @@ -102,7 +118,7 @@ def lost_password
token = Token.new(:user => user, :action => "recovery")
if token.save
# Don't use the param to send the email
recipent = user.mails.detect {|e| e.downcase == email.downcase} || user.mail
recipent = user.mails.detect {|e| email.casecmp(e) == 0} || user.mail
Mailer.lost_password(token, recipent).deliver
flash[:notice] = l(:notice_account_lost_email_sent)
redirect_to signin_path
Expand All @@ -115,13 +131,14 @@ def lost_password
# User self-registration
def register
(redirect_to(home_url); return) unless Setting.self_registration? || session[:auth_source_registration]
if request.get?
if !request.post?
session[:auth_source_registration] = nil
@user = User.new(:language => current_language.to_s)
else
user_params = params[:user] || {}
@user = User.new
@user.safe_attributes = user_params
@user.pref.safe_attributes = params[:pref]
@user.admin = false
@user.register
if session[:auth_source_registration]
Expand All @@ -135,7 +152,6 @@ def register
redirect_to my_account_path
end
else
@user.login = params[:user][:login]
unless user_params[:identity_url].present? && user_params[:password].blank? && user_params[:password_confirmation].blank?
@user.password, @user.password_confirmation = user_params[:password], user_params[:password_confirmation]
end
Expand Down Expand Up @@ -201,6 +217,7 @@ def password_authentication
# Valid user
if user.active?
successful_authentication(user)
update_sudo_timestamp! # activate Sudo Mode
else
handle_inactive_user(user)
end
Expand Down Expand Up @@ -263,12 +280,16 @@ def successful_authentication(user)
end

def set_autologin_cookie(user)
token = Token.create(:user => user, :action => 'autologin')
token = user.generate_autologin_token
secure = Redmine::Configuration['autologin_cookie_secure']
if secure.nil?
secure = request.ssl?
end
cookie_options = {
:value => token.value,
:value => token,
:expires => 1.year.from_now,
:path => (Redmine::Configuration['autologin_cookie_path'] || '/'),
:secure => (Redmine::Configuration['autologin_cookie_secure'] ? true : false),
:path => (Redmine::Configuration['autologin_cookie_path'] || RedmineApp::Application.config.relative_url_root || '/'),
:secure => secure,
:httponly => true
}
cookies[autologin_cookie_name] = cookie_options
Expand All @@ -283,7 +304,7 @@ def onthefly_creation_failed(user, auth_source_options = { })

def invalid_credentials
logger.warn "Failed login for '#{params[:username]}' from #{request.remote_ip} at #{Time.now.utc}"
flash.now[:error] = l(:notice_account_invalid_creditentials)
flash.now[:error] = l(:notice_account_invalid_credentials)
end

# Register a user for email activation.
Expand Down
21 changes: 17 additions & 4 deletions app/controllers/activities_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -17,7 +17,7 @@

class ActivitiesController < ApplicationController
menu_item :activity
before_filter :find_optional_project
before_action :find_optional_project
accept_rss_auth :index

def index
Expand All @@ -27,7 +27,7 @@ def index
begin; @date_to = params[:from].to_date + 1; rescue; end
end

@date_to ||= Date.today + 1
@date_to ||= User.current.today + 1
@date_from = @date_to - @days
@with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
if params[:user_id].present?
Expand All @@ -37,8 +37,21 @@ def index
@activity = Redmine::Activity::Fetcher.new(User.current, :project => @project,
:with_subprojects => @with_subprojects,
:author => @author)
pref = User.current.pref
@activity.scope_select {|t| !params["show_#{t}"].nil?}
@activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty?
if @activity.scope.present?
if params[:submit].present?
pref.activity_scope = @activity.scope
pref.save
end
else
if @author.nil?
scope = pref.activity_scope & @activity.event_types
@activity.scope = scope.present? ? scope : :default
else
@activity.scope = :all
end
end

events = @activity.events(@date_from, @date_to)

Expand Down
13 changes: 7 additions & 6 deletions app/controllers/admin_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -17,13 +17,12 @@

class AdminController < ApplicationController
layout 'admin'
self.main_menu = false
menu_item :projects, :only => :projects
menu_item :plugins, :only => :plugins
menu_item :info, :only => :info

before_filter :require_admin
helper :sort
include SortHelper
before_action :require_admin

def index
@no_configuration_data = Redmine::DefaultData::Loader::no_data?
Expand All @@ -34,7 +33,10 @@ def projects

scope = Project.status(@status).sorted
scope = scope.like(params[:name]) if params[:name].present?
@projects = scope.to_a

@project_count = scope.count
@project_pages = Paginator.new @project_count, per_page_option, params['page']
@projects = scope.limit(@project_pages.per_page).offset(@project_pages.offset).to_a

render :action => "projects", :layout => false if request.xhr?
end
Expand Down Expand Up @@ -72,7 +74,6 @@ def test_email
end

def info
@db_adapter_name = ActiveRecord::Base.connection.adapter_name
@checklist = [
[:text_default_administrator_account_changed, User.default_admin_account_changed?],
[:text_file_repository_writable, File.writable?(Attachment.storage_path)],
Expand Down
129 changes: 73 additions & 56 deletions app/controllers/application_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -23,6 +23,7 @@ class Unauthorized < Exception; end
class ApplicationController < ActionController::Base
include Redmine::I18n
include Redmine::Pagination
include Redmine::Hook::Helper
include RoutesHelper
helper :routes

Expand Down Expand Up @@ -50,7 +51,7 @@ def handle_unverified_request
end
end

before_filter :session_expiration, :user_setup, :force_logout_if_password_changed, :check_if_login_required, :check_password_change, :set_localization
before_action :session_expiration, :user_setup, :check_if_login_required, :set_localization, :check_password_change

rescue_from ::Unauthorized, :with => :deny_access
rescue_from ::ActionView::MissingTemplate, :with => :missing_template
Expand All @@ -62,36 +63,23 @@ def handle_unverified_request
include Redmine::SudoMode::Controller

def session_expiration
if session[:user_id]
if session[:user_id] && Rails.application.config.redmine_verify_sessions != false
if session_expired? && !try_to_autologin
set_localization(User.active.find_by_id(session[:user_id]))
self.logged_user = nil
flash[:error] = l(:error_session_expired)
require_login
else
session[:atime] = Time.now.utc.to_i
end
end
end

def session_expired?
if Setting.session_lifetime?
unless session[:ctime] && (Time.now.utc.to_i - session[:ctime].to_i <= Setting.session_lifetime.to_i * 60)
return true
end
end
if Setting.session_timeout?
unless session[:atime] && (Time.now.utc.to_i - session[:atime].to_i <= Setting.session_timeout.to_i * 60)
return true
end
end
false
! User.verify_session_token(session[:user_id], session[:tk])
end

def start_user_session(user)
session[:user_id] = user.id
session[:ctime] = Time.now.utc.to_i
session[:atime] = Time.now.utc.to_i
session[:tk] = user.generate_session_token
if user.must_change_password?
session[:pwd] = '1'
end
Expand Down Expand Up @@ -145,21 +133,11 @@ def find_current_user
end
end
end
# store current ip address in user object ephemerally
user.remote_ip = request.remote_ip if user
user
end

def force_logout_if_password_changed
passwd_changed_on = User.current.passwd_changed_on || Time.at(0)
# Make sure we force logout only for web browser sessions, not API calls
# if the password was changed after the session creation.
if session[:user_id] && passwd_changed_on.utc.to_i > session[:ctime].to_i
reset_session
set_localization
flash[:error] = l(:error_session_expired)
redirect_to signin_url
end
end

def autologin_cookie_name
Redmine::Configuration['autologin_cookie_name'].presence || 'autologin'
end
Expand Down Expand Up @@ -190,8 +168,10 @@ def logged_user=(user)
# Logs out current user
def logout_user
if User.current.logged?
cookies.delete(autologin_cookie_name)
Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin'])
if autologin = cookies.delete(autologin_cookie_name)
User.current.delete_autologin_token(autologin)
end
User.current.delete_session_token(session[:tk])
self.logged_user = nil
end
end
Expand Down Expand Up @@ -234,7 +214,7 @@ def require_login
if !User.current.logged?
# Extract only the basic url parameters on non-GET requests
if request.get?
url = url_for(params)
url = request.original_url
else
url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
end
Expand Down Expand Up @@ -351,7 +331,10 @@ def find_issue
# Find issues with a single :id param or :ids array param
# Raises a Unauthorized exception if one of the issues is not visible
def find_issues
@issues = Issue.where(:id => (params[:id] || params[:ids])).preload(:project, :status, :tracker, :priority, :author, :assigned_to, :relations_to).to_a
@issues = Issue.
where(:id => (params[:id] || params[:ids])).
preload(:project, :status, :tracker, :priority, :author, :assigned_to, :relations_to, {:custom_values => :custom_field}).
to_a
raise ActiveRecord::RecordNotFound if @issues.empty?
raise Unauthorized unless @issues.all?(&:visible?)
@projects = @issues.collect(&:project).compact.uniq
Expand All @@ -370,8 +353,24 @@ def find_attachments
@attachments = att || []
end

def parse_params_for_bulk_update(params)
attributes = (params || {}).reject {|k,v| v.blank?}
attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
if custom = attributes[:custom_field_values]
custom.reject! {|k,v| v.blank?}
custom.keys.each do |k|
if custom[k].is_a?(Array)
custom[k] << '' if custom[k].delete('__none__')
else
custom[k] = '' if custom[k] == '__none__'
end
end
end
attributes
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
# used as a before_action for actions that do not require any particular permission on the project
def check_project_privacy
if @project && !@project.archived?
if @project.visible?
Expand All @@ -396,8 +395,8 @@ def back_url

def redirect_back_or_default(default, options={})
back_url = params[:back_url].to_s
if back_url.present? && valid_back_url?(back_url)
redirect_to(back_url)
if back_url.present? && valid_url = validate_back_url(back_url)
redirect_to(valid_url)
return
elsif options[:referer]
redirect_to_referer_or default
Expand All @@ -407,8 +406,9 @@ def redirect_back_or_default(default, options={})
false
end

# Returns true if back_url is a valid url for redirection, otherwise false
def valid_back_url?(back_url)
# Returns a validated URL string if back_url is a valid url for redirection,
# otherwise false
def validate_back_url(back_url)
if CGI.unescape(back_url).include?('..')
return false
end
Expand All @@ -419,32 +419,51 @@ def valid_back_url?(back_url)
return false
end

if uri.host.present? && uri.host != request.host
[:scheme, :host, :port].each do |component|
if uri.send(component).present? && uri.send(component) != request.send(component)
return false
end
uri.send(:"#{component}=", nil)
end
# Always ignore basic user:password in the URL
uri.userinfo = nil

path = uri.to_s
# Ensure that the remaining URL starts with a slash, followed by a
# non-slash character or the end
if path !~ %r{\A/([^/]|\z)}
return false
end

if uri.path.match(%r{/(login|account/register)})
if path.match(%r{/(login|account/register|account/lost_password)})
return false
end

if relative_url_root.present? && !uri.path.starts_with?(relative_url_root)
if relative_url_root.present? && !path.starts_with?(relative_url_root)
return false
end

return true
return path
end
private :validate_back_url

def valid_back_url?(back_url)
!!validate_back_url(back_url)
end
private :valid_back_url?

# Redirects to the request referer if present, redirects to args or call block otherwise.
def redirect_to_referer_or(*args, &block)
redirect_to :back
rescue ::ActionController::RedirectBackError
if args.any?
redirect_to *args
elsif block_given?
block.call
if referer = request.headers["Referer"]
redirect_to referer
else
raise "#redirect_to_referer_or takes arguments or a block"
if args.any?
redirect_to *args
elsif block_given?
block.call
else
raise "#redirect_to_referer_or takes arguments or a block"
end
end
end

Expand Down Expand Up @@ -597,7 +616,7 @@ def parse_qvalues(value)

# Returns a string that can be used as filename value in Content-Disposition header
def filename_for_content_disposition(name)
request.env['HTTP_USER_AGENT'] =~ %r{(MSIE|Trident)} ? ERB::Util.url_encode(name) : name
request.env['HTTP_USER_AGENT'] =~ %r{(MSIE|Trident|Edge)} ? ERB::Util.url_encode(name) : name
end

def api_request?
Expand Down Expand Up @@ -626,20 +645,18 @@ def render_attachment_warning_if_needed(obj)
# Rescues an invalid query statement. Just in case...
def query_statement_invalid(exception)
logger.error "Query::StatementInvalid: #{exception.message}" if logger
session.delete(:query)
sort_clear if respond_to?(:sort_clear)
session.delete(:issue_query)
render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator."
end

# Renders a 200 response for successfull updates or deletions via the API
# Renders a 200 response for successful updates or deletions via the API
def render_api_ok
render_api_head :ok
end

# Renders a head API response
def render_api_head(status)
# #head would return a response body with one space
render :text => '', :status => status, :layout => nil
head status
end

# Renders API response on validation failure
Expand Down
96 changes: 76 additions & 20 deletions app/controllers/attachments_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -16,19 +16,24 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

class AttachmentsController < ApplicationController
before_filter :find_attachment, :only => [:show, :download, :thumbnail, :destroy]
before_filter :find_editable_attachments, :only => [:edit, :update]
before_filter :file_readable, :read_authorize, :only => [:show, :download, :thumbnail]
before_filter :delete_authorize, :only => :destroy
before_filter :authorize_global, :only => :upload
before_action :find_attachment, :only => [:show, :download, :thumbnail, :update, :destroy]
before_action :find_editable_attachments, :only => [:edit_all, :update_all]
before_action :file_readable, :read_authorize, :only => [:show, :download, :thumbnail]
before_action :update_authorize, :only => :update
before_action :delete_authorize, :only => :destroy
before_action :authorize_global, :only => :upload

accept_api_auth :show, :download, :thumbnail, :upload
# Disable check for same origin requests for JS files, i.e. attachments with
# MIME type text/javascript.
skip_after_action :verify_same_origin_request, :only => :download

accept_api_auth :show, :download, :thumbnail, :upload, :update, :destroy

def show
respond_to do |format|
format.html {
if @attachment.is_diff?
@diff = File.new(@attachment.diskfile, "rb").read
@diff = File.read(@attachment.diskfile, :mode => "rb")
@diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
@diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
# Save diff type as user preference
Expand All @@ -38,10 +43,12 @@ def show
end
render :action => 'diff'
elsif @attachment.is_text? && @attachment.filesize <= Setting.file_max_size_displayed.to_i.kilobyte
@content = File.new(@attachment.diskfile, "rb").read
@content = File.read(@attachment.diskfile, :mode => "rb")
render :action => 'file'
elsif @attachment.is_image?
render :action => 'image'
else
download
render :action => 'other'
end
}
format.api
Expand All @@ -57,7 +64,7 @@ def download
# images are sent inline
send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename),
:type => detect_content_type(@attachment),
:disposition => (@attachment.image? ? 'inline' : 'attachment')
:disposition => disposition(@attachment)
end
end

Expand All @@ -71,15 +78,15 @@ def thumbnail
end
else
# No thumbnail for the attachment or thumbnail could not be created
render :nothing => true, :status => 404
head 404
end
end

def upload
# Make sure that API users get used to set this content type
# as it won't trigger Rails' automatic parsing of the request body for parameters
unless request.content_type == 'application/octet-stream'
render :nothing => true, :status => 406
head 406
return
end

Expand All @@ -101,17 +108,32 @@ def upload
end
end

def edit
# Edit all the attachments of a container
def edit_all
end

# Update all the attachments of a container
def update_all
if Attachment.update_attachments(@attachments, update_all_params)
redirect_back_or_default home_path
return
end
render :action => 'edit_all'
end

def update
if params[:attachments].is_a?(Hash)
if Attachment.update_attachments(@attachments, params[:attachments])
redirect_back_or_default home_path
return
end
@attachment.safe_attributes = params[:attachment]
saved = @attachment.save

respond_to do |format|
format.api {
if saved
render_api_ok
else
render_validation_errors(@attachment)
end
}
end
render :action => 'edit'
end

def destroy
Expand All @@ -128,6 +150,23 @@ def destroy
respond_to do |format|
format.html { redirect_to_referer_or project_path(@project) }
format.js
format.api { render_api_ok }
end
end

# Returns the menu item that should be selected when viewing an attachment
def current_menu_item
if @attachment
case @attachment.container
when WikiPage
:wiki
when Message
:boards
when Project, Version
:files
else
@attachment.container.class.name.pluralize.downcase.to_sym
end
end
end

Expand Down Expand Up @@ -177,6 +216,10 @@ def read_authorize
@attachment.visible? ? true : deny_access
end

def update_authorize
@attachment.editable? ? true : deny_access
end

def delete_authorize
@attachment.deletable? ? true : deny_access
end
Expand All @@ -188,4 +231,17 @@ def detect_content_type(attachment)
end
content_type.to_s
end

def disposition(attachment)
if attachment.is_pdf?
'inline'
else
'attachment'
end
end

# Returns attachments param for #update_all
def update_all_params
params.permit(:attachments => [:filename, :description]).require(:attachments)
end
end
24 changes: 16 additions & 8 deletions app/controllers/auth_sources_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -17,24 +17,22 @@

class AuthSourcesController < ApplicationController
layout 'admin'
self.main_menu = false
menu_item :ldap_authentication

before_filter :require_admin
before_filter :find_auth_source, :only => [:edit, :update, :test_connection, :destroy]
before_action :require_admin
before_action :build_new_auth_source, :only => [:new, :create]
before_action :find_auth_source, :only => [:edit, :update, :test_connection, :destroy]
require_sudo_mode :update, :destroy

def index
@auth_source_pages, @auth_sources = paginate AuthSource, :per_page => 25
end

def new
klass_name = params[:type] || 'AuthSourceLdap'
@auth_source = AuthSource.new_subclass_instance(klass_name, params[:auth_source])
render_404 unless @auth_source
end

def create
@auth_source = AuthSource.new_subclass_instance(params[:type], params[:auth_source])
if @auth_source.save
flash[:notice] = l(:notice_successful_create)
redirect_to auth_sources_path
Expand All @@ -47,7 +45,8 @@ def edit
end

def update
if @auth_source.update_attributes(params[:auth_source])
@auth_source.safe_attributes = params[:auth_source]
if @auth_source.save
flash[:notice] = l(:notice_successful_update)
redirect_to auth_sources_path
else
Expand Down Expand Up @@ -89,6 +88,15 @@ def autocomplete_for_new_user

private

def build_new_auth_source
@auth_source = AuthSource.new_subclass_instance(params[:type] || 'AuthSourceLdap')
if @auth_source
@auth_source.safe_attributes = params[:auth_source]
else
render_404
end
end

def find_auth_source
@auth_source = AuthSource.find(params[:id])
rescue ActiveRecord::RecordNotFound
Expand Down
15 changes: 12 additions & 3 deletions app/controllers/auto_completes_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -16,17 +16,26 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

class AutoCompletesController < ApplicationController
before_filter :find_project
before_action :find_project

def issues
@issues = []
q = (params[:q] || params[:term]).to_s.strip
status = params[:status].to_s
issue_id = params[:issue_id].to_s
if q.present?
scope = Issue.cross_project_scope(@project, params[:scope]).visible
if status.present?
scope = scope.open(status == 'o')
end
if issue_id.present?
scope = scope.where.not(:id => issue_id.to_i)
end
if q.match(/\A#?(\d+)\z/)
@issues << scope.find_by_id($1.to_i)
end
@issues += scope.where("LOWER(#{Issue.table_name}.subject) LIKE LOWER(?)", "%#{q}%").order("#{Issue.table_name}.id DESC").limit(10).to_a

@issues += scope.like(q).order(:id => :desc).limit(10).to_a
@issues.compact!
end
render :layout => false
Expand Down
32 changes: 21 additions & 11 deletions app/controllers/boards_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -17,15 +17,15 @@

class BoardsController < ApplicationController
default_search_scope :messages
before_filter :find_project_by_project_id, :find_board_if_available, :authorize
before_action :find_project_by_project_id, :find_board_if_available, :authorize
accept_rss_auth :index, :show

helper :sort
include SortHelper
helper :watchers

def index
@boards = @project.boards.preload(:project, :last_message => :author).to_a
@boards = @project.boards.preload(:last_message => :author).to_a
# show the board if there is only one
if @boards.size == 1
@board = @boards.first
Expand All @@ -37,15 +37,14 @@ def show
respond_to do |format|
format.html {
sort_init 'updated_on', 'desc'
sort_update 'created_on' => "#{Message.table_name}.created_on",
sort_update 'created_on' => "#{Message.table_name}.id",
'replies' => "#{Message.table_name}.replies_count",
'updated_on' => "COALESCE(last_replies_messages.created_on, #{Message.table_name}.created_on)"
'updated_on' => "COALESCE(#{Message.table_name}.last_reply_id, #{Message.table_name}.id)"

@topic_count = @board.topics.count
@topic_pages = Paginator.new @topic_count, per_page_option, params['page']
@topics = @board.topics.
reorder("#{Message.table_name}.sticky DESC").
joins("LEFT OUTER JOIN #{Message.table_name} last_replies_messages ON last_replies_messages.id = #{Message.table_name}.last_reply_id").
reorder(:sticky => :desc).
limit(@topic_pages.per_page).
offset(@topic_pages.offset).
order(sort_clause).
Expand All @@ -56,7 +55,7 @@ def show
}
format.atom {
@messages = @board.messages.
reorder('created_on DESC').
reorder(:id => :desc).
includes(:author, :board).
limit(Setting.feeds_limit.to_i).
to_a
Expand Down Expand Up @@ -87,14 +86,25 @@ def edit
def update
@board.safe_attributes = params[:board]
if @board.save
redirect_to_settings_in_projects
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_update)
redirect_to_settings_in_projects
}
format.js { head 200 }
end
else
render :action => 'edit'
respond_to do |format|
format.html { render :action => 'edit' }
format.js { head 422 }
end
end
end

def destroy
@board.destroy
if @board.destroy
flash[:notice] = l(:notice_successful_delete)
end
redirect_to_settings_in_projects
end

Expand Down
10 changes: 4 additions & 6 deletions app/controllers/calendars_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -17,16 +17,14 @@

class CalendarsController < ApplicationController
menu_item :calendar
before_filter :find_optional_project
before_action :find_optional_project

rescue_from Query::StatementInvalid, :with => :query_statement_invalid

helper :issues
helper :projects
helper :queries
include QueriesHelper
helper :sort
include SortHelper

def show
if params[:year] and params[:year].to_i > 1900
Expand All @@ -35,8 +33,8 @@ def show
@month = params[:month].to_i
end
end
@year ||= Date.today.year
@month ||= Date.today.month
@year ||= User.current.today.year
@month ||= User.current.today.month

@calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
retrieve_query
Expand Down
10 changes: 5 additions & 5 deletions app/controllers/comments_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -18,9 +18,9 @@
class CommentsController < ApplicationController
default_search_scope :news
model_object News
before_filter :find_model_object
before_filter :find_project_from_association
before_filter :authorize
before_action :find_model_object
before_action :find_project_from_association
before_action :authorize

def create
raise Unauthorized unless @news.commentable?
Expand All @@ -43,7 +43,7 @@ def destroy
private

# ApplicationController's find_model_object sets it based on the controller
# name so it needs to be overriden and set to @news instead
# name so it needs to be overridden and set to @news instead
def find_model_object
super
@news = @object
Expand Down
35 changes: 15 additions & 20 deletions app/controllers/context_menus_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -19,7 +19,7 @@ class ContextMenusController < ApplicationController
helper :watchers
helper :issues

before_filter :find_issues, :only => :issues
before_action :find_issues, :only => :issues

def issues
if (@issues.size == 1)
Expand All @@ -29,31 +29,23 @@ def issues

@allowed_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)

@can = {:edit => User.current.allowed_to?(:edit_issues, @projects),
@can = {:edit => @issues.all?(&:attributes_editable?),
:log_time => (@project && User.current.allowed_to?(:log_time, @project)),
:copy => User.current.allowed_to?(:copy_issues, @projects) && Issue.allowed_target_projects.any?,
:delete => User.current.allowed_to?(:delete_issues, @projects)
:add_watchers => User.current.allowed_to?(:add_issue_watchers, @projects),
:delete => @issues.all?(&:deletable?)
}
if @project
if @issue
@assignables = @issue.assignable_users
else
@assignables = @project.assignable_users
end
@trackers = @project.trackers
else
#when multiple projects, we only keep the intersection of each set
@assignables = @projects.map(&:assignable_users).reduce(:&)
@trackers = @projects.map(&:trackers).reduce(:&)
end

@assignables = @issues.map(&:assignable_users).reduce(:&)
@trackers = @projects.map {|p| Issue.allowed_target_trackers(p) }.reduce(:&)
@versions = @projects.map {|p| p.shared_versions.open}.reduce(:&)

@priorities = IssuePriority.active.reverse
@back = back_url

@options_by_custom_field = {}
if @can[:edit]
custom_fields = @issues.map(&:editable_custom_fields).reduce(:&).reject(&:multiple?)
custom_fields = @issues.map(&:editable_custom_fields).reduce(:&).reject(&:multiple?).select {|field| field.format.bulk_edit_supported}
custom_fields.each do |field|
values = field.possible_values_options(@projects)
if values.present?
Expand All @@ -67,23 +59,26 @@ def issues
end

def time_entries
@time_entries = TimeEntry.where(:id => params[:ids]).preload(:project).to_a
@time_entries = TimeEntry.where(:id => params[:ids]).
preload(:project => :time_entry_activities).
preload(:user).to_a

(render_404; return) unless @time_entries.present?
if (@time_entries.size == 1)
@time_entry = @time_entries.first
end

@projects = @time_entries.collect(&:project).compact.uniq
@project = @projects.first if @projects.size == 1
@activities = TimeEntryActivity.shared.active
@activities = @projects.map(&:activities).reduce(:&)

edit_allowed = @time_entries.all? {|t| t.editable_by?(User.current)}
@can = {:edit => edit_allowed, :delete => edit_allowed}
@back = back_url

@options_by_custom_field = {}
if @can[:edit]
custom_fields = @time_entries.map(&:editable_custom_fields).reduce(:&).reject(&:multiple?)
custom_fields = @time_entries.map(&:editable_custom_fields).reduce(:&).reject(&:multiple?).select {|field| field.format.bulk_edit_supported}
custom_fields.each do |field|
values = field.possible_values_options(@projects)
if values.present?
Expand Down
84 changes: 84 additions & 0 deletions app/controllers/custom_field_enumerations_controller.rb
@@ -0,0 +1,84 @@
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

class CustomFieldEnumerationsController < ApplicationController
layout 'admin'
self.main_menu = false

before_action :require_admin
before_action :find_custom_field
before_action :find_enumeration, :only => :destroy

helper :custom_fields

def index
@values = @custom_field.enumerations.order(:position)
end

def create
@value = @custom_field.enumerations.build
@value.attributes = enumeration_params
@value.save
respond_to do |format|
format.html { redirect_to custom_field_enumerations_path(@custom_field) }
format.js
end
end

def update_each
saved = CustomFieldEnumeration.update_each(@custom_field, update_each_params)
if saved
flash[:notice] = l(:notice_successful_update)
end
redirect_to :action => 'index'
end

def destroy
reassign_to = @custom_field.enumerations.find_by_id(params[:reassign_to_id])
if reassign_to.nil? && @value.in_use?
@enumerations = @custom_field.enumerations - [@value]
render :action => 'destroy'
return
end
@value.destroy(reassign_to)
redirect_to custom_field_enumerations_path(@custom_field)
end

private

def find_custom_field
@custom_field = CustomField.find(params[:custom_field_id])
rescue ActiveRecord::RecordNotFound
render_404
end

def find_enumeration
@value = @custom_field.enumerations.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end

def enumeration_params
params.require(:custom_field_enumeration).permit(:name, :active, :position)
end

def update_each_params
# params.require(:custom_field_enumerations).permit(:name, :active, :position) does not work here with param like this:
# "custom_field_enumerations":{"0":{"name": ...}, "1":{"name...}}
params.permit(:custom_field_enumerations => [:name, :active, :position]).require(:custom_field_enumerations)
end
end
38 changes: 27 additions & 11 deletions app/controllers/custom_fields_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -17,16 +17,19 @@

class CustomFieldsController < ApplicationController
layout 'admin'
self.main_menu = false

before_filter :require_admin
before_filter :build_new_custom_field, :only => [:new, :create]
before_filter :find_custom_field, :only => [:edit, :update, :destroy]
before_action :require_admin
before_action :build_new_custom_field, :only => [:new, :create]
before_action :find_custom_field, :only => [:edit, :update, :destroy]
accept_api_auth :index

def index
respond_to do |format|
format.html {
@custom_fields_by_type = CustomField.all.group_by {|f| f.class.name }
@custom_fields_projects_count =
IssueCustomField.where(is_for_all: false).joins(:projects).group(:custom_field_id).count
}
format.api {
@custom_fields = CustomField.all
Expand All @@ -43,7 +46,7 @@ def create
if @custom_field.save
flash[:notice] = l(:notice_successful_create)
call_hook(:controller_custom_fields_new_after_save, :params => params, :custom_field => @custom_field)
redirect_to custom_fields_path(:tab => @custom_field.class.name)
redirect_to edit_custom_field_path(@custom_field)
else
render :action => 'new'
end
Expand All @@ -53,18 +56,29 @@ def edit
end

def update
if @custom_field.update_attributes(params[:custom_field])
flash[:notice] = l(:notice_successful_update)
@custom_field.safe_attributes = params[:custom_field]
if @custom_field.save
call_hook(:controller_custom_fields_edit_after_save, :params => params, :custom_field => @custom_field)
redirect_to custom_fields_path(:tab => @custom_field.class.name)
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_update)
redirect_back_or_default edit_custom_field_path(@custom_field)
}
format.js { head 200 }
end
else
render :action => 'edit'
respond_to do |format|
format.html { render :action => 'edit' }
format.js { head 422 }
end
end
end

def destroy
begin
@custom_field.destroy
if @custom_field.destroy
flash[:notice] = l(:notice_successful_delete)
end
rescue
flash[:error] = l(:error_can_not_delete_custom_field)
end
Expand All @@ -74,9 +88,11 @@ def destroy
private

def build_new_custom_field
@custom_field = CustomField.new_subclass_instance(params[:type], params[:custom_field])
@custom_field = CustomField.new_subclass_instance(params[:type])
if @custom_field.nil?
render :action => 'select_type'
else
@custom_field.safe_attributes = params[:custom_field]
end
end

Expand Down
10 changes: 5 additions & 5 deletions app/controllers/documents_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -18,10 +18,10 @@
class DocumentsController < ApplicationController
default_search_scope :documents
model_object Document
before_filter :find_project_by_project_id, :only => [:index, :new, :create]
before_filter :find_model_object, :except => [:index, :new, :create]
before_filter :find_project_from_association, :except => [:index, :new, :create]
before_filter :authorize
before_action :find_project_by_project_id, :only => [:index, :new, :create]
before_action :find_model_object, :except => [:index, :new, :create]
before_action :find_project_from_association, :except => [:index, :new, :create]
before_action :authorize

helper :attachments
helper :custom_fields
Expand Down
12 changes: 5 additions & 7 deletions app/controllers/email_addresses_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -16,8 +16,9 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

class EmailAddressesController < ApplicationController
before_filter :find_user, :require_admin_or_current_user
before_filter :find_email_address, :only => [:update, :destroy]
self.main_menu = false
before_action :find_user, :require_admin_or_current_user
before_action :find_email_address, :only => [:update, :destroy]
require_sudo_mode :create, :update, :destroy

def index
Expand All @@ -29,10 +30,7 @@ def create
saved = false
if @user.email_addresses.count <= Setting.max_additional_emails.to_i
@address = EmailAddress.new(:user => @user, :is_default => false)
attrs = params[:email_address]
if attrs.is_a?(Hash)
@address.address = attrs[:address].to_s
end
@address.safe_attributes = params[:email_address]
saved = @address.save
end

Expand Down
34 changes: 24 additions & 10 deletions app/controllers/enumerations_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -17,11 +17,12 @@

class EnumerationsController < ApplicationController
layout 'admin'
self.main_menu = false

before_filter :require_admin, :except => :index
before_filter :require_admin_or_api_request, :only => :index
before_filter :build_new_enumeration, :only => [:new, :create]
before_filter :find_enumeration, :only => [:edit, :update, :destroy]
before_action :require_admin, :except => :index
before_action :require_admin_or_api_request, :only => :index
before_action :build_new_enumeration, :only => [:new, :create]
before_action :find_enumeration, :only => [:edit, :update, :destroy]
accept_api_auth :index

helper :custom_fields
Expand Down Expand Up @@ -56,11 +57,19 @@ def edit
end

def update
if @enumeration.update_attributes(params[:enumeration])
flash[:notice] = l(:notice_successful_update)
redirect_to enumerations_path
if @enumeration.update_attributes(enumeration_params)
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_update)
redirect_to enumerations_path
}
format.js { head 200 }
end
else
render :action => 'edit'
respond_to do |format|
format.html { render :action => 'edit' }
format.js { head 422 }
end
end
end

Expand All @@ -82,7 +91,7 @@ def destroy

def build_new_enumeration
class_name = params[:enumeration] && params[:enumeration][:type] || params[:type]
@enumeration = Enumeration.new_subclass_instance(class_name, params[:enumeration])
@enumeration = Enumeration.new_subclass_instance(class_name, enumeration_params)
if @enumeration.nil?
render_404
end
Expand All @@ -93,4 +102,9 @@ def find_enumeration
rescue ActiveRecord::RecordNotFound
render_404
end

def enumeration_params
# can't require enumeration on #new action
params.permit(:enumeration => [:name, :active, :is_default])[:enumeration]
end
end
36 changes: 25 additions & 11 deletions app/controllers/files_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -18,9 +18,11 @@
class FilesController < ApplicationController
menu_item :files

before_filter :find_project_by_project_id
before_filter :authorize
before_action :find_project_by_project_id
before_action :authorize
accept_api_auth :index, :create

helper :attachments
helper :sort
include SortHelper

Expand All @@ -35,28 +37,40 @@ def index
references(:attachments).reorder(sort_clause).find(@project.id)]
@containers += @project.versions.includes(:attachments).
references(:attachments).reorder(sort_clause).to_a.sort.reverse
render :layout => !request.xhr?
respond_to do |format|
format.html { render :layout => !request.xhr? }
format.api
end
end

def new
@versions = @project.versions.sort
end

def create
container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id]))
attachments = Attachment.attach_files(container, params[:attachments])
version_id = params[:version_id] || (params[:file] && params[:file][:version_id])
container = version_id.blank? ? @project : @project.versions.find_by_id(version_id)
attachments = Attachment.attach_files(container, (params[:attachments] || (params[:file] && params[:file][:token] && params)))
render_attachment_warning_if_needed(container)

if attachments[:files].present?
if Setting.notified_events.include?('file_added')
Mailer.attachments_added(attachments[:files]).deliver
end
flash[:notice] = l(:label_file_added)
redirect_to project_files_path(@project)
respond_to do |format|
format.html {
flash[:notice] = l(:label_file_added)
redirect_to project_files_path(@project) }
format.api { render_api_ok }
end
else
flash.now[:error] = l(:label_attachment) + " " + l('activerecord.errors.messages.invalid')
new
render :action => 'new'
respond_to do |format|
format.html {
flash.now[:error] = l(:label_attachment) + " " + l('activerecord.errors.messages.invalid')
new
render :action => 'new' }
format.api { render :status => :bad_request }
end
end
end
end
6 changes: 2 additions & 4 deletions app/controllers/gantts_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -17,7 +17,7 @@

class GanttsController < ApplicationController
menu_item :gantt
before_filter :find_optional_project
before_action :find_optional_project

rescue_from Query::StatementInvalid, :with => :query_statement_invalid

Expand All @@ -26,8 +26,6 @@ class GanttsController < ApplicationController
helper :projects
helper :queries
include QueriesHelper
helper :sort
include SortHelper
include Redmine::Export::PDF

def show
Expand Down
18 changes: 12 additions & 6 deletions app/controllers/groups_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -17,9 +17,10 @@

class GroupsController < ApplicationController
layout 'admin'
self.main_menu = false

before_filter :require_admin
before_filter :find_group, :except => [:index, :new, :create]
before_action :require_admin
before_action :find_group, :except => [:index, :new, :create]
accept_api_auth :index, :show, :create, :update, :destroy, :add_users, :remove_user

require_sudo_mode :add_users, :remove_user, :create, :update, :destroy, :edit_membership, :destroy_membership
Expand All @@ -30,7 +31,12 @@ class GroupsController < ApplicationController
def index
respond_to do |format|
format.html {
@groups = Group.sorted.to_a
scope = Group.sorted
scope = scope.like(params[:name]) if params[:name].present?

@group_count = scope.count
@group_pages = Paginator.new @group_count, per_page_option, params['page']
@groups = scope.limit(@group_pages.per_page).offset(@group_pages.offset).to_a
@user_count_by_group_id = user_count_by_group_id
}
format.api {
Expand Down Expand Up @@ -79,7 +85,7 @@ def update
respond_to do |format|
if @group.save
flash[:notice] = l(:notice_successful_update)
format.html { redirect_to(groups_path) }
format.html { redirect_to_referer_or(groups_path) }
format.api { render_api_ok }
else
format.html { render :action => "edit" }
Expand All @@ -92,7 +98,7 @@ def destroy
@group.destroy

respond_to do |format|
format.html { redirect_to(groups_path) }
format.html { redirect_to_referer_or(groups_path) }
format.api { render_api_ok }
end
end
Expand Down
122 changes: 122 additions & 0 deletions app/controllers/imports_controller.rb
@@ -0,0 +1,122 @@
# Redmine - project management software
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

require 'csv'

class ImportsController < ApplicationController
menu_item :issues

before_action :find_import, :only => [:show, :settings, :mapping, :run]
before_action :authorize_global

helper :issues
helper :queries

def new
end

def create
@import = IssueImport.new
@import.user = User.current
@import.file = params[:file]
@import.set_default_settings

if @import.save
redirect_to import_settings_path(@import)
else
render :action => 'new'
end
end

def show
end

def settings
if request.post? && @import.parse_file
redirect_to import_mapping_path(@import)
end

rescue CSV::MalformedCSVError => e
flash.now[:error] = l(:error_invalid_csv_file_or_settings)
rescue ArgumentError, EncodingError => e
flash.now[:error] = l(:error_invalid_file_encoding, :encoding => ERB::Util.h(@import.settings['encoding']))
rescue SystemCallError => e
flash.now[:error] = l(:error_can_not_read_import_file)
end

def mapping
@custom_fields = @import.mappable_custom_fields

if request.post?
respond_to do |format|
format.html {
if params[:previous]
redirect_to import_settings_path(@import)
else
redirect_to import_run_path(@import)
end
}
format.js # updates mapping form on project or tracker change
end
end
end

def run
if request.post?
@current = @import.run(
:max_items => max_items_per_request,
:max_time => 10.seconds
)
respond_to do |format|
format.html {
if @import.finished?
redirect_to import_path(@import)
else
redirect_to import_run_path(@import)
end
}
format.js
end
end
end

private

def find_import
@import = Import.where(:user_id => User.current.id, :filename => params[:id]).first
if @import.nil?
render_404
return
elsif @import.finished? && action_name != 'show'
redirect_to import_path(@import)
return
end
update_from_params if request.post?
end

def update_from_params
if params[:import_settings].is_a?(Hash)
@import.settings ||= {}
@import.settings.merge!(params[:import_settings])
@import.save!
end
end

def max_items_per_request
5
end
end
10 changes: 5 additions & 5 deletions app/controllers/issue_categories_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -18,10 +18,10 @@
class IssueCategoriesController < ApplicationController
menu_item :settings
model_object IssueCategory
before_filter :find_model_object, :except => [:index, :new, :create]
before_filter :find_project_from_association, :except => [:index, :new, :create]
before_filter :find_project_by_project_id, :only => [:index, :new, :create]
before_filter :authorize
before_action :find_model_object, :except => [:index, :new, :create]
before_action :find_project_from_association, :except => [:index, :new, :create]
before_action :find_project_by_project_id, :only => [:index, :new, :create]
before_action :authorize
accept_api_auth :index, :show, :create, :update, :destroy

def index
Expand Down
18 changes: 9 additions & 9 deletions app/controllers/issue_relations_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -16,16 +16,18 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

class IssueRelationsController < ApplicationController
before_filter :find_issue, :authorize, :only => [:index, :create]
before_filter :find_relation, :only => [:show, :destroy]
helper :issues

before_action :find_issue, :authorize, :only => [:index, :create]
before_action :find_relation, :only => [:show, :destroy]

accept_api_auth :index, :show, :create, :destroy

def index
@relations = @issue.relations

respond_to do |format|
format.html { render :nothing => true }
format.html { head 200 }
format.api
end
end
Expand All @@ -34,17 +36,15 @@ def show
raise Unauthorized unless @relation.visible?

respond_to do |format|
format.html { render :nothing => true }
format.html { head 200 }
format.api
end
end

def create
@relation = IssueRelation.new(params[:relation])
@relation = IssueRelation.new
@relation.issue_from = @issue
if params[:relation] && m = params[:relation][:issue_to_id].to_s.strip.match(/^#?(\d+)$/)
@relation.issue_to = Issue.visible.find_by_id(m[1].to_i)
end
@relation.safe_attributes = params[:relation]
@relation.init_journals(User.current)
saved = @relation.save

Expand Down
37 changes: 22 additions & 15 deletions app/controllers/issue_statuses_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -17,20 +17,17 @@

class IssueStatusesController < ApplicationController
layout 'admin'
self.main_menu = false

before_filter :require_admin, :except => :index
before_filter :require_admin_or_api_request, :only => :index
before_action :require_admin, :except => :index
before_action :require_admin_or_api_request, :only => :index
accept_api_auth :index

def index
@issue_statuses = IssueStatus.sorted.to_a
respond_to do |format|
format.html {
@issue_status_pages, @issue_statuses = paginate IssueStatus.sorted, :per_page => 25
render :action => "index", :layout => false if request.xhr?
}
format.api {
@issue_statuses = IssueStatus.order('position').to_a
}
format.html { render :layout => false if request.xhr? }
format.api
end
end

Expand All @@ -39,7 +36,8 @@ def new
end

def create
@issue_status = IssueStatus.new(params[:issue_status])
@issue_status = IssueStatus.new
@issue_status.safe_attributes = params[:issue_status]
if @issue_status.save
flash[:notice] = l(:notice_successful_create)
redirect_to issue_statuses_path
Expand All @@ -54,11 +52,20 @@ def edit

def update
@issue_status = IssueStatus.find(params[:id])
if @issue_status.update_attributes(params[:issue_status])
flash[:notice] = l(:notice_successful_update)
redirect_to issue_statuses_path(:page => params[:page])
@issue_status.safe_attributes = params[:issue_status]
if @issue_status.save
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_update)
redirect_to issue_statuses_path(:page => params[:page])
}
format.js { head 200 }
end
else
render :action => 'edit'
respond_to do |format|
format.html { render :action => 'edit' }
format.js { head 422 }
end
end
end

Expand Down
307 changes: 193 additions & 114 deletions app/controllers/issues_controller.rb

Large diffs are not rendered by default.

42 changes: 20 additions & 22 deletions app/controllers/journals_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -16,24 +16,20 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

class JournalsController < ApplicationController
before_filter :find_journal, :only => [:edit, :diff]
before_filter :find_issue, :only => [:new]
before_filter :find_optional_project, :only => [:index]
before_filter :authorize, :only => [:new, :edit, :diff]
before_action :find_journal, :only => [:edit, :update, :diff]
before_action :find_issue, :only => [:new]
before_action :find_optional_project, :only => [:index]
before_action :authorize, :only => [:new, :edit, :update, :diff]
accept_rss_auth :index
menu_item :issues

helper :issues
helper :custom_fields
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)
Expand Down Expand Up @@ -82,19 +78,21 @@ def new

def edit
(render_403; return false) unless @journal.editable_by?(User.current)
if request.post?
@journal.update_attributes(:notes => params[:notes]) if params[:notes]
@journal.destroy if @journal.details.empty? && @journal.notes.blank?
call_hook(:controller_journals_edit_post, { :journal => @journal, :params => params})
respond_to do |format|
format.html { redirect_to issue_path(@journal.journalized) }
format.js { render :action => 'update' }
end
else
respond_to do |format|
# TODO: implement non-JS journal update
format.js
end
respond_to do |format|
# TODO: implement non-JS journal update
format.js
end
end

def update
(render_403; return false) unless @journal.editable_by?(User.current)
@journal.safe_attributes = params[:journal]
@journal.save
@journal.destroy if @journal.details.empty? && @journal.notes.blank?
call_hook(:controller_journals_edit_post, { :journal => @journal, :params => params})
respond_to do |format|
format.html { redirect_to issue_path(@journal.journalized) }
format.js
end
end

Expand Down
10 changes: 5 additions & 5 deletions app/controllers/mail_handler_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -16,7 +16,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

class MailHandlerController < ActionController::Base
before_filter :check_credential
before_action :check_credential

# Displays the email submission form
def new
Expand All @@ -27,9 +27,9 @@ def index
options = params.dup
email = options.delete(:email)
if MailHandler.receive(email, options)
render :nothing => true, :status => :created
head :created
else
render :nothing => true, :status => :unprocessable_entity
head :unprocessable_entity
end
end

Expand All @@ -38,7 +38,7 @@ def index
def check_credential
User.current = nil
unless Setting.mail_handler_api_enabled? && params[:key].to_s == Setting.mail_handler_api_key
render :text => 'Access denied. Incoming emails WS is disabled or key is invalid.', :status => 403
render :plain => 'Access denied. Incoming emails WS is disabled or key is invalid.', :status => 403
end
end
end
16 changes: 10 additions & 6 deletions app/controllers/members_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -17,16 +17,16 @@

class MembersController < ApplicationController
model_object Member
before_filter :find_model_object, :except => [:index, :new, :create, :autocomplete]
before_filter :find_project_from_association, :except => [:index, :new, :create, :autocomplete]
before_filter :find_project_by_project_id, :only => [:index, :new, :create, :autocomplete]
before_filter :authorize
before_action :find_model_object, :except => [:index, :new, :create, :autocomplete]
before_action :find_project_from_association, :except => [:index, :new, :create, :autocomplete]
before_action :find_project_by_project_id, :only => [:index, :new, :create, :autocomplete]
before_action :authorize
accept_api_auth :index, :show, :create, :update, :destroy

require_sudo_mode :create, :update, :destroy

def index
scope = @project.memberships.active
scope = @project.memberships
@offset, @limit = api_offset_and_limit
@member_count = scope.count
@member_pages = Paginator.new @member_count, @limit, params['page']
Expand Down Expand Up @@ -80,6 +80,10 @@ def create
end
end

def edit
@roles = Role.givable.to_a
end

def update
if params[:membership]
@member.set_editable_role_ids(params[:membership][:role_ids])
Expand Down
10 changes: 5 additions & 5 deletions app/controllers/messages_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -18,10 +18,10 @@
class MessagesController < ApplicationController
menu_item :boards
default_search_scope :messages
before_filter :find_board, :only => [:new, :preview]
before_filter :find_attachments, :only => [:preview]
before_filter :find_message, :except => [:new, :preview]
before_filter :authorize, :except => [:preview, :edit, :destroy]
before_action :find_board, :only => [:new, :preview]
before_action :find_attachments, :only => [:preview]
before_action :find_message, :except => [:new, :preview]
before_action :authorize, :except => [:preview, :edit, :destroy]

helper :boards
helper :watchers
Expand Down
101 changes: 38 additions & 63 deletions app/controllers/my_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -16,29 +16,18 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

class MyController < ApplicationController
before_filter :require_login
self.main_menu = false
before_action :require_login
# let user change user's password when user has to
skip_before_filter :check_password_change, :only => :password
skip_before_action :check_password_change, :only => :password

require_sudo_mode :account, only: :post
require_sudo_mode :reset_rss_key, :reset_api_key, :show_api_key, :destroy

helper :issues
helper :users
helper :custom_fields

BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_issues,
'issuesreportedbyme' => :label_reported_issues,
'issueswatched' => :label_watched_issues,
'news' => :label_news_latest,
'calendar' => :label_calendar,
'documents' => :label_document_plural,
'timelog' => :label_spent_time
}.merge(Redmine::Views::MyPage::Block.additional_blocks).freeze

DEFAULT_LAYOUT = { 'left' => ['issuesassignedtome'],
'right' => ['issuesreportedbyme']
}.freeze
helper :queries

def index
page
Expand All @@ -48,16 +37,17 @@ def index
# Show user's page
def page
@user = User.current
@blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT
@groups = @user.pref.my_page_groups
@blocks = @user.pref.my_page_layout
end

# Edit user's account
def account
@user = User.current
@pref = @user.pref
if request.post?
@user.safe_attributes = params[:user] if params[:user]
@user.pref.attributes = params[:pref] if params[:pref]
@user.safe_attributes = params[:user]
@user.pref.safe_attributes = params[:pref]
if @user.save
@user.pref.save
set_language_if_valid @user.language
Expand Down Expand Up @@ -103,9 +93,9 @@ def password
@user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
@user.must_change_passwd = false
if @user.save
# Reset the session creation time to not log out this session on next
# request due to ApplicationController#force_logout_if_password_changed
session[:ctime] = User.current.passwd_changed_on.utc.to_i
# The session token was destroyed by the password change, generate a new one
session[:tk] = @user.generate_session_token
Mailer.password_updated(@user)
flash[:notice] = l(:notice_account_password_updated)
redirect_to my_account_path
end
Expand Down Expand Up @@ -143,69 +133,54 @@ def reset_api_key
redirect_to my_account_path
end

# User's page layout configuration
def page_layout
def update_page
@user = User.current
@blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup
@block_options = []
BLOCKS.each do |k, v|
unless @blocks.values.flatten.include?(k)
@block_options << [l("my.blocks.#{v}", :default => [v, v.to_s.humanize]), k.dasherize]
end
block_settings = params[:settings] || {}

block_settings.each do |block, settings|
@user.pref.update_block_settings(block, settings)
end
@user.pref.save
@updated_blocks = block_settings.keys
end

# Add a block to user's page
# The block is added on top of the page
# params[:block] : id of the block to add
def add_block
block = params[:block].to_s.underscore
if block.present? && BLOCKS.key?(block)
@user = User.current
layout = @user.pref[:my_page_layout] || {}
# remove if already present in a group
%w(top left right).each {|f| (layout[f] ||= []).delete block }
# add it on top
layout['top'].unshift block
@user.pref[:my_page_layout] = layout
@user = User.current
@block = params[:block]
if @user.pref.add_block @block
@user.pref.save
respond_to do |format|
format.html { redirect_to my_page_path }
format.js
end
else
render_error :status => 422
end
redirect_to my_page_layout_path
end

# Remove a block to user's page
# params[:block] : id of the block to remove
def remove_block
block = params[:block].to_s.underscore
@user = User.current
# remove block in all groups
layout = @user.pref[:my_page_layout] || {}
%w(top left right).each {|f| (layout[f] ||= []).delete block }
@user.pref[:my_page_layout] = layout
@block = params[:block]
@user.pref.remove_block @block
@user.pref.save
redirect_to my_page_layout_path
respond_to do |format|
format.html { redirect_to my_page_path }
format.js
end
end

# Change blocks order on user's page
# params[:group] : group to order (top, left or right)
# params[:list-(top|left|right)] : array of block ids of the group
# params[:blocks] : array of block ids of the group
def order_blocks
group = params[:group]
@user = User.current
if group.is_a?(String)
group_items = (params["blocks"] || []).collect(&:underscore)
group_items.each {|s| s.sub!(/^block_/, '')}
if group_items and group_items.is_a? Array
layout = @user.pref[:my_page_layout] || {}
# remove group blocks if they are presents in other groups
%w(top left right).each {|f|
layout[f] = (layout[f] || []) - group_items
}
layout[group] = group_items
@user.pref[:my_page_layout] = layout
@user.pref.save
end
end
render :nothing => true
@user.pref.order_blocks params[:group], params[:blocks]
@user.pref.save
head 200
end
end
22 changes: 6 additions & 16 deletions app/controllers/news_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -18,11 +18,11 @@
class NewsController < ApplicationController
default_search_scope :news
model_object News
before_filter :find_model_object, :except => [:new, :create, :index]
before_filter :find_project_from_association, :except => [:new, :create, :index]
before_filter :find_project_by_project_id, :only => [:new, :create]
before_filter :authorize, :except => [:index]
before_filter :find_optional_project, :only => :index
before_action :find_model_object, :except => [:new, :create, :index]
before_action :find_project_from_association, :except => [:new, :create, :index]
before_action :find_project_by_project_id, :only => [:new, :create]
before_action :authorize, :except => [:index]
before_action :find_optional_project, :only => :index
accept_rss_auth :index
accept_api_auth :index

Expand Down Expand Up @@ -98,14 +98,4 @@ def destroy
@news.destroy
redirect_to project_news_index_path(@project)
end

private

def find_optional_project
return true unless params[:project_id]
@project = Project.find(params[:project_id])
authorize
rescue ActiveRecord::RecordNotFound
render_404
end
end
8 changes: 4 additions & 4 deletions app/controllers/previews_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -16,7 +16,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

class PreviewsController < ApplicationController
before_filter :find_project, :find_attachments
before_action :find_project, :find_attachments

def issue
@issue = Issue.visible.find_by_id(params[:id]) unless params[:id].blank?
Expand All @@ -25,8 +25,8 @@ def issue
if @description && @description.gsub(/(\r?\n|\n\r?)/, "\n") == @issue.description.to_s.gsub(/(\r?\n|\n\r?)/, "\n")
@description = nil
end
# params[:notes] is useful for preview of notes in issue history
@notes = params[:notes] || (params[:issue] ? params[:issue][:notes] : nil)
@notes = params[:journal] ? params[:journal][:notes] : nil
@notes ||= params[:issue] ? params[:issue][:notes] : nil
else
@description = (params[:issue] ? params[:issue][:description] : nil)
end
Expand Down
15 changes: 10 additions & 5 deletions app/controllers/principal_memberships_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -17,10 +17,11 @@

class PrincipalMembershipsController < ApplicationController
layout 'admin'
self.main_menu = false

before_filter :require_admin
before_filter :find_principal, :only => [:new, :create]
before_filter :find_membership, :only => [:update, :destroy]
before_action :require_admin
before_action :find_principal, :only => [:new, :create]
before_action :find_membership, :only => [:edit, :update, :destroy]

def new
@projects = Project.active.all
Expand All @@ -39,8 +40,12 @@ def create
end
end

def edit
@roles = Role.givable.to_a
end

def update
@membership.attributes = params[:membership]
@membership.attributes = params.require(:membership).permit(:role_ids => [])
@membership.save
respond_to do |format|
format.html { redirect_to_principal @principal }
Expand Down
12 changes: 7 additions & 5 deletions app/controllers/project_enumerations_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -16,17 +16,19 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

class ProjectEnumerationsController < ApplicationController
before_filter :find_project_by_project_id
before_filter :authorize
before_action :find_project_by_project_id
before_action :authorize

def update
if params[:enumerations]
Project.transaction do
saved = Project.transaction do
params[:enumerations].each do |id, activity|
@project.update_or_create_time_entry_activity(id, activity)
end
end
flash[:notice] = l(:notice_successful_update)
if saved
flash[:notice] = l(:notice_successful_update)
end
end

redirect_to settings_project_path(@project, :tab => 'activities')
Expand Down
44 changes: 30 additions & 14 deletions app/controllers/projects_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -18,21 +18,16 @@
class ProjectsController < ApplicationController
menu_item :overview
menu_item :settings, :only => :settings
menu_item :projects, :only => [:index, :new, :copy, :create]

before_filter :find_project, :except => [ :index, :list, :new, :create, :copy ]
before_filter :authorize, :except => [ :index, :list, :new, :create, :copy, :archive, :unarchive, :destroy]
before_filter :authorize_global, :only => [:new, :create]
before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ]
before_action :find_project, :except => [ :index, :autocomplete, :list, :new, :create, :copy ]
before_action :authorize, :except => [ :index, :autocomplete, :list, :new, :create, :copy, :archive, :unarchive, :destroy]
before_action :authorize_global, :only => [:new, :create]
before_action :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ]
accept_rss_auth :index
accept_api_auth :index, :show, :create, :update, :destroy
require_sudo_mode :destroy

after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller|
if controller.request.post?
controller.send :expire_action, :controller => 'welcome', :action => 'robots'
end
end

helper :custom_fields
helper :issues
helper :queries
Expand All @@ -41,6 +36,11 @@ class ProjectsController < ApplicationController

# Lists visible projects
def index
# try to redirect to the requested menu item
if params[:jump] && redirect_to_menu_item(params[:jump])
return
end

scope = Project.visible.sorted

respond_to do |format|
Expand All @@ -62,6 +62,18 @@ def index
end
end

def autocomplete
respond_to do |format|
format.js {
if params[:q].present?
@projects = Project.visible.like(params[:q]).to_a
else
@projects = User.current.projects.to_a
end
}
end
end

def new
@issue_custom_fields = IssueCustomField.sorted.to_a
@trackers = Tracker.sorted.to_a
Expand Down Expand Up @@ -137,7 +149,7 @@ def show
@users_by_role = @project.users_by_role
@subprojects = @project.children.visible.to_a
@news = @project.news.limit(5).includes(:author, :project).reorder("#{News.table_name}.created_on DESC").to_a
@trackers = @project.rolled_up_trackers
@trackers = @project.rolled_up_trackers.visible

cond = @project.project_condition(Setting.display_subprojects_issues?)

Expand All @@ -161,6 +173,10 @@ def settings
@issue_category ||= IssueCategory.new
@member ||= @project.members.new
@trackers = Tracker.sorted.to_a

@version_status = params[:version_status] || 'open'
@version_name = params[:version_name]
@versions = @project.shared_versions.status(@version_status).like(@version_name)
@wiki ||= @project.wiki || Wiki.new(:project => @project)
end

Expand Down Expand Up @@ -198,14 +214,14 @@ def archive
unless @project.archive
flash[:error] = l(:error_can_not_archive_project)
end
redirect_to admin_projects_path(:status => params[:status])
redirect_to_referer_or admin_projects_path(:status => params[:status])
end

def unarchive
unless @project.active?
@project.unarchive
end
redirect_to admin_projects_path(:status => params[:status])
redirect_to_referer_or admin_projects_path(:status => params[:status])
end

def close
Expand Down
90 changes: 67 additions & 23 deletions app/controllers/queries_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -17,8 +17,8 @@

class QueriesController < ApplicationController
menu_item :issues
before_filter :find_query, :except => [:new, :create, :index]
before_filter :find_optional_project, :only => [:new, :create]
before_action :find_query, :only => [:edit, :update, :destroy]
before_action :find_optional_project, :only => [:new, :create]

accept_api_auth :index

Expand All @@ -31,9 +31,10 @@ def index
else
@limit = per_page_option
end
@query_count = IssueQuery.visible.count
scope = query_class.visible
@query_count = scope.count
@query_pages = Paginator.new @query_count, @limit, params['page']
@queries = IssueQuery.visible.
@queries = scope.
order("#{Query.table_name}.name").
limit(@limit).
offset(@offset).
Expand All @@ -45,24 +46,21 @@ def index
end

def new
@query = IssueQuery.new
@query = query_class.new
@query.user = User.current
@query.project = @project
@query.visibility = IssueQuery::VISIBILITY_PRIVATE unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
@query.build_from_params(params)
end

def create
@query = IssueQuery.new(params[:query])
@query = query_class.new
@query.user = User.current
@query.project = params[:query_is_for_all] ? nil : @project
@query.visibility = IssueQuery::VISIBILITY_PRIVATE unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
@query.build_from_params(params)
@query.column_names = nil if params[:default_columns]
@query.project = @project
update_query_from_params

if @query.save
flash[:notice] = l(:notice_successful_create)
redirect_to_issues(:query_id => @query)
redirect_to_items(:query_id => @query)
else
render :action => 'new', :layout => !request.xhr?
end
Expand All @@ -72,28 +70,44 @@ def edit
end

def update
@query.attributes = params[:query]
@query.project = nil if params[:query_is_for_all]
@query.visibility = IssueQuery::VISIBILITY_PRIVATE unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
@query.build_from_params(params)
@query.column_names = nil if params[:default_columns]
update_query_from_params

if @query.save
flash[:notice] = l(:notice_successful_update)
redirect_to_issues(:query_id => @query)
redirect_to_items(:query_id => @query)
else
render :action => 'edit'
end
end

def destroy
@query.destroy
redirect_to_issues(:set_filter => 1)
redirect_to_items(:set_filter => 1)
end

# Returns the values for a query filter
def filter
q = query_class.new
if params[:project_id].present?
q.project = Project.find(params[:project_id])
end

unless User.current.allowed_to?(q.class.view_permission, q.project, :global => true)
raise Unauthorized
end

filter = q.available_filters[params[:name].to_s]
values = filter ? filter.values : []

render :json => values
rescue ActiveRecord::RecordNotFound
render_404
end

private
private

def find_query
@query = IssueQuery.find(params[:id])
@query = Query.find(params[:id])
@project = @query.project
render_403 unless @query.editable_by?(User.current)
rescue ActiveRecord::RecordNotFound
Expand All @@ -107,7 +121,27 @@ def find_optional_project
render_404
end

def redirect_to_issues(options)
def update_query_from_params
@query.project = params[:query_is_for_all] ? nil : @project
@query.build_from_params(params)
@query.column_names = nil if params[:default_columns]
@query.sort_criteria = params[:query] && params[:query][:sort_criteria]
@query.name = params[:query] && params[:query][:name]
if User.current.allowed_to?(:manage_public_queries, @query.project) || User.current.admin?
@query.visibility = (params[:query] && params[:query][:visibility]) || Query::VISIBILITY_PRIVATE
@query.role_ids = params[:query] && params[:query][:role_ids]
else
@query.visibility = Query::VISIBILITY_PRIVATE
end
@query
end

def redirect_to_items(options)
method = "redirect_to_#{@query.class.name.underscore}"
send method, options
end

def redirect_to_issue_query(options)
if params[:gantt]
if @project
redirect_to project_gantt_path(@project, options)
Expand All @@ -118,4 +152,14 @@ def redirect_to_issues(options)
redirect_to _project_issues_path(@project, options)
end
end

def redirect_to_time_entry_query(options)
redirect_to _time_entries_path(@project, nil, options)
end

# Returns the Query subclass, IssueQuery by default
# for compatibility with previous behaviour
def query_class
Query.get_subclass(params[:type] || 'IssueQuery')
end
end
18 changes: 6 additions & 12 deletions app/controllers/reports_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -17,10 +17,10 @@

class ReportsController < ApplicationController
menu_item :issues
before_filter :find_project, :authorize, :find_issue_statuses
before_action :find_project, :authorize, :find_issue_statuses

def issue_report
@trackers = @project.trackers
@trackers = @project.rolled_up_trackers(false).visible
@versions = @project.shared_versions.sort
@priorities = IssuePriority.all.reverse
@categories = @project.issue_categories
Expand All @@ -43,7 +43,7 @@ def issue_report_details
case params[:detail]
when "tracker"
@field = "tracker_id"
@rows = @project.trackers
@rows = @project.rolled_up_trackers(false).visible
@data = Issue.by_tracker(@project)
@report_title = l(:field_tracker)
when "version"
Expand Down Expand Up @@ -76,14 +76,8 @@ def issue_report_details
@rows = @project.descendants.visible
@data = Issue.by_subproject(@project) || []
@report_title = l(:field_subproject)
end

respond_to do |format|
if @field
format.html {}
else
format.html { redirect_to :action => 'issue_report', :id => @project }
end
else
render_404
end
end

Expand Down
122 changes: 61 additions & 61 deletions app/controllers/repositories_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand Down Expand Up @@ -28,31 +28,22 @@ class RepositoriesController < ApplicationController
menu_item :settings, :only => [:new, :create, :edit, :update, :destroy, :committers]
default_search_scope :changesets

before_filter :find_project_by_project_id, :only => [:new, :create]
before_filter :find_repository, :only => [:edit, :update, :destroy, :committers]
before_filter :find_project_repository, :except => [:new, :create, :edit, :update, :destroy, :committers]
before_filter :find_changeset, :only => [:revision, :add_related_issue, :remove_related_issue]
before_filter :authorize
before_action :find_project_by_project_id, :only => [:new, :create]
before_action :build_new_repository_from_params, :only => [:new, :create]
before_action :find_repository, :only => [:edit, :update, :destroy, :committers]
before_action :find_project_repository, :except => [:new, :create, :edit, :update, :destroy, :committers]
before_action :find_changeset, :only => [:revision, :add_related_issue, :remove_related_issue]
before_action :authorize
accept_rss_auth :revisions

rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed

def new
scm = params[:repository_scm] || (Redmine::Scm::Base.all & Setting.enabled_scm).first
@repository = Repository.factory(scm)
@repository.is_default = @project.repository.nil?
@repository.project = @project
end

def create
attrs = pickup_extra_info
@repository = Repository.factory(params[:repository_scm])
@repository.safe_attributes = params[:repository]
if attrs[:attrs_extra].keys.any?
@repository.merge_extra_info(attrs[:attrs_extra])
end
@repository.project = @project
if request.post? && @repository.save
if @repository.save
redirect_to settings_project_path(@project, :tab => 'repositories')
else
render :action => 'new'
Expand All @@ -63,41 +54,22 @@ def edit
end

def update
attrs = pickup_extra_info
@repository.safe_attributes = attrs[:attrs]
if attrs[:attrs_extra].keys.any?
@repository.merge_extra_info(attrs[:attrs_extra])
end
@repository.project = @project
@repository.safe_attributes = params[:repository]
if @repository.save
redirect_to settings_project_path(@project, :tab => 'repositories')
else
render :action => 'edit'
end
end

def pickup_extra_info
p = {}
p_extra = {}
params[:repository].each do |k, v|
if k =~ /^extra_/
p_extra[k] = v
else
p[k] = v
end
end
{:attrs => p, :attrs_extra => p_extra}
end
private :pickup_extra_info

def committers
@committers = @repository.committers
@users = @project.users.to_a
additional_user_ids = @committers.collect(&:last).collect(&:to_i) - @users.collect(&:id)
@users += User.where(:id => additional_user_ids).to_a unless additional_user_ids.empty?
@users.compact!
@users.sort!
if request.post? && params[:committers].is_a?(Hash)
if request.post? && params[:committers].present?
# Build a hash with repository usernames as keys and corresponding user ids as values
@repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h}
flash[:notice] = l(:notice_successful_update)
Expand All @@ -116,7 +88,7 @@ def show
@entries = @repository.entries(@path, @rev)
@changeset = @repository.find_changeset_by_name(@rev)
if request.xhr?
@entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
@entries ? render(:partial => 'dir_list_content') : head(200)
else
(show_error_not_found; return) unless @entries
@changesets = @repository.latest_changesets(@path, @rev)
Expand Down Expand Up @@ -168,22 +140,26 @@ def entry_and_raw(is_raw)
# If the entry is a dir, show the browser
(show; return) if @entry.is_dir?

@content = @repository.cat(@path, @rev)
(show_error_not_found; return) unless @content
if is_raw ||
(@content.size && @content.size > Setting.file_max_size_displayed.to_i.kilobyte) ||
! is_entry_text_data?(@content, @path)
if is_raw
# Force the download
send_opt = { :filename => filename_for_content_disposition(@path.split('/').last) }
send_type = Redmine::MimeType.of(@path)
send_opt[:type] = send_type.to_s if send_type
send_opt[:disposition] = (Redmine::MimeType.is_type?('image', @path) && !is_raw ? 'inline' : 'attachment')
send_data @content, send_opt
send_opt[:disposition] = disposition(@path)
send_data @repository.cat(@path, @rev), send_opt
else
# Prevent empty lines when displaying a file with Windows style eol
# TODO: UTF-16
# Is this needs? AttachmentsController reads file simply.
@content.gsub!("\r\n", "\n")
if !@entry.size || @entry.size <= Setting.file_max_size_displayed.to_i.kilobyte
content = @repository.cat(@path, @rev)
(show_error_not_found; return) unless content

if content.size <= Setting.file_max_size_displayed.to_i.kilobyte &&
is_entry_text_data?(content, @path)
# TODO: UTF-16
# Prevent empty lines when displaying a file with Windows style eol
# Is this needed? AttachmentsController simply reads file.
@content = content.gsub("\r\n", "\n")
end
end
@changeset = @repository.find_changeset_by_name(@rev)
end
end
Expand All @@ -196,7 +172,7 @@ def is_entry_text_data?(ent, path)
return true if Redmine::MimeType.is_type?('text', path)
# Ruby 1.8.6 has a bug of integer divisions.
# http://apidock.com/ruby/v1_8_6_287/String/is_binary_data%3F
return false if ent.is_binary_data?
return false if Redmine::Scm::Adapters::ScmData.binary?(ent)
true
end
private :is_entry_text_data?
Expand All @@ -207,14 +183,17 @@ def annotate

@annotate = @repository.scm.annotate(@path, @rev)
if @annotate.nil? || @annotate.empty?
(render_error l(:error_scm_annotate); return)
end
ann_buf_size = 0
@annotate.lines.each do |buf|
ann_buf_size += buf.size
end
if ann_buf_size > Setting.file_max_size_displayed.to_i.kilobyte
(render_error l(:error_scm_annotate_big_text_file); return)
@annotate = nil
@error_message = l(:error_scm_annotate)
else
ann_buf_size = 0
@annotate.lines.each do |buf|
ann_buf_size += buf.size
end
if ann_buf_size > Setting.file_max_size_displayed.to_i.kilobyte
@annotate = nil
@error_message = l(:error_scm_annotate_big_text_file)
end
end
@changeset = @repository.find_changeset_by_name(@rev)
end
Expand Down Expand Up @@ -301,6 +280,18 @@ def graph

private

def build_new_repository_from_params
scm = params[:repository_scm] || (Redmine::Scm::Base.all & Setting.enabled_scm).first
unless @repository = Repository.factory(scm)
render_404
return
end

@repository.project = @project
@repository.safe_attributes = params[:repository]
@repository
end

def find_repository
@repository = Repository.find(params[:id])
@project = @repository.project
Expand Down Expand Up @@ -350,7 +341,7 @@ def show_error_command_failed(exception)
end

def graph_commits_per_month(repository)
@date_to = Date.today
@date_to = User.current.today
@date_from = @date_to << 11
@date_from = Date.civil(@date_from.year, @date_from.month, 1)
commits_by_day = Changeset.
Expand All @@ -369,7 +360,8 @@ def graph_commits_per_month(repository)
changes_by_day.each {|c| changes_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last }

fields = []
12.times {|m| fields << month_name(((Date.today.month - 1 - m) % 12) + 1)}
today = User.current.today
12.times {|m| fields << month_name(((today.month - 1 - m) % 12) + 1)}

graph = SVG::Graph::Bar.new(
:height => 300,
Expand Down Expand Up @@ -436,4 +428,12 @@ def graph_commits_per_author(repository)
)
graph.burn
end

def disposition(path)
if Redmine::MimeType.of(@path) == "application/pdf"
'inline'
else
'attachment'
end
end
end
47 changes: 30 additions & 17 deletions app/controllers/roles_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -17,19 +17,20 @@

class RolesController < ApplicationController
layout 'admin'
self.main_menu = false

before_filter :require_admin, :except => [:index, :show]
before_filter :require_admin_or_api_request, :only => [:index, :show]
before_filter :find_role, :only => [:show, :edit, :update, :destroy]
before_action :require_admin, :except => [:index, :show]
before_action :require_admin_or_api_request, :only => [:index, :show]
before_action :find_role, :only => [:show, :edit, :update, :destroy]
accept_api_auth :index, :show

require_sudo_mode :create, :update, :destroy

def index
respond_to do |format|
format.html {
@role_pages, @roles = paginate Role.sorted, :per_page => 25
render :action => "index", :layout => false if request.xhr?
@roles = Role.sorted.to_a
render :layout => false if request.xhr?
}
format.api {
@roles = Role.givable.to_a
Expand All @@ -45,19 +46,21 @@ def show

def new
# Prefills the form with 'Non member' role permissions by default
@role = Role.new(params[:role] || {:permissions => Role.non_member.permissions})
@role = Role.new
@role.safe_attributes = params[:role] || {:permissions => Role.non_member.permissions}
if params[:copy].present? && @copy_from = Role.find_by_id(params[:copy])
@role.copy_from(@copy_from)
end
@roles = Role.sorted.to_a
end

def create
@role = Role.new(params[:role])
@role = Role.new
@role.safe_attributes = params[:role]
if request.post? && @role.save
# workflow copy
if !params[:copy_workflow_from].blank? && (copy_from = Role.find_by_id(params[:copy_workflow_from]))
@role.workflow_rules.copy(copy_from)
@role.copy_workflow_rules(copy_from)
end
flash[:notice] = l(:notice_successful_create)
redirect_to roles_path
Expand All @@ -71,19 +74,29 @@ def edit
end

def update
if @role.update_attributes(params[:role])
flash[:notice] = l(:notice_successful_update)
redirect_to roles_path(:page => params[:page])
@role.safe_attributes = params[:role]
if @role.save
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_update)
redirect_to roles_path(:page => params[:page])
}
format.js { head 200 }
end
else
render :action => 'edit'
respond_to do |format|
format.html { render :action => 'edit' }
format.js { head 422 }
end
end
end

def destroy
@role.destroy
redirect_to roles_path
rescue
flash[:error] = l(:error_can_not_remove_role)
begin
@role.destroy
rescue
flash[:error] = l(:error_can_not_remove_role)
end
redirect_to roles_path
end

Expand Down
28 changes: 20 additions & 8 deletions app/controllers/search_controller.rb
@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2015 Jean-Philippe Lang
# Copyright (C) 2006-2017 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -16,7 +16,8 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

class SearchController < ApplicationController
before_filter :find_optional_project
before_action :find_optional_project
accept_api_auth :index

def index
@question = params[:q] || ""
Expand All @@ -26,6 +27,15 @@ def index
@search_attachments = params[:attachments].presence || '0'
@open_issues = params[:open_issues] ? params[:open_issues].present? : false

case params[:format]
when 'xml', 'json'
@offset, @limit = api_offset_and_limit
else
@offset = nil
@limit = Setting.search_results_per_page.to_i
@limit = 10 if @limit == 0
end

# quick jump to an issue
if (m = @question.match(/^#?(\d+)$/)) && (issue = Issue.visible.find_by_id(m[1].to_i))
redirect_to issue_path(issue)
Expand Down Expand Up @@ -58,22 +68,24 @@ def index
fetcher = Redmine::Search::Fetcher.new(
@question, User.current, @scope, projects_to_search,
:all_words => @all_words, :titles_only => @titles_only, :attachments => @search_attachments, :open_issues => @open_issues,
:cache => params[:page].present?
:cache => params[:page].present?, :params => params
)

if fetcher.tokens.present?
@result_count = fetcher.result_count
@result_count_by_type = fetcher.result_count_by_type
@tokens = fetcher.tokens

per_page = Setting.search_results_per_page.to_i
per_page = 10 if per_page == 0
@result_pages = Paginator.new @result_count, per_page, params['page']
@results = fetcher.results(@result_pages.offset, @result_pages.per_page)
@result_pages = Paginator.new @result_count, @limit, params['page']
@offset ||= @result_pages.offset
@results = fetcher.results(@offset, @result_pages.per_page)
else
@question = ""
end
render :layout => false if request.xhr?
respond_to do |format|
format.html { render :layout => false if request.xhr? }
format.api { @results ||= []; render :layout => false }
end
end

private
Expand Down