tagged version 3.4.7

git-svn-id: http://svn.redmine.org/redmine/tags/3.4.7@17716 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
Jean-Philippe Lang
2018-12-09 06:40:58 +00:00
1069 changed files with 24248 additions and 14916 deletions

10
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,10 @@
____________________________________________________________________
**Do not send a pull request to this GitHub repository**.
For more detail, please see [official website] wiki [Contribute].
[official website]: http://www.redmine.org
[Contribute]: http://www.redmine.org/projects/redmine/wiki/Contribute

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
/.project
/.idea
/.loadpath
/.powrc
/.rvmrc

View File

@@ -1,6 +1,7 @@
syntax: glob
.project
.idea
.loadpath
.powrc
.rvmrc

28
Gemfile
View File

@@ -11,26 +11,24 @@ if RUBY_VERSION < "2.1"
end
gem "jquery-rails", "~> 3.1.4"
gem "coderay", "~> 1.1.1"
gem "builder", ">= 3.0.4"
gem "request_store", "1.0.5"
gem "mime-types", (RUBY_VERSION >= "2.0" ? "~> 3.0" : "~> 2.99")
gem "protected_attributes"
gem "actionpack-action_caching"
gem "actionpack-xml_parser"
gem "roadie-rails", "~> 1.1.1"
gem "roadie", "~> 3.2.1"
gem "mimemagic"
gem "mail", "~> 2.6.4"
gem "nokogiri", (RUBY_VERSION >= "2.1" ? "~> 1.7.2" : "~> 1.6.8")
gem "nokogiri", (RUBY_VERSION >= "2.1" ? "~> 1.8.1" : "~> 1.6.8")
gem "i18n", "~> 0.7.0"
gem "ffi", "1.9.14", :platforms => :mingw if RUBY_VERSION < "2.0"
# Request at least rails-html-sanitizer 1.0.3 because of security advisories
# Request at least rails-html-sanitizer 1.0.3 because of security advisories
gem "rails-html-sanitizer", ">= 1.0.3"
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :x64_mingw, :mswin, :jruby]
gem 'tzinfo-data', platforms: [:mingw, :x64_mingw, :mswin]
gem "rbpdf", "~> 1.19.6"
# Optional gem for LDAP authentication
@@ -52,16 +50,10 @@ platforms :mri, :mingw, :x64_mingw do
# Optional Markdown support, not for JRuby
group :markdown do
gem "redcarpet", "~> 3.3.2"
gem "redcarpet", "~> 3.4.0"
end
end
platforms :jruby do
# jruby-openssl is bundled with JRuby 1.7.0
gem "jruby-openssl" if Object.const_defined?(:JRUBY_VERSION) && JRUBY_VERSION < '1.7.0'
gem "activerecord-jdbc-adapter", "~> 1.3.2"
end
# Include database gems for the adapters found in the database
# configuration file
require 'erb'
@@ -75,19 +67,13 @@ if File.exist?(database_file)
case adapter
when 'mysql2'
gem "mysql2", "~> 0.4.6", :platforms => [:mri, :mingw, :x64_mingw]
gem "activerecord-jdbcmysql-adapter", :platforms => :jruby
when 'mysql'
gem "activerecord-jdbcmysql-adapter", :platforms => :jruby
when /postgresql/
gem "pg", "~> 0.18.1", :platforms => [:mri, :mingw, :x64_mingw]
gem "activerecord-jdbcpostgresql-adapter", :platforms => :jruby
when /sqlite3/
gem "sqlite3", (RUBY_VERSION < "2.0" && RUBY_PLATFORM =~ /mingw/ ? "1.3.12" : "~>1.3.12"),
:platforms => [:mri, :mingw, :x64_mingw]
gem "jdbc-sqlite3", ">= 3.8.10.1", :platforms => :jruby
gem "activerecord-jdbcsqlite3-adapter", :platforms => :jruby
when /sqlserver/
gem "tiny_tds", "~> 0.6.2", :platforms => [:mri, :mingw, :x64_mingw]
gem "tiny_tds", (RUBY_VERSION >= "2.0" ? "~> 1.0.5" : "~> 0.7.0"), :platforms => [:mri, :mingw, :x64_mingw]
gem "activerecord-sqlserver-adapter", :platforms => [:mri, :mingw, :x64_mingw]
else
warn("Unknown database adapter `#{adapter}` found in config/database.yml, use Gemfile.local to load your own database gems")
@@ -110,8 +96,10 @@ group :test do
gem "rails-dom-testing"
gem "mocha"
gem "simplecov", "~> 0.9.1", :require => false
# TODO: remove this after upgrading to Rails 5
gem "test_after_commit", "~> 0.4.2"
# For running UI tests
gem "capybara"
gem "capybara", '~> 2.13'
gem "selenium-webdriver", "~> 2.53.4"
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -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
@@ -40,7 +42,7 @@ class AccountController < ApplicationController
end
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
@@ -78,20 +80,25 @@ class AccountController < ApplicationController
return
end
if request.post?
@user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
if @user.save
@token.destroy
Mailer.password_updated(@user, { remote_ip: request.remote_ip })
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, { remote_ip: request.remote_ip })
flash[:notice] = l(:notice_account_password_updated)
redirect_to signin_path
return
end
end
end
render :template => "account/password_recovery"
return
else
if request.post?
email = params[:mail].to_s
email = params[:mail].to_s.strip
user = User.find_by_mail(email)
# user not found
unless user
@@ -131,7 +138,7 @@ class AccountController < ApplicationController
user_params = params[:user] || {}
@user = User.new
@user.safe_attributes = user_params
@user.pref.attributes = params[:pref] if params[:pref]
@user.pref.safe_attributes = params[:pref]
@user.admin = false
@user.register
if session[:auth_source_registration]
@@ -145,7 +152,6 @@ class AccountController < ApplicationController
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
@@ -274,13 +280,13 @@ class AccountController < ApplicationController
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'] || RedmineApp::Application.config.relative_url_root || '/'),
:secure => secure,

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -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

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -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?
@@ -34,7 +33,10 @@ class AdminController < ApplicationController
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
@@ -72,7 +74,6 @@ class AdminController < ApplicationController
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)],

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -51,7 +51,7 @@ class ApplicationController < ActionController::Base
end
end
before_filter :session_expiration, :user_setup, :check_if_login_required, :set_localization, :check_password_change
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
@@ -168,9 +168,10 @@ class ApplicationController < ActionController::Base
# 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'])
Token.delete_all(["user_id = ? AND action = ? AND value = ?", User.current.id, 'session', session[:tk]])
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
@@ -213,7 +214,7 @@ class ApplicationController < ActionController::Base
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
@@ -369,7 +370,7 @@ class ApplicationController < ActionController::Base
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?
@@ -434,7 +435,7 @@ class ApplicationController < ActionController::Base
return false
end
if path.match(%r{/(login|account/register)})
if path.match(%r{/(login|account/register|account/lost_password)})
return false
end
@@ -453,14 +454,16 @@ class ApplicationController < ActionController::Base
# 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
@@ -642,20 +645,18 @@ class ApplicationController < ActionController::Base
# 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

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -16,17 +16,18 @@
# 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
# Disable check for same origin requests for JS files, i.e. attachments with
# MIME type text/javascript.
skip_after_filter :verify_same_origin_request, :only => :download
skip_after_action :verify_same_origin_request, :only => :download
accept_api_auth :show, :download, :thumbnail, :upload, :destroy
accept_api_auth :show, :download, :thumbnail, :upload, :update, :destroy
def show
respond_to do |format|
@@ -77,7 +78,7 @@ class AttachmentsController < ApplicationController
end
else
# No thumbnail for the attachment or thumbnail could not be created
render :nothing => true, :status => 404
head 404
end
end
@@ -85,7 +86,7 @@ class AttachmentsController < ApplicationController
# 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
@@ -107,17 +108,32 @@ class AttachmentsController < ApplicationController
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
@@ -138,6 +154,22 @@ class AttachmentsController < ApplicationController
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
private
def find_attachment
@@ -184,6 +216,10 @@ class AttachmentsController < ApplicationController
@attachment.visible? ? true : deny_access
end
def update_authorize
@attachment.editable? ? true : deny_access
end
def delete_authorize
@attachment.deletable? ? true : deny_access
end
@@ -203,4 +239,9 @@ class AttachmentsController < ApplicationController
'attachment'
end
end
# Returns attachments param for #update_all
def update_all_params
params.permit(:attachments => [:filename, :description]).require(:attachments)
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -17,10 +17,12 @@
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
@@ -28,13 +30,9 @@ class AuthSourcesController < ApplicationController
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
@@ -47,7 +45,8 @@ class AuthSourcesController < ApplicationController
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
@@ -89,6 +88,15 @@ class AuthSourcesController < ApplicationController
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

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -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

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -17,7 +17,7 @@
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
@@ -25,7 +25,7 @@ class BoardsController < ApplicationController
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
@@ -37,15 +37,14 @@ class BoardsController < ApplicationController
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).
@@ -56,7 +55,7 @@ class BoardsController < ApplicationController
}
format.atom {
@messages = @board.messages.
reorder('created_on DESC').
reorder(:id => :desc).
includes(:author, :board).
limit(Setting.feeds_limit.to_i).
to_a
@@ -92,18 +91,20 @@ class BoardsController < ApplicationController
flash[:notice] = l(:notice_successful_update)
redirect_to_settings_in_projects
}
format.js { render :nothing => true }
format.js { head 200 }
end
else
respond_to do |format|
format.html { render :action => 'edit' }
format.js { render :nothing => true, :status => 422 }
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

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -17,7 +17,7 @@
class CalendarsController < ApplicationController
menu_item :calendar
before_filter :find_optional_project
before_action :find_optional_project
rescue_from Query::StatementInvalid, :with => :query_statement_invalid
@@ -25,8 +25,6 @@ class CalendarsController < ApplicationController
helper :projects
helper :queries
include QueriesHelper
helper :sort
include SortHelper
def show
if params[:year] and params[:year].to_i > 1900
@@ -41,6 +39,7 @@ class CalendarsController < ApplicationController
@calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
retrieve_query
@query.group_by = nil
@query.sort_criteria = nil
if @query.valid?
events = []
events += @query.issues(:include => [:tracker, :assigned_to, :priority],

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -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?
@@ -43,7 +43,7 @@ class CommentsController < ApplicationController
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

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -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)
@@ -45,7 +45,7 @@ class ContextMenusController < ApplicationController
@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?
@@ -78,7 +78,7 @@ class ContextMenusController < ApplicationController
@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?

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -17,10 +17,11 @@
class CustomFieldEnumerationsController < ApplicationController
layout 'admin'
self.main_menu = false
before_filter :require_admin
before_filter :find_custom_field
before_filter :find_enumeration, :only => :destroy
before_action :require_admin
before_action :find_custom_field
before_action :find_enumeration, :only => :destroy
helper :custom_fields
@@ -29,7 +30,8 @@ class CustomFieldEnumerationsController < ApplicationController
end
def create
@value = @custom_field.enumerations.build(params[:custom_field_enumeration])
@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) }
@@ -38,7 +40,8 @@ class CustomFieldEnumerationsController < ApplicationController
end
def update_each
if CustomFieldEnumeration.update_each(@custom_field, params[:custom_field_enumerations])
saved = CustomFieldEnumeration.update_each(@custom_field, update_each_params)
if saved
flash[:notice] = l(:notice_successful_update)
end
redirect_to :action => 'index'
@@ -68,4 +71,14 @@ class CustomFieldEnumerationsController < ApplicationController
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

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -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
@@ -53,26 +56,29 @@ class CustomFieldsController < ApplicationController
end
def update
if @custom_field.update_attributes(params[:custom_field])
@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)
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_update)
redirect_back_or_default edit_custom_field_path(@custom_field)
}
format.js { render :nothing => true }
format.js { head 200 }
end
else
respond_to do |format|
format.html { render :action => 'edit' }
format.js { render :nothing => true, :status => 422 }
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
@@ -82,9 +88,11 @@ class CustomFieldsController < ApplicationController
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

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -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

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -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
@@ -29,10 +30,7 @@ class EmailAddressesController < ApplicationController
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

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -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
@@ -56,18 +57,18 @@ class EnumerationsController < ApplicationController
end
def update
if @enumeration.update_attributes(params[:enumeration])
if @enumeration.update_attributes(enumeration_params)
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_update)
redirect_to enumerations_path
}
format.js { render :nothing => true }
format.js { head 200 }
end
else
respond_to do |format|
format.html { render :action => 'edit' }
format.js { render :nothing => true, :status => 422 }
format.js { head 422 }
end
end
end
@@ -90,8 +91,10 @@ class EnumerationsController < ApplicationController
def build_new_enumeration
class_name = params[:enumeration] && params[:enumeration][:type] || params[:type]
@enumeration = Enumeration.new_subclass_instance(class_name, params[:enumeration])
if @enumeration.nil?
@enumeration = Enumeration.new_subclass_instance(class_name)
if @enumeration
@enumeration.attributes = enumeration_params || {}
else
render_404
end
end
@@ -101,4 +104,10 @@ class EnumerationsController < ApplicationController
rescue ActiveRecord::RecordNotFound
render_404
end
def enumeration_params
# can't require enumeration on #new action
cf_ids = @enumeration.available_custom_fields.map{|c| c.id.to_s}
params.permit(:enumeration => [:name, :active, :is_default, :position, :custom_field_values => cf_ids])[:enumeration]
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -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
@@ -35,7 +37,10 @@ class FilesController < ApplicationController
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
@@ -43,20 +48,29 @@ class FilesController < ApplicationController
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

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -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
@@ -26,8 +26,6 @@ class GanttsController < ApplicationController
helper :projects
helper :queries
include QueriesHelper
helper :sort
include SortHelper
include Redmine::Export::PDF
def show

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -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
@@ -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 {
@@ -79,7 +85,7 @@ class GroupsController < ApplicationController
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" }
@@ -92,7 +98,7 @@ class GroupsController < ApplicationController
@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

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -18,11 +18,13 @@
require 'csv'
class ImportsController < ApplicationController
menu_item :issues
before_filter :find_import, :only => [:show, :settings, :mapping, :run]
before_filter :authorize_global
before_action :find_import, :only => [:show, :settings, :mapping, :run]
before_action :authorize_global
helper :issues
helper :queries
def new
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -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

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -16,8 +16,10 @@
# 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
@@ -25,7 +27,7 @@ class IssueRelationsController < ApplicationController
@relations = @issue.relations
respond_to do |format|
format.html { render :nothing => true }
format.html { head 200 }
format.api
end
end
@@ -34,7 +36,7 @@ class IssueRelationsController < ApplicationController
raise Unauthorized unless @relation.visible?
respond_to do |format|
format.html { render :nothing => true }
format.html { head 200 }
format.api
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -17,9 +17,10 @@
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
@@ -35,7 +36,8 @@ class IssueStatusesController < ApplicationController
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
@@ -50,18 +52,19 @@ class IssueStatusesController < ApplicationController
def update
@issue_status = IssueStatus.find(params[:id])
if @issue_status.update_attributes(params[:issue_status])
@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 { render :nothing => true }
format.js { head 200 }
end
else
respond_to do |format|
format.html { render :action => 'edit' }
format.js { render :nothing => true, :status => 422 }
format.js { head 422 }
end
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -18,11 +18,11 @@
class IssuesController < ApplicationController
default_search_scope :issues
before_filter :find_issue, :only => [:show, :edit, :update]
before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy]
before_filter :authorize, :except => [:index, :new, :create]
before_filter :find_optional_project, :only => [:index, :new, :create]
before_filter :build_new_issue_from_params, :only => [:new, :create]
before_action :find_issue, :only => [:show, :edit, :update]
before_action :find_issues, :only => [:bulk_edit, :bulk_update, :destroy]
before_action :authorize, :except => [:index, :new, :create]
before_action :find_optional_project, :only => [:index, :new, :create]
before_action :build_new_issue_from_params, :only => [:new, :create]
accept_rss_auth :index, :show
accept_api_auth :index, :show, :create, :update, :destroy
@@ -37,54 +37,44 @@ class IssuesController < ApplicationController
helper :queries
include QueriesHelper
helper :repositories
helper :sort
include SortHelper
helper :timelog
def index
retrieve_query
sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
sort_update(@query.sortable_columns)
@query.sort_criteria = sort_criteria.to_a
use_session = !request.format.csv?
retrieve_query(IssueQuery, use_session)
if @query.valid?
case params[:format]
when 'csv', 'pdf'
@limit = Setting.issues_export_limit.to_i
if params[:columns] == 'all'
@query.column_names = @query.available_inline_columns.map(&:name)
end
when 'atom'
@limit = Setting.feeds_limit.to_i
when 'xml', 'json'
@offset, @limit = api_offset_and_limit
@query.column_names = %w(author)
else
@limit = per_page_option
end
@issue_count = @query.issue_count
@issue_pages = Paginator.new @issue_count, @limit, params['page']
@offset ||= @issue_pages.offset
@issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
:order => sort_clause,
:offset => @offset,
:limit => @limit)
@issue_count_by_group = @query.issue_count_by_group
respond_to do |format|
format.html { render :template => 'issues/index', :layout => !request.xhr? }
format.html {
@issue_count = @query.issue_count
@issue_pages = Paginator.new @issue_count, per_page_option, params['page']
@issues = @query.issues(:offset => @issue_pages.offset, :limit => @issue_pages.per_page)
render :layout => !request.xhr?
}
format.api {
@offset, @limit = api_offset_and_limit
@query.column_names = %w(author)
@issue_count = @query.issue_count
@issues = @query.issues(:offset => @offset, :limit => @limit)
Issue.load_visible_relations(@issues) if include_in_api_response?('relations')
}
format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
format.csv { send_data(query_to_csv(@issues, @query, params[:csv]), :type => 'text/csv; header=present', :filename => 'issues.csv') }
format.pdf { send_file_headers! :type => 'application/pdf', :filename => 'issues.pdf' }
format.atom {
@issues = @query.issues(:limit => Setting.feeds_limit.to_i)
render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}")
}
format.csv {
@issues = @query.issues(:limit => Setting.issues_export_limit.to_i)
send_data(query_to_csv(@issues, @query, params[:csv]), :type => 'text/csv; header=present', :filename => 'issues.csv')
}
format.pdf {
@issues = @query.issues(:limit => Setting.issues_export_limit.to_i)
send_file_headers! :type => 'application/pdf', :filename => 'issues.pdf'
}
end
else
respond_to do |format|
format.html { render(:template => 'issues/index', :layout => !request.xhr?) }
format.any(:atom, :csv, :pdf) { render(:nothing => true) }
format.html { render :layout => !request.xhr? }
format.any(:atom, :csv, :pdf) { head 422 }
format.api { render_validation_errors(@query) }
end
end
@@ -93,23 +83,14 @@ class IssuesController < ApplicationController
end
def show
@journals = @issue.journals.includes(:user, :details).
references(:user, :details).
reorder(:created_on, :id).to_a
@journals.each_with_index {|j,i| j.indice = i+1}
@journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
Journal.preload_journals_details_custom_fields(@journals)
@journals.select! {|journal| journal.notes? || journal.visible_details.any?}
@journals.reverse! if User.current.wants_comments_in_reverse_order?
@journals = @issue.visible_journals_with_index
@changesets = @issue.changesets.visible.preload(:repository, :user).to_a
@changesets.reverse! if User.current.wants_comments_in_reverse_order?
@relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
@priorities = IssuePriority.active
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
@relation = IssueRelation.new
if User.current.wants_comments_in_reverse_order?
@journals.reverse!
@changesets.reverse!
end
if User.current.allowed_to?(:view_time_entries, @project)
Issue.load_visible_spent_hours([@issue])
@@ -118,6 +99,10 @@ class IssuesController < ApplicationController
respond_to do |format|
format.html {
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
@priorities = IssuePriority.active
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
@relation = IssueRelation.new
retrieve_previous_and_next_issue_ids
render :template => 'issues/show'
}
@@ -222,32 +207,69 @@ class IssuesController < ApplicationController
end
end
edited_issues = Issue.where(:id => @issues.map(&:id)).to_a
@values_by_custom_field = {}
edited_issues.each do |issue|
issue.custom_field_values.each do |c|
if c.value_present?
@values_by_custom_field[c.custom_field] ||= []
@values_by_custom_field[c.custom_field] << issue.id
end
end
end
@allowed_projects = Issue.allowed_target_projects
if params[:issue]
@target_project = @allowed_projects.detect {|p| p.id.to_s == params[:issue][:project_id].to_s}
if @target_project
target_projects = [@target_project]
edited_issues.each {|issue| issue.project = @target_project}
end
end
target_projects ||= @projects
@trackers = target_projects.map {|p| Issue.allowed_target_trackers(p) }.reduce(:&)
if params[:issue]
@target_tracker = @trackers.detect {|t| t.id.to_s == params[:issue][:tracker_id].to_s}
if @target_tracker
edited_issues.each {|issue| issue.tracker = @target_tracker}
end
end
if @copy
# Copied issues will get their default statuses
@available_statuses = []
else
@available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
@available_statuses = edited_issues.map(&:new_statuses_allowed_to).reduce(:&)
end
@custom_fields = @issues.map{|i|i.editable_custom_fields}.reduce(:&)
if params[:issue]
@target_status = @available_statuses.detect {|t| t.id.to_s == params[:issue][:status_id].to_s}
if @target_status
edited_issues.each {|issue| issue.status = @target_status}
end
end
edited_issues.each do |issue|
issue.custom_field_values.each do |c|
if c.value_present? && @values_by_custom_field[c.custom_field]
@values_by_custom_field[c.custom_field].delete(issue.id)
end
end
end
@values_by_custom_field.delete_if {|k,v| v.blank?}
@custom_fields = edited_issues.map{|i|i.editable_custom_fields}.reduce(:&).select {|field| field.format.bulk_edit_supported}
@assignables = target_projects.map(&:assignable_users).reduce(:&)
@trackers = target_projects.map {|p| Issue.allowed_target_trackers(p) }.reduce(:&)
@versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
@categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
if @copy
@attachments_present = @issues.detect {|i| i.attachments.any?}.present?
@subtasks_present = @issues.detect {|i| !i.leaf?}.present?
@watchers_present = User.current.allowed_to?(:add_issue_watchers, @projects) && Watcher.where(:watchable_type => 'Issue', :watchable_id => @issues.map(&:id)).exists?
end
@safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
@safe_attributes = edited_issues.map(&:safe_attribute_names).reduce(:&)
@issue_params = params[:issue] || {}
@issue_params[:custom_field_values] ||= {}
@@ -260,6 +282,7 @@ class IssuesController < ApplicationController
attributes = parse_params_for_bulk_update(params[:issue])
copy_subtasks = (params[:copy_subtasks] == '1')
copy_attachments = (params[:copy_attachments] == '1')
copy_watchers = (params[:copy_watchers] == '1')
if @copy
unless User.current.allowed_to?(:copy_issues, @projects)
@@ -272,6 +295,9 @@ class IssuesController < ApplicationController
unless User.current.allowed_to?(:add_issues, target_projects)
raise ::Unauthorized
end
unless User.current.allowed_to?(:add_issue_watchers, @projects)
copy_watchers = false
end
else
unless @issues.all?(&:attributes_editable?)
raise ::Unauthorized
@@ -293,6 +319,7 @@ class IssuesController < ApplicationController
issue = orig_issue.copy({},
:attachments => copy_attachments,
:subtasks => copy_subtasks,
:watchers => copy_watchers,
:link => link_copy?(params[:link_copy])
)
else
@@ -341,7 +368,12 @@ class IssuesController < ApplicationController
when 'destroy'
# nothing to do
when 'nullify'
if Setting.timelog_required_fields.include?('issue_id')
flash.now[:error] = l(:field_issue) + " " + ::I18n.t('activerecord.errors.messages.blank')
return
else
time_entries.update_all(:issue_id => nil)
end
when 'reassign'
reassign_to = @project && @project.issues.find_by_id(params[:reassign_to_id])
if reassign_to.nil?
@@ -374,7 +406,7 @@ class IssuesController < ApplicationController
# Overrides Redmine::MenuManager::MenuController::ClassMethods for
# when the "New issue" tab is enabled
def current_menu_item
if Setting.new_item_menu_tab == '1' && [:new, :create].include?(action_name.to_sym)
if Setting.new_item_menu_tab == '1' && [:new, :create].include?(action_name.to_sym)
:new_issue
else
super
@@ -392,10 +424,9 @@ class IssuesController < ApplicationController
else
retrieve_query_from_session
if @query
sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
sort_update(@query.sortable_columns, 'issues_index_sort')
@per_page = per_page_option
limit = 500
issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1), :include => [:assigned_to, :tracker, :priority, :category, :fixed_version])
issue_ids = @query.issue_ids(:limit => (limit + 1))
if (idx = issue_ids.index(@issue.id)) && idx < limit
if issue_ids.size < 500
@issue_position = idx + 1
@@ -404,6 +435,11 @@ class IssuesController < ApplicationController
@prev_issue_id = issue_ids[idx - 1] if idx > 0
@next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1)
end
query_params = @query.as_params
if @issue_position
query_params = query_params.merge(:page => (@issue_position / per_page_option) + 1, :per_page => per_page_option)
end
@query_path = _project_issues_path(@query.project, query_params)
end
end
end
@@ -460,7 +496,8 @@ class IssuesController < ApplicationController
@link_copy = link_copy?(params[:link_copy]) || request.get?
@copy_attachments = params[:copy_attachments].present? || request.get?
@copy_subtasks = params[:copy_subtasks].present? || request.get?
@issue.copy_from(@copy_from, :attachments => @copy_attachments, :subtasks => @copy_subtasks, :link => @link_copy)
@copy_watchers = User.current.allowed_to?(:add_issue_watchers, @project)
@issue.copy_from(@copy_from, :attachments => @copy_attachments, :subtasks => @copy_subtasks, :watchers => @copy_watchers, :link => @link_copy)
@issue.parent_issue_id = @copy_from.parent_id
rescue ActiveRecord::RecordNotFound
render_404
@@ -519,7 +556,7 @@ class IssuesController < ApplicationController
time_entry.issue = @issue
time_entry.user = User.current
time_entry.spent_on = User.current.today
time_entry.attributes = params[:time_entry]
time_entry.safe_attributes = params[:time_entry]
@issue.time_entries << time_entry
end
@@ -548,15 +585,18 @@ class IssuesController < ApplicationController
# Redirects user after a successful issue creation
def redirect_after_create
if params[:continue]
attrs = {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?}
url_params = {}
url_params[:issue] = {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?}
url_params[:back_url] = params[:back_url].presence
if params[:project_id]
redirect_to new_project_issue_path(@issue.project, :issue => attrs)
redirect_to new_project_issue_path(@issue.project, url_params)
else
attrs.merge! :project_id => @issue.project_id
redirect_to new_issue_path(:issue => attrs)
url_params[:issue].merge! :project_id => @issue.project_id
redirect_to new_issue_path(url_params)
end
else
redirect_to issue_path(@issue)
redirect_back_or_default issue_path(@issue)
end
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -16,10 +16,10 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class JournalsController < ApplicationController
before_filter :find_journal, :only => [:edit, :update, :diff]
before_filter :find_issue, :only => [:new]
before_filter :find_optional_project, :only => [:index]
before_filter :authorize, :only => [:new, :edit, :update, :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
@@ -27,13 +27,9 @@ class JournalsController < ApplicationController
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)
@@ -90,7 +86,8 @@ class JournalsController < ApplicationController
def update
(render_403; return false) unless @journal.editable_by?(User.current)
@journal.update_attributes(:notes => params[:notes]) if params[:notes]
@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|

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -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
@@ -27,9 +27,9 @@ class MailHandlerController < ActionController::Base
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
@@ -38,7 +38,7 @@ class MailHandlerController < ActionController::Base
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

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -17,10 +17,10 @@
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
@@ -80,6 +80,10 @@ class MembersController < ApplicationController
end
end
def edit
@roles = Role.givable.to_a
end
def update
if params[:membership]
@member.set_editable_role_ids(params[:membership][:role_ids])

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -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

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -16,9 +16,10 @@
# 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
@@ -26,19 +27,7 @@ class MyController < ApplicationController
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
@@ -48,7 +37,8 @@ class MyController < ApplicationController
# 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
@@ -56,8 +46,8 @@ class MyController < ApplicationController
@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
@@ -143,69 +133,54 @@ class MyController < ApplicationController
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

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -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
@@ -98,14 +98,4 @@ class NewsController < ApplicationController
@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

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -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?
@@ -25,8 +25,8 @@ class PreviewsController < ApplicationController
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

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -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
@@ -39,8 +40,12 @@ class PrincipalMembershipsController < ApplicationController
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 }

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -16,8 +16,8 @@
# 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]

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -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
@@ -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|
@@ -62,6 +62,18 @@ class ProjectsController < ApplicationController
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
@@ -161,6 +173,10 @@ class ProjectsController < ApplicationController
@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
@@ -198,14 +214,14 @@ class ProjectsController < ApplicationController
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

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -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
@@ -31,9 +31,10 @@ class QueriesController < ApplicationController
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).
@@ -45,21 +46,21 @@ class QueriesController < ApplicationController
end
def new
@query = IssueQuery.new
@query = query_class.new
@query.user = User.current
@query.project = @project
@query.build_from_params(params)
end
def create
@query = IssueQuery.new
@query = query_class.new
@query.user = User.current
@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
@@ -73,7 +74,7 @@ class QueriesController < ApplicationController
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
@@ -81,12 +82,32 @@ class QueriesController < ApplicationController
def destroy
@query.destroy
redirect_to_issues(:set_filter => 1)
redirect_to_items(:set_filter => 1)
end
private
# 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
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
@@ -107,15 +128,20 @@ private
@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]) || IssueQuery::VISIBILITY_PRIVATE
@query.visibility = (params[:query] && params[:query][:visibility]) || Query::VISIBILITY_PRIVATE
@query.role_ids = params[:query] && params[:query][:role_ids]
else
@query.visibility = IssueQuery::VISIBILITY_PRIVATE
@query.visibility = Query::VISIBILITY_PRIVATE
end
@query
end
def redirect_to_issues(options)
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)
@@ -126,4 +152,14 @@ private
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

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -17,7 +17,7 @@
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.rolled_up_trackers(false).visible
@@ -76,14 +76,8 @@ class ReportsController < ApplicationController
@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

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -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'
@@ -63,12 +54,7 @@ class RepositoriesController < ApplicationController
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
@@ -76,20 +62,6 @@ class RepositoriesController < ApplicationController
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
@@ -97,7 +69,7 @@ class RepositoriesController < ApplicationController
@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)
@@ -116,7 +88,7 @@ class RepositoriesController < ApplicationController
@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)
@@ -200,7 +172,7 @@ class RepositoriesController < ApplicationController
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?
@@ -211,14 +183,17 @@ class RepositoriesController < ApplicationController
@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
@@ -305,6 +280,18 @@ class RepositoriesController < ApplicationController
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

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -17,10 +17,11 @@
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
@@ -45,7 +46,8 @@ class RolesController < ApplicationController
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
@@ -53,11 +55,12 @@ class RolesController < ApplicationController
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
@@ -71,27 +74,29 @@ class RolesController < ApplicationController
end
def update
if @role.update_attributes(params[:role])
@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 { render :nothing => true }
format.js { head 200 }
end
else
respond_to do |format|
format.html { render :action => 'edit' }
format.js { render :nothing => true, :status => 422 }
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

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -16,7 +16,7 @@
# 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
@@ -49,7 +49,7 @@ class SearchController < ApplicationController
when 'my_projects'
User.current.projects
when 'subprojects'
@project ? (@project.self_and_descendants.active.to_a) : nil
@project ? (@project.self_and_descendants.to_a) : nil
else
@project
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -17,11 +17,12 @@
class SettingsController < ApplicationController
layout 'admin'
self.main_menu = false
menu_item :plugins, :only => :plugin
helper :queries
before_filter :require_admin
before_action :require_admin
require_sudo_mode :index, :edit, :plugin
@@ -32,24 +33,30 @@ class SettingsController < ApplicationController
def edit
@notifiables = Redmine::Notifiable.all
if request.post? && params[:settings] && params[:settings].is_a?(Hash)
Setting.set_all_from_params(params[:settings])
flash[:notice] = l(:notice_successful_update)
redirect_to settings_path(:tab => params[:tab])
else
@options = {}
user_format = User::USER_FORMATS.collect{|key, value| [key, value[:setting_order]]}.sort{|a, b| a[1] <=> b[1]}
@options[:user_format] = user_format.collect{|f| [User.current.name(f[0]), f[0].to_s]}
@deliveries = ActionMailer::Base.perform_deliveries
@guessed_host_and_path = request.host_with_port.dup
@guessed_host_and_path << ('/'+ Redmine::Utils.relative_url_root.gsub(%r{^\/}, '')) unless Redmine::Utils.relative_url_root.blank?
@commit_update_keywords = Setting.commit_update_keywords.dup
@commit_update_keywords = [{}] unless @commit_update_keywords.is_a?(Array) && @commit_update_keywords.any?
Redmine::Themes.rescan
if request.post?
errors = Setting.set_all_from_params(params[:settings])
if errors.blank?
flash[:notice] = l(:notice_successful_update)
redirect_to settings_path(:tab => params[:tab])
return
else
@setting_errors = errors
# render the edit form with error messages
end
end
@options = {}
user_format = User::USER_FORMATS.collect{|key, value| [key, value[:setting_order]]}.sort{|a, b| a[1] <=> b[1]}
@options[:user_format] = user_format.collect{|f| [User.current.name(f[0]), f[0].to_s]}
@deliveries = ActionMailer::Base.perform_deliveries
@guessed_host_and_path = request.host_with_port.dup
@guessed_host_and_path << ('/'+ Redmine::Utils.relative_url_root.gsub(%r{^\/}, '')) unless Redmine::Utils.relative_url_root.blank?
@commit_update_keywords = Setting.commit_update_keywords.dup
@commit_update_keywords = [{}] unless @commit_update_keywords.is_a?(Array) && @commit_update_keywords.any?
Redmine::Themes.rescan
end
def plugin
@@ -60,7 +67,8 @@ class SettingsController < ApplicationController
end
if request.post?
Setting.send "plugin_#{@plugin.id}=", params[:settings]
setting = params[:settings] ? params[:settings].permit!.to_h : {}
Setting.send "plugin_#{@plugin.id}=", setting
flash[:notice] = l(:notice_successful_update)
redirect_to plugin_settings_path(@plugin)
else

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -16,13 +16,13 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class SysController < ActionController::Base
before_filter :check_enabled
before_action :check_enabled
def projects
p = Project.active.has_module(:repository).
order("#{Project.table_name}.identifier").preload(:repository).to_a
# extra_info attribute from repository breaks activeresource client
render :xml => p.to_xml(
render :json => p.to_json(
:only => [:id, :identifier, :name, :is_public, :status],
:include => {:repository => {:only => [:id, :url]}}
)
@@ -31,15 +31,16 @@ class SysController < ActionController::Base
def create_project_repository
project = Project.find(params[:id])
if project.repository
render :nothing => true, :status => 409
head 409
else
logger.info "Repository for #{project.name} was reported to be created by #{request.remote_ip}."
repository = Repository.factory(params[:vendor], params[:repository])
repository = Repository.factory(params[:vendor])
repository.safe_attributes = params[:repository]
repository.project = project
if repository.save
render :xml => {repository.class.name.underscore.gsub('/', '-') => {:id => repository.id, :url => repository.url}}, :status => 201
render :json => {repository.class.name.underscore.gsub('/', '-') => {:id => repository.id, :url => repository.url}}, :status => 201
else
render :nothing => true, :status => 422
head 422
end
end
end
@@ -64,9 +65,9 @@ class SysController < ActionController::Base
repository.fetch_changesets
end
end
render :nothing => true, :status => 200
head 200
rescue ActiveRecord::RecordNotFound
render :nothing => true, :status => 404
head 404
end
protected
@@ -74,7 +75,7 @@ class SysController < ActionController::Base
def check_enabled
User.current = nil
unless Setting.sys_api_enabled? && params[:key].to_s == Setting.sys_api_key
render :text => 'Access denied. Repository management WS is disabled or key is invalid.', :status => 403
render :plain => 'Access denied. Repository management WS is disabled or key is invalid.', :status => 403
return false
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -16,23 +16,22 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class TimelogController < ApplicationController
menu_item :issues
menu_item :time_entries
before_filter :find_time_entry, :only => [:show, :edit, :update]
before_filter :check_editability, :only => [:edit, :update]
before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy]
before_filter :authorize, :only => [:show, :edit, :update, :bulk_edit, :bulk_update, :destroy]
before_action :find_time_entry, :only => [:show, :edit, :update]
before_action :check_editability, :only => [:edit, :update]
before_action :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy]
before_action :authorize, :only => [:show, :edit, :update, :bulk_edit, :bulk_update, :destroy]
before_filter :find_optional_project, :only => [:new, :create, :index, :report]
before_filter :authorize_global, :only => [:new, :create, :index, :report]
before_action :find_optional_issue, :only => [:new, :create]
before_action :find_optional_project, :only => [:index, :report]
before_action :authorize_global, :only => [:new, :create, :index, :report]
accept_rss_auth :index
accept_api_auth :index, :show, :create, :update, :destroy
rescue_from Query::StatementInvalid, :with => :query_statement_invalid
helper :sort
include SortHelper
helper :issues
include TimelogHelper
helper :custom_fields
@@ -41,20 +40,16 @@ class TimelogController < ApplicationController
include QueriesHelper
def index
@query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_')
sort_init(@query.sort_criteria.empty? ? [['spent_on', 'desc']] : @query.sort_criteria)
sort_update(@query.sortable_columns)
scope = time_entry_scope(:order => sort_clause).
includes(:project, :user, :issue).
preload(:issue => [:project, :tracker, :status, :assigned_to, :priority])
retrieve_time_entry_query
scope = time_entry_scope.
preload(:issue => [:project, :tracker, :status, :assigned_to, :priority]).
preload(:project, :user)
respond_to do |format|
format.html {
@entry_count = scope.count
@entry_pages = Paginator.new @entry_count, per_page_option, params['page']
@entries = scope.offset(@entry_pages.offset).limit(@entry_pages.per_page).to_a
@total_hours = scope.sum(:hours).to_f
render :layout => !request.xhr?
}
@@ -76,7 +71,7 @@ class TimelogController < ApplicationController
end
def report
@query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_')
retrieve_time_entry_query
scope = time_entry_scope
@report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], scope)
@@ -90,7 +85,7 @@ class TimelogController < ApplicationController
def show
respond_to do |format|
# TODO: Implement html response
format.html { render :nothing => true, :status => 406 }
format.html { head 406 }
format.api
end
end
@@ -172,25 +167,39 @@ class TimelogController < ApplicationController
def bulk_edit
@available_activities = @projects.map(&:activities).reduce(:&)
@custom_fields = TimeEntry.first.available_custom_fields
@custom_fields = TimeEntry.first.available_custom_fields.select {|field| field.format.bulk_edit_supported}
end
def bulk_update
attributes = parse_params_for_bulk_update(params[:time_entry])
unsaved_time_entry_ids = []
unsaved_time_entries = []
saved_time_entries = []
@time_entries.each do |time_entry|
time_entry.reload
time_entry.safe_attributes = attributes
call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry })
unless time_entry.save
logger.info "time entry could not be updated: #{time_entry.errors.full_messages}" if logger && logger.info?
# Keep unsaved time_entry ids to display them in flash error
unsaved_time_entry_ids << time_entry.id
if time_entry.save
saved_time_entries << time_entry
else
unsaved_time_entries << time_entry
end
end
set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids)
redirect_back_or_default project_time_entries_path(@projects.first)
if unsaved_time_entries.empty?
flash[:notice] = l(:notice_successful_update) unless saved_time_entries.empty?
redirect_back_or_default project_time_entries_path(@projects.first)
else
@saved_time_entries = @time_entries
@unsaved_time_entries = unsaved_time_entries
@time_entries = TimeEntry.where(:id => unsaved_time_entries.map(&:id)).
preload(:project => :time_entry_activities).
preload(:user).to_a
bulk_edit
render :action => 'bulk_edit'
end
end
def destroy
@@ -249,22 +258,17 @@ private
render_404
end
def set_flash_from_bulk_time_entry_save(time_entries, unsaved_time_entry_ids)
if unsaved_time_entry_ids.empty?
flash[:notice] = l(:notice_successful_update) unless time_entries.empty?
def find_optional_issue
if params[:issue_id].present?
@issue = Issue.find(params[:issue_id])
@project = @issue.project
else
flash[:error] = l(:notice_failed_to_save_time_entries,
:count => unsaved_time_entry_ids.size,
:total => time_entries.size,
:ids => '#' + unsaved_time_entry_ids.join(', #'))
find_optional_project
end
end
def find_optional_project
if params[:issue_id].present?
@issue = Issue.find(params[:issue_id])
@project = @issue.project
elsif params[:project_id].present?
if params[:project_id].present?
@project = Project.find(params[:project_id])
end
rescue ActiveRecord::RecordNotFound
@@ -273,10 +277,10 @@ private
# Returns the TimeEntry scope for index and report actions
def time_entry_scope(options={})
scope = @query.results_scope(options)
if @issue
scope = scope.on_issue(@issue)
end
scope
@query.results_scope(options)
end
def retrieve_time_entry_query
retrieve_query(TimeEntryQuery, false)
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -17,9 +17,10 @@
class TrackersController < 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
@@ -31,17 +32,19 @@ class TrackersController < ApplicationController
end
def new
@tracker ||= Tracker.new(params[:tracker])
@tracker ||= Tracker.new
@tracker.safe_attributes = params[:tracker]
@trackers = Tracker.sorted.to_a
@projects = Project.all
end
def create
@tracker = Tracker.new(params[:tracker])
@tracker = Tracker.new
@tracker.safe_attributes = params[:tracker]
if @tracker.save
# workflow copy
if !params[:copy_workflow_from].blank? && (copy_from = Tracker.find_by_id(params[:copy_workflow_from]))
@tracker.workflow_rules.copy(copy_from)
@tracker.copy_workflow_rules(copy_from)
end
flash[:notice] = l(:notice_successful_create)
redirect_to trackers_path
@@ -58,13 +61,14 @@ class TrackersController < ApplicationController
def update
@tracker = Tracker.find(params[:id])
if @tracker.update_attributes(params[:tracker])
@tracker.safe_attributes = params[:tracker]
if @tracker.save
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_update)
redirect_to trackers_path(:page => params[:page])
}
format.js { render :nothing => true }
format.js { head 200 }
end
else
respond_to do |format|
@@ -72,7 +76,7 @@ class TrackersController < ApplicationController
edit
render :action => 'edit'
}
format.js { render :nothing => true, :status => 422 }
format.js { head 422 }
end
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -17,9 +17,11 @@
class UsersController < ApplicationController
layout 'admin'
self.main_menu = false
before_filter :require_admin, :except => :show
before_filter :find_user, :only => [:show, :edit, :update, :destroy]
before_action :require_admin, :except => :show
before_action ->{ find_user(false) }, :only => :show
before_action :find_user, :only => [:edit, :update, :destroy]
accept_api_auth :index, :show, :create, :update, :destroy
helper :sort
@@ -68,7 +70,7 @@ class UsersController < ApplicationController
end
# show projects based on current user visibility
@memberships = @user.memberships.where(Project.visible_condition(User.current)).to_a
@memberships = @user.memberships.preload(:roles, :project).where(Project.visible_condition(User.current)).to_a
respond_to do |format|
format.html {
@@ -87,12 +89,10 @@ class UsersController < ApplicationController
end
def create
@user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
@user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option, :admin => false)
@user.safe_attributes = params[:user]
@user.admin = params[:user][:admin] || false
@user.login = params[:user][:login]
@user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id
@user.pref.attributes = params[:pref] if params[:pref]
@user.pref.safe_attributes = params[:pref]
if @user.save
Mailer.account_information(@user, @user.password).deliver if params[:send_information]
@@ -127,8 +127,6 @@ class UsersController < ApplicationController
end
def update
@user.admin = params[:user][:admin] if params[:user][:admin]
@user.login = params[:user][:login] if params[:user][:login]
if params[:user][:password].present? && (@user.auth_source_id.nil? || params[:user][:auth_source_id].blank?)
@user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation]
end
@@ -136,14 +134,14 @@ class UsersController < ApplicationController
# Was the account actived ? (do it before User#save clears the change)
was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE])
# TODO: Similar to My#account
@user.pref.attributes = params[:pref] if params[:pref]
@user.pref.safe_attributes = params[:pref]
if @user.save
@user.pref.save
if was_activated
Mailer.account_activated(@user).deliver
elsif @user.active? && params[:send_information] && @user.password.present? && @user.auth_source_id.nil? && @user != User.current
elsif @user.active? && params[:send_information] && @user != User.current
Mailer.account_information(@user, @user.password).deliver
end
@@ -177,10 +175,12 @@ class UsersController < ApplicationController
private
def find_user
def find_user(logged = true)
if params[:id] == 'current'
require_login || return
@user = User.current
elsif logged
@user = User.logged.find(params[:id])
else
@user = User.find(params[:id])
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -18,10 +18,10 @@
class VersionsController < ApplicationController
menu_item :roadmap
model_object Version
before_filter :find_model_object, :except => [:index, :new, :create, :close_completed]
before_filter :find_project_from_association, :except => [:index, :new, :create, :close_completed]
before_filter :find_project_by_project_id, :only => [:index, :new, :create, :close_completed]
before_filter :authorize
before_action :find_model_object, :except => [:index, :new, :create, :close_completed]
before_action :find_project_from_association, :except => [:index, :new, :create, :close_completed]
before_action :find_project_by_project_id, :only => [:index, :new, :create, :close_completed]
before_action :authorize
accept_api_auth :index, :show, :create, :update, :destroy
@@ -38,9 +38,9 @@ class VersionsController < ApplicationController
@versions = @project.shared_versions.preload(:custom_values)
@versions += @project.rolled_up_versions.visible.preload(:custom_values) if @with_subprojects
@versions = @versions.uniq.sort
@versions = @versions.to_a.uniq.sort
unless params[:completed]
@completed_versions = @versions.select(&:completed?)
@completed_versions = @versions.select(&:completed?).reverse
@versions -= @completed_versions
end
@@ -66,6 +66,7 @@ class VersionsController < ApplicationController
format.html {
@issues = @version.fixed_issues.visible.
includes(:status, :tracker, :priority).
preload(:project).
reorder("#{Tracker.table_name}.position, #{Issue.table_name}.id").
to_a
}

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -16,7 +16,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class WatchersController < ApplicationController
before_filter :require_login, :find_watchables, :only => [:watch, :unwatch]
before_action :require_login, :find_watchables, :only => [:watch, :unwatch]
def watch
set_watcher(@watchables, User.current, true)
@@ -26,7 +26,7 @@ class WatchersController < ApplicationController
set_watcher(@watchables, User.current, false)
end
before_filter :find_project, :authorize, :only => [:new, :create, :append, :destroy, :autocomplete_for_user]
before_action :find_project, :authorize, :only => [:new, :create, :append, :destroy, :autocomplete_for_user]
accept_api_auth :create, :destroy
def new
@@ -35,7 +35,7 @@ class WatchersController < ApplicationController
def create
user_ids = []
if params[:watcher].is_a?(Hash)
if params[:watcher]
user_ids << (params[:watcher][:user_ids] || params[:watcher][:user_id])
else
user_ids << params[:user_id]
@@ -47,19 +47,19 @@ class WatchersController < ApplicationController
end
end
respond_to do |format|
format.html { redirect_to_referer_or {render :text => 'Watcher added.', :layout => true}}
format.html { redirect_to_referer_or {render :html => 'Watcher added.', :status => 200, :layout => true}}
format.js { @users = users_for_new_watcher }
format.api { render_api_ok }
end
end
def append
if params[:watcher].is_a?(Hash)
if params[:watcher]
user_ids = params[:watcher][:user_ids] || [params[:watcher][:user_id]]
@users = User.active.visible.where(:id => user_ids).to_a
end
if @users.blank?
render :nothing => true
head 200
end
end
@@ -69,7 +69,7 @@ class WatchersController < ApplicationController
watchable.set_watcher(user, false)
end
respond_to do |format|
format.html { redirect_to :back }
format.html { redirect_to_referer_or {render :html => 'Watcher removed.', :status => 200, :layout => true} }
format.js
format.api { render_api_ok }
end
@@ -108,7 +108,10 @@ class WatchersController < ApplicationController
watchable.set_watcher(user, watching)
end
respond_to do |format|
format.html { redirect_to_referer_or {render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true}}
format.html {
text = watching ? 'Watcher added.' : 'Watcher removed.'
redirect_to_referer_or {render :html => text, :status => 200, :layout => true}
}
format.js { render :partial => 'set_watcher', :locals => {:user => user, :watched => watchables} }
end
end

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -16,7 +16,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class WelcomeController < ApplicationController
caches_action :robots
self.main_menu = false
def index
@news = News.latest User.current

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -31,11 +31,11 @@
# TODO: still being worked on
class WikiController < ApplicationController
default_search_scope :wiki_pages
before_filter :find_wiki, :authorize
before_filter :find_existing_or_new_page, :only => [:show, :edit, :update]
before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy, :destroy_version]
before_action :find_wiki, :authorize
before_action :find_existing_or_new_page, :only => [:show, :edit, :update]
before_action :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy, :destroy_version]
before_action :find_attachments, :only => [:preview]
accept_api_auth :index, :show, :update, :destroy
before_filter :find_attachments, :only => [:preview]
helper :attachments
include AttachmentsHelper
@@ -95,6 +95,9 @@ class WikiController < ApplicationController
end
return
end
call_hook :controller_wiki_show_before_render, content: @content, format: params[:format]
if User.current.allowed_to?(:export_wiki_pages, @project)
if params[:format] == 'pdf'
send_file_headers! :type => 'application/pdf', :filename => filename_for_content_disposition("#{@page.title}.pdf")
@@ -153,7 +156,7 @@ class WikiController < ApplicationController
@content = @page.content || WikiContent.new(:page => @page)
content_params = params[:content]
if content_params.nil? && params[:wiki_page].is_a?(Hash)
if content_params.nil? && params[:wiki_page].present?
content_params = params[:wiki_page].slice(:text, :comments, :version)
end
content_params ||= {}

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -17,7 +17,7 @@
class WikisController < ApplicationController
menu_item :settings
before_filter :find_project, :authorize
before_action :find_project, :authorize
# Create or update a project's wiki
def edit

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -17,8 +17,9 @@
class WorkflowsController < ApplicationController
layout 'admin'
self.main_menu = false
before_filter :require_admin
before_action :require_admin
def index
@roles = Role.sorted.select(&:consider_workflow?)
@@ -137,7 +138,11 @@ class WorkflowsController < ApplicationController
def find_statuses
@used_statuses_only = (params[:used_statuses_only] == '0' ? false : true)
if @trackers && @used_statuses_only
@statuses = @trackers.map(&:issue_statuses).flatten.uniq.sort.presence
role_ids = Role.all.select(&:consider_workflow?).map(&:id)
status_ids = WorkflowTransition.where(
:tracker_id => @trackers.map(&:id), :role_id => role_ids
).distinct.pluck(:old_status_id, :new_status_id).flatten.uniq
@statuses = IssueStatus.where(:id => status_ids).sorted.to_a.presence
end
@statuses ||= IssueStatus.sorted.to_a
end

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -198,6 +198,8 @@ module ApplicationHelper
l(:general_text_No)
when 'Issue'
object.visible? && html ? link_to_issue(object) : "##{object.id}"
when 'Attachment'
html ? link_to_attachment(object) : object.filename
when 'CustomValue', 'CustomFieldValue'
if object.custom_field
f = object.custom_field.format.formatted_custom_value(self, object, html)
@@ -219,20 +221,32 @@ module ApplicationHelper
end
def thumbnail_tag(attachment)
link_to image_tag(thumbnail_path(attachment)),
named_attachment_path(attachment, attachment.filename),
thumbnail_size = Setting.thumbnails_size.to_i
link_to(
image_tag(
thumbnail_path(attachment),
:srcset => "#{thumbnail_path(attachment, :size => thumbnail_size * 2)} 2x",
:style => "max-width: #{thumbnail_size}px; max-height: #{thumbnail_size}px;"
),
named_attachment_path(
attachment,
attachment.filename
),
:title => attachment.filename
)
end
def toggle_link(name, id, options={})
onclick = "$('##{id}').toggle(); "
onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ")
onclick << "$(window).scrollTop($('##{options[:focus]}').position().top); " if options[:scroll]
onclick << "return false;"
link_to(name, "#", :onclick => onclick)
end
# Used to format item titles on the activity view
def format_activity_title(text)
h(truncate_single_line_raw(text, 100))
text
end
def format_activity_day(date)
@@ -252,6 +266,11 @@ module ApplicationHelper
end
end
def format_changeset_comments(changeset, options={})
method = options[:short] ? :short_comments : :comments
textilizable changeset, method, :formatting => Setting.commit_logs_formatting?
end
def due_date_distance_in_words(date)
if date
l((date < User.current.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(User.current.today, date))
@@ -329,22 +348,54 @@ module ApplicationHelper
end
end
# Returns the default scope for the quick search form
# Could be 'all', 'my_projects', 'subprojects' or nil (current project)
def default_search_project_scope
if @project && !@project.leaf?
'subprojects'
end
end
# Returns an array of projects that are displayed in the quick-jump box
def projects_for_jump_box(user=User.current)
if user.logged?
user.projects.active.select(:id, :name, :identifier, :lft, :rgt).to_a
else
[]
end
end
def render_projects_for_jump_box(projects, selected=nil)
jump = params[:jump].presence || current_menu_item
s = ''.html_safe
project_tree(projects) do |project, level|
padding = level * 16
text = content_tag('span', project.name, :style => "padding-left:#{padding}px;")
s << link_to(text, project_path(project, :jump => jump), :title => project.name, :class => (project == selected ? 'selected' : nil))
end
s
end
# Renders the project quick-jump box
def render_project_jump_box
return unless User.current.logged?
projects = User.current.projects.active.select(:id, :name, :identifier, :lft, :rgt).to_a
if projects.any?
options =
("<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
'<option value="" disabled="disabled">---</option>').html_safe
options << project_tree_options_for_select(projects, :selected => @project) do |p|
{ :value => project_path(:id => p, :jump => current_menu_item) }
end
content_tag( :span, nil, :class => 'jump-box-arrow') +
select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }')
projects = projects_for_jump_box(User.current)
if @project && @project.persisted?
text = @project.name_was
end
text ||= l(:label_jump_to_a_project)
url = autocomplete_projects_path(:format => 'js', :jump => current_menu_item)
trigger = content_tag('span', text, :class => 'drdn-trigger')
q = text_field_tag('q', '', :id => 'projects-quick-search', :class => 'autocomplete', :data => {:automcomplete_url => url}, :autocomplete => 'off')
all = link_to(l(:label_project_all), projects_path(:jump => current_menu_item), :class => (@project.nil? && controller.class.main_menu ? 'selected' : nil))
content = content_tag('div',
content_tag('div', q, :class => 'quick-search') +
content_tag('div', render_projects_for_jump_box(projects, @project), :class => 'drdn-items projects selection') +
content_tag('div', all, :class => 'drdn-items all-projects selection'),
:class => 'drdn-content'
)
content_tag('div', trigger + content, :id => "project-jump", :class => "drdn")
end
def project_tree_options_for_select(projects, options = {})
@@ -372,8 +423,8 @@ module ApplicationHelper
# Yields the given block for each project with its level in the tree
#
# Wrapper for Project#project_tree
def project_tree(projects, &block)
Project.project_tree(projects, &block)
def project_tree(projects, options={}, &block)
Project.project_tree(projects, options, &block)
end
def principals_check_box_tags(name, principals)
@@ -424,7 +475,7 @@ module ApplicationHelper
end
def html_hours(text)
text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
text.gsub(%r{(\d+)([\.:])(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">\2\3</span>').html_safe
end
def authoring(created, author, options={})
@@ -441,9 +492,7 @@ module ApplicationHelper
end
def syntax_highlight_lines(name, content)
lines = []
syntax_highlight(name, content).each_line { |line| lines << line }
lines
syntax_highlight(name, content).each_line.to_a
end
def syntax_highlight(name, content)
@@ -562,6 +611,9 @@ module ApplicationHelper
css << 'project-' + @project.identifier if @project && @project.identifier.present?
css << 'controller-' + controller_name
css << 'action-' + action_name
if UserPreference::TEXTAREA_FONT_OPTIONS.include?(User.current.pref.textarea_font)
css << "textarea-#{User.current.pref.textarea_font}"
end
css.join(' ')
end
@@ -596,7 +648,13 @@ module ApplicationHelper
text = text.dup
macros = catch_macros(text)
text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
if options[:formatting] == false
text = h(text)
else
formatting = Setting.text_formatting
text = Redmine::WikiFormatting.to_html(formatting, text, :object => obj, :attribute => attr)
end
@parsed_headings = []
@heading_anchors = {}
@@ -604,7 +662,7 @@ module ApplicationHelper
parse_sections(text, project, obj, attr, only_path, options)
text = parse_non_pre_blocks(text, obj, macros) do |text|
[:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
[:parse_inline_attachments, :parse_hires_images, :parse_wiki_links, :parse_redmine_links].each do |method_name|
send method_name, text, project, obj, attr, only_path, options
end
end
@@ -649,6 +707,15 @@ module ApplicationHelper
parsed
end
# add srcset attribute to img tags if filename includes @2x, @3x, etc.
# to support hires displays
def parse_hires_images(text, project, obj, attr, only_path, options)
text.gsub!(/src="([^"]+@(\dx)\.(bmp|gif|jpg|jpe|jpeg|png))"/i) do |m|
filename, dpr = $1, $2
m + " srcset=\"#{filename} #{dpr}\""
end
end
def parse_inline_attachments(text, project, obj, attr, only_path, options)
return if options[:inline_attachments] == false
@@ -751,11 +818,23 @@ module ApplicationHelper
# source:some/file#L120 -> Link to line 120 of the file
# source:some/file@52#L120 -> Link to line 120 of the file's revision 52
# export:some/file -> Force the download of the file
# Forums:
# forum#1 -> Link to forum with id 1
# forum:Support -> Link to forum named "Support"
# forum:"Technical Support" -> Link to forum named "Technical Support"
# Forum messages:
# message#1218 -> Link to message with id 1218
# Projects:
# Projects:
# project:someproject -> Link to project named "someproject"
# project#3 -> Link to project with id 3
# News:
# news#2 -> Link to news item with id 1
# news:Greetings -> Link to news item named "Greetings"
# news:"First Release" -> Link to news item named "First Release"
# Users:
# user:jsmith -> Link to user with login jsmith
# @jsmith -> Link to user with login jsmith
# user#2 -> Link to user with id 2
#
# Links can refer other objects from other projects, using project identifier:
# identifier:r52
@@ -763,8 +842,20 @@ module ApplicationHelper
# identifier:version:1.0.0
# identifier:source:some/file
def parse_redmine_links(text, default_project, obj, attr, only_path, options)
text.gsub!(%r{<a( [^>]+?)?>(.*?)</a>|([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-_]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
tag_content, leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $2, $3, $4, $5, $6, $7, $12, $13, $10 || $14 || $20, $16 || $21, $17, $19
text.gsub!(LINKS_RE) do |_|
tag_content = $~[:tag_content]
leading = $~[:leading]
esc = $~[:esc]
project_prefix = $~[:project_prefix]
project_identifier = $~[:project_identifier]
prefix = $~[:prefix]
repo_prefix = $~[:repo_prefix]
repo_identifier = $~[:repo_identifier]
sep = $~[:sep1] || $~[:sep2] || $~[:sep3] || $~[:sep4]
identifier = $~[:identifier1] || $~[:identifier2] || $~[:identifier3]
comment_suffix = $~[:comment_suffix]
comment_id = $~[:comment_id]
if tag_content
$&
else
@@ -831,11 +922,12 @@ module ApplicationHelper
if p = Project.visible.find_by_id(oid)
link = link_to_project(p, {:only_path => only_path}, :class => 'project')
end
when 'user'
u = User.visible.where(:id => oid, :type => 'User').first
link = link_to_user(u) if u
end
elsif sep == ':'
# removes the double quotes if any
name = identifier.gsub(%r{^"(.*)"$}, "\\1")
name = CGI.unescapeHTML(name)
name = remove_double_quotes(identifier)
case prefix
when 'document'
if project && document = project.documents.visible.find_by_title(name)
@@ -885,13 +977,20 @@ module ApplicationHelper
attachments = options[:attachments] || []
attachments += obj.attachments if obj.respond_to?(:attachments)
if attachments && attachment = Attachment.latest_attach(attachments, name)
link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment')
link = link_to_attachment(attachment, :only_path => only_path, :class => 'attachment')
end
when 'project'
if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first
link = link_to_project(p, {:only_path => only_path}, :class => 'project')
end
when 'user'
u = User.visible.where(:login => name, :type => 'User').first
link = link_to_user(u) if u
end
elsif sep == "@"
name = remove_double_quotes(identifier)
u = User.visible.where(:login => name, :type => 'User').first
link = link_to_user(u) if u
end
end
(leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
@@ -899,6 +998,45 @@ module ApplicationHelper
end
end
LINKS_RE =
%r{
<a( [^>]+?)?>(?<tag_content>.*?)</a>|
(?<leading>[\s\(,\-\[\>]|^)
(?<esc>!)?
(?<project_prefix>(?<project_identifier>[a-z0-9\-_]+):)?
(?<prefix>attachment|document|version|forum|news|message|project|commit|source|export|user)?
(
(
(?<sep1>\#)|
(
(?<repo_prefix>(?<repo_identifier>[a-z0-9\-_]+)\|)?
(?<sep2>r)
)
)
(
(?<identifier1>\d+)
(?<comment_suffix>
(\#note)?
-(?<comment_id>\d+)
)?
)|
(
(?<sep3>:)
(?<identifier2>[^"\s<>][^\s<>]*?|"[^"]+?")
)|
(
(?<sep4>@)
(?<identifier3>[a-z0-9_\-@\.]*)
)
)
(?=
(?=[[:punct:]][^A-Za-z0-9_/])|
,|
\s|
\]|
<|
$)
}x
HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
def parse_sections(text, project, obj, attr, only_path, options)
@@ -1007,7 +1145,7 @@ module ApplicationHelper
div_class = 'toc'
div_class << ' right' if right_align
div_class << ' left' if left_align
out = "<ul class=\"#{div_class}\"><li>"
out = "<ul class=\"#{div_class}\"><li><strong>#{l :label_table_of_contents}</strong></li><li>"
root = headings.map(&:first).min
current = root
started = false
@@ -1110,6 +1248,11 @@ module ApplicationHelper
url = params[:back_url]
if url.nil? && referer = request.env['HTTP_REFERER']
url = CGI.unescape(referer.to_s)
# URLs that contains the utf8=[checkmark] parameter added by Rails are
# parsed as invalid by URI.parse so the redirect to the back URL would
# not be accepted (ApplicationController#validate_back_url would return
# false)
url.gsub!(/(\?|&)utf8=\u2713&?/, '\1')
end
url
end
@@ -1129,7 +1272,7 @@ module ApplicationHelper
link_to_function '',
"toggleCheckboxesBySelector('#{selector}')",
:title => "#{l(:button_check_all)} / #{l(:button_uncheck_all)}",
:class => 'toggle-checkboxes'
:class => 'icon icon-checked'
end
def progress_bar(pcts, options={})
@@ -1155,7 +1298,7 @@ module ApplicationHelper
end
end
def context_menu(url)
def context_menu
unless @context_menu_included
content_for :header_tags do
javascript_include_tag('context_menu') +
@@ -1168,7 +1311,7 @@ module ApplicationHelper
end
@context_menu_included = true
end
javascript_tag "contextMenuInit('#{ url_for(url) }')"
nil
end
def calendar_for(field_id)
@@ -1371,7 +1514,9 @@ module ApplicationHelper
return self
end
def link_to_content_update(text, url_params = {}, html_options = {})
link_to(text, url_params, html_options)
# remove double quotes if any
def remove_double_quotes(identifier)
name = identifier.gsub(%r{^"(.*)"$}, "\\1")
return CGI.unescapeHTML(name)
end
end

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -34,7 +34,12 @@ module AttachmentsHelper
def link_to_attachments(container, options = {})
options.assert_valid_keys(:author, :thumbnails)
attachments = container.attachments.preload(:author).to_a
attachments = if container.attachments.loaded?
container.attachments
else
container.attachments.preload(:author).to_a
end
if attachments.any?
options = {
:editable => container.attachments_editable?,
@@ -51,19 +56,26 @@ module AttachmentsHelper
end
end
def render_api_attachment(attachment, api)
def render_api_attachment(attachment, api, options={})
api.attachment do
api.id attachment.id
api.filename attachment.filename
api.filesize attachment.filesize
api.content_type attachment.content_type
api.description attachment.description
api.content_url download_named_attachment_url(attachment, attachment.filename)
if attachment.thumbnailable?
api.thumbnail_url thumbnail_url(attachment)
end
api.author(:id => attachment.author.id, :name => attachment.author.name) if attachment.author
api.created_on attachment.created_on
render_api_attachment_attributes(attachment, api)
options.each { |key, value| eval("api.#{key} value") }
end
end
def render_api_attachment_attributes(attachment, api)
api.id attachment.id
api.filename attachment.filename
api.filesize attachment.filesize
api.content_type attachment.content_type
api.description attachment.description
api.content_url download_named_attachment_url(attachment, attachment.filename)
if attachment.thumbnailable?
api.thumbnail_url thumbnail_url(attachment)
end
if attachment.author
api.author(:id => attachment.author.id, :name => attachment.author.name)
end
api.created_on attachment.created_on
end
end

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -53,6 +53,6 @@ module CalendarsHelper
end
def link_to_month(link_name, year, month, options={})
link_to_content_update(h(link_name), params.merge(:year => year, :month => month), options)
link_to(link_name, {:params => request.query_parameters.merge(:year => year, :month => month)}, options)
end
end

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -23,8 +23,8 @@ module GanttHelper
case in_or_out
when :in
if gantt.zoom < 4
link_to_content_update l(:text_zoom_in),
params.merge(gantt.params.merge(:zoom => (gantt.zoom + 1))),
link_to l(:text_zoom_in),
{:params => request.query_parameters.merge(gantt.params.merge(:zoom => (gantt.zoom + 1)))},
:class => 'icon icon-zoom-in'
else
content_tag(:span, l(:text_zoom_in), :class => 'icon icon-zoom-in').html_safe
@@ -32,8 +32,8 @@ module GanttHelper
when :out
if gantt.zoom > 1
link_to_content_update l(:text_zoom_out),
params.merge(gantt.params.merge(:zoom => (gantt.zoom - 1))),
link_to l(:text_zoom_out),
{:params => request.query_parameters.merge(gantt.params.merge(:zoom => (gantt.zoom - 1)))},
:class => 'icon icon-zoom-out'
else
content_tag(:span, l(:text_zoom_out), :class => 'icon icon-zoom-out').html_safe

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -32,29 +32,14 @@ module IssuesHelper
end
end
def grouped_issue_list(issues, query, issue_count_by_group, &block)
previous_group, first = false, true
totals_by_group = query.totalable_columns.inject({}) do |h, column|
h[column] = query.total_by_group_for(column)
h
end
issue_list(issues) do |issue, level|
group_name = group_count = nil
if query.grouped?
group = query.group_by_column.value(issue)
if first || group != previous_group
if group.blank? && group != false
group_name = "(#{l(:label_blank_value)})"
else
group_name = format_object(group)
end
group_name ||= ""
group_count = issue_count_by_group[group]
group_totals = totals_by_group.map {|column, t| total_tag(column, t[group] || 0)}.join(" ").html_safe
end
def grouped_issue_list(issues, query, &block)
ancestors = []
grouped_query_results(issues, query) do |issue, group_name, group_count, group_totals|
while (ancestors.any? && !issue.is_descendant_of?(ancestors.last))
ancestors.pop
end
yield issue, level, group_name, group_count, group_totals
previous_group, first = group, false
yield issue, ancestors.size, group_name, group_count, group_totals
ancestors << issue unless issue.leaf?
end
end
@@ -105,7 +90,7 @@ module IssuesHelper
end
def render_descendants_tree(issue)
s = '<form><table class="list issues">'
s = '<table class="list issues odd-even">'
issue_list(issue.descendants.visible.preload(:status, :priority, :tracker, :assigned_to).sort_by(&:lft)) do |child, level|
css = "issue issue-#{child.id} hascontextmenu #{child.css_classes}"
css << " idnt idnt-#{level}" if level > 0
@@ -117,10 +102,42 @@ module IssuesHelper
content_tag('td', child.disabled_core_fields.include?('done_ratio') ? '' : progress_bar(child.done_ratio), :class=> 'done_ratio'),
:class => css)
end
s << '</table></form>'
s << '</table>'
s.html_safe
end
# Renders the list of related issues on the issue details view
def render_issue_relations(issue, relations)
manage_relations = User.current.allowed_to?(:manage_issue_relations, issue.project)
s = ''.html_safe
relations.each do |relation|
other_issue = relation.other_issue(issue)
css = "issue hascontextmenu #{other_issue.css_classes}"
link = manage_relations ? link_to(l(:label_relation_delete),
relation_path(relation),
:remote => true,
:method => :delete,
:data => {:confirm => l(:text_are_you_sure)},
:title => l(:label_relation_delete),
:class => 'icon-only icon-link-break'
) : nil
s << content_tag('tr',
content_tag('td', check_box_tag("ids[]", other_issue.id, false, :id => nil), :class => 'checkbox') +
content_tag('td', relation.to_s(@issue) {|other| link_to_issue(other, :project => Setting.cross_project_issue_relations?)}.html_safe, :class => 'subject', :style => 'width: 50%') +
content_tag('td', other_issue.status, :class => 'status') +
content_tag('td', format_date(other_issue.start_date), :class => 'start_date') +
content_tag('td', format_date(other_issue.due_date), :class => 'due_date') +
content_tag('td', other_issue.disabled_core_fields.include?('done_ratio') ? '' : progress_bar(other_issue.done_ratio), :class=> 'done_ratio') +
content_tag('td', link, :class => 'buttons'),
:id => "relation-#{relation.id}",
:class => css)
end
content_tag('table', s, :class => 'list issues odd-even')
end
def issue_estimated_hours_details(issue)
if issue.total_estimated_hours.present?
if issue.total_estimated_hours == issue.estimated_hours
@@ -135,11 +152,13 @@ module IssuesHelper
def issue_spent_hours_details(issue)
if issue.total_spent_hours > 0
path = project_time_entries_path(issue.project, :issue_id => "~#{issue.id}")
if issue.total_spent_hours == issue.spent_hours
link_to(l_hours_short(issue.spent_hours), issue_time_entries_path(issue))
link_to(l_hours_short(issue.spent_hours), path)
else
s = issue.spent_hours > 0 ? l_hours_short(issue.spent_hours) : ""
s << " (#{l(:label_total)}: #{link_to l_hours_short(issue.total_spent_hours), issue_time_entries_path(issue)})"
s << " (#{l(:label_total)}: #{link_to l_hours_short(issue.total_spent_hours), path})"
s.html_safe
end
end
@@ -165,7 +184,7 @@ module IssuesHelper
:parent_issue_id => issue
}
attrs[:tracker_id] = issue.tracker unless issue.tracker.disabled_core_fields.include?('parent_issue_id')
link_to(l(:button_add), new_project_issue_path(issue.project, :issue => attrs))
link_to(l(:button_add), new_project_issue_path(issue.project, :issue => attrs, :back_url => issue_path(issue)))
end
def trackers_options_for_select(issue)
@@ -220,19 +239,45 @@ module IssuesHelper
r.to_html
end
def render_custom_fields_rows(issue)
values = issue.visible_custom_field_values
def render_half_width_custom_fields_rows(issue)
values = issue.visible_custom_field_values.reject {|value| value.custom_field.full_width_layout?}
return if values.empty?
half = (values.size / 2.0).ceil
issue_fields_rows do |rows|
values.each_with_index do |value, i|
css = "cf_#{value.custom_field.id}"
attr_value = show_value(value)
if value.custom_field.text_formatting == 'full'
attr_value = content_tag('div', attr_value, class: 'wiki')
end
m = (i < half ? :left : :right)
rows.send m, custom_field_name_tag(value.custom_field), show_value(value), :class => css
rows.send m, custom_field_name_tag(value.custom_field), attr_value, :class => css
end
end
end
def render_full_width_custom_fields_rows(issue)
values = issue.visible_custom_field_values.select {|value| value.custom_field.full_width_layout?}
return if values.empty?
s = ''.html_safe
values.each_with_index do |value, i|
attr_value = show_value(value)
next if attr_value.blank?
if value.custom_field.text_formatting == 'full'
attr_value = content_tag('div', attr_value, class: 'wiki')
end
content =
content_tag('hr') +
content_tag('p', content_tag('strong', custom_field_name_tag(value.custom_field) )) +
content_tag('div', attr_value, class: 'value')
s << content_tag('div', content, class: "cf_#{value.custom_field.id} attribute")
end
s
end
# Returns the path for updating the issue form
# with project as the current project
def update_issue_form_path(project, issue)
@@ -269,64 +314,38 @@ module IssuesHelper
# Returns an array of users that are proposed as watchers
# on the new issue form
def users_for_new_issue_watchers(issue)
users = issue.watcher_users
users = issue.watcher_users.select{|u| u.status == User::STATUS_ACTIVE}
if issue.project.users.count <= 20
users = (users + issue.project.users.sort).uniq
end
users
end
def sidebar_queries
unless @sidebar_queries
@sidebar_queries = IssueQuery.visible.
order("#{Query.table_name}.name ASC").
# Project specific queries and global queries
where(@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id]).
to_a
end
@sidebar_queries
end
def query_links(title, queries)
return '' if queries.empty?
# links to #index on issues/show
url_params = controller_name == 'issues' ? {:controller => 'issues', :action => 'index', :project_id => @project} : params
content_tag('h3', title) + "\n" +
content_tag('ul',
queries.collect {|query|
css = 'query'
css << ' selected' if query == @query
content_tag('li', link_to(query.name, url_params.merge(:query_id => query), :class => css))
}.join("\n").html_safe,
:class => 'queries'
) + "\n"
end
def render_sidebar_queries
out = ''.html_safe
out << query_links(l(:label_my_queries), sidebar_queries.select(&:is_private?))
out << query_links(l(:label_query_plural), sidebar_queries.reject(&:is_private?))
out
end
def email_issue_attributes(issue, user)
def email_issue_attributes(issue, user, html)
items = []
%w(author status priority assigned_to category fixed_version).each do |attribute|
unless issue.disabled_core_fields.include?(attribute+"_id")
items << "#{l("field_#{attribute}")}: #{issue.send attribute}"
if html
items << content_tag('strong', "#{l("field_#{attribute}")}: ") + (issue.send attribute)
else
items << "#{l("field_#{attribute}")}: #{issue.send attribute}"
end
end
end
issue.visible_custom_field_values(user).each do |value|
items << "#{value.custom_field.name}: #{show_value(value, false)}"
if html
items << content_tag('strong', "#{value.custom_field.name}: ") + show_value(value, false)
else
items << "#{value.custom_field.name}: #{show_value(value, false)}"
end
end
items
end
def render_email_issue_attributes(issue, user, html=false)
items = email_issue_attributes(issue, user)
items = email_issue_attributes(issue, user, html)
if html
content_tag('ul', items.map{|s| content_tag('li', s)}.join("\n").html_safe)
content_tag('ul', items.map{|s| content_tag('li', s)}.join("\n").html_safe, :class => "details")
else
items.map{|s| "* #{s}"}.join("\n")
end
@@ -376,6 +395,7 @@ module IssuesHelper
def show_detail(detail, no_html=false, options={})
multiple = false
show_diff = false
no_details = false
case detail.property
when 'attr'
@@ -411,7 +431,9 @@ module IssuesHelper
custom_field = detail.custom_field
if custom_field
label = custom_field.name
if custom_field.format.class.change_as_diff
if custom_field.format.class.change_no_details
no_details = true
elsif custom_field.format.class.change_as_diff
show_diff = true
else
multiple = custom_field.multiple?
@@ -450,21 +472,19 @@ module IssuesHelper
if detail.property == 'attachment' && value.present? &&
atta = detail.journal.journalized.attachments.detect {|a| a.id == detail.prop_key.to_i}
# Link to the attachment if it has not been removed
value = link_to_attachment(atta, :download => true, :only_path => options[:only_path])
if options[:only_path] != false && (atta.is_text? || atta.is_image?)
value = link_to_attachment(atta, only_path: options[:only_path])
if options[:only_path] != false
value += ' '
value += link_to(l(:button_view),
{ :controller => 'attachments', :action => 'show',
:id => atta, :filename => atta.filename },
:class => 'icon-only icon-magnifier',
:title => l(:button_view))
value += link_to_attachment atta, class: 'icon-only icon-download', title: l(:button_download), download: true
end
else
value = content_tag("i", h(value)) if value
end
end
if show_diff
if no_details
s = l(:text_journal_changed_no_detail, :label => label).html_safe
elsif show_diff
s = l(:text_journal_changed_no_detail, :label => label)
unless no_html
diff_link = link_to 'diff',

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -27,9 +27,9 @@ module JournalsHelper
def render_notes(issue, journal, options={})
content = ''
editable = User.current.logged? && (User.current.allowed_to?(:edit_issue_notes, issue.project) || (journal.user == User.current && User.current.allowed_to?(:edit_own_issue_notes, issue.project)))
css_classes = "wiki"
links = []
if !journal.notes.blank?
if journal.notes.present?
links << link_to(l(:button_quote),
quoted_issue_path(issue, :journal_id => journal),
:remote => true,
@@ -37,25 +37,33 @@ module JournalsHelper
:title => l(:button_quote),
:class => 'icon-only icon-comment'
) if options[:reply_links]
links << link_to(l(:button_edit),
edit_journal_path(journal),
:remote => true,
:method => 'get',
:title => l(:button_edit),
:class => 'icon-only icon-edit'
) if editable
links << link_to(l(:button_delete),
journal_path(journal, :notes => ""),
:remote => true,
:method => 'put', :data => {:confirm => l(:text_are_you_sure)},
:title => l(:button_delete),
:class => 'icon-only icon-del'
) if editable
if journal.editable_by?(User.current)
links << link_to(l(:button_edit),
edit_journal_path(journal),
:remote => true,
:method => 'get',
:title => l(:button_edit),
:class => 'icon-only icon-edit'
)
links << link_to(l(:button_delete),
journal_path(journal, :journal => {:notes => ""}),
:remote => true,
:method => 'put', :data => {:confirm => l(:text_are_you_sure)},
:title => l(:button_delete),
:class => 'icon-only icon-del'
)
css_classes << " editable"
end
end
content << content_tag('div', links.join(' ').html_safe, :class => 'contextual') unless links.empty?
content << textilizable(journal, :notes)
css_classes = "wiki"
css_classes << " editable" if editable
content_tag('div', content.html_safe, :id => "journal-#{journal.id}-notes", :class => css_classes)
end
def render_private_notes_indicator(journal)
content = journal.private_notes? ? l(:field_is_private) : ''
css_classes = journal.private_notes? ? 'private' : ''
content_tag('span', content.html_safe, :id => "journal-#{journal.id}-private_notes", :class => css_classes)
end
end

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -18,58 +18,150 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module MyHelper
def calendar_items(startdt, enddt)
Issue.visible.
# Renders the blocks
def render_blocks(blocks, user, options={})
s = ''.html_safe
if blocks.present?
blocks.each do |block|
s << render_block(block, user).to_s
end
end
s
end
# Renders a single block
def render_block(block, user)
content = render_block_content(block, user)
if content.present?
handle = content_tag('span', '', :class => 'sort-handle', :title => l(:button_move))
close = link_to(l(:button_delete),
{:action => "remove_block", :block => block},
:remote => true, :method => 'post',
:class => "icon-only icon-close", :title => l(:button_delete))
content = content_tag('div', handle + close, :class => 'contextual') + content
content_tag('div', content, :class => "mypage-box", :id => "block-#{block}")
end
end
# Renders a single block content
def render_block_content(block, user)
unless block_definition = Redmine::MyPage.find_block(block)
Rails.logger.warn("Unknown block \"#{block}\" found in #{user.login} (id=#{user.id}) preferences")
return
end
settings = user.pref.my_page_settings(block)
if partial = block_definition[:partial]
begin
render(:partial => partial, :locals => {:user => user, :settings => settings, :block => block})
rescue ActionView::MissingTemplate
Rails.logger.warn("Partial \"#{partial}\" missing for block \"#{block}\" found in #{user.login} (id=#{user.id}) preferences")
return nil
end
else
send "render_#{block_definition[:name]}_block", block, settings
end
end
# Returns the select tag used to add a block to My page
def block_select_tag(user)
blocks_in_use = user.pref.my_page_layout.values.flatten
options = content_tag('option')
Redmine::MyPage.block_options(blocks_in_use).each do |label, block|
options << content_tag('option', label, :value => block, :disabled => block.blank?)
end
select_tag('block', options, :id => "block-select", :onchange => "$('#block-form').submit();")
end
def render_calendar_block(block, settings)
calendar = Redmine::Helpers::Calendar.new(User.current.today, current_language, :week)
calendar.events = Issue.visible.
where(:project_id => User.current.projects.map(&:id)).
where("(start_date>=? and start_date<=?) or (due_date>=? and due_date<=?)", startdt, enddt, startdt, enddt).
where("(start_date>=? and start_date<=?) or (due_date>=? and due_date<=?)", calendar.startdt, calendar.enddt, calendar.startdt, calendar.enddt).
includes(:project, :tracker, :priority, :assigned_to).
references(:project, :tracker, :priority, :assigned_to).
to_a
render :partial => 'my/blocks/calendar', :locals => {:calendar => calendar, :block => block}
end
def documents_items
Document.visible.order("#{Document.table_name}.created_on DESC").limit(10).to_a
def render_documents_block(block, settings)
documents = Document.visible.order("#{Document.table_name}.created_on DESC").limit(10).to_a
render :partial => 'my/blocks/documents', :locals => {:block => block, :documents => documents}
end
def issuesassignedtome_items
Issue.visible.open.
assigned_to(User.current).
limit(10).
includes(:status, :project, :tracker, :priority).
references(:status, :project, :tracker, :priority).
order("#{IssuePriority.table_name}.position DESC, #{Issue.table_name}.updated_on DESC")
def render_issuesassignedtome_block(block, settings)
query = IssueQuery.new(:name => l(:label_assigned_to_me_issues), :user => User.current)
query.add_filter 'assigned_to_id', '=', ['me']
query.column_names = settings[:columns].presence || ['project', 'tracker', 'status', 'subject']
query.sort_criteria = settings[:sort].presence || [['priority', 'desc'], ['updated_on', 'desc']]
issues = query.issues(:limit => 10)
render :partial => 'my/blocks/issues', :locals => {:query => query, :issues => issues, :block => block}
end
def issuesreportedbyme_items
Issue.visible.open.
where(:author_id => User.current.id).
limit(10).
includes(:status, :project, :tracker).
references(:status, :project, :tracker).
order("#{Issue.table_name}.updated_on DESC")
def render_issuesreportedbyme_block(block, settings)
query = IssueQuery.new(:name => l(:label_reported_issues), :user => User.current)
query.add_filter 'author_id', '=', ['me']
query.column_names = settings[:columns].presence || ['project', 'tracker', 'status', 'subject']
query.sort_criteria = settings[:sort].presence || [['updated_on', 'desc']]
issues = query.issues(:limit => 10)
render :partial => 'my/blocks/issues', :locals => {:query => query, :issues => issues, :block => block}
end
def issueswatched_items
Issue.visible.open.on_active_project.watched_by(User.current.id).recently_updated.limit(10)
def render_issueswatched_block(block, settings)
query = IssueQuery.new(:name => l(:label_watched_issues), :user => User.current)
query.add_filter 'watcher_id', '=', ['me']
query.column_names = settings[:columns].presence || ['project', 'tracker', 'status', 'subject']
query.sort_criteria = settings[:sort].presence || [['updated_on', 'desc']]
issues = query.issues(:limit => 10)
render :partial => 'my/blocks/issues', :locals => {:query => query, :issues => issues, :block => block}
end
def news_items
News.visible.
def render_issuequery_block(block, settings)
query = IssueQuery.visible.find_by_id(settings[:query_id])
if query
query.column_names = settings[:columns] if settings[:columns].present?
query.sort_criteria = settings[:sort] if settings[:sort].present?
issues = query.issues(:limit => 10)
render :partial => 'my/blocks/issues', :locals => {:query => query, :issues => issues, :block => block, :settings => settings}
else
queries = IssueQuery.visible.sorted
render :partial => 'my/blocks/issue_query_selection', :locals => {:queries => queries, :block => block, :settings => settings}
end
end
def render_news_block(block, settings)
news = News.visible.
where(:project_id => User.current.projects.map(&:id)).
limit(10).
includes(:project, :author).
references(:project, :author).
order("#{News.table_name}.created_on DESC").
to_a
render :partial => 'my/blocks/news', :locals => {:block => block, :news => news}
end
def timelog_items
TimeEntry.
where("#{TimeEntry.table_name}.user_id = ? AND #{TimeEntry.table_name}.spent_on BETWEEN ? AND ?", User.current.id, User.current.today - 6, User.current.today).
def render_timelog_block(block, settings)
days = settings[:days].to_i
days = 7 if days < 1 || days > 365
entries = TimeEntry.
where("#{TimeEntry.table_name}.user_id = ? AND #{TimeEntry.table_name}.spent_on BETWEEN ? AND ?", User.current.id, User.current.today - (days - 1), User.current.today).
joins(:activity, :project).
references(:issue => [:tracker, :status]).
includes(:issue => [:tracker, :status]).
order("#{TimeEntry.table_name}.spent_on DESC, #{Project.table_name}.name ASC, #{Tracker.table_name}.position ASC, #{Issue.table_name}.id ASC").
to_a
entries_by_day = entries.group_by(&:spent_on)
render :partial => 'my/blocks/timelog', :locals => {:block => block, :entries => entries, :entries_by_day => entries_by_day, :days => days}
end
end

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -46,6 +46,14 @@ module PrincipalMembershipsHelper
end
end
def edit_principal_membership_path(principal, *args)
if principal.is_a?(Group)
edit_group_membership_path(principal, *args)
else
edit_user_membership_path(principal, *args)
end
end
def principal_membership_path(principal, membership, *args)
if principal.is_a?(Group)
group_membership_path(principal, membership, *args)

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -22,7 +22,8 @@ module ProjectsHelper
tabs = [{:name => 'info', :action => :edit_project, :partial => 'projects/edit', :label => :label_information_plural},
{:name => 'modules', :action => :select_project_modules, :partial => 'projects/settings/modules', :label => :label_module_plural},
{:name => 'members', :action => :manage_members, :partial => 'projects/settings/members', :label => :label_member_plural},
{:name => 'versions', :action => :manage_versions, :partial => 'projects/settings/versions', :label => :label_version_plural},
{:name => 'versions', :action => :manage_versions, :partial => 'projects/settings/versions', :label => :label_version_plural,
:url => {:tab => 'versions', :version_status => params[:version_status], :version_name => params[:version_name]}},
{:name => 'categories', :action => :manage_categories, :partial => 'projects/settings/issue_categories', :label => :label_issue_category_plural},
{:name => 'wiki', :action => :manage_wiki, :partial => 'projects/settings/wiki', :label => :label_wiki},
{:name => 'repositories', :action => :manage_repository, :partial => 'projects/settings/repositories', :label => :label_repository_plural},
@@ -47,24 +48,17 @@ module ProjectsHelper
end
def render_project_action_links
links = []
links = "".html_safe
if User.current.allowed_to?(:add_project, nil, :global => true)
links << link_to(l(:label_project_new), new_project_path, :class => 'icon icon-add')
end
if User.current.allowed_to?(:view_issues, nil, :global => true)
links << link_to(l(:label_issue_view_all), issues_path)
end
if User.current.allowed_to?(:view_time_entries, nil, :global => true)
links << link_to(l(:label_overall_spent_time), time_entries_path)
end
links << link_to(l(:label_overall_activity), activity_path)
links.join(" | ").html_safe
links
end
# Renders the projects index
def render_project_hierarchy(projects)
render_project_nested_lists(projects) do |project|
s = link_to_project(project, {}, :class => "#{project.css_classes} #{User.current.member_of?(project) ? 'my-project' : nil}")
s = link_to_project(project, {}, :class => "#{project.css_classes} #{User.current.member_of?(project) ? 'icon icon-fav my-project' : nil}")
if project.description.present?
s << content_tag('div', textilizable(project.short_description, :project => project), :class => 'wiki description')
end
@@ -95,6 +89,11 @@ module ProjectsHelper
version_options_for_select(versions, project.default_version)
end
def project_default_assigned_to_options(project)
assignable_users = (project.assignable_users.to_a + [project.default_assigned_to]).uniq.compact
principals_options_for_select(assignable_users, project.default_assigned_to)
end
def format_version_sharing(sharing)
sharing = 'none' unless Version::VERSION_SHARINGS.include?(sharing)
l("label_version_sharing_#{sharing}")
@@ -126,11 +125,16 @@ module ProjectsHelper
end
end if include_in_api_response?('issue_categories')
api.array :time_entry_activities do
project.activities.each do |activity|
api.time_entry_activity(:id => activity.id, :name => activity.name)
end
end if include_in_api_response?('time_entry_activities')
api.array :enabled_modules do
project.enabled_modules.each do |enabled_module|
api.enabled_module(:id => enabled_module.id, :name => enabled_module.name)
end
end if include_in_api_response?('enabled_modules')
end
end

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -24,11 +24,15 @@ module QueriesHelper
ungrouped = []
grouped = {}
query.available_filters.map do |field, field_options|
if [:tree, :relation].include?(field_options[:type])
if field_options[:type] == :relation
group = :label_relations
elsif field_options[:type] == :tree
group = query.is_a?(IssueQuery) ? :label_relations : nil
elsif field =~ /^cf_\d+\./
group = (field_options[:through] || field_options[:field]).try(:name)
elsif field =~ /^(.+)\./
# association filters
group = "field_#{$1}"
group = "field_#{$1}".to_sym
elsif %w(member_of_group assigned_to_role).include?(field)
group = :field_assigned_to
elsif field_options[:type] == :date_past || field_options[:type] == :date
@@ -41,12 +45,12 @@ module QueriesHelper
end
end
# Don't group dates if there's only one (eg. time entries filters)
if grouped[:label_date].try(:size) == 1
if grouped[:label_date].try(:size) == 1
ungrouped << grouped.delete(:label_date).first
end
s = options_for_select([[]] + ungrouped)
if grouped.present?
localized_grouped = grouped.map {|k,v| [l(k), v]}
localized_grouped = grouped.map {|k,v| [k.is_a?(Symbol) ? l(k) : k.to_s, v]}
s << grouped_options_for_select(localized_grouped)
end
s
@@ -76,6 +80,11 @@ module QueriesHelper
query_filters_hidden_tags(query) + query_columns_hidden_tags(query)
end
def group_by_column_select_tag(query)
options = [[]] + query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}
select_tag('group_by', options_for_select(options, @query.group_by))
end
def available_block_columns_tags(query)
tags = ''.html_safe
query.available_block_columns.each do |column|
@@ -106,6 +115,33 @@ module QueriesHelper
render :partial => 'queries/columns', :locals => {:query => query, :tag_name => tag_name}
end
def grouped_query_results(items, query, &block)
result_count_by_group = query.result_count_by_group
previous_group, first = false, true
totals_by_group = query.totalable_columns.inject({}) do |h, column|
h[column] = query.total_by_group_for(column)
h
end
items.each do |item|
group_name = group_count = nil
if query.grouped?
group = query.group_by_column.value(item)
if first || group != previous_group
if group.blank? && group != false
group_name = "(#{l(:label_blank_value)})"
else
group_name = format_object(group)
end
group_name ||= ""
group_count = result_count_by_group ? result_count_by_group[group] : nil
group_totals = totals_by_group.map {|column, t| total_tag(column, t[group] || 0)}.join(" ").html_safe
end
end
yield item, group_name, group_count, group_totals
previous_group, first = group, false
end
end
def render_query_totals(query)
return unless query.totalable_columns.present?
totals = query.totalable_columns.map do |column|
@@ -116,83 +152,125 @@ module QueriesHelper
def total_tag(column, value)
label = content_tag('span', "#{column.caption}:")
value = content_tag('span', format_object(value), :class => 'value')
value = if [:hours, :spent_hours, :total_spent_hours, :estimated_hours].include? column.name
format_hours(value)
else
format_object(value)
end
value = content_tag('span', value, :class => 'value')
content_tag('span', label + " " + value, :class => "total-for-#{column.name.to_s.dasherize}")
end
def column_header(column)
column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption,
:default_order => column.default_order) :
content_tag('th', h(column.caption))
def column_header(query, column, options={})
if column.sortable?
css, order = nil, column.default_order
if column.name.to_s == query.sort_criteria.first_key
if query.sort_criteria.first_asc?
css = 'sort asc'
order = 'desc'
else
css = 'sort desc'
order = 'asc'
end
end
param_key = options[:sort_param] || :sort
sort_param = { param_key => query.sort_criteria.add(column.name, order).to_param }
while sort_param.keys.first.to_s =~ /^(.+)\[(.+)\]$/
sort_param = {$1 => {$2 => sort_param.values.first}}
end
link_options = {
:title => l(:label_sort_by, "\"#{column.caption}\""),
:class => css
}
if options[:sort_link_options]
link_options.merge! options[:sort_link_options]
end
content = link_to(column.caption,
{:params => request.query_parameters.deep_merge(sort_param)},
link_options
)
else
content = column.caption
end
content_tag('th', content)
end
def column_content(column, issue)
value = column.value_object(issue)
def column_content(column, item)
value = column.value_object(item)
if value.is_a?(Array)
values = value.collect {|v| column_value(column, issue, v)}.compact
values = value.collect {|v| column_value(column, item, v)}.compact
safe_join(values, ', ')
else
column_value(column, issue, value)
column_value(column, item, value)
end
end
def column_value(column, issue, value)
def column_value(column, item, value)
case column.name
when :id
link_to value, issue_path(issue)
link_to value, issue_path(item)
when :subject
link_to value, issue_path(issue)
link_to value, issue_path(item)
when :parent
value ? (value.visible? ? link_to_issue(value, :subject => false) : "##{value.id}") : ''
when :description
issue.description? ? content_tag('div', textilizable(issue, :description), :class => "wiki") : ''
item.description? ? content_tag('div', textilizable(item, :description), :class => "wiki") : ''
when :last_notes
item.last_notes.present? ? content_tag('div', textilizable(item, :last_notes), :class => "wiki") : ''
when :done_ratio
progress_bar(value)
when :relations
content_tag('span',
value.to_s(issue) {|other| link_to_issue(other, :subject => false, :tracker => false)}.html_safe,
:class => value.css_classes_for(issue))
value.to_s(item) {|other| link_to_issue(other, :subject => false, :tracker => false)}.html_safe,
:class => value.css_classes_for(item))
when :hours, :estimated_hours
format_hours(value)
when :spent_hours
link_to_if(value > 0, format_hours(value), project_time_entries_path(item.project, :issue_id => "#{item.id}"))
when :total_spent_hours
link_to_if(value > 0, format_hours(value), project_time_entries_path(item.project, :issue_id => "~#{item.id}"))
when :attachments
value.to_a.map {|a| format_object(a)}.join(" ").html_safe
else
format_object(value)
end
end
def csv_content(column, issue)
value = column.value_object(issue)
def csv_content(column, item)
value = column.value_object(item)
if value.is_a?(Array)
value.collect {|v| csv_value(column, issue, v)}.compact.join(', ')
value.collect {|v| csv_value(column, item, v)}.compact.join(', ')
else
csv_value(column, issue, value)
csv_value(column, item, value)
end
end
def csv_value(column, object, value)
format_object(value, false) do |value|
case value.class.name
when 'Float'
sprintf("%.2f", value).gsub('.', l(:general_csv_decimal_separator))
when 'IssueRelation'
value.to_s(object)
when 'Issue'
if object.is_a?(TimeEntry)
"#{value.tracker} ##{value.id}: #{value.subject}"
case column.name
when :attachments
value.to_a.map {|a| a.filename}.join("\n")
else
format_object(value, false) do |value|
case value.class.name
when 'Float'
sprintf("%.2f", value).gsub('.', l(:general_csv_decimal_separator))
when 'IssueRelation'
value.to_s(object)
when 'Issue'
if object.is_a?(TimeEntry)
"#{value.tracker} ##{value.id}: #{value.subject}"
else
value.id
end
else
value.id
value
end
else
value
end
end
end
def query_to_csv(items, query, options={})
options ||= {}
columns = (options[:columns] == 'all' ? query.available_inline_columns : query.inline_columns)
query.available_block_columns.each do |column|
if options[column.name].present?
columns << column
end
end
columns = query.columns
Redmine::Export::CSV.generate do |csv|
# csv header fields
@@ -205,40 +283,51 @@ module QueriesHelper
end
# Retrieve query from session or build a new query
def retrieve_query
if !params[:query_id].blank?
def retrieve_query(klass=IssueQuery, use_session=true)
session_key = klass.name.underscore.to_sym
if params[:query_id].present?
cond = "project_id IS NULL"
cond << " OR project_id = #{@project.id}" if @project
@query = IssueQuery.where(cond).find(params[:query_id])
@query = klass.where(cond).find(params[:query_id])
raise ::Unauthorized unless @query.visible?
@query.project = @project
session[:query] = {:id => @query.id, :project_id => @query.project_id}
sort_clear
elsif api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
session[session_key] = {:id => @query.id, :project_id => @query.project_id} if use_session
elsif api_request? || params[:set_filter] || !use_session || session[session_key].nil? || session[session_key][:project_id] != (@project ? @project.id : nil)
# Give it a name, required to be valid
@query = IssueQuery.new(:name => "_")
@query.project = @project
@query = klass.new(:name => "_", :project => @project)
@query.build_from_params(params)
session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names, :totalable_names => @query.totalable_names}
session[session_key] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names, :totalable_names => @query.totalable_names, :sort => @query.sort_criteria.to_a} if use_session
else
# retrieve from session
@query = nil
@query = IssueQuery.find_by_id(session[:query][:id]) if session[:query][:id]
@query ||= IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names], :totalable_names => session[:query][:totalable_names])
@query = klass.find_by_id(session[session_key][:id]) if session[session_key][:id]
@query ||= klass.new(:name => "_", :filters => session[session_key][:filters], :group_by => session[session_key][:group_by], :column_names => session[session_key][:column_names], :totalable_names => session[session_key][:totalable_names], :sort_criteria => session[session_key][:sort])
@query.project = @project
end
if params[:sort].present?
@query.sort_criteria = params[:sort]
if use_session
session[session_key] ||= {}
session[session_key][:sort] = @query.sort_criteria.to_a
end
end
@query
end
def retrieve_query_from_session
if session[:query]
if session[:query][:id]
@query = IssueQuery.find_by_id(session[:query][:id])
def retrieve_query_from_session(klass=IssueQuery)
session_key = klass.name.underscore.to_sym
session_data = session[session_key]
if session_data
if session_data[:id]
@query = IssueQuery.find_by_id(session_data[:id])
return unless @query
else
@query = IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names], :totalable_names => session[:query][:totalable_names])
@query = IssueQuery.new(:name => "_", :filters => session_data[:filters], :group_by => session_data[:group_by], :column_names => session_data[:column_names], :totalable_names => session_data[:totalable_names], :sort_criteria => session[session_key][:sort])
end
if session[:query].has_key?(:project_id)
@query.project_id = session[:query][:project_id]
if session_data.has_key?(:project_id)
@query.project_id = session_data[:project_id]
else
@query.project = @project
end
@@ -261,10 +350,8 @@ module QueriesHelper
else
tags << hidden_field_tag("f[]", "", :id => nil)
end
if query.column_names.present?
query.column_names.each do |name|
tags << hidden_field_tag("c[]", name, :id => nil)
end
query.columns.each do |column|
tags << hidden_field_tag("c[]", column.name, :id => nil)
end
if query.totalable_names.present?
query.totalable_names.each do |name|
@@ -274,7 +361,46 @@ module QueriesHelper
if query.group_by.present?
tags << hidden_field_tag("group_by", query.group_by, :id => nil)
end
if query.sort_criteria.present?
tags << hidden_field_tag("sort", query.sort_criteria.to_param, :id => nil)
end
tags
end
def query_hidden_sort_tag(query)
hidden_field_tag("sort", query.sort_criteria.to_param, :id => nil)
end
# Returns the queries that are rendered in the sidebar
def sidebar_queries(klass, project)
klass.visible.global_or_on_project(@project).sorted.to_a
end
# Renders a group of queries
def query_links(title, queries)
return '' if queries.empty?
# links to #index on issues/show
url_params = controller_name == 'issues' ? {:controller => 'issues', :action => 'index', :project_id => @project} : {}
content_tag('h3', title) + "\n" +
content_tag('ul',
queries.collect {|query|
css = 'query'
css << ' selected' if query == @query
content_tag('li', link_to(query.name, url_params.merge(:query_id => query), :class => css))
}.join("\n").html_safe,
:class => 'queries'
) + "\n"
end
# Renders the list of queries for the sidebar
def render_sidebar_queries(klass, project)
queries = sidebar_queries(klass, project)
out = ''.html_safe
out << query_links(l(:label_my_queries), queries.select(&:is_private?))
out << query_links(l(:label_query_plural), queries.reject(&:is_private?))
out
end
end

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -185,7 +185,7 @@ module RepositoriesHelper
scm_path_info_tag(repository)) +
scm_path_encoding_tag(form, repository) +
content_tag('p', form.check_box(
:extra_report_last_commit,
:report_last_commit,
:label => l(:label_git_report_last_commit)
))
end

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -29,6 +29,14 @@ module RoutesHelper
end
end
def _project_news_path(project, *args)
if project
project_news_index_path(project, *args)
else
news_index_path(*args)
end
end
def _new_project_issue_path(project, *args)
if project
new_project_issue_path(project, *args)
@@ -46,9 +54,7 @@ module RoutesHelper
end
def _time_entries_path(project, issue, *args)
if issue
issue_time_entries_path(issue, *args)
elsif project
if project
project_time_entries_path(project, *args)
else
time_entries_path(*args)
@@ -56,9 +62,7 @@ module RoutesHelper
end
def _report_time_entries_path(project, issue, *args)
if issue
report_issue_time_entries_path(issue, *args)
elsif project
if project
report_project_time_entries_path(project, *args)
else
report_time_entries_path(*args)

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -25,6 +25,7 @@ module SettingsHelper
{:name => 'api', :partial => 'settings/api', :label => :label_api},
{:name => 'projects', :partial => 'settings/projects', :label => :label_project_plural},
{:name => 'issues', :partial => 'settings/issues', :label => :label_issue_tracking},
{:name => 'timelog', :partial => 'settings/timelog', :label => :label_time_tracking},
{:name => 'attachments', :partial => 'settings/attachments', :label => :label_attachment_plural},
{:name => 'notifications', :partial => 'settings/notifications', :label => :field_mail_notification},
{:name => 'mail_handler', :partial => 'settings/mail_handler', :label => :label_incoming_emails},
@@ -32,18 +33,35 @@ module SettingsHelper
]
end
def render_settings_error(errors)
return if errors.blank?
s = ''.html_safe
errors.each do |name, message|
s << content_tag('li', content_tag('b', l("setting_#{name}")) + " " + message)
end
content_tag('div', content_tag('ul', s), :id => 'errorExplanation')
end
def setting_value(setting)
value = nil
if params[:settings]
value = params[:settings][setting]
end
value || Setting.send(setting)
end
def setting_select(setting, choices, options={})
if blank_text = options.delete(:blank)
choices = [[blank_text.is_a?(Symbol) ? l(blank_text) : blank_text, '']] + choices
end
setting_label(setting, options).html_safe +
select_tag("settings[#{setting}]",
options_for_select(choices, Setting.send(setting).to_s),
options_for_select(choices, setting_value(setting).to_s),
options).html_safe
end
def setting_multiselect(setting, choices, options={})
setting_values = Setting.send(setting)
setting_values = setting_value(setting)
setting_values = [] unless setting_values.is_a?(Array)
content_tag("label", l(options[:label] || "setting_#{setting}")) +
@@ -65,18 +83,18 @@ module SettingsHelper
def setting_text_field(setting, options={})
setting_label(setting, options).html_safe +
text_field_tag("settings[#{setting}]", Setting.send(setting), options).html_safe
text_field_tag("settings[#{setting}]", setting_value(setting), options).html_safe
end
def setting_text_area(setting, options={})
setting_label(setting, options).html_safe +
text_area_tag("settings[#{setting}]", Setting.send(setting), options).html_safe
text_area_tag("settings[#{setting}]", setting_value(setting), options).html_safe
end
def setting_check_box(setting, options={})
setting_label(setting, options).html_safe +
hidden_field_tag("settings[#{setting}]", 0, :id => nil).html_safe +
check_box_tag("settings[#{setting}]", 1, Setting.send("#{setting}?"), options).html_safe
check_box_tag("settings[#{setting}]", 1, setting_value(setting).to_s != '0', options).html_safe
end
def setting_label(setting, options={})
@@ -97,7 +115,7 @@ module SettingsHelper
tag = check_box_tag('settings[notified_events][]',
notifiable.name,
Setting.notified_events.include?(notifiable.name),
setting_value('notified_events').include?(notifiable.name),
:id => nil,
:data => tag_data)

View File

@@ -53,97 +53,6 @@
#
module SortHelper
class SortCriteria
def initialize
@criteria = []
end
def available_criteria=(criteria)
unless criteria.is_a?(Hash)
criteria = criteria.inject({}) {|h,k| h[k] = k; h}
end
@available_criteria = criteria
end
def from_param(param)
@criteria = param.to_s.split(',').collect {|s| s.split(':')[0..1]}
normalize!
end
def criteria=(arg)
@criteria = arg
normalize!
end
def to_param
@criteria.collect {|k,o| k + (o ? '' : ':desc')}.join(',')
end
# Returns an array of SQL fragments used to sort the list
def to_sql
sql = @criteria.collect do |k,o|
if s = @available_criteria[k]
s = [s] unless s.is_a?(Array)
s.collect {|c| append_order(c, o ? "ASC" : "DESC")}
end
end.flatten.compact
sql.blank? ? nil : sql
end
def to_a
@criteria.dup
end
def add!(key, asc)
@criteria.delete_if {|k,o| k == key}
@criteria = [[key, asc]] + @criteria
normalize!
end
def add(*args)
r = self.class.new.from_param(to_param)
r.add!(*args)
r
end
def first_key
@criteria.first && @criteria.first.first
end
def first_asc?
@criteria.first && @criteria.first.last
end
def empty?
@criteria.empty?
end
private
def normalize!
@criteria ||= []
@criteria = @criteria.collect {|s| s = Array(s); [s.first, (s.last == false || s.last == 'desc') ? false : true]}
@criteria = @criteria.select {|k,o| @available_criteria.has_key?(k)} if @available_criteria
@criteria.slice!(3)
self
end
# Appends ASC/DESC to the sort criterion unless it has a fixed order
def append_order(criterion, order)
if criterion =~ / (asc|desc)$/i
criterion
else
"#{criterion} #{order}"
end
end
# Appends DESC to the sort criterion unless it has a fixed order
def append_desc(criterion)
append_order(criterion, "DESC")
end
end
def sort_name
controller_name + '_' + action_name + '_sort'
end
@@ -173,10 +82,8 @@ module SortHelper
#
def sort_update(criteria, sort_name=nil)
sort_name ||= self.sort_name
@sort_criteria = SortCriteria.new
@sort_criteria.available_criteria = criteria
@sort_criteria.from_param(params[:sort] || session[sort_name])
@sort_criteria.criteria = @sort_default if @sort_criteria.empty?
@sort_criteria = Redmine::SortCriteria.new(params[:sort] || session[sort_name] || @sort_default)
@sortable_columns = criteria
session[sort_name] = @sort_criteria.to_param
end
@@ -190,7 +97,7 @@ module SortHelper
# Use this to sort the controller's table items collection.
#
def sort_clause()
@sort_criteria.to_sql
@sort_criteria.sort_clause(@sortable_columns)
end
def sort_criteria
@@ -218,12 +125,7 @@ module SortHelper
caption = column.to_s.humanize unless caption
sort_options = { :sort => @sort_criteria.add(column.to_s, order).to_param }
url_options = params.merge(sort_options)
# Add project_id to url_options
url_options = url_options.merge(:project_id => params[:project_id]) if params.has_key?(:project_id)
link_to_content_update(h(caption), url_options, :class => css)
link_to(caption, {:params => request.query_parameters.merge(sort_options)}, :class => css)
end
# Returns a table header <th> tag with a sort link for the named column

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -20,20 +20,6 @@
module TimelogHelper
include ApplicationHelper
def render_timelog_breadcrumb
links = []
links << link_to(l(:label_project_all), {:project_id => nil, :issue_id => nil})
links << link_to(h(@project), {:project_id => @project, :issue_id => nil}) if @project
if @issue
if @issue.visible?
links << link_to_issue(@issue, :subject => false)
else
links << "##{@issue.id}"
end
end
breadcrumb links
end
# Returns a collection of activities for a select field. time_entry
# is optional and will be used to check if the selected TimeEntryActivity
# is active.

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -30,6 +30,10 @@ module UsersHelper
user.valid_notification_options.collect {|o| [l(o.last), o.first]}
end
def textarea_font_options
[[l(:label_font_default), '']] + UserPreference::TEXTAREA_FONT_OPTIONS.map {|o| [l("label_font_#{o}"), o]}
end
def change_status_link(user)
url = {:controller => 'users', :action => 'update', :id => user, :page => params[:page], :status => params[:status], :tab => nil}

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -47,7 +47,7 @@ module WatchersHelper
def watchers_list(object)
remove_allowed = User.current.allowed_to?("delete_#{object.class.name.underscore}_watchers".to_sym, object.project)
content = ''.html_safe
lis = object.watcher_users.collect do |user|
lis = object.watcher_users.preload(:email_address).collect do |user|
s = ''.html_safe
s << avatar(user, :size => "16").to_s
s << link_to_user(user, :class => 'user')

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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

View File

@@ -1,7 +1,7 @@
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2016 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

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -15,10 +15,11 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require "digest/md5"
require "digest"
require "fileutils"
class Attachment < ActiveRecord::Base
include Redmine::SafeAttributes
belongs_to :container, :polymorphic => true
belongs_to :author, :class_name => "User"
@@ -30,7 +31,7 @@ class Attachment < ActiveRecord::Base
attr_protected :id
acts_as_event :title => :filename,
:url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id, :filename => o.filename}}
:url => Proc.new {|o| {:controller => 'attachments', :action => 'show', :id => o.id, :filename => o.filename}}
acts_as_activity_provider :type => 'files',
:permission => :view_files,
@@ -55,6 +56,9 @@ class Attachment < ActiveRecord::Base
before_create :files_to_final_location
after_rollback :delete_from_disk, :on => :create
after_commit :delete_from_disk, :on => :destroy
after_commit :reuse_existing_file_if_possible, :on => :create
safe_attributes 'filename', 'content_type', 'description'
# Returns an unsaved copy of the attachment
def copy(attributes=nil)
@@ -113,20 +117,20 @@ class Attachment < ActiveRecord::Base
unless File.directory?(path)
FileUtils.mkdir_p(path)
end
md5 = Digest::MD5.new
sha = Digest::SHA256.new
File.open(diskfile, "wb") do |f|
if @temp_file.respond_to?(:read)
buffer = ""
while (buffer = @temp_file.read(8192))
f.write(buffer)
md5.update(buffer)
sha.update(buffer)
end
else
f.write(@temp_file)
md5.update(@temp_file)
sha.update(@temp_file)
end
end
self.digest = md5.hexdigest
self.digest = sha.hexdigest
end
@temp_file = nil
@@ -247,9 +251,13 @@ class Attachment < ActiveRecord::Base
Redmine::MimeType.of(filename) == "application/pdf"
end
def previewable?
is_text? || is_image?
end
# Returns true if the file is readable
def readable?
File.readable?(diskfile)
disk_filename.present? && File.readable?(diskfile)
end
# Returns the attachment token
@@ -349,24 +357,100 @@ class Attachment < ActiveRecord::Base
end
end
# Returns true if the extension is allowed, otherwise false
def self.valid_extension?(extension)
extension = extension.downcase.sub(/\A\.+/, '')
denied, allowed = [:attachment_extensions_denied, :attachment_extensions_allowed].map do |setting|
Setting.send(setting).to_s.split(",").map {|s| s.strip.downcase.sub(/\A\.+/, '')}.reject(&:blank?)
# Updates digests to SHA256 for all attachments that have a MD5 digest
# (ie. created before Redmine 3.4)
def self.update_digests_to_sha256
Attachment.where("length(digest) < 64").find_each do |attachment|
attachment.update_digest_to_sha256!
end
if denied.present? && denied.include?(extension)
end
# Updates attachment digest to SHA256
def update_digest_to_sha256!
if readable?
sha = Digest::SHA256.new
File.open(diskfile, 'rb') do |f|
while buffer = f.read(8192)
sha.update(buffer)
end
end
update_column :digest, sha.hexdigest
end
end
# Returns true if the extension is allowed regarding allowed/denied
# extensions defined in application settings, otherwise false
def self.valid_extension?(extension)
denied, allowed = [:attachment_extensions_denied, :attachment_extensions_allowed].map do |setting|
Setting.send(setting)
end
if denied.present? && extension_in?(extension, denied)
return false
end
unless allowed.blank? || allowed.include?(extension)
if allowed.present? && !extension_in?(extension, allowed)
return false
end
true
end
# Returns true if extension belongs to extensions list.
def self.extension_in?(extension, extensions)
extension = extension.downcase.sub(/\A\.+/, '')
unless extensions.is_a?(Array)
extensions = extensions.to_s.split(",").map(&:strip)
end
extensions = extensions.map {|s| s.downcase.sub(/\A\.+/, '')}.reject(&:blank?)
extensions.include?(extension)
end
# Returns true if attachment's extension belongs to extensions list.
def extension_in?(extensions)
self.class.extension_in?(File.extname(filename), extensions)
end
# returns either MD5 or SHA256 depending on the way self.digest was computed
def digest_type
digest.size < 64 ? "MD5" : "SHA256" if digest.present?
end
private
def reuse_existing_file_if_possible
original_diskfile = nil
reused = with_lock do
if existing = Attachment
.where(digest: self.digest, filesize: self.filesize)
.where('id <> ? and disk_filename <> ?',
self.id, self.disk_filename)
.first
existing.with_lock do
original_diskfile = self.diskfile
existing_diskfile = existing.diskfile
if File.readable?(original_diskfile) &&
File.readable?(existing_diskfile) &&
FileUtils.identical?(original_diskfile, existing_diskfile)
self.update_columns disk_directory: existing.disk_directory,
disk_filename: existing.disk_filename
end
end
end
end
if reused
File.delete(original_diskfile)
end
rescue ActiveRecord::StatementInvalid, ActiveRecord::RecordNotFound
# Catch and ignore lock errors. It is not critical if deduplication does
# not happen, therefore we do not retry.
# with_lock throws ActiveRecord::RecordNotFound if the record isnt there
# anymore, thats why this is caught and ignored as well.
end
# Physically deletes the file from the file system
def delete_from_disk!
if disk_filename.present? && File.exist?(diskfile)
@@ -393,7 +477,7 @@ class Attachment < ActiveRecord::Base
def self.disk_filename(filename, directory=nil)
timestamp = DateTime.now.strftime("%y%m%d%H%M%S")
ascii = ''
if filename =~ %r{^[a-zA-Z0-9_\.\-]*$}
if filename =~ %r{^[a-zA-Z0-9_\.\-]*$} && filename.length <= 50
ascii = filename
else
ascii = Digest::MD5.hexdigest(filename)

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2016 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
@@ -21,6 +21,7 @@ class AuthSourceException < Exception; end
class AuthSourceTimeoutException < AuthSourceException; end
class AuthSource < ActiveRecord::Base
include Redmine::SafeAttributes
include Redmine::SubclassFactory
include Redmine::Ciphering
@@ -31,6 +32,21 @@ class AuthSource < ActiveRecord::Base
validates_length_of :name, :maximum => 60
attr_protected :id
safe_attributes 'name',
'host',
'port',
'account',
'account_password',
'base_dn',
'attr_login',
'attr_firstname',
'attr_lastname',
'attr_mail',
'onthefly_register',
'tls',
'filter',
'timeout'
def authenticate(login, password)
end

Some files were not shown because too many files have changed in this diff Show More