mirror of
https://github.com/redmine/redmine.git
synced 2026-07-15 14:11:52 +02:00
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:
10
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
10
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal 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
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
/.project
|
||||
/.idea
|
||||
/.loadpath
|
||||
/.powrc
|
||||
/.rvmrc
|
||||
|
||||
28
Gemfile
28
Gemfile
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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|
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ||= {}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user