Redmine 4.1.1
This commit is contained in:
parent
33e7b881a5
commit
3d976f1b3b
1593 changed files with 36180 additions and 19489 deletions
103
.rubocop.yml
Normal file
103
.rubocop.yml
Normal file
|
@ -0,0 +1,103 @@
|
|||
inherit_from: .rubocop_todo.yml
|
||||
|
||||
AllCops:
|
||||
TargetRubyVersion: 2.3
|
||||
TargetRailsVersion: 5.2
|
||||
|
||||
Exclude:
|
||||
- '**/vendor/**/*'
|
||||
- '**/tmp/**/*'
|
||||
- '**/bin/**/*'
|
||||
- '**/plugins/**/*'
|
||||
- '**/extra/**/*'
|
||||
- '**/lib/generators/**/templates/*'
|
||||
- '**/lib/tasks/**/*'
|
||||
- '**/files/**/*'
|
||||
- 'db/schema.rb'
|
||||
|
||||
# Enable extensions
|
||||
|
||||
require:
|
||||
- rubocop-performance
|
||||
- rubocop-rails
|
||||
|
||||
# Rules for Redmine
|
||||
|
||||
Bundler/OrderedGems:
|
||||
Enabled: false
|
||||
|
||||
Layout/EmptyLineBetweenDefs:
|
||||
AllowAdjacentOneLineDefs: true
|
||||
|
||||
Layout/SpaceBeforeBlockBraces:
|
||||
# "space" is used more than "no_space".
|
||||
# But "no_space" is more natural in one liner.
|
||||
# str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
|
||||
Enabled: false
|
||||
|
||||
# You can see results by "rubocop --only Layout/SpaceInsideBlockBraces"
|
||||
Layout/SpaceInsideBlockBraces:
|
||||
EnforcedStyle: no_space
|
||||
SpaceBeforeBlockParameters: false
|
||||
|
||||
# You can see results by "rubocop --only Layout/SpaceInsideHashLiteralBraces"
|
||||
Layout/SpaceInsideHashLiteralBraces:
|
||||
EnforcedStyle: no_space
|
||||
|
||||
Layout/TrailingWhitespace:
|
||||
AllowInHeredoc: true
|
||||
|
||||
Lint/HandleExceptions:
|
||||
AllowComments: true
|
||||
|
||||
Metrics:
|
||||
Enabled: false
|
||||
|
||||
Naming/AccessorMethodName:
|
||||
Enabled: false
|
||||
|
||||
Naming/BinaryOperatorParameterName:
|
||||
Enabled: false
|
||||
|
||||
Naming/PredicateName:
|
||||
Enabled: false
|
||||
|
||||
Rails/BulkChangeTable:
|
||||
Exclude:
|
||||
- 'db/migrate/20120714122200_add_workflows_rule_fields.rb'
|
||||
- 'db/migrate/20131214094309_remove_custom_fields_min_max_length_default_values.rb'
|
||||
|
||||
Rails/HelperInstanceVariable:
|
||||
Enabled: false
|
||||
|
||||
# Configuration parameters: AllowedChars.
|
||||
Style/AsciiComments:
|
||||
Exclude:
|
||||
# Copyright credit has non ascii character.
|
||||
# We can not change nor remove it.
|
||||
- 'app/models/repository/git.rb'
|
||||
|
||||
Style/FormatStringToken:
|
||||
Enabled: false
|
||||
|
||||
Style/FrozenStringLiteralComment:
|
||||
Enabled: true
|
||||
EnforcedStyle: always
|
||||
Exclude:
|
||||
- 'db/**/*.rb'
|
||||
- 'Gemfile'
|
||||
- 'Rakefile'
|
||||
- 'config.ru'
|
||||
- 'config/additional_environment.rb'
|
||||
|
||||
Style/HashSyntax:
|
||||
Enabled: true
|
||||
EnforcedStyle: no_mixed_keys
|
||||
|
||||
Style/IdenticalConditionalBranches:
|
||||
Exclude:
|
||||
- 'config/initializers/10-patches.rb'
|
||||
- 'lib/redmine/wiki_formatting/textile/redcloth3.rb'
|
||||
|
||||
Style/TrailingCommaInArrayLiteral:
|
||||
Enabled: false
|
1637
.rubocop_todo.yml
Normal file
1637
.rubocop_todo.yml
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,8 +1,10 @@
|
|||
Your contributions to Redmine are welcome!
|
||||
|
||||
**Do not send a pull request to this GitHub repository**.
|
||||
Please **open an issue on the [official website]** instead of sending pull requests.
|
||||
|
||||
For more detail, please see [official website] wiki [Contribute].
|
||||
Since the development of Redmine is not conducted on GitHub but on the [official website] and core developers are not monitoring the GitHub repo, pull requests might not get reviewed.
|
||||
|
||||
[official website]: http://www.redmine.org
|
||||
[Contribute]: http://www.redmine.org/projects/redmine/wiki/Contribute
|
||||
For more detail about how to contribute, please see the wiki page [Contribute] on the [official website].
|
||||
|
||||
[official website]: https://www.redmine.org/
|
||||
[Contribute]: https://www.redmine.org/projects/redmine/wiki/Contribute
|
||||
|
|
86
Gemfile
86
Gemfile
|
@ -1,41 +1,28 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
gem "bundler", ">= 1.5.0", "< 2.0.0"
|
||||
ruby '>= 2.3.0', '< 2.7.0' if Bundler::VERSION >= '1.12.0'
|
||||
gem "bundler", ">= 1.5.0"
|
||||
|
||||
gem "rails", "4.2.11.1"
|
||||
gem "addressable", "2.4.0" if RUBY_VERSION < "2.0"
|
||||
if RUBY_VERSION < "2.1"
|
||||
gem "public_suffix", (RUBY_VERSION < "2.0" ? "~> 1.4" : "~> 2.0.5")
|
||||
end
|
||||
gem "jquery-rails", "~> 3.1.4"
|
||||
gem "coderay", "~> 1.1.1"
|
||||
gem "request_store", "1.0.5"
|
||||
gem "mime-types", (RUBY_VERSION >= "2.0" ? "~> 3.0" : "~> 2.99")
|
||||
gem "protected_attributes"
|
||||
gem 'rails', '5.2.4.2'
|
||||
gem 'sprockets', '~> 3.7.2' if RUBY_VERSION < '2.5'
|
||||
gem "rouge", "~> 3.12.0"
|
||||
gem "request_store", "~> 1.4.1"
|
||||
gem "mini_mime", "~> 1.0.1"
|
||||
gem "actionpack-xml_parser"
|
||||
gem "roadie-rails", "~> 1.1.1"
|
||||
gem "roadie", "~> 3.2.1"
|
||||
gem "roadie-rails", (RUBY_VERSION < "2.5" ? "~> 1.3.0" : "~> 2.1.0")
|
||||
gem "mimemagic"
|
||||
gem "mail", "~> 2.6.4"
|
||||
|
||||
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"
|
||||
gem "xpath", "< 3.2.0" if RUBY_VERSION < "2.3"
|
||||
|
||||
# Request at least rails-html-sanitizer 1.0.3 because of security advisories
|
||||
gem "rails-html-sanitizer", ">= 1.0.3"
|
||||
|
||||
# TODO: Remove the following line when #32223 is fixed
|
||||
gem "sprockets", "~> 3.7.2"
|
||||
gem "mail", "~> 2.7.1"
|
||||
gem "csv", "~> 3.1.1"
|
||||
gem "nokogiri", "~> 1.10.0"
|
||||
gem "i18n", "~> 1.6.0"
|
||||
gem "rbpdf", "~> 1.20.0"
|
||||
|
||||
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
|
||||
gem 'tzinfo-data', platforms: [:mingw, :x64_mingw, :mswin]
|
||||
gem "rbpdf", "~> 1.19.6"
|
||||
|
||||
# Optional gem for LDAP authentication
|
||||
group :ldap do
|
||||
gem "net-ldap", "~> 0.12.0"
|
||||
gem "net-ldap", "~> 0.16.0"
|
||||
end
|
||||
|
||||
# Optional gem for OpenID authentication
|
||||
|
@ -44,16 +31,14 @@ group :openid do
|
|||
gem "rack-openid"
|
||||
end
|
||||
|
||||
platforms :mri, :mingw, :x64_mingw do
|
||||
# Optional gem for exporting the gantt to a PNG file, not supported with jruby
|
||||
group :rmagick do
|
||||
gem "rmagick", "~> 2.16.0"
|
||||
end
|
||||
# Optional gem for exporting the gantt to a PNG file
|
||||
group :minimagick do
|
||||
gem "mini_magick", "~> 4.9.5"
|
||||
end
|
||||
|
||||
# Optional Markdown support, not for JRuby
|
||||
group :markdown do
|
||||
gem "redcarpet", "~> 3.4.0"
|
||||
end
|
||||
# Optional Markdown support, not for JRuby
|
||||
group :markdown do
|
||||
gem "redcarpet", "~> 3.5.0"
|
||||
end
|
||||
|
||||
# Include database gems for the adapters found in the database
|
||||
|
@ -68,15 +53,14 @@ if File.exist?(database_file)
|
|||
adapters.each do |adapter|
|
||||
case adapter
|
||||
when 'mysql2'
|
||||
gem "mysql2", "~> 0.4.6", :platforms => [:mri, :mingw, :x64_mingw]
|
||||
gem "mysql2", "~> 0.5.0", :platforms => [:mri, :mingw, :x64_mingw]
|
||||
when /postgresql/
|
||||
gem "pg", "~> 0.18.1", :platforms => [:mri, :mingw, :x64_mingw]
|
||||
gem "pg", "~> 1.1.4", :platforms => [:mri, :mingw, :x64_mingw]
|
||||
when /sqlite3/
|
||||
gem "sqlite3", (RUBY_VERSION < "2.0" && RUBY_PLATFORM =~ /mingw/ ? "1.3.12" : "~>1.3.12"),
|
||||
:platforms => [:mri, :mingw, :x64_mingw]
|
||||
gem "sqlite3", "~> 1.4.0", :platforms => [:mri, :mingw, :x64_mingw]
|
||||
when /sqlserver/
|
||||
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]
|
||||
gem "tiny_tds", "~> 2.1.2", :platforms => [:mri, :mingw, :x64_mingw]
|
||||
gem "activerecord-sqlserver-adapter", "~> 5.2.1", :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")
|
||||
end
|
||||
|
@ -89,20 +73,22 @@ else
|
|||
end
|
||||
|
||||
group :development do
|
||||
gem "rdoc", "~> 4.3"
|
||||
gem "yard"
|
||||
end
|
||||
|
||||
group :test do
|
||||
gem "minitest"
|
||||
gem "rails-dom-testing"
|
||||
gem 'mocha', '>= 1.4.0'
|
||||
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", '~> 2.13'
|
||||
gem "selenium-webdriver", "~> 2.53.4"
|
||||
gem "simplecov", "~> 0.17.0", :require => false
|
||||
gem "ffi", platforms: [:mingw, :x64_mingw, :mswin]
|
||||
# For running system tests
|
||||
gem 'puma', '~> 3.7'
|
||||
gem "capybara", (RUBY_VERSION < "2.4" ? "~> 3.15.1" : "~> 3.25.0")
|
||||
gem "selenium-webdriver"
|
||||
# RuboCop
|
||||
gem 'rubocop', '~> 0.76.0'
|
||||
gem 'rubocop-performance', '~> 1.5.0'
|
||||
gem 'rubocop-rails', '~> 2.3.0'
|
||||
end
|
||||
|
||||
local_gemfile = File.join(File.dirname(__FILE__), "Gemfile.local")
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
|
@ -62,9 +64,15 @@ class AccountController < ApplicationController
|
|||
(redirect_to(home_url); return) unless Setting.lost_password?
|
||||
if prt = (params[:token] || session[:password_recovery_token])
|
||||
@token = Token.find_token("recovery", prt.to_s)
|
||||
if @token.nil? || @token.expired?
|
||||
if @token.nil?
|
||||
redirect_to home_url
|
||||
return
|
||||
elsif @token.expired?
|
||||
# remove expired token from session and let user try again
|
||||
session[:password_recovery_token] = nil
|
||||
flash[:error] = l(:error_token_expired)
|
||||
redirect_to lost_password_url
|
||||
return
|
||||
end
|
||||
|
||||
# redirect to remove the token query parameter from the URL and add it to the session
|
||||
|
@ -87,7 +95,7 @@ class AccountController < ApplicationController
|
|||
@user.must_change_passwd = false
|
||||
if @user.save
|
||||
@token.destroy
|
||||
Mailer.password_updated(@user, { remote_ip: request.remote_ip })
|
||||
Mailer.deliver_password_updated(@user, User.current)
|
||||
flash[:notice] = l(:notice_account_password_updated)
|
||||
redirect_to signin_path
|
||||
return
|
||||
|
@ -119,7 +127,7 @@ class AccountController < ApplicationController
|
|||
if token.save
|
||||
# Don't use the param to send the email
|
||||
recipent = user.mails.detect {|e| email.casecmp(e) == 0} || user.mail
|
||||
Mailer.lost_password(token, recipent).deliver
|
||||
Mailer.deliver_lost_password(user, token, recipent)
|
||||
flash[:notice] = l(:notice_account_lost_email_sent)
|
||||
redirect_to signin_path
|
||||
return
|
||||
|
@ -313,7 +321,7 @@ class AccountController < ApplicationController
|
|||
def register_by_email_activation(user, &block)
|
||||
token = Token.new(:user => user, :action => "register")
|
||||
if user.save and token.save
|
||||
Mailer.register(token).deliver
|
||||
Mailer.deliver_register(user, token)
|
||||
flash[:notice] = l(:notice_account_register_done, :email => ERB::Util.h(user.mail))
|
||||
redirect_to signin_path
|
||||
else
|
||||
|
@ -343,7 +351,7 @@ class AccountController < ApplicationController
|
|||
def register_manually_by_administrator(user, &block)
|
||||
if user.save
|
||||
# Sends an email to the administrators
|
||||
Mailer.account_activation_request(user).deliver
|
||||
Mailer.deliver_account_activation_request(user)
|
||||
account_pending(user)
|
||||
else
|
||||
yield if block_given?
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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 +19,7 @@
|
|||
|
||||
class ActivitiesController < ApplicationController
|
||||
menu_item :activity
|
||||
before_action :find_optional_project
|
||||
before_action :find_optional_project_by_id, :authorize_global
|
||||
accept_rss_auth :index
|
||||
|
||||
def index
|
||||
|
@ -76,15 +78,4 @@ class ActivitiesController < ApplicationController
|
|||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# TODO: refactor, duplicated in projects_controller
|
||||
def find_optional_project
|
||||
return true unless params[:id]
|
||||
@project = Project.find(params[:id])
|
||||
authorize
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
|
@ -52,7 +54,7 @@ class AdminController < ApplicationController
|
|||
begin
|
||||
Redmine::DefaultData::Loader::load(params[:lang])
|
||||
flash[:notice] = l(:notice_default_data_loaded)
|
||||
rescue Exception => e
|
||||
rescue => e
|
||||
flash[:error] = l(:error_can_t_load_default_data, ERB::Util.h(e.message))
|
||||
end
|
||||
end
|
||||
|
@ -60,16 +62,12 @@ class AdminController < ApplicationController
|
|||
end
|
||||
|
||||
def test_email
|
||||
raise_delivery_errors = ActionMailer::Base.raise_delivery_errors
|
||||
# Force ActionMailer to raise delivery errors so we can catch it
|
||||
ActionMailer::Base.raise_delivery_errors = true
|
||||
begin
|
||||
@test = Mailer.test_email(User.current).deliver
|
||||
Mailer.deliver_test_email(User.current)
|
||||
flash[:notice] = l(:notice_email_sent, ERB::Util.h(User.current.mail))
|
||||
rescue Exception => e
|
||||
rescue => e
|
||||
flash[:error] = l(:notice_email_error, ERB::Util.h(Redmine::CodesetUtil.replace_invalid_utf8(e.message.dup)))
|
||||
end
|
||||
ActionMailer::Base.raise_delivery_errors = raise_delivery_errors
|
||||
redirect_to settings_path(:tab => 'notifications')
|
||||
end
|
||||
|
||||
|
@ -78,8 +76,9 @@ class AdminController < ApplicationController
|
|||
[:text_default_administrator_account_changed, User.default_admin_account_changed?],
|
||||
[:text_file_repository_writable, File.writable?(Attachment.storage_path)],
|
||||
["#{l :text_plugin_assets_writable} (./public/plugin_assets)", File.writable?(Redmine::Plugin.public_directory)],
|
||||
[:text_rmagick_available, Object.const_defined?(:Magick)],
|
||||
[:text_convert_available, Redmine::Thumbnail.convert_available?]
|
||||
[:text_minimagick_available, Object.const_defined?(:MiniMagick)],
|
||||
[:text_convert_available, Redmine::Thumbnail.convert_available?],
|
||||
[:text_gs_available, Redmine::Thumbnail.gs_available?]
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,14 +20,17 @@
|
|||
require 'uri'
|
||||
require 'cgi'
|
||||
|
||||
class Unauthorized < Exception; end
|
||||
class Unauthorized < StandardError; end
|
||||
|
||||
class ApplicationController < ActionController::Base
|
||||
include Redmine::I18n
|
||||
include Redmine::Pagination
|
||||
include Redmine::Hook::Helper
|
||||
include RoutesHelper
|
||||
include AvatarsHelper
|
||||
|
||||
helper :routes
|
||||
helper :avatars
|
||||
|
||||
class_attribute :accept_api_auth_actions
|
||||
class_attribute :accept_rss_auth_actions
|
||||
|
@ -52,6 +57,7 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
|
||||
before_action :session_expiration, :user_setup, :check_if_login_required, :set_localization, :check_password_change
|
||||
after_action :record_project_usage
|
||||
|
||||
rescue_from ::Unauthorized, :with => :deny_access
|
||||
rescue_from ::ActionView::MissingTemplate, :with => :missing_template
|
||||
|
@ -112,7 +118,7 @@ class ApplicationController < ActionController::Base
|
|||
if (key = api_key_from_request)
|
||||
# Use API key
|
||||
user = User.find_by_api_key(key)
|
||||
elsif request.authorization.to_s =~ /\ABasic /i
|
||||
elsif /\ABasic /i.match?(request.authorization.to_s)
|
||||
# HTTP Basic, either username/password or API key/random
|
||||
authenticate_with_http_basic do |username, password|
|
||||
user = User.try_to_login(username, password) || User.find_by_api_key(username)
|
||||
|
@ -229,9 +235,14 @@ class ApplicationController < ActionController::Base
|
|||
format.any(:atom, :pdf, :csv) {
|
||||
redirect_to signin_path(:back_url => url)
|
||||
}
|
||||
format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
|
||||
format.api {
|
||||
if Setting.rest_api_enabled? && accept_api_auth?
|
||||
head(:unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"')
|
||||
else
|
||||
head(:forbidden)
|
||||
end
|
||||
}
|
||||
format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
|
||||
format.json { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
|
||||
format.any { head :unauthorized }
|
||||
end
|
||||
return false
|
||||
|
@ -259,7 +270,11 @@ class ApplicationController < ActionController::Base
|
|||
true
|
||||
else
|
||||
if @project && @project.archived?
|
||||
@archived_project = @project
|
||||
render_403 :message => :notice_not_authorized_archived_project
|
||||
elsif @project && !@project.allows_to?(:controller => ctrl, :action => action)
|
||||
# Project module is disabled
|
||||
render_403
|
||||
else
|
||||
deny_access
|
||||
end
|
||||
|
@ -272,27 +287,31 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
|
||||
# Find project of id params[:id]
|
||||
def find_project
|
||||
@project = Project.find(params[:id])
|
||||
def find_project(project_id=params[:id])
|
||||
@project = Project.find(project_id)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
# Find project of id params[:project_id]
|
||||
def find_project_by_project_id
|
||||
@project = Project.find(params[:project_id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
find_project(params[:project_id])
|
||||
end
|
||||
|
||||
# Find project of id params[:id] if present
|
||||
def find_optional_project_by_id
|
||||
if params[:id].present?
|
||||
find_project(params[:id])
|
||||
end
|
||||
end
|
||||
|
||||
# Find a project based on params[:project_id]
|
||||
# TODO: some subclasses override this, see about merging their logic
|
||||
# and authorize the user for the requested action
|
||||
def find_optional_project
|
||||
@project = Project.find(params[:project_id]) unless params[:project_id].blank?
|
||||
allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
|
||||
allowed ? true : deny_access
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
if params[:project_id].present?
|
||||
find_project(params[:project_id])
|
||||
end
|
||||
authorize_global
|
||||
end
|
||||
|
||||
# Finds and sets @project based on @object.project
|
||||
|
@ -385,18 +404,30 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
end
|
||||
|
||||
def record_project_usage
|
||||
if @project && @project.id && User.current.logged? && User.current.allowed_to?(:view_project, @project)
|
||||
Redmine::ProjectJumpBox.new(User.current).project_used(@project)
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
def back_url
|
||||
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
|
||||
helper_method :back_url
|
||||
|
||||
def redirect_back_or_default(default, options={})
|
||||
back_url = params[:back_url].to_s
|
||||
if back_url.present? && valid_url = validate_back_url(back_url)
|
||||
redirect_to(valid_url)
|
||||
if back_url = validate_back_url(params[:back_url].to_s)
|
||||
redirect_to(back_url)
|
||||
return
|
||||
elsif options[:referer]
|
||||
redirect_to_referer_or default
|
||||
|
@ -409,6 +440,8 @@ class ApplicationController < ActionController::Base
|
|||
# Returns a validated URL string if back_url is a valid url for redirection,
|
||||
# otherwise false
|
||||
def validate_back_url(back_url)
|
||||
return false if back_url.blank?
|
||||
|
||||
if CGI.unescape(back_url).include?('..')
|
||||
return false
|
||||
end
|
||||
|
@ -431,11 +464,11 @@ class ApplicationController < ActionController::Base
|
|||
path = uri.to_s
|
||||
# Ensure that the remaining URL starts with a slash, followed by a
|
||||
# non-slash character or the end
|
||||
if path !~ %r{\A/([^/]|\z)}
|
||||
if !%r{\A/([^/]|\z)}.match?(path)
|
||||
return false
|
||||
end
|
||||
|
||||
if path.match(%r{/(login|account/register|account/lost_password)})
|
||||
if %r{/(login|account/register|account/lost_password)}.match?(path)
|
||||
return false
|
||||
end
|
||||
|
||||
|
@ -446,11 +479,13 @@ class ApplicationController < ActionController::Base
|
|||
return path
|
||||
end
|
||||
private :validate_back_url
|
||||
helper_method :validate_back_url
|
||||
|
||||
def valid_back_url?(back_url)
|
||||
!!validate_back_url(back_url)
|
||||
end
|
||||
private :valid_back_url?
|
||||
helper_method :valid_back_url?
|
||||
|
||||
# Redirects to the request referer if present, redirects to args or call block otherwise.
|
||||
def redirect_to_referer_or(*args, &block)
|
||||
|
@ -460,7 +495,7 @@ class ApplicationController < ActionController::Base
|
|||
if args.any?
|
||||
redirect_to *args
|
||||
elsif block_given?
|
||||
block.call
|
||||
yield
|
||||
else
|
||||
raise "#redirect_to_referer_or takes arguments or a block"
|
||||
end
|
||||
|
@ -495,8 +530,8 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
|
||||
# Handler for ActionView::MissingTemplate exception
|
||||
def missing_template
|
||||
logger.warn "Missing template, responding with 404"
|
||||
def missing_template(exception)
|
||||
logger.warn "Missing template, responding with 404: #{exception}"
|
||||
@project = nil
|
||||
render_404
|
||||
end
|
||||
|
@ -616,7 +651,7 @@ class ApplicationController < ActionController::Base
|
|||
|
||||
# Returns a string that can be used as filename value in Content-Disposition header
|
||||
def filename_for_content_disposition(name)
|
||||
request.env['HTTP_USER_AGENT'] =~ %r{(MSIE|Trident|Edge)} ? ERB::Util.url_encode(name) : name
|
||||
%r{(MSIE|Trident|Edge)}.match?(request.env['HTTP_USER_AGENT']) ? ERB::Util.url_encode(name) : name
|
||||
end
|
||||
|
||||
def api_request?
|
||||
|
@ -649,9 +684,9 @@ class ApplicationController < ActionController::Base
|
|||
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 successful updates or deletions via the API
|
||||
# Renders a 204 response for successful updates or deletions via the API
|
||||
def render_api_ok
|
||||
render_api_head :ok
|
||||
render_api_head :no_content
|
||||
end
|
||||
|
||||
# Renders a head API response
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,6 +34,14 @@ class AttachmentsController < ApplicationController
|
|||
def show
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
if @attachment.container.respond_to?(:attachments)
|
||||
@attachments = @attachment.container.attachments.to_a
|
||||
if index = @attachments.index(@attachment)
|
||||
@paginator = Redmine::Pagination::Paginator.new(
|
||||
@attachments.size, 1, index+1
|
||||
)
|
||||
end
|
||||
end
|
||||
if @attachment.is_diff?
|
||||
@diff = File.read(@attachment.diskfile, :mode => "rb")
|
||||
@diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
|
||||
|
@ -71,10 +81,11 @@ class AttachmentsController < ApplicationController
|
|||
def thumbnail
|
||||
if @attachment.thumbnailable? && tbnail = @attachment.thumbnail(:size => params[:size])
|
||||
if stale?(:etag => tbnail, :template => false)
|
||||
send_file tbnail,
|
||||
send_file(
|
||||
tbnail,
|
||||
:filename => filename_for_content_disposition(@attachment.filename),
|
||||
:type => detect_content_type(@attachment),
|
||||
:disposition => 'inline'
|
||||
:type => detect_content_type(@attachment, true),
|
||||
:disposition => 'inline')
|
||||
end
|
||||
else
|
||||
# No thumbnail for the attachment or thumbnail could not be created
|
||||
|
@ -156,8 +167,10 @@ class AttachmentsController < ApplicationController
|
|||
|
||||
# Returns the menu item that should be selected when viewing an attachment
|
||||
def current_menu_item
|
||||
if @attachment
|
||||
case @attachment.container
|
||||
container = @attachment.try(:container) || @container
|
||||
|
||||
if container
|
||||
case container
|
||||
when WikiPage
|
||||
:wiki
|
||||
when Message
|
||||
|
@ -165,7 +178,7 @@ class AttachmentsController < ApplicationController
|
|||
when Project, Version
|
||||
:files
|
||||
else
|
||||
@attachment.container.class.name.pluralize.downcase.to_sym
|
||||
container.class.name.pluralize.downcase.to_sym
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -224,12 +237,20 @@ class AttachmentsController < ApplicationController
|
|||
@attachment.deletable? ? true : deny_access
|
||||
end
|
||||
|
||||
def detect_content_type(attachment)
|
||||
def detect_content_type(attachment, is_thumb = false)
|
||||
content_type = attachment.content_type
|
||||
if content_type.blank? || content_type == "application/octet-stream"
|
||||
content_type = Redmine::MimeType.of(attachment.filename)
|
||||
content_type =
|
||||
Redmine::MimeType.of(attachment.filename).presence ||
|
||||
"application/octet-stream"
|
||||
end
|
||||
content_type.to_s
|
||||
|
||||
if is_thumb && content_type == "application/pdf"
|
||||
# PDF previews are stored in PNG format
|
||||
content_type = "image/png"
|
||||
end
|
||||
|
||||
content_type
|
||||
end
|
||||
|
||||
def disposition(attachment)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
|
@ -58,7 +60,7 @@ class AuthSourcesController < ApplicationController
|
|||
begin
|
||||
@auth_source.test_connection
|
||||
flash[:notice] = l(:notice_successful_connection)
|
||||
rescue Exception => e
|
||||
rescue => e
|
||||
flash[:error] = l(:error_unable_to_connect, e.message)
|
||||
end
|
||||
redirect_to auth_sources_path
|
||||
|
@ -68,22 +70,25 @@ class AuthSourcesController < ApplicationController
|
|||
unless @auth_source.users.exists?
|
||||
@auth_source.destroy
|
||||
flash[:notice] = l(:notice_successful_delete)
|
||||
else
|
||||
flash[:error] = l(:error_can_not_delete_auth_source)
|
||||
end
|
||||
redirect_to auth_sources_path
|
||||
end
|
||||
|
||||
def autocomplete_for_new_user
|
||||
results = AuthSource.search(params[:term])
|
||||
|
||||
render :json => results.map {|result| {
|
||||
'value' => result[:login],
|
||||
'label' => "#{result[:login]} (#{result[:firstname]} #{result[:lastname]})",
|
||||
'login' => result[:login].to_s,
|
||||
'firstname' => result[:firstname].to_s,
|
||||
'lastname' => result[:lastname].to_s,
|
||||
'mail' => result[:mail].to_s,
|
||||
'auth_source_id' => result[:auth_source_id].to_s
|
||||
}}
|
||||
render :json => results.map {|result|
|
||||
{
|
||||
'value' => result[:login],
|
||||
'label' => "#{result[:login]} (#{result[:firstname]} #{result[:lastname]})",
|
||||
'login' => result[:login].to_s,
|
||||
'firstname' => result[:firstname].to_s,
|
||||
'lastname' => result[:lastname].to_s,
|
||||
'mail' => result[:mail].to_s,
|
||||
'auth_source_id' => result[:auth_source_id].to_s
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,20 +25,18 @@ class AutoCompletesController < ApplicationController
|
|||
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
|
||||
|
||||
scope = Issue.cross_project_scope(@project, params[:scope]).visible
|
||||
scope = scope.open(status == 'o') if status.present?
|
||||
scope = scope.where.not(:id => issue_id.to_i) if issue_id.present?
|
||||
if q.present?
|
||||
if q =~ /\A#?(\d+)\z/
|
||||
issues << scope.find_by(:id => $1.to_i)
|
||||
end
|
||||
issues += scope.like(q).order(:id => :desc).limit(10).to_a
|
||||
issues.compact!
|
||||
else
|
||||
issues += scope.order(:id => :desc).limit(10).to_a
|
||||
end
|
||||
|
||||
render :json => format_issues_json(issues)
|
||||
|
@ -53,10 +53,11 @@ class AutoCompletesController < ApplicationController
|
|||
end
|
||||
|
||||
def format_issues_json(issues)
|
||||
issues.map {|issue| {
|
||||
'id' => issue.id,
|
||||
'label' => "#{issue.tracker} ##{issue.id}: #{issue.subject.to_s.truncate(60)}",
|
||||
'value' => issue.id
|
||||
issues.map {|issue|
|
||||
{
|
||||
'id' => issue.id,
|
||||
'label' => "#{issue.tracker} ##{issue.id}: #{issue.subject.to_s.truncate(60)}",
|
||||
'value' => issue.id
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
|
@ -108,7 +110,8 @@ class BoardsController < ApplicationController
|
|||
redirect_to_settings_in_projects
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
|
||||
def redirect_to_settings_in_projects
|
||||
redirect_to settings_project_path(@project, :tab => 'boards')
|
||||
end
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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 +24,7 @@ class ContextMenusController < ApplicationController
|
|||
before_action :find_issues, :only => :issues
|
||||
|
||||
def issues
|
||||
if (@issues.size == 1)
|
||||
if @issues.size == 1
|
||||
@issue = @issues.first
|
||||
end
|
||||
@issue_ids = @issues.map(&:id).sort
|
||||
|
@ -43,6 +45,8 @@ class ContextMenusController < ApplicationController
|
|||
@priorities = IssuePriority.active.reverse
|
||||
@back = back_url
|
||||
|
||||
@columns = params[:c]
|
||||
|
||||
@options_by_custom_field = {}
|
||||
if @can[:edit]
|
||||
custom_fields = @issues.map(&:editable_custom_fields).reduce(:&).reject(&:multiple?).select {|field| field.format.bulk_edit_supported}
|
||||
|
@ -64,7 +68,7 @@ class ContextMenusController < ApplicationController
|
|||
preload(:user).to_a
|
||||
|
||||
(render_404; return) unless @time_entries.present?
|
||||
if (@time_entries.size == 1)
|
||||
if @time_entries.size == 1
|
||||
@time_entry = @time_entries.first
|
||||
end
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,7 +48,11 @@ class CustomFieldsController < ApplicationController
|
|||
if @custom_field.save
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
call_hook(:controller_custom_fields_new_after_save, :params => params, :custom_field => @custom_field)
|
||||
redirect_to edit_custom_field_path(@custom_field)
|
||||
if params[:continue]
|
||||
redirect_to new_custom_field_path({:type => @custom_field.type})
|
||||
else
|
||||
redirect_to edit_custom_field_path(@custom_field)
|
||||
end
|
||||
else
|
||||
render :action => 'new'
|
||||
end
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,7 +33,8 @@ class DocumentsController < ApplicationController
|
|||
documents = @project.documents.includes(:attachments, :category).to_a
|
||||
case @sort_by
|
||||
when 'date'
|
||||
@grouped = documents.group_by {|d| d.updated_on.to_date }
|
||||
documents.sort!{|a,b| b.updated_on <=> a.updated_on}
|
||||
@grouped = documents.group_by {|d| d.updated_on.to_date}
|
||||
when 'title'
|
||||
@grouped = documents.group_by {|d| d.title.first.upcase}
|
||||
when 'author'
|
||||
|
@ -88,7 +91,7 @@ class DocumentsController < ApplicationController
|
|||
render_attachment_warning_if_needed(@document)
|
||||
|
||||
if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added')
|
||||
Mailer.attachments_added(attachments[:files]).deliver
|
||||
Mailer.deliver_attachments_added(attachments[:files])
|
||||
end
|
||||
redirect_to document_path(@document)
|
||||
end
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
|
@ -57,7 +59,7 @@ class EnumerationsController < ApplicationController
|
|||
end
|
||||
|
||||
def update
|
||||
if @enumeration.update_attributes(enumeration_params)
|
||||
if @enumeration.update(enumeration_params)
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
|
@ -107,7 +109,7 @@ class EnumerationsController < ApplicationController
|
|||
|
||||
def enumeration_params
|
||||
# can't require enumeration on #new action
|
||||
cf_ids = @enumeration.available_custom_fields.map{|c| c.id.to_s}
|
||||
cf_ids = @enumeration.available_custom_fields.map {|c| c.multiple? ? {c.id.to_s => []} : c.id.to_s}
|
||||
params.permit(:enumeration => [:name, :active, :is_default, :position, :custom_field_values => cf_ids])[:enumeration]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
|
@ -44,7 +46,7 @@ class FilesController < ApplicationController
|
|||
end
|
||||
|
||||
def new
|
||||
@versions = @project.versions.sort
|
||||
@versions = @project.versions.sorted
|
||||
end
|
||||
|
||||
def create
|
||||
|
@ -55,12 +57,13 @@ class FilesController < ApplicationController
|
|||
|
||||
if attachments[:files].present?
|
||||
if Setting.notified_events.include?('file_added')
|
||||
Mailer.attachments_added(attachments[:files]).deliver
|
||||
Mailer.deliver_attachments_added(attachments[:files])
|
||||
end
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
flash[:notice] = l(:label_file_added)
|
||||
redirect_to project_files_path(@project) }
|
||||
redirect_to project_files_path(@project)
|
||||
}
|
||||
format.api { render_api_ok }
|
||||
end
|
||||
else
|
||||
|
@ -68,7 +71,8 @@ class FilesController < ApplicationController
|
|||
format.html {
|
||||
flash.now[:error] = l(:label_attachment) + " " + l('activerecord.errors.messages.invalid')
|
||||
new
|
||||
render :action => 'new' }
|
||||
render :action => 'new'
|
||||
}
|
||||
format.api { render :status => :bad_request }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,22 +20,23 @@
|
|||
require 'csv'
|
||||
|
||||
class ImportsController < ApplicationController
|
||||
menu_item :issues
|
||||
|
||||
before_action :find_import, :only => [:show, :settings, :mapping, :run]
|
||||
before_action :authorize_global
|
||||
before_action :authorize_import
|
||||
|
||||
layout :import_layout
|
||||
|
||||
helper :issues
|
||||
helper :queries
|
||||
|
||||
def new
|
||||
@import = import_type.new
|
||||
end
|
||||
|
||||
def create
|
||||
@import = IssueImport.new
|
||||
@import = import_type.new
|
||||
@import.user = User.current
|
||||
@import.file = params[:file]
|
||||
@import.set_default_settings
|
||||
@import.set_default_settings(:project_id => params[:project_id])
|
||||
|
||||
if @import.save
|
||||
redirect_to import_settings_path(@import)
|
||||
|
@ -50,10 +53,12 @@ class ImportsController < ApplicationController
|
|||
redirect_to import_mapping_path(@import)
|
||||
end
|
||||
|
||||
rescue CSV::MalformedCSVError => e
|
||||
flash.now[:error] = l(:error_invalid_csv_file_or_settings)
|
||||
rescue ArgumentError, EncodingError => e
|
||||
flash.now[:error] = l(:error_invalid_file_encoding, :encoding => ERB::Util.h(@import.settings['encoding']))
|
||||
rescue CSV::MalformedCSVError, EncodingError => e
|
||||
if e.is_a?(CSV::MalformedCSVError) && e.message !~ /Invalid byte sequence/
|
||||
flash.now[:error] = l(:error_invalid_csv_file_or_settings)
|
||||
else
|
||||
flash.now[:error] = l(:error_invalid_file_encoding, :encoding => ERB::Util.h(@import.settings['encoding']))
|
||||
end
|
||||
rescue SystemCallError => e
|
||||
flash.now[:error] = l(:error_can_not_read_import_file)
|
||||
end
|
||||
|
@ -94,6 +99,14 @@ class ImportsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def current_menu(project)
|
||||
if import_layout == 'admin'
|
||||
nil
|
||||
else
|
||||
:application_menu
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_import
|
||||
|
@ -109,9 +122,9 @@ class ImportsController < ApplicationController
|
|||
end
|
||||
|
||||
def update_from_params
|
||||
if params[:import_settings].is_a?(Hash)
|
||||
if params[:import_settings].present?
|
||||
@import.settings ||= {}
|
||||
@import.settings.merge!(params[:import_settings])
|
||||
@import.settings.merge!(params[:import_settings].to_unsafe_hash)
|
||||
@import.save!
|
||||
end
|
||||
end
|
||||
|
@ -119,4 +132,31 @@ class ImportsController < ApplicationController
|
|||
def max_items_per_request
|
||||
5
|
||||
end
|
||||
|
||||
def import_layout
|
||||
import_type && import_type.layout || 'base'
|
||||
end
|
||||
|
||||
def menu_items
|
||||
menu_item = import_type ? import_type.menu_item : nil
|
||||
|
||||
{ self.controller_name.to_sym => { :actions => {}, :default => menu_item } }
|
||||
end
|
||||
|
||||
def authorize_import
|
||||
return render_404 unless import_type
|
||||
return render_403 unless import_type.authorized?(User.current)
|
||||
end
|
||||
|
||||
def import_type
|
||||
return @import_type if defined? @import_type
|
||||
|
||||
@import_type =
|
||||
if @import
|
||||
@import.class
|
||||
else
|
||||
type = Object.const_get(params[:type]) rescue nil
|
||||
type && type < Import ? type : nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
|
@ -72,8 +74,8 @@ class IssueStatusesController < ApplicationController
|
|||
def destroy
|
||||
IssueStatus.find(params[:id]).destroy
|
||||
redirect_to issue_statuses_path
|
||||
rescue
|
||||
flash[:error] = l(:error_unable_delete_issue_status)
|
||||
rescue => e
|
||||
flash[:error] = l(:error_unable_delete_issue_status, ERB::Util.h(e.message))
|
||||
redirect_to issue_statuses_path
|
||||
end
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,7 +20,7 @@
|
|||
class IssuesController < ApplicationController
|
||||
default_search_scope :issues
|
||||
|
||||
before_action :find_issue, :only => [:show, :edit, :update]
|
||||
before_action :find_issue, :only => [:show, :edit, :update, :issue_tab]
|
||||
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]
|
||||
|
@ -84,13 +86,10 @@ class IssuesController < ApplicationController
|
|||
|
||||
def show
|
||||
@journals = @issue.visible_journals_with_index
|
||||
@changesets = @issue.changesets.visible.preload(:repository, :user).to_a
|
||||
@has_changesets = @issue.changesets.visible.preload(:repository, :user).exists?
|
||||
@relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
|
||||
|
||||
if User.current.wants_comments_in_reverse_order?
|
||||
@journals.reverse!
|
||||
@changesets.reverse!
|
||||
end
|
||||
@journals.reverse! if User.current.wants_comments_in_reverse_order?
|
||||
|
||||
if User.current.allowed_to?(:view_time_entries, @project)
|
||||
Issue.load_visible_spent_hours([@issue])
|
||||
|
@ -102,11 +101,15 @@ class IssuesController < ApplicationController
|
|||
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
|
||||
@priorities = IssuePriority.active
|
||||
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
|
||||
@time_entries = @issue.time_entries.visible.preload(:activity, :user)
|
||||
@relation = IssueRelation.new
|
||||
retrieve_previous_and_next_issue_ids
|
||||
render :template => 'issues/show'
|
||||
}
|
||||
format.api
|
||||
format.api {
|
||||
@changesets = @issue.changesets.visible.preload(:repository, :user).to_a
|
||||
@changesets.reverse! if User.current.wants_comments_in_reverse_order?
|
||||
}
|
||||
format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
|
||||
format.pdf {
|
||||
send_file_headers! :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf"
|
||||
|
@ -177,7 +180,7 @@ class IssuesController < ApplicationController
|
|||
|
||||
if saved
|
||||
render_attachment_warning_if_needed(@issue)
|
||||
flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
|
||||
flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record? || params[:no_flash]
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_back_or_default issue_path(@issue, previous_and_next_issue_ids_params) }
|
||||
|
@ -191,6 +194,21 @@ class IssuesController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def issue_tab
|
||||
return render_error :status => 422 unless request.xhr?
|
||||
tab = params[:name]
|
||||
|
||||
case tab
|
||||
when 'time_entries'
|
||||
@time_entries = @issue.time_entries.visible.preload(:activity, :user).to_a
|
||||
render :partial => 'issues/tabs/time_entries', :locals => {:time_entries => @time_entries}
|
||||
when 'changesets'
|
||||
@changesets = @issue.changesets.visible.preload(:repository, :user).to_a
|
||||
@changesets.reverse! if User.current.wants_comments_in_reverse_order?
|
||||
render :partial => 'issues/tabs/changesets', :locals => {:changesets => @changesets}
|
||||
end
|
||||
end
|
||||
|
||||
# Bulk edit/copy a set of issues
|
||||
def bulk_edit
|
||||
@issues.sort!
|
||||
|
@ -259,14 +277,16 @@ class IssuesController < ApplicationController
|
|||
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}
|
||||
@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(:&)
|
||||
@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?
|
||||
@watchers_present = User.current.allowed_to?(:add_issue_watchers, @projects) &&
|
||||
Watcher.where(:watchable_type => 'Issue',
|
||||
:watchable_id => @issues.map(&:id)).exists?
|
||||
end
|
||||
|
||||
@safe_attributes = edited_issues.map(&:safe_attribute_names).reduce(:&)
|
||||
|
@ -316,7 +336,8 @@ class IssuesController < ApplicationController
|
|||
@issues.each do |orig_issue|
|
||||
orig_issue.reload
|
||||
if @copy
|
||||
issue = orig_issue.copy({},
|
||||
issue = orig_issue.copy(
|
||||
{},
|
||||
:attachments => copy_attachments,
|
||||
:subtasks => copy_subtasks,
|
||||
:watchers => copy_watchers,
|
||||
|
@ -464,6 +485,7 @@ class IssuesController < ApplicationController
|
|||
@issue.init_journal(User.current)
|
||||
|
||||
issue_attributes = params[:issue]
|
||||
issue_attributes[:assigned_to_id] = User.current.id if issue_attributes && issue_attributes[:assigned_to_id] == 'me'
|
||||
if issue_attributes && params[:conflict_resolution]
|
||||
case params[:conflict_resolution]
|
||||
when 'overwrite'
|
||||
|
@ -520,6 +542,7 @@ class IssuesController < ApplicationController
|
|||
# so we can use the default version for the new project
|
||||
attrs.delete(:fixed_version_id)
|
||||
end
|
||||
attrs[:assigned_to_id] = User.current.id if attrs[:assigned_to_id] == 'me'
|
||||
@issue.safe_attributes = attrs
|
||||
|
||||
if @issue.project
|
||||
|
@ -554,6 +577,7 @@ class IssuesController < ApplicationController
|
|||
time_entry = @time_entry || TimeEntry.new
|
||||
time_entry.project = @issue.project
|
||||
time_entry.issue = @issue
|
||||
time_entry.author = User.current
|
||||
time_entry.user = User.current
|
||||
time_entry.spent_on = User.current.today
|
||||
time_entry.safe_attributes = params[:time_entry]
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
|
@ -64,13 +66,14 @@ class JournalsController < ApplicationController
|
|||
if @journal
|
||||
user = @journal.user
|
||||
text = @journal.notes
|
||||
@content = +"#{ll(Setting.default_language, :text_user_wrote_in, {:value => user, :link => "#note-#{params[:journal_indice]}"})}\n> "
|
||||
else
|
||||
user = @issue.author
|
||||
text = @issue.description
|
||||
@content = +"#{ll(Setting.default_language, :text_user_wrote, user)}\n> "
|
||||
end
|
||||
# Replaces pre blocks with [...]
|
||||
text = text.to_s.strip.gsub(%r{<pre>(.*?)</pre>}m, '[...]')
|
||||
@content = "#{ll(Setting.default_language, :text_user_wrote, user)}\n> "
|
||||
@content << text.gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n"
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
|
@ -26,7 +28,7 @@ class MailHandlerController < ActionController::Base
|
|||
def index
|
||||
options = params.dup
|
||||
email = options.delete(:email)
|
||||
if MailHandler.receive(email, options)
|
||||
if MailHandler.safe_receive(email, options)
|
||||
head :created
|
||||
else
|
||||
head :unprocessable_entity
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
|
@ -63,6 +65,7 @@ class MessagesController < ApplicationController
|
|||
if @message.save
|
||||
call_hook(:controller_messages_new_after_save, { :params => params, :message => @message})
|
||||
render_attachment_warning_if_needed(@message)
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
redirect_to board_message_path(@board, @message)
|
||||
end
|
||||
end
|
||||
|
@ -80,6 +83,7 @@ class MessagesController < ApplicationController
|
|||
attachments = Attachment.attach_files(@reply, params[:attachments])
|
||||
render_attachment_warning_if_needed(@reply)
|
||||
end
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to board_message_path(@board, @topic, :r => @reply)
|
||||
end
|
||||
|
||||
|
@ -101,6 +105,7 @@ class MessagesController < ApplicationController
|
|||
(render_403; return false) unless @message.destroyable_by?(User.current)
|
||||
r = @message.to_param
|
||||
@message.destroy
|
||||
flash[:notice] = l(:notice_successful_delete)
|
||||
if @message.parent
|
||||
redirect_to board_message_path(@board, @message.parent, :r => r)
|
||||
else
|
||||
|
@ -112,18 +117,23 @@ class MessagesController < ApplicationController
|
|||
@subject = @message.subject
|
||||
@subject = "RE: #{@subject}" unless @subject.starts_with?('RE:')
|
||||
|
||||
@content = "#{ll(Setting.default_language, :text_user_wrote, @message.author)}\n> "
|
||||
if @message.root == @message
|
||||
@content = +"#{ll(Setting.default_language, :text_user_wrote, @message.author)}\n> "
|
||||
else
|
||||
@content = +"#{ll(Setting.default_language, :text_user_wrote_in, {:value => @message.author, :link => "message##{@message.id}"})}\n> "
|
||||
end
|
||||
@content << @message.content.to_s.strip.gsub(%r{<pre>(.*?)</pre>}m, '[...]').gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n"
|
||||
end
|
||||
|
||||
def preview
|
||||
message = @board.messages.find_by_id(params[:id])
|
||||
@text = (params[:message] || params[:reply])[:content]
|
||||
@text = params[:text] ? params[:text] : nil
|
||||
@previewed = message
|
||||
render :partial => 'common/preview'
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
|
||||
def find_message
|
||||
return unless find_board
|
||||
@message = @board.messages.includes(:parent).find(params[:id])
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,13 +23,17 @@ class MyController < ApplicationController
|
|||
# let user change user's password when user has to
|
||||
skip_before_action :check_password_change, :only => :password
|
||||
|
||||
require_sudo_mode :account, only: :post
|
||||
accept_api_auth :account
|
||||
|
||||
require_sudo_mode :account, only: :put
|
||||
require_sudo_mode :reset_rss_key, :reset_api_key, :show_api_key, :destroy
|
||||
|
||||
helper :issues
|
||||
helper :users
|
||||
helper :custom_fields
|
||||
helper :queries
|
||||
helper :activities
|
||||
helper :calendars
|
||||
|
||||
def index
|
||||
page
|
||||
|
@ -45,15 +51,25 @@ class MyController < ApplicationController
|
|||
def account
|
||||
@user = User.current
|
||||
@pref = @user.pref
|
||||
if request.post?
|
||||
if request.put?
|
||||
@user.safe_attributes = params[:user]
|
||||
@user.pref.safe_attributes = params[:pref]
|
||||
if @user.save
|
||||
@user.pref.save
|
||||
set_language_if_valid @user.language
|
||||
flash[:notice] = l(:notice_account_updated)
|
||||
redirect_to my_account_path
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
flash[:notice] = l(:notice_account_updated)
|
||||
redirect_to my_account_path
|
||||
}
|
||||
format.api { render_api_ok }
|
||||
end
|
||||
return
|
||||
else
|
||||
respond_to do |format|
|
||||
format.html { render :action => :account }
|
||||
format.api { render_validation_errors(@user) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -95,7 +111,7 @@ class MyController < ApplicationController
|
|||
if @user.save
|
||||
# The session token was destroyed by the password change, generate a new one
|
||||
session[:tk] = @user.generate_session_token
|
||||
Mailer.password_updated(@user)
|
||||
Mailer.deliver_password_updated(@user, User.current)
|
||||
flash[:notice] = l(:notice_account_password_updated)
|
||||
redirect_to my_account_path
|
||||
end
|
||||
|
@ -138,7 +154,7 @@ class MyController < ApplicationController
|
|||
block_settings = params[:settings] || {}
|
||||
|
||||
block_settings.each do |block, settings|
|
||||
@user.pref.update_block_settings(block, settings)
|
||||
@user.pref.update_block_settings(block, settings.to_unsafe_hash)
|
||||
end
|
||||
@user.pref.save
|
||||
@updated_blocks = block_settings.keys
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,7 +26,7 @@ class NewsController < ApplicationController
|
|||
before_action :authorize, :except => [:index]
|
||||
before_action :find_optional_project, :only => :index
|
||||
accept_rss_auth :index
|
||||
accept_api_auth :index
|
||||
accept_api_auth :index, :show, :create, :update, :destroy
|
||||
|
||||
helper :watchers
|
||||
helper :attachments
|
||||
|
@ -69,13 +71,21 @@ class NewsController < ApplicationController
|
|||
def create
|
||||
@news = News.new(:project => @project, :author => User.current)
|
||||
@news.safe_attributes = params[:news]
|
||||
@news.save_attachments(params[:attachments])
|
||||
@news.save_attachments(params[:attachments] || (params[:news] && params[:news][:uploads]))
|
||||
if @news.save
|
||||
render_attachment_warning_if_needed(@news)
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
redirect_to project_news_index_path(@project)
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
render_attachment_warning_if_needed(@news)
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
redirect_to project_news_index_path(@project)
|
||||
}
|
||||
format.api { render_api_ok }
|
||||
end
|
||||
else
|
||||
render :action => 'new'
|
||||
respond_to do |format|
|
||||
format.html { render :action => 'new' }
|
||||
format.api { render_validation_errors(@news) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -84,18 +94,29 @@ class NewsController < ApplicationController
|
|||
|
||||
def update
|
||||
@news.safe_attributes = params[:news]
|
||||
@news.save_attachments(params[:attachments])
|
||||
@news.save_attachments(params[:attachments] || (params[:news] && params[:news][:uploads]))
|
||||
if @news.save
|
||||
render_attachment_warning_if_needed(@news)
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to news_path(@news)
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
render_attachment_warning_if_needed(@news)
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to news_path(@news)
|
||||
}
|
||||
format.api { render_api_ok }
|
||||
end
|
||||
else
|
||||
render :action => 'edit'
|
||||
respond_to do |format|
|
||||
format.html { render :action => 'edit' }
|
||||
format.api { render_validation_errors(@news) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@news.destroy
|
||||
redirect_to project_news_index_path(@project)
|
||||
respond_to do |format|
|
||||
format.html { redirect_to project_news_index_path(@project) }
|
||||
format.api { render_api_ok }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,28 +18,28 @@
|
|||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
class PreviewsController < ApplicationController
|
||||
before_action :find_project, :find_attachments
|
||||
before_action :find_project, :except => :text
|
||||
before_action :find_attachments
|
||||
|
||||
def issue
|
||||
@issue = Issue.visible.find_by_id(params[:id]) unless params[:id].blank?
|
||||
@issue = Issue.visible.find_by_id(params[:issue_id]) unless params[:issue_id].blank?
|
||||
if @issue
|
||||
@description = params[:issue] && params[:issue][:description]
|
||||
if @description && @description.gsub(/(\r?\n|\n\r?)/, "\n") == @issue.description.to_s.gsub(/(\r?\n|\n\r?)/, "\n")
|
||||
@description = nil
|
||||
end
|
||||
@notes = params[:journal] ? params[:journal][:notes] : nil
|
||||
@notes ||= params[:issue] ? params[:issue][:notes] : nil
|
||||
else
|
||||
@description = (params[:issue] ? params[:issue][:description] : nil)
|
||||
@previewed = @issue
|
||||
end
|
||||
render :layout => false
|
||||
@text = params[:text] ? params[:text] : nil
|
||||
render :partial => 'common/preview'
|
||||
end
|
||||
|
||||
def news
|
||||
if params[:id].present? && news = News.visible.find_by_id(params[:id])
|
||||
@previewed = news
|
||||
end
|
||||
@text = (params[:news] ? params[:news][:description] : nil)
|
||||
@text = params[:text] ? params[:text] : nil
|
||||
render :partial => 'common/preview'
|
||||
end
|
||||
|
||||
def text
|
||||
@text = params[:text] ? params[:text] : nil
|
||||
render :partial => 'common/preview'
|
||||
end
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,6 +21,8 @@ class PrincipalMembershipsController < ApplicationController
|
|||
layout 'admin'
|
||||
self.main_menu = false
|
||||
|
||||
helper :members
|
||||
|
||||
before_action :require_admin
|
||||
before_action :find_principal, :only => [:new, :create]
|
||||
before_action :find_membership, :only => [:edit, :update, :destroy]
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,15 +22,8 @@ class ProjectEnumerationsController < ApplicationController
|
|||
before_action :authorize
|
||||
|
||||
def update
|
||||
if params[:enumerations]
|
||||
saved = Project.transaction do
|
||||
params[:enumerations].each do |id, activity|
|
||||
@project.update_or_create_time_entry_activity(id, activity)
|
||||
end
|
||||
end
|
||||
if saved
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
end
|
||||
if @project.update_or_create_time_entry_activities(update_params)
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
end
|
||||
|
||||
redirect_to settings_project_path(@project, :tab => 'activities')
|
||||
|
@ -41,4 +36,12 @@ class ProjectEnumerationsController < ApplicationController
|
|||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to settings_project_path(@project, :tab => 'activities')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_params
|
||||
params.
|
||||
permit(:enumerations => [:parent_id, :active, {:custom_field_values => {}}]).
|
||||
require(:enumerations)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,8 +33,12 @@ class ProjectsController < ApplicationController
|
|||
helper :custom_fields
|
||||
helper :issues
|
||||
helper :queries
|
||||
include QueriesHelper
|
||||
helper :projects_queries
|
||||
include ProjectsQueriesHelper
|
||||
helper :repositories
|
||||
helper :members
|
||||
helper :trackers
|
||||
|
||||
# Lists visible projects
|
||||
def index
|
||||
|
@ -41,14 +47,19 @@ class ProjectsController < ApplicationController
|
|||
return
|
||||
end
|
||||
|
||||
scope = Project.visible.sorted
|
||||
retrieve_project_query
|
||||
scope = project_scope
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
unless params[:closed]
|
||||
scope = scope.active
|
||||
# TODO: see what to do with the board view and pagination
|
||||
if @query.display_type == 'board'
|
||||
@entries = scope.to_a
|
||||
else
|
||||
@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
|
||||
end
|
||||
@projects = scope.to_a
|
||||
}
|
||||
format.api {
|
||||
@offset, @limit = api_offset_and_limit
|
||||
|
@ -59,6 +70,11 @@ class ProjectsController < ApplicationController
|
|||
projects = scope.reorder(:created_on => :desc).limit(Setting.feeds_limit.to_i).to_a
|
||||
render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
|
||||
}
|
||||
format.csv {
|
||||
# Export all entries
|
||||
@entries = scope.to_a
|
||||
send_data(query_to_csv(@entries, @query, params), :type => 'text/csv; header=present', :filename => 'projects.csv')
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -158,6 +174,7 @@ class ProjectsController < ApplicationController
|
|||
|
||||
if User.current.allowed_to_view_all_time_entries?(@project)
|
||||
@total_hours = TimeEntry.visible.where(cond).sum(:hours).to_f
|
||||
@total_estimated_hours = Issue.visible.where(cond).sum(:estimated_hours).to_f
|
||||
end
|
||||
|
||||
@key = User.current.rss_key
|
||||
|
@ -176,8 +193,7 @@ class ProjectsController < ApplicationController
|
|||
|
||||
@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)
|
||||
@versions = @project.shared_versions.status(@version_status).like(@version_name).sorted
|
||||
end
|
||||
|
||||
def edit
|
||||
|
@ -189,7 +205,7 @@ class ProjectsController < ApplicationController
|
|||
respond_to do |format|
|
||||
format.html {
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to settings_project_path(@project)
|
||||
redirect_to settings_project_path(@project, params[:tab])
|
||||
}
|
||||
format.api { render_api_ok }
|
||||
end
|
||||
|
@ -204,12 +220,6 @@ class ProjectsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def modules
|
||||
@project.enabled_module_names = params[:enabled_module_names]
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to settings_project_path(@project, :tab => 'modules')
|
||||
end
|
||||
|
||||
def archive
|
||||
unless @project.archive
|
||||
flash[:error] = l(:error_can_not_archive_project)
|
||||
|
@ -224,6 +234,19 @@ class ProjectsController < ApplicationController
|
|||
redirect_to_referer_or admin_projects_path(:status => params[:status])
|
||||
end
|
||||
|
||||
def bookmark
|
||||
jump_box = Redmine::ProjectJumpBox.new User.current
|
||||
if request.delete?
|
||||
jump_box.delete_project_bookmark @project
|
||||
elsif request.post?
|
||||
jump_box.bookmark_project @project
|
||||
end
|
||||
respond_to do |format|
|
||||
format.js
|
||||
format.html { redirect_to project_path(@project) }
|
||||
end
|
||||
end
|
||||
|
||||
def close
|
||||
@project.close
|
||||
redirect_to project_path(@project)
|
||||
|
@ -247,4 +270,15 @@ class ProjectsController < ApplicationController
|
|||
# hide project in layout
|
||||
@project = nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Returns the ProjectEntry scope for index
|
||||
def project_scope(options={})
|
||||
@query.results_scope(options)
|
||||
end
|
||||
|
||||
def retrieve_project_query
|
||||
retrieve_query(ProjectQuery, false, :defaults => @default_columns_names)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
|
@ -104,6 +106,10 @@ class QueriesController < ApplicationController
|
|||
render_404
|
||||
end
|
||||
|
||||
def current_menu_item
|
||||
@query ? @query.queried_class.to_s.underscore.pluralize.to_sym : nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_query
|
||||
|
@ -114,18 +120,11 @@ class QueriesController < ApplicationController
|
|||
render_404
|
||||
end
|
||||
|
||||
def find_optional_project
|
||||
@project = Project.find(params[:project_id]) if params[:project_id]
|
||||
render_403 unless User.current.allowed_to?(:save_queries, @project, :global => true)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def update_query_from_params
|
||||
@query.project = params[:query_is_for_all] ? nil : @project
|
||||
@query.build_from_params(params)
|
||||
@query.column_names = nil if params[:default_columns]
|
||||
@query.sort_criteria = params[:query] && params[:query][:sort_criteria]
|
||||
@query.sort_criteria = (params[:query] && params[:query][:sort_criteria]) || @query.sort_criteria
|
||||
@query.name = params[:query] && params[:query][:name]
|
||||
if User.current.allowed_to?(:manage_public_queries, @query.project) || User.current.admin?
|
||||
@query.visibility = (params[:query] && params[:query][:visibility]) || Query::VISIBILITY_PRIVATE
|
||||
|
@ -157,6 +156,10 @@ class QueriesController < ApplicationController
|
|||
redirect_to _time_entries_path(@project, nil, options)
|
||||
end
|
||||
|
||||
def redirect_to_project_query(options)
|
||||
redirect_to projects_path(options)
|
||||
end
|
||||
|
||||
# Returns the Query subclass, IssueQuery by default
|
||||
# for compatibility with previous behaviour
|
||||
def query_class
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,55 +23,56 @@ class ReportsController < ApplicationController
|
|||
|
||||
def issue_report
|
||||
@trackers = @project.rolled_up_trackers(false).visible
|
||||
@versions = @project.shared_versions.sort
|
||||
@versions = @project.shared_versions.sorted
|
||||
@priorities = IssuePriority.all.reverse
|
||||
@categories = @project.issue_categories
|
||||
@assignees = (Setting.issue_group_assignment? ? @project.principals : @project.users).sort
|
||||
@authors = @project.users.sort
|
||||
@assignees = (Setting.issue_group_assignment? ? @project.principals : @project.users).sorted
|
||||
@authors = @project.users.sorted
|
||||
@subprojects = @project.descendants.visible
|
||||
|
||||
@issues_by_tracker = Issue.by_tracker(@project)
|
||||
@issues_by_version = Issue.by_version(@project)
|
||||
@issues_by_priority = Issue.by_priority(@project)
|
||||
@issues_by_category = Issue.by_category(@project)
|
||||
@issues_by_assigned_to = Issue.by_assigned_to(@project)
|
||||
@issues_by_author = Issue.by_author(@project)
|
||||
with_subprojects = Setting.display_subprojects_issues?
|
||||
@issues_by_tracker = Issue.by_tracker(@project, with_subprojects)
|
||||
@issues_by_version = Issue.by_version(@project, with_subprojects)
|
||||
@issues_by_priority = Issue.by_priority(@project, with_subprojects)
|
||||
@issues_by_category = Issue.by_category(@project, with_subprojects)
|
||||
@issues_by_assigned_to = Issue.by_assigned_to(@project, with_subprojects)
|
||||
@issues_by_author = Issue.by_author(@project, with_subprojects)
|
||||
@issues_by_subproject = Issue.by_subproject(@project) || []
|
||||
|
||||
render :template => "reports/issue_report"
|
||||
end
|
||||
|
||||
def issue_report_details
|
||||
with_subprojects = Setting.display_subprojects_issues?
|
||||
case params[:detail]
|
||||
when "tracker"
|
||||
@field = "tracker_id"
|
||||
@rows = @project.rolled_up_trackers(false).visible
|
||||
@data = Issue.by_tracker(@project)
|
||||
@data = Issue.by_tracker(@project, with_subprojects)
|
||||
@report_title = l(:field_tracker)
|
||||
when "version"
|
||||
@field = "fixed_version_id"
|
||||
@rows = @project.shared_versions.sort
|
||||
@data = Issue.by_version(@project)
|
||||
@rows = @project.shared_versions.sorted
|
||||
@data = Issue.by_version(@project, with_subprojects)
|
||||
@report_title = l(:field_version)
|
||||
when "priority"
|
||||
@field = "priority_id"
|
||||
@rows = IssuePriority.all.reverse
|
||||
@data = Issue.by_priority(@project)
|
||||
@data = Issue.by_priority(@project, with_subprojects)
|
||||
@report_title = l(:field_priority)
|
||||
when "category"
|
||||
@field = "category_id"
|
||||
@rows = @project.issue_categories
|
||||
@data = Issue.by_category(@project)
|
||||
@data = Issue.by_category(@project, with_subprojects)
|
||||
@report_title = l(:field_category)
|
||||
when "assigned_to"
|
||||
@field = "assigned_to_id"
|
||||
@rows = (Setting.issue_group_assignment? ? @project.principals : @project.users).sort
|
||||
@data = Issue.by_assigned_to(@project)
|
||||
@rows = (Setting.issue_group_assignment? ? @project.principals : @project.users).sorted
|
||||
@data = Issue.by_assigned_to(@project, with_subprojects)
|
||||
@report_title = l(:field_assigned_to)
|
||||
when "author"
|
||||
@field = "author_id"
|
||||
@rows = @project.users.sort
|
||||
@data = Issue.by_author(@project)
|
||||
@rows = @project.users.sorted
|
||||
@data = Issue.by_author(@project, with_subprojects)
|
||||
@report_title = l(:field_author)
|
||||
when "subproject"
|
||||
@field = "project_id"
|
||||
|
@ -84,6 +87,6 @@ class ReportsController < ApplicationController
|
|||
private
|
||||
|
||||
def find_issue_statuses
|
||||
@statuses = IssueStatus.sorted.to_a
|
||||
@statuses = @project.rolled_up_statuses.sorted.to_a
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,13 +17,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 'SVG/Graph/Bar'
|
||||
require 'SVG/Graph/BarHorizontal'
|
||||
require 'digest/sha1'
|
||||
require 'redmine/scm/adapters'
|
||||
|
||||
class ChangesetNotFound < Exception; end
|
||||
class InvalidRevisionParam < Exception; end
|
||||
class ChangesetNotFound < StandardError; end
|
||||
class InvalidRevisionParam < StandardError; end
|
||||
|
||||
class RepositoriesController < ApplicationController
|
||||
menu_item :repository
|
||||
|
@ -148,6 +148,13 @@ class RepositoriesController < ApplicationController
|
|||
send_opt[:disposition] = disposition(@path)
|
||||
send_data @repository.cat(@path, @rev), send_opt
|
||||
else
|
||||
# set up pagination from entry to entry
|
||||
parent_path = @path.split('/')[0...-1].join('/')
|
||||
@entries = @repository.entries(parent_path, @rev).reject(&:is_dir?)
|
||||
if index = @entries.index{|e| e.name == @entry.name}
|
||||
@paginator = Redmine::Pagination::Paginator.new(@entries.size, 1, index+1)
|
||||
end
|
||||
|
||||
if !@entry.size || @entry.size <= Setting.file_max_size_displayed.to_i.kilobyte
|
||||
content = @repository.cat(@path, @rev)
|
||||
(show_error_not_found; return) unless content
|
||||
|
@ -232,7 +239,7 @@ class RepositoriesController < ApplicationController
|
|||
if params[:format] == 'diff'
|
||||
@diff = @repository.diff(@path, @rev, @rev_to)
|
||||
(show_error_not_found; return) unless @diff
|
||||
filename = "changeset_r#{@rev}"
|
||||
filename = +"changeset_r#{@rev}"
|
||||
filename << "_r#{@rev_to}" if @rev_to
|
||||
send_data @diff.join, :filename => "#{filename}.diff",
|
||||
:type => 'text/x-patch',
|
||||
|
@ -250,18 +257,20 @@ class RepositoriesController < ApplicationController
|
|||
Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}-#{current_language}")
|
||||
unless read_fragment(@cache_key)
|
||||
@diff = @repository.diff(@path, @rev, @rev_to)
|
||||
show_error_not_found unless @diff
|
||||
(show_error_not_found; return) unless @diff
|
||||
end
|
||||
|
||||
@changeset = @repository.find_changeset_by_name(@rev)
|
||||
@changeset_to = @rev_to ? @repository.find_changeset_by_name(@rev_to) : nil
|
||||
@diff_format_revisions = @repository.diff_format_revisions(@changeset, @changeset_to)
|
||||
render :diff, :formats => :html, :layout => 'base.html.erb'
|
||||
end
|
||||
end
|
||||
|
||||
def stats
|
||||
end
|
||||
|
||||
# Returns JSON data for repository graphs
|
||||
def graph
|
||||
data = nil
|
||||
case params[:graph]
|
||||
|
@ -271,8 +280,7 @@ class RepositoriesController < ApplicationController
|
|||
data = graph_commits_per_author(@repository)
|
||||
end
|
||||
if data
|
||||
headers["Content-Type"] = "image/svg+xml"
|
||||
send_data(data, :type => "image/svg+xml", :disposition => "inline")
|
||||
render :json => data
|
||||
else
|
||||
render_404
|
||||
end
|
||||
|
@ -313,7 +321,7 @@ class RepositoriesController < ApplicationController
|
|||
@rev = params[:rev].blank? ? @repository.default_branch : params[:rev].to_s.strip
|
||||
@rev_to = params[:rev_to]
|
||||
|
||||
unless @rev.to_s.match(REV_PARAM_RE) && @rev_to.to_s.match(REV_PARAM_RE)
|
||||
unless REV_PARAM_RE.match?(@rev.to_s) && REV_PARAM_RE.match?(@rev_to.to_s)
|
||||
if @repository.branches.blank?
|
||||
raise InvalidRevisionParam
|
||||
end
|
||||
|
@ -341,55 +349,37 @@ class RepositoriesController < ApplicationController
|
|||
end
|
||||
|
||||
def graph_commits_per_month(repository)
|
||||
@date_to = User.current.today
|
||||
@date_from = @date_to << 11
|
||||
@date_from = Date.civil(@date_from.year, @date_from.month, 1)
|
||||
date_to = User.current.today
|
||||
date_from = date_to << 11
|
||||
date_from = Date.civil(date_from.year, date_from.month, 1)
|
||||
commits_by_day = Changeset.
|
||||
where("repository_id = ? AND commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to).
|
||||
where("repository_id = ? AND commit_date BETWEEN ? AND ?", repository.id, date_from, date_to).
|
||||
group(:commit_date).
|
||||
count
|
||||
commits_by_month = [0] * 12
|
||||
commits_by_day.each {|c| commits_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last }
|
||||
commits_by_day.each {|c| commits_by_month[(date_to.month - c.first.to_date.month) % 12] += c.last }
|
||||
|
||||
changes_by_day = Change.
|
||||
joins(:changeset).
|
||||
where("#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to).
|
||||
where("#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, date_from, date_to).
|
||||
group(:commit_date).
|
||||
count
|
||||
changes_by_month = [0] * 12
|
||||
changes_by_day.each {|c| changes_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last }
|
||||
changes_by_day.each {|c| changes_by_month[(date_to.month - c.first.to_date.month) % 12] += c.last }
|
||||
|
||||
fields = []
|
||||
today = User.current.today
|
||||
12.times {|m| fields << month_name(((today.month - 1 - m) % 12) + 1)}
|
||||
|
||||
graph = SVG::Graph::Bar.new(
|
||||
:height => 300,
|
||||
:width => 800,
|
||||
:fields => fields.reverse,
|
||||
:stack => :side,
|
||||
:scale_integers => true,
|
||||
:step_x_labels => 2,
|
||||
:show_data_values => false,
|
||||
:graph_title => l(:label_commits_per_month),
|
||||
:show_graph_title => true
|
||||
)
|
||||
|
||||
graph.add_data(
|
||||
:data => commits_by_month[0..11].reverse,
|
||||
:title => l(:label_revision_plural)
|
||||
)
|
||||
|
||||
graph.add_data(
|
||||
:data => changes_by_month[0..11].reverse,
|
||||
:title => l(:label_change_plural)
|
||||
)
|
||||
|
||||
graph.burn
|
||||
data = {
|
||||
:labels => fields.reverse,
|
||||
:commits => commits_by_month[0..11].reverse,
|
||||
:changes => changes_by_month[0..11].reverse
|
||||
}
|
||||
end
|
||||
|
||||
def graph_commits_per_author(repository)
|
||||
#data
|
||||
# data
|
||||
stats = repository.stats_by_author
|
||||
fields, commits_data, changes_data = [], [], []
|
||||
stats.each do |name, hsh|
|
||||
|
@ -398,7 +388,7 @@ class RepositoriesController < ApplicationController
|
|||
changes_data << hsh[:changes_count]
|
||||
end
|
||||
|
||||
#expand to 10 values if needed
|
||||
# expand to 10 values if needed
|
||||
fields = fields + [""]*(10 - fields.length) if fields.length<10
|
||||
commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
|
||||
changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
|
||||
|
@ -406,27 +396,11 @@ class RepositoriesController < ApplicationController
|
|||
# Remove email address in usernames
|
||||
fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
|
||||
|
||||
#prepare graph
|
||||
graph = SVG::Graph::BarHorizontal.new(
|
||||
:height => 30 * commits_data.length,
|
||||
:width => 800,
|
||||
:fields => fields,
|
||||
:stack => :side,
|
||||
:scale_integers => true,
|
||||
:show_data_values => false,
|
||||
:rotate_y_labels => false,
|
||||
:graph_title => l(:label_commits_per_author),
|
||||
:show_graph_title => true
|
||||
)
|
||||
graph.add_data(
|
||||
:data => commits_data,
|
||||
:title => l(:label_revision_plural)
|
||||
)
|
||||
graph.add_data(
|
||||
:data => changes_data,
|
||||
:title => l(:label_change_plural)
|
||||
)
|
||||
graph.burn
|
||||
data = {
|
||||
:labels => fields.reverse,
|
||||
:commits => commits_data.reverse,
|
||||
:changes => changes_data.reverse
|
||||
}
|
||||
end
|
||||
|
||||
def disposition(path)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
|
@ -101,16 +103,22 @@ class RolesController < ApplicationController
|
|||
end
|
||||
|
||||
def permissions
|
||||
@roles = Role.sorted.to_a
|
||||
@permissions = Redmine::AccessControl.permissions.select { |p| !p.public? }
|
||||
if request.post?
|
||||
@roles.each do |role|
|
||||
role.permissions = params[:permissions][role.id.to_s]
|
||||
role.save
|
||||
end
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to roles_path
|
||||
scope = Role.sorted
|
||||
if params[:ids].present?
|
||||
scope = scope.where(:id => params[:ids])
|
||||
end
|
||||
@roles = scope.to_a
|
||||
@permissions = Redmine::AccessControl.permissions.select { |p| !p.public? }
|
||||
end
|
||||
|
||||
def update_permissions
|
||||
@roles = Role.where(:id => params[:permissions].keys)
|
||||
@roles.each do |role|
|
||||
role.permissions = params[:permissions][role.id.to_s]
|
||||
role.save
|
||||
end
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to roles_path
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,12 +18,11 @@
|
|||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
class SearchController < ApplicationController
|
||||
before_action :find_optional_project
|
||||
before_action :find_optional_project_by_id, :authorize_global
|
||||
accept_api_auth :index
|
||||
|
||||
def index
|
||||
@question = params[:q] || ""
|
||||
@question.strip!
|
||||
@question = params[:q]&.strip || ""
|
||||
@all_words = params[:all_words] ? params[:all_words].present? : true
|
||||
@titles_only = params[:titles_only] ? params[:titles_only].present? : false
|
||||
@search_attachments = params[:attachments].presence || '0'
|
||||
|
@ -68,7 +69,7 @@ class SearchController < ApplicationController
|
|||
fetcher = Redmine::Search::Fetcher.new(
|
||||
@question, User.current, @scope, projects_to_search,
|
||||
:all_words => @all_words, :titles_only => @titles_only, :attachments => @search_attachments, :open_issues => @open_issues,
|
||||
:cache => params[:page].present?, :params => params
|
||||
:cache => params[:page].present?, :params => params.to_unsafe_hash
|
||||
)
|
||||
|
||||
if fetcher.tokens.present?
|
||||
|
@ -87,13 +88,4 @@ class SearchController < ApplicationController
|
|||
format.api { @results ||= []; render :layout => false }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def find_optional_project
|
||||
return true unless params[:id]
|
||||
@project = Project.find(params[:id])
|
||||
check_project_privacy
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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 +36,7 @@ class SettingsController < ApplicationController
|
|||
def edit
|
||||
@notifiables = Redmine::Notifiable.all
|
||||
if request.post?
|
||||
errors = Setting.set_all_from_params(params[:settings])
|
||||
errors = Setting.set_all_from_params(params[:settings].to_unsafe_hash)
|
||||
if errors.blank?
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to settings_path(:tab => params[:tab])
|
||||
|
@ -46,7 +48,7 @@ class SettingsController < ApplicationController
|
|||
end
|
||||
|
||||
@options = {}
|
||||
user_format = User::USER_FORMATS.collect{|key, value| [key, value[:setting_order]]}.sort{|a, b| a[1] <=> b[1]}
|
||||
user_format = User::USER_FORMATS.collect{|key, value| [key, value[:setting_order]]}.sort_by{|f| f[1]}
|
||||
@options[:user_format] = user_format.collect{|f| [User.current.name(f[0]), f[0].to_s]}
|
||||
@deliveries = ActionMailer::Base.perform_deliveries
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
|
@ -38,7 +40,7 @@ class SysController < ActionController::Base
|
|||
repository.safe_attributes = params[:repository]
|
||||
repository.project = project
|
||||
if repository.save
|
||||
render :json => {repository.class.name.underscore.gsub('/', '-') => {:id => repository.id, :url => repository.url}}, :status => 201
|
||||
render :json => {repository.class.name.underscore.tr('/', '-') => {:id => repository.id, :url => repository.url}}, :status => 201
|
||||
else
|
||||
head 422
|
||||
end
|
||||
|
@ -50,7 +52,7 @@ class SysController < ActionController::Base
|
|||
scope = Project.active.has_module(:repository)
|
||||
if params[:id]
|
||||
project = nil
|
||||
if params[:id].to_s =~ /^\d*$/
|
||||
if /^\d*$/.match?(params[:id].to_s)
|
||||
project = scope.find(params[:id])
|
||||
else
|
||||
project = scope.find_by_identifier(params[:id])
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,7 +27,6 @@ class TimelogController < ApplicationController
|
|||
|
||||
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
|
||||
|
@ -91,12 +92,12 @@ class TimelogController < ApplicationController
|
|||
end
|
||||
|
||||
def new
|
||||
@time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
|
||||
@time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :author => User.current, :spent_on => User.current.today)
|
||||
@time_entry.safe_attributes = params[:time_entry]
|
||||
end
|
||||
|
||||
def create
|
||||
@time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
|
||||
@time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :author => User.current, :user => User.current, :spent_on => User.current.today)
|
||||
@time_entry.safe_attributes = params[:time_entry]
|
||||
if @time_entry.project && !User.current.allowed_to?(:log_time, @time_entry.project)
|
||||
render_403
|
||||
|
@ -146,7 +147,6 @@ class TimelogController < ApplicationController
|
|||
|
||||
def update
|
||||
@time_entry.safe_attributes = params[:time_entry]
|
||||
|
||||
call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
|
||||
|
||||
if @time_entry.save
|
||||
|
@ -166,8 +166,18 @@ class TimelogController < ApplicationController
|
|||
end
|
||||
|
||||
def bulk_edit
|
||||
@available_activities = @projects.map(&:activities).reduce(:&)
|
||||
@target_projects = Project.allowed_to(:log_time).to_a
|
||||
@custom_fields = TimeEntry.first.available_custom_fields.select {|field| field.format.bulk_edit_supported}
|
||||
if params[:time_entry]
|
||||
@target_project = @target_projects.detect {|p| p.id.to_s == params[:time_entry][:project_id].to_s}
|
||||
end
|
||||
if @target_project
|
||||
@available_activities = @target_project.activities
|
||||
else
|
||||
@available_activities = @projects.map(&:activities).reduce(:&)
|
||||
end
|
||||
@time_entry_params = params[:time_entry] || {}
|
||||
@time_entry_params[:custom_field_values] ||= {}
|
||||
end
|
||||
|
||||
def bulk_update
|
||||
|
@ -230,7 +240,8 @@ class TimelogController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
|
||||
def find_time_entry
|
||||
@time_entry = TimeEntry.find(params[:id])
|
||||
@project = @time_entry.project
|
||||
|
@ -262,25 +273,18 @@ private
|
|||
if params[:issue_id].present?
|
||||
@issue = Issue.find(params[:issue_id])
|
||||
@project = @issue.project
|
||||
authorize
|
||||
else
|
||||
find_optional_project
|
||||
end
|
||||
end
|
||||
|
||||
def find_optional_project
|
||||
if params[:project_id].present?
|
||||
@project = Project.find(params[:project_id])
|
||||
end
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
# Returns the TimeEntry scope for index and report actions
|
||||
def time_entry_scope(options={})
|
||||
@query.results_scope(options)
|
||||
end
|
||||
|
||||
def retrieve_time_entry_query
|
||||
retrieve_query(TimeEntryQuery, false)
|
||||
retrieve_query(TimeEntryQuery, false, :defaults => @default_columns_names)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,7 +34,7 @@ class TrackersController < ApplicationController
|
|||
end
|
||||
|
||||
def new
|
||||
@tracker ||= Tracker.new
|
||||
@tracker ||= Tracker.new(:default_status => IssueStatus.sorted.first)
|
||||
@tracker.safe_attributes = params[:tracker]
|
||||
@trackers = Tracker.sorted.to_a
|
||||
@projects = Project.all
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,7 +30,10 @@ class UsersController < ApplicationController
|
|||
include SortHelper
|
||||
helper :custom_fields
|
||||
include CustomFieldsHelper
|
||||
include UsersHelper
|
||||
helper :principal_memberships
|
||||
helper :activities
|
||||
include ActivitiesHelper
|
||||
|
||||
require_sudo_mode :create, :update, :destroy
|
||||
|
||||
|
@ -59,6 +64,9 @@ class UsersController < ApplicationController
|
|||
@groups = Group.givable.sort
|
||||
render :layout => !request.xhr?
|
||||
}
|
||||
format.csv {
|
||||
send_data(users_to_csv(scope.order(sort_clause)), :type => 'text/csv; header=present', :filename => 'users.csv')
|
||||
}
|
||||
format.api
|
||||
end
|
||||
end
|
||||
|
@ -72,6 +80,16 @@ class UsersController < ApplicationController
|
|||
# show projects based on current user visibility
|
||||
@memberships = @user.memberships.preload(:roles, :project).where(Project.visible_condition(User.current)).to_a
|
||||
|
||||
@issue_counts = {}
|
||||
@issue_counts[:assigned] = {
|
||||
:total => Issue.visible.assigned_to(@user).count,
|
||||
:open => Issue.visible.open.assigned_to(@user).count
|
||||
}
|
||||
@issue_counts[:reported] = {
|
||||
:total => Issue.visible.where(:author_id => @user.id).count,
|
||||
:open => Issue.visible.open.where(:author_id => @user.id).count
|
||||
}
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10)
|
||||
|
@ -95,13 +113,13 @@ class UsersController < ApplicationController
|
|||
@user.pref.safe_attributes = params[:pref]
|
||||
|
||||
if @user.save
|
||||
Mailer.account_information(@user, @user.password).deliver if params[:send_information]
|
||||
Mailer.deliver_account_information(@user, @user.password) if params[:send_information]
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
flash[:notice] = l(:notice_user_successful_create, :id => view_context.link_to(@user.login, user_path(@user)))
|
||||
if params[:continue]
|
||||
attrs = params[:user].slice(:generate_password)
|
||||
attrs = {:generate_password => @user.generate_password }
|
||||
redirect_to new_user_path(:user => attrs)
|
||||
else
|
||||
redirect_to edit_user_path(@user)
|
||||
|
@ -140,9 +158,9 @@ class UsersController < ApplicationController
|
|||
@user.pref.save
|
||||
|
||||
if was_activated
|
||||
Mailer.account_activated(@user).deliver
|
||||
Mailer.deliver_account_activated(@user)
|
||||
elsif @user.active? && params[:send_information] && @user != User.current
|
||||
Mailer.account_information(@user, @user.password).deliver
|
||||
Mailer.deliver_account_information(@user, @user.password)
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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 +36,7 @@ class VersionsController < ApplicationController
|
|||
@trackers = @project.trackers.sorted.to_a
|
||||
retrieve_selected_tracker_ids(@trackers, @trackers.select {|t| t.is_in_roadmap?})
|
||||
@with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
|
||||
project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id]
|
||||
project_ids = @with_subprojects ? @project.self_and_descendants.pluck(:id) : [@project.id]
|
||||
|
||||
@versions = @project.shared_versions.preload(:custom_values)
|
||||
@versions += @project.rolled_up_versions.visible.preload(:custom_values) if @with_subprojects
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
|
@ -42,8 +44,6 @@ class WikiController < ApplicationController
|
|||
helper :watchers
|
||||
include Redmine::Export::PDF
|
||||
|
||||
include ActionView::Helpers::SanitizeHelper
|
||||
|
||||
# List of pages, sorted alphabetically and by parent (hierarchy)
|
||||
def index
|
||||
load_pages_for_index
|
||||
|
@ -72,7 +72,7 @@ class WikiController < ApplicationController
|
|||
@page.title = '' unless editable?
|
||||
@page.validate
|
||||
if @page.errors[:title].blank?
|
||||
path = project_wiki_page_path(@project, @page.title)
|
||||
path = project_wiki_page_path(@project, @page.title, :parent => params[:parent])
|
||||
respond_to do |format|
|
||||
format.html { redirect_to path }
|
||||
format.js { render :js => "window.location = #{path.to_json}" }
|
||||
|
@ -109,7 +109,7 @@ class WikiController < ApplicationController
|
|||
send_data(export, :type => 'text/html', :filename => filename_for_content_disposition("#{@page.title}.html"))
|
||||
return
|
||||
elsif params[:format] == 'txt'
|
||||
send_data(strip_tags(@content.text), :type => 'text/plain', :filename => filename_for_content_disposition("#{@page.title}.txt"))
|
||||
send_data(@content.text, :type => 'text/plain', :filename => filename_for_content_disposition("#{@page.title}.txt"))
|
||||
return
|
||||
end
|
||||
end
|
||||
|
@ -325,7 +325,7 @@ class WikiController < ApplicationController
|
|||
@attachments += page.attachments
|
||||
@previewed = page.content
|
||||
end
|
||||
@text = params[:content][:text]
|
||||
@text = params[:content].present? ? params[:content][:text] : params[:text]
|
||||
render :partial => 'common/preview'
|
||||
end
|
||||
|
||||
|
@ -336,7 +336,7 @@ class WikiController < ApplicationController
|
|||
redirect_to :action => 'show', :id => @page.title, :project_id => @project
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
|
||||
def find_wiki
|
||||
@project = Project.find(params[:project_id])
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,13 +21,6 @@ class WikisController < ApplicationController
|
|||
menu_item :settings
|
||||
before_action :find_project, :authorize
|
||||
|
||||
# Create or update a project's wiki
|
||||
def edit
|
||||
@wiki = @project.wiki || Wiki.new(:project => @project)
|
||||
@wiki.safe_attributes = params[:wiki]
|
||||
@wiki.save if request.post?
|
||||
end
|
||||
|
||||
# Delete a project's wiki
|
||||
def destroy
|
||||
if request.post? && params[:confirm] && @project.wiki
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
|
@ -118,7 +120,7 @@ class WorkflowsController < ApplicationController
|
|||
def find_roles
|
||||
ids = Array.wrap(params[:role_id])
|
||||
if ids == ['all']
|
||||
@roles = Role.sorted.to_a
|
||||
@roles = Role.sorted.select(&:consider_workflow?)
|
||||
elsif ids.present?
|
||||
@roles = Role.where(:id => ids).to_a
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,9 +21,9 @@ module ActivitiesHelper
|
|||
def sort_activity_events(events)
|
||||
events_by_group = events.group_by(&:event_group)
|
||||
sorted_events = []
|
||||
events.sort {|x, y| y.event_datetime <=> x.event_datetime}.each do |event|
|
||||
events.sort_by(&:event_datetime).reverse_each do |event|
|
||||
if group_events = events_by_group.delete(event.event_group)
|
||||
group_events.sort {|x, y| y.event_datetime <=> x.event_datetime}.each_with_index do |e, i|
|
||||
group_events.sort_by(&:event_datetime).reverse.each_with_index do |e, i|
|
||||
sorted_events << [e, i > 0]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,7 +23,6 @@ require 'cgi'
|
|||
module ApplicationHelper
|
||||
include Redmine::WikiFormatting::Macros::Definitions
|
||||
include Redmine::I18n
|
||||
include GravatarHelper::PublicMethods
|
||||
include Redmine::Pagination::Helper
|
||||
include Redmine::SudoMode::Helper
|
||||
include Redmine::Themes::Helper
|
||||
|
@ -53,7 +52,8 @@ module ApplicationHelper
|
|||
if user.is_a?(User)
|
||||
name = h(user.name(options[:format]))
|
||||
if user.active? || (User.current.admin? && user.logged?)
|
||||
link_to name, user_path(user), :class => user.css_classes
|
||||
only_path = options[:only_path].nil? ? true : options[:only_path]
|
||||
link_to name, user_url(user, :only_path => only_path), :class => user.css_classes
|
||||
else
|
||||
name
|
||||
end
|
||||
|
@ -62,6 +62,20 @@ module ApplicationHelper
|
|||
end
|
||||
end
|
||||
|
||||
# Displays a link to edit group page if current user is admin
|
||||
# Otherwise display only the group name
|
||||
def link_to_group(group, options={})
|
||||
if group.is_a?(Group)
|
||||
name = h(group.name)
|
||||
if User.current.admin?
|
||||
only_path = options[:only_path].nil? ? true : options[:only_path]
|
||||
link_to name, edit_group_path(group, :only_path => only_path)
|
||||
else
|
||||
name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Displays a link to +issue+ with its subject.
|
||||
# Examples:
|
||||
#
|
||||
|
@ -97,10 +111,17 @@ module ApplicationHelper
|
|||
# * :download - Force download (default: false)
|
||||
def link_to_attachment(attachment, options={})
|
||||
text = options.delete(:text) || attachment.filename
|
||||
route_method = options.delete(:download) ? :download_named_attachment_url : :named_attachment_url
|
||||
html_options = options.slice!(:only_path)
|
||||
if options.delete(:download)
|
||||
route_method = :download_named_attachment_url
|
||||
options[:filename] = attachment.filename
|
||||
else
|
||||
route_method = :attachment_url
|
||||
# make sure we don't have an extraneous :filename in the options
|
||||
options.delete(:filename)
|
||||
end
|
||||
html_options = options.slice!(:only_path, :filename)
|
||||
options[:only_path] = true unless options.key?(:only_path)
|
||||
url = send(route_method, attachment, attachment.filename, options)
|
||||
url = send(route_method, attachment, options)
|
||||
link_to text, url, html_options
|
||||
end
|
||||
|
||||
|
@ -146,8 +167,8 @@ module ApplicationHelper
|
|||
h(project.name)
|
||||
else
|
||||
link_to project.name,
|
||||
project_url(project, {:only_path => true}.merge(options)),
|
||||
html_options
|
||||
project_url(project, {:only_path => true}.merge(options)),
|
||||
html_options
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -169,6 +190,39 @@ module ApplicationHelper
|
|||
link_to_if version.visible?, format_version_name(version), version_path(version), options
|
||||
end
|
||||
|
||||
RECORD_LINK = {
|
||||
'CustomValue' => -> (custom_value) { link_to_record(custom_value.customized) },
|
||||
'Document' => -> (document) { link_to(document.title, document_path(document)) },
|
||||
'Group' => -> (group) { link_to(group.name, group_path(group)) },
|
||||
'Issue' => -> (issue) { link_to_issue(issue, :subject => false) },
|
||||
'Message' => -> (message) { link_to_message(message) },
|
||||
'News' => -> (news) { link_to(news.title, news_path(news)) },
|
||||
'Project' => -> (project) { link_to_project(project) },
|
||||
'User' => -> (user) { link_to_user(user) },
|
||||
'Version' => -> (version) { link_to_version(version) },
|
||||
'WikiPage' => -> (wiki_page) { link_to(wiki_page.pretty_title, project_wiki_page_path(wiki_page.project, wiki_page.title)) }
|
||||
}
|
||||
|
||||
def link_to_record(record)
|
||||
if link = RECORD_LINK[record.class.name]
|
||||
self.instance_exec(record, &link)
|
||||
end
|
||||
end
|
||||
|
||||
ATTACHMENT_CONTAINER_LINK = {
|
||||
# Custom list, since project/version attachments are listed in the files
|
||||
# view and not in the project/milestone view
|
||||
'Project' => -> (project) { link_to(l(:project_module_files), project_files_path(project)) },
|
||||
'Version' => -> (version) { link_to(l(:project_module_files), project_files_path(version.project)) },
|
||||
}
|
||||
|
||||
def link_to_attachment_container(attachment_container)
|
||||
if link = ATTACHMENT_CONTAINER_LINK[attachment_container.class.name] ||
|
||||
RECORD_LINK[attachment_container.class.name]
|
||||
self.instance_exec(attachment_container, &link)
|
||||
end
|
||||
end
|
||||
|
||||
# Helper that formats object for html or text rendering
|
||||
def format_object(object, html=true, &block)
|
||||
if block_given?
|
||||
|
@ -228,22 +282,57 @@ module ApplicationHelper
|
|||
: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
|
||||
attachment_path(
|
||||
attachment
|
||||
),
|
||||
:title => attachment.filename
|
||||
)
|
||||
end
|
||||
|
||||
def toggle_link(name, id, options={})
|
||||
onclick = "$('##{id}').toggle(); "
|
||||
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
|
||||
|
||||
def link_to_previous_month(year, month, options={})
|
||||
target_year, target_month = if month == 1
|
||||
[year - 1, 12]
|
||||
else
|
||||
[year, month - 1]
|
||||
end
|
||||
|
||||
name = if target_month == 12
|
||||
"#{month_name(target_month)} #{target_year}"
|
||||
else
|
||||
month_name(target_month)
|
||||
end
|
||||
|
||||
link_to_month(("« " + name), target_year, target_month, options)
|
||||
end
|
||||
|
||||
def link_to_next_month(year, month, options={})
|
||||
target_year, target_month = if month == 12
|
||||
[year + 1, 1]
|
||||
else
|
||||
[year, month + 1]
|
||||
end
|
||||
|
||||
name = if target_month == 1
|
||||
"#{month_name(target_month)} #{target_year}"
|
||||
else
|
||||
month_name(target_month)
|
||||
end
|
||||
|
||||
link_to_month((name + " »"), target_year, target_month, options)
|
||||
end
|
||||
|
||||
def link_to_month(link_name, year, month, options={})
|
||||
link_to(link_name, {:params => request.query_parameters.merge(:year => year, :month => month)}, options)
|
||||
end
|
||||
|
||||
# Used to format item titles on the activity view
|
||||
def format_activity_title(text)
|
||||
text
|
||||
|
@ -281,19 +370,19 @@ module ApplicationHelper
|
|||
# The given collection may be a subset of the whole project tree
|
||||
# (eg. some intermediate nodes are private and can not be seen)
|
||||
def render_project_nested_lists(projects, &block)
|
||||
s = ''
|
||||
s = +''
|
||||
if projects.any?
|
||||
ancestors = []
|
||||
original_project = @project
|
||||
projects.sort_by(&:lft).each do |project|
|
||||
# set the project environment to please macros.
|
||||
@project = project
|
||||
if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
|
||||
if ancestors.empty? || project.is_descendant_of?(ancestors.last)
|
||||
s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
|
||||
else
|
||||
ancestors.pop
|
||||
s << "</li>"
|
||||
while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
|
||||
while ancestors.any? && !project.is_descendant_of?(ancestors.last)
|
||||
ancestors.pop
|
||||
s << "</ul></li>\n"
|
||||
end
|
||||
|
@ -311,12 +400,17 @@ module ApplicationHelper
|
|||
end
|
||||
|
||||
def render_page_hierarchy(pages, node=nil, options={})
|
||||
content = ''
|
||||
content = +''
|
||||
if pages[node]
|
||||
content << "<ul class=\"pages-hierarchy\">\n"
|
||||
pages[node].each do |page|
|
||||
content << "<li>"
|
||||
content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title, :version => nil},
|
||||
if controller.controller_name == 'wiki' && controller.action_name == 'export'
|
||||
href = "##{page.title}"
|
||||
else
|
||||
href = {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title, :version => nil}
|
||||
end
|
||||
content << link_to(h(page.pretty_title), href,
|
||||
:title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
|
||||
content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
|
||||
content << "</li>\n"
|
||||
|
@ -328,7 +422,7 @@ module ApplicationHelper
|
|||
|
||||
# Renders flash messages
|
||||
def render_flash_messages
|
||||
s = ''
|
||||
s = +''
|
||||
flash.each do |k,v|
|
||||
s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}")
|
||||
end
|
||||
|
@ -348,6 +442,17 @@ module ApplicationHelper
|
|||
end
|
||||
end
|
||||
|
||||
# Returns the tab action depending on the tab properties
|
||||
def get_tab_action(tab)
|
||||
if tab[:onclick]
|
||||
return tab[:onclick]
|
||||
elsif tab[:partial]
|
||||
return "showTab('#{tab[:name]}', this.href)"
|
||||
else
|
||||
return nil
|
||||
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
|
||||
|
@ -366,12 +471,35 @@ module ApplicationHelper
|
|||
end
|
||||
|
||||
def render_projects_for_jump_box(projects, selected=nil)
|
||||
jump_box = Redmine::ProjectJumpBox.new User.current
|
||||
query = params[:q] if request.format.js?
|
||||
bookmarked = jump_box.bookmarked_projects(query)
|
||||
recents = jump_box.recently_used_projects(query)
|
||||
projects = projects - (recents + bookmarked)
|
||||
projects_label = (bookmarked.any? || recents.any?) ? :label_optgroup_others : :label_project_plural
|
||||
jump = params[:jump].presence || current_menu_item
|
||||
s = ''.html_safe
|
||||
project_tree(projects) do |project, level|
|
||||
s = (+'').html_safe
|
||||
build_project_link = ->(project, level = 0){
|
||||
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))
|
||||
s << link_to(text, project_path(project, :jump => jump),
|
||||
:title => project.name,
|
||||
:class => (project == selected ? 'selected' : nil))
|
||||
}
|
||||
[
|
||||
[bookmarked, :label_optgroup_bookmarks, true],
|
||||
[recents, :label_optgroup_recents, false],
|
||||
[projects, projects_label, true]
|
||||
].each do |projects, label, is_tree|
|
||||
next if projects.blank?
|
||||
s << content_tag(:strong, l(label))
|
||||
if is_tree
|
||||
project_tree(projects, &build_project_link)
|
||||
else
|
||||
# we do not want to render recently used projects as a tree, but in the
|
||||
# order they were used (most recent first)
|
||||
projects.each(&build_project_link)
|
||||
end
|
||||
end
|
||||
s
|
||||
end
|
||||
|
@ -384,17 +512,21 @@ module ApplicationHelper
|
|||
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'
|
||||
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
|
||||
|
||||
|
@ -428,20 +560,20 @@ module ApplicationHelper
|
|||
end
|
||||
|
||||
def principals_check_box_tags(name, principals)
|
||||
s = ''
|
||||
s = +''
|
||||
principals.each do |principal|
|
||||
s << "<label>#{ check_box_tag name, principal.id, false, :id => nil } #{h principal}</label>\n"
|
||||
s << "<label>#{ check_box_tag name, principal.id, false, :id => nil } <span class='name icon icon-#{principal.class.name.downcase}'></span>#{h principal}</label>\n"
|
||||
end
|
||||
s.html_safe
|
||||
end
|
||||
|
||||
# Returns a string for users/groups option tags
|
||||
def principals_options_for_select(collection, selected=nil)
|
||||
s = ''
|
||||
s = +''
|
||||
if collection.include?(User.current)
|
||||
s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id)
|
||||
end
|
||||
groups = ''
|
||||
groups = +''
|
||||
collection.sort.each do |element|
|
||||
selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) || element.id.to_s == selected
|
||||
(element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
|
||||
|
@ -471,7 +603,7 @@ module ApplicationHelper
|
|||
end
|
||||
|
||||
def anchor(text)
|
||||
text.to_s.gsub(' ', '_')
|
||||
text.to_s.tr(' ', '_')
|
||||
end
|
||||
|
||||
def html_hours(text)
|
||||
|
@ -504,33 +636,15 @@ module ApplicationHelper
|
|||
str.blank? ? nil : str
|
||||
end
|
||||
|
||||
def reorder_links(name, url, method = :post)
|
||||
# TODO: remove associated styles from application.css too
|
||||
ActiveSupport::Deprecation.warn "Application#reorder_links will be removed in Redmine 4."
|
||||
|
||||
link_to(l(:label_sort_highest),
|
||||
url.merge({"#{name}[move_to]" => 'highest'}), :method => method,
|
||||
:title => l(:label_sort_highest), :class => 'icon-only icon-move-top') +
|
||||
link_to(l(:label_sort_higher),
|
||||
url.merge({"#{name}[move_to]" => 'higher'}), :method => method,
|
||||
:title => l(:label_sort_higher), :class => 'icon-only icon-move-up') +
|
||||
link_to(l(:label_sort_lower),
|
||||
url.merge({"#{name}[move_to]" => 'lower'}), :method => method,
|
||||
:title => l(:label_sort_lower), :class => 'icon-only icon-move-down') +
|
||||
link_to(l(:label_sort_lowest),
|
||||
url.merge({"#{name}[move_to]" => 'lowest'}), :method => method,
|
||||
:title => l(:label_sort_lowest), :class => 'icon-only icon-move-bottom')
|
||||
end
|
||||
|
||||
def reorder_handle(object, options={})
|
||||
data = {
|
||||
:reorder_url => options[:url] || url_for(object),
|
||||
:reorder_param => options[:param] || object.class.name.underscore
|
||||
}
|
||||
content_tag('span', '',
|
||||
:class => "sort-handle",
|
||||
:data => data,
|
||||
:title => l(:button_sort))
|
||||
:class => "icon-only icon-sort-handle sort-handle",
|
||||
:data => data,
|
||||
:title => l(:button_sort))
|
||||
end
|
||||
|
||||
def breadcrumb(*args)
|
||||
|
@ -600,6 +714,17 @@ module ApplicationHelper
|
|||
end
|
||||
end
|
||||
|
||||
def actions_dropdown(&block)
|
||||
content = capture(&block)
|
||||
if content.present?
|
||||
trigger = content_tag('span', l(:button_actions), :class => 'icon-only icon-actions', :title => l(:button_actions))
|
||||
trigger = content_tag('span', trigger, :class => 'drdn-trigger')
|
||||
content = content_tag('div', content, :class => 'drdn-items')
|
||||
content = content_tag('div', content, :class => 'drdn-content')
|
||||
content_tag('span', trigger + content, :class => 'drdn')
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the theme, controller name, and action as css classes for the
|
||||
# HTML body.
|
||||
def body_css_classes
|
||||
|
@ -609,8 +734,10 @@ module ApplicationHelper
|
|||
end
|
||||
|
||||
css << 'project-' + @project.identifier if @project && @project.identifier.present?
|
||||
css << 'has-main-menu' if display_main_menu?(@project)
|
||||
css << 'controller-' + controller_name
|
||||
css << 'action-' + action_name
|
||||
css << 'avatars-' + (Setting.gravatar_enabled? ? 'on' : 'off')
|
||||
if UserPreference::TEXTAREA_FONT_OPTIONS.include?(User.current.pref.textarea_font)
|
||||
css << "textarea-#{User.current.pref.textarea_font}"
|
||||
end
|
||||
|
@ -661,7 +788,7 @@ module ApplicationHelper
|
|||
@current_section = 0 if options[:edit_section_links]
|
||||
|
||||
parse_sections(text, project, obj, attr, only_path, options)
|
||||
text = parse_non_pre_blocks(text, obj, macros) do |text|
|
||||
text = parse_non_pre_blocks(text, obj, macros, options) do |text|
|
||||
[: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
|
||||
|
@ -675,18 +802,18 @@ module ApplicationHelper
|
|||
text.html_safe
|
||||
end
|
||||
|
||||
def parse_non_pre_blocks(text, obj, macros)
|
||||
def parse_non_pre_blocks(text, obj, macros, options={})
|
||||
s = StringScanner.new(text)
|
||||
tags = []
|
||||
parsed = ''
|
||||
parsed = +''
|
||||
while !s.eos?
|
||||
s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
|
||||
text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
|
||||
if tags.empty?
|
||||
yield text
|
||||
inject_macros(text, obj, macros) if macros.any?
|
||||
inject_macros(text, obj, macros, true, options) if macros.any?
|
||||
else
|
||||
inject_macros(text, obj, macros, false) if macros.any?
|
||||
inject_macros(text, obj, macros, false, options) if macros.any?
|
||||
end
|
||||
parsed << text
|
||||
if tag
|
||||
|
@ -724,7 +851,7 @@ module ApplicationHelper
|
|||
attachments += obj.attachments if obj.respond_to?(:attachments)
|
||||
if attachments.present?
|
||||
text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
|
||||
filename, ext, alt, alttext = $1.downcase, $2, $3, $4
|
||||
filename, ext, alt, alttext = $1, $2, $3, $4
|
||||
# search for the picture in attachments
|
||||
if found = Attachment.latest_attach(attachments, CGI.unescape(filename))
|
||||
image_url = download_named_attachment_url(found, found.filename, :only_path => only_path)
|
||||
|
@ -751,10 +878,17 @@ module ApplicationHelper
|
|||
# [[project:mypage]]
|
||||
# [[project:mypage|mytext]]
|
||||
def parse_wiki_links(text, project, obj, attr, only_path, options)
|
||||
text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
|
||||
text.gsub!(/(!)?(\[\[([^\n\|]+?)(\|([^\n\|]+?))?\]\])/) do |m|
|
||||
link_project = project
|
||||
esc, all, page, title = $1, $2, $3, $5
|
||||
if esc.nil?
|
||||
page = CGI.unescapeHTML(page)
|
||||
if page =~ /^\#(.+)$/
|
||||
anchor = sanitize_anchor_name($1)
|
||||
url = "##{anchor}"
|
||||
next link_to(title.present? ? title.html_safe : h(page), url, :class => 'wiki-page')
|
||||
end
|
||||
|
||||
if page =~ /^([^\:]+)\:(.*)$/
|
||||
identifier, page = $1, $2
|
||||
link_project = Project.find_by_identifier(identifier) || Project.find_by_name(identifier)
|
||||
|
@ -770,19 +904,27 @@ module ApplicationHelper
|
|||
anchor = sanitize_anchor_name(anchor) if anchor.present?
|
||||
# check if page exists
|
||||
wiki_page = link_project.wiki.find_page(page)
|
||||
url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
|
||||
"##{anchor}"
|
||||
else
|
||||
case options[:wiki_links]
|
||||
when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
|
||||
when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
|
||||
url =
|
||||
if anchor.present? && wiki_page.present? &&
|
||||
(obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) &&
|
||||
obj.page == wiki_page
|
||||
"##{anchor}"
|
||||
else
|
||||
wiki_page_id = page.present? ? Wiki.titleize(page) : nil
|
||||
parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
|
||||
url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
|
||||
:id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent)
|
||||
case options[:wiki_links]
|
||||
when :local
|
||||
"#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
|
||||
when :anchor
|
||||
# used for single-file wiki export
|
||||
"##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '')
|
||||
else
|
||||
wiki_page_id = page.present? ? Wiki.titleize(page) : nil
|
||||
parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
|
||||
url_for(:only_path => only_path, :controller => 'wiki',
|
||||
:action => 'show', :project_id => link_project,
|
||||
:id => wiki_page_id, :version => nil, :anchor => anchor,
|
||||
:parent => parent)
|
||||
end
|
||||
end
|
||||
end
|
||||
link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
|
||||
else
|
||||
# project or wiki doesn't exist
|
||||
|
@ -799,6 +941,7 @@ module ApplicationHelper
|
|||
# Examples:
|
||||
# Issues:
|
||||
# #52 -> Link to issue #52
|
||||
# ##52 -> Link to issue #52, including the issue's subject
|
||||
# Changesets:
|
||||
# r52 -> Link to revision 52
|
||||
# commit:a85130f -> Link to scmid starting with a85130f
|
||||
|
@ -886,17 +1029,28 @@ module ApplicationHelper
|
|||
:title => truncate_single_line_raw(changeset.comments, 100))
|
||||
end
|
||||
end
|
||||
elsif sep == '#'
|
||||
elsif sep == '#' || sep == '##'
|
||||
oid = identifier.to_i
|
||||
case prefix
|
||||
when nil
|
||||
if oid.to_s == identifier &&
|
||||
issue = Issue.visible.find_by_id(oid)
|
||||
anchor = comment_id ? "note-#{comment_id}" : nil
|
||||
link = link_to("##{oid}#{comment_suffix}",
|
||||
issue_url(issue, :only_path => only_path, :anchor => anchor),
|
||||
:class => issue.css_classes,
|
||||
:title => "#{issue.tracker.name}: #{issue.subject.truncate(100)} (#{issue.status.name})")
|
||||
url = issue_url(issue, :only_path => only_path, :anchor => anchor)
|
||||
link =
|
||||
if sep == '##'
|
||||
link_to("#{issue.tracker.name} ##{oid}#{comment_suffix}: #{issue.subject}",
|
||||
url,
|
||||
:class => issue.css_classes,
|
||||
:title => "#{l(:field_status)}: #{issue.status.name}")
|
||||
else
|
||||
link_to("##{oid}#{comment_suffix}",
|
||||
url,
|
||||
:class => issue.css_classes,
|
||||
:title => "#{issue.tracker.name}: #{issue.subject.truncate(100)} (#{issue.status.name})")
|
||||
end
|
||||
elsif identifier == 'note'
|
||||
link = link_to("#note-#{comment_id}", "#note-#{comment_id}")
|
||||
end
|
||||
when 'document'
|
||||
if document = Document.visible.find_by_id(oid)
|
||||
|
@ -923,8 +1077,8 @@ module ApplicationHelper
|
|||
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
|
||||
u = User.visible.find_by(:id => oid, :type => 'User')
|
||||
link = link_to_user(u, :only_path => only_path) if u
|
||||
end
|
||||
elsif sep == ':'
|
||||
name = remove_double_quotes(identifier)
|
||||
|
@ -956,19 +1110,29 @@ module ApplicationHelper
|
|||
end
|
||||
if prefix == 'commit'
|
||||
if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first)
|
||||
link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier},
|
||||
:class => 'changeset',
|
||||
:title => truncate_single_line_raw(changeset.comments, 100)
|
||||
link = link_to(
|
||||
h("#{project_prefix}#{repo_prefix}#{name}"),
|
||||
{:only_path => only_path, :controller => 'repositories',
|
||||
:action => 'revision', :id => project,
|
||||
:repository_id => repository.identifier_param,
|
||||
:rev => changeset.identifier},
|
||||
:class => 'changeset',
|
||||
:title => truncate_single_line_raw(changeset.comments, 100))
|
||||
end
|
||||
else
|
||||
if repository && User.current.allowed_to?(:browse_repository, project)
|
||||
name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$}
|
||||
path, rev, anchor = $1, $3, $5
|
||||
link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param,
|
||||
:path => to_path_param(path),
|
||||
:rev => rev,
|
||||
:anchor => anchor},
|
||||
:class => (prefix == 'export' ? 'source download' : 'source')
|
||||
link =
|
||||
link_to(
|
||||
h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"),
|
||||
{:only_path => only_path, :controller => 'repositories',
|
||||
:action => (prefix == 'export' ? 'raw' : 'entry'),
|
||||
:id => project, :repository_id => repository.identifier_param,
|
||||
:path => to_path_param(path),
|
||||
:rev => rev,
|
||||
:anchor => anchor},
|
||||
:class => (prefix == 'export' ? 'source download' : 'source'))
|
||||
end
|
||||
end
|
||||
repo_prefix = nil
|
||||
|
@ -984,13 +1148,13 @@ module ApplicationHelper
|
|||
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
|
||||
u = User.visible.find_by("LOWER(login) = :s AND type = 'User'", :s => name.downcase)
|
||||
link = link_to_user(u, :only_path => only_path) 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
|
||||
u = User.visible.find_by("LOWER(login) = :s AND type = 'User'", :s => name.downcase)
|
||||
link = link_to_user(u, :only_path => only_path) if u
|
||||
end
|
||||
end
|
||||
(leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
|
||||
|
@ -999,7 +1163,7 @@ module ApplicationHelper
|
|||
end
|
||||
|
||||
LINKS_RE =
|
||||
%r{
|
||||
%r{
|
||||
<a( [^>]+?)?>(?<tag_content>.*?)</a>|
|
||||
(?<leading>[\s\(,\-\[\>]|^)
|
||||
(?<esc>!)?
|
||||
|
@ -1007,14 +1171,14 @@ module ApplicationHelper
|
|||
(?<prefix>attachment|document|version|forum|news|message|project|commit|source|export|user)?
|
||||
(
|
||||
(
|
||||
(?<sep1>\#)|
|
||||
(?<sep1>\#\#?)|
|
||||
(
|
||||
(?<repo_prefix>(?<repo_identifier>[a-z0-9\-_]+)\|)?
|
||||
(?<sep2>r)
|
||||
)
|
||||
)
|
||||
(
|
||||
(?<identifier1>\d+)
|
||||
(?<identifier1>((\d)+|(note)))
|
||||
(?<comment_suffix>
|
||||
(\#note)?
|
||||
-(?<comment_id>\d+)
|
||||
|
@ -1026,7 +1190,7 @@ module ApplicationHelper
|
|||
)|
|
||||
(
|
||||
(?<sep4>@)
|
||||
(?<identifier3>[a-z0-9_\-@\.]*)
|
||||
(?<identifier3>[A-Za-z0-9_\-@\.]*)
|
||||
)
|
||||
)
|
||||
(?=
|
||||
|
@ -1036,7 +1200,7 @@ module ApplicationHelper
|
|||
\]|
|
||||
<|
|
||||
$)
|
||||
}x
|
||||
}x
|
||||
HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
|
||||
|
||||
def parse_sections(text, project, obj, attr, only_path, options)
|
||||
|
@ -1045,9 +1209,13 @@ module ApplicationHelper
|
|||
heading, level = $1, $2
|
||||
@current_section += 1
|
||||
if @current_section > 1
|
||||
content_tag('div',
|
||||
link_to(l(:button_edit_section), options[:edit_section_links].merge(:section => @current_section),
|
||||
:class => 'icon-only icon-edit'),
|
||||
content_tag(
|
||||
'div',
|
||||
link_to(
|
||||
l(:button_edit_section),
|
||||
options[:edit_section_links].merge(
|
||||
:section => @current_section),
|
||||
:class => 'icon-only icon-edit'),
|
||||
:class => "contextual heading-#{level}",
|
||||
:title => l(:button_edit_section),
|
||||
:id => "section-#{@current_section}") + heading.html_safe
|
||||
|
@ -1112,14 +1280,14 @@ module ApplicationHelper
|
|||
end
|
||||
|
||||
# Executes and replaces macros in text
|
||||
def inject_macros(text, obj, macros, execute=true)
|
||||
def inject_macros(text, obj, macros, execute=true, options={})
|
||||
text.gsub!(MACRO_SUB_RE) do
|
||||
all, index = $1, $2.to_i
|
||||
orig = macros.delete(index)
|
||||
if execute && orig && orig =~ MACROS_RE
|
||||
esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
|
||||
if esc.nil?
|
||||
h(exec_macro(macro, obj, args, block) || all)
|
||||
h(exec_macro(macro, obj, args, block, options) || all)
|
||||
else
|
||||
h(all)
|
||||
end
|
||||
|
@ -1142,10 +1310,10 @@ module ApplicationHelper
|
|||
if headings.empty?
|
||||
''
|
||||
else
|
||||
div_class = 'toc'
|
||||
div_class = +'toc'
|
||||
div_class << ' right' if right_align
|
||||
div_class << ' left' if left_align
|
||||
out = "<ul class=\"#{div_class}\"><li><strong>#{l :label_table_of_contents}</strong></li><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
|
||||
|
@ -1197,6 +1365,13 @@ module ApplicationHelper
|
|||
fields_for(*args, &proc)
|
||||
end
|
||||
|
||||
def form_tag_html(html_options)
|
||||
# Set a randomized name attribute on all form fields by default
|
||||
# as a workaround to https://bugzilla.mozilla.org/show_bug.cgi?id=1279253
|
||||
html_options['name'] ||= "#{html_options['id'] || 'form'}-#{SecureRandom.hex(4)}"
|
||||
super
|
||||
end
|
||||
|
||||
# Render the error messages for the given objects
|
||||
def error_messages_for(*objects)
|
||||
objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
|
||||
|
@ -1206,7 +1381,7 @@ module ApplicationHelper
|
|||
|
||||
# Renders a list of error messages
|
||||
def render_error_messages(errors)
|
||||
html = ""
|
||||
html = +""
|
||||
if errors.present?
|
||||
html << "<div id='errorExplanation'><ul>\n"
|
||||
errors.each do |error|
|
||||
|
@ -1227,39 +1402,27 @@ module ApplicationHelper
|
|||
link_to l(:button_delete), url, options
|
||||
end
|
||||
|
||||
def preview_link(url, form, target='preview', options={})
|
||||
content_tag 'a', l(:label_preview), {
|
||||
:href => "#",
|
||||
:onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
|
||||
:accesskey => accesskey(:preview)
|
||||
}.merge(options)
|
||||
end
|
||||
|
||||
def link_to_function(name, function, html_options={})
|
||||
content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
|
||||
end
|
||||
|
||||
def link_to_context_menu
|
||||
link_to l(:button_actions), '#', title: l(:button_actions), class: 'icon-only icon-actions js-contextmenu'
|
||||
end
|
||||
|
||||
# Helper to render JSON in views
|
||||
def raw_json(arg)
|
||||
arg.to_json.to_s.gsub('/', '\/').html_safe
|
||||
end
|
||||
|
||||
def back_url
|
||||
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
|
||||
def back_url_hidden_field_tag
|
||||
url = validate_back_url(back_url)
|
||||
hidden_field_tag('back_url', url, :id => nil) unless url.blank?
|
||||
end
|
||||
|
||||
def back_url_hidden_field_tag
|
||||
url = back_url
|
||||
hidden_field_tag('back_url', url, :id => nil) unless url.blank?
|
||||
def cancel_button_tag(fallback_url)
|
||||
url = validate_back_url(back_url) || fallback_url
|
||||
link_to l(:button_cancel), url
|
||||
end
|
||||
|
||||
def check_all_links(form_name)
|
||||
|
@ -1270,24 +1433,41 @@ module ApplicationHelper
|
|||
|
||||
def toggle_checkboxes_link(selector)
|
||||
link_to_function '',
|
||||
"toggleCheckboxesBySelector('#{selector}')",
|
||||
:title => "#{l(:button_check_all)} / #{l(:button_uncheck_all)}",
|
||||
:class => 'icon icon-checked'
|
||||
"toggleCheckboxesBySelector('#{selector}')",
|
||||
:title => "#{l(:button_check_all)} / #{l(:button_uncheck_all)}",
|
||||
:class => 'icon icon-checked'
|
||||
end
|
||||
|
||||
def progress_bar(pcts, options={})
|
||||
pcts = [pcts, pcts] unless pcts.is_a?(Array)
|
||||
pcts = pcts.collect(&:round)
|
||||
pcts = pcts.collect(&:floor)
|
||||
pcts[1] = pcts[1] - pcts[0]
|
||||
pcts << (100 - pcts[1] - pcts[0])
|
||||
titles = options[:titles].to_a
|
||||
titles[0] = "#{pcts[0]}%" if titles[0].blank?
|
||||
legend = options[:legend] || ''
|
||||
content_tag('table',
|
||||
content_tag('tr',
|
||||
(pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed', :title => titles[0]) : ''.html_safe) +
|
||||
(pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done', :title => titles[1]) : ''.html_safe) +
|
||||
(pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo', :title => titles[2]) : ''.html_safe)
|
||||
content_tag(
|
||||
'table',
|
||||
content_tag(
|
||||
'tr',
|
||||
(if pcts[0] > 0
|
||||
content_tag('td', '', :style => "width: #{pcts[0]}%;",
|
||||
:class => 'closed', :title => titles[0])
|
||||
else
|
||||
''.html_safe
|
||||
end) +
|
||||
(if pcts[1] > 0
|
||||
content_tag('td', '', :style => "width: #{pcts[1]}%;",
|
||||
:class => 'done', :title => titles[1])
|
||||
else
|
||||
''.html_safe
|
||||
end) +
|
||||
(if pcts[2] > 0
|
||||
content_tag('td', '', :style => "width: #{pcts[2]}%;",
|
||||
:class => 'todo', :title => titles[2])
|
||||
else
|
||||
''.html_safe
|
||||
end)
|
||||
), :class => "progress progress-#{pcts[0]}").html_safe +
|
||||
content_tag('p', legend, :class => 'percent').html_safe
|
||||
end
|
||||
|
@ -1410,49 +1590,15 @@ module ApplicationHelper
|
|||
!!ActionMailer::Base.perform_deliveries
|
||||
end
|
||||
|
||||
# Returns the avatar image tag for the given +user+ if avatars are enabled
|
||||
# +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
|
||||
def avatar(user, options = { })
|
||||
if Setting.gravatar_enabled?
|
||||
options.merge!(:default => Setting.gravatar_default)
|
||||
email = nil
|
||||
if user.respond_to?(:mail)
|
||||
email = user.mail
|
||||
elsif user.to_s =~ %r{<(.+?)>}
|
||||
email = $1
|
||||
end
|
||||
if email.present?
|
||||
gravatar(email.to_s.downcase, options) rescue nil
|
||||
elsif user.is_a?(AnonymousUser)
|
||||
options[:size] &&= options[:size].to_s
|
||||
image_tag 'anonymous.png',
|
||||
GravatarHelper::DEFAULT_OPTIONS
|
||||
.except(:default, :rating, :ssl).merge(options)
|
||||
else
|
||||
nil
|
||||
end
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a link to edit user's avatar if avatars are enabled
|
||||
def avatar_edit_link(user, options={})
|
||||
if Setting.gravatar_enabled?
|
||||
url = "https://gravatar.com"
|
||||
link_to avatar(user, {:title => l(:button_edit)}.merge(options)), url, :target => '_blank'
|
||||
end
|
||||
end
|
||||
|
||||
def sanitize_anchor_name(anchor)
|
||||
anchor.gsub(%r{[^\s\-\p{Word}]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
|
||||
end
|
||||
|
||||
# Returns the javascript tags that are included in the html layout head
|
||||
def javascript_heads
|
||||
tags = javascript_include_tag('jquery-1.11.1-ui-1.11.0-ujs-3.1.4', 'application', 'responsive')
|
||||
tags = javascript_include_tag('jquery-2.2.4-ui-1.11.0-ujs-5.2.3', 'tribute-3.7.3.min', 'application', 'responsive')
|
||||
unless User.current.pref.warn_on_leaving_unsaved == '0'
|
||||
tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
|
||||
tags << "\n".html_safe + javascript_tag("$(window).on('load', function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
|
||||
end
|
||||
tags
|
||||
end
|
||||
|
@ -1501,9 +1647,31 @@ module ApplicationHelper
|
|||
end
|
||||
end
|
||||
|
||||
def generate_csv(&block)
|
||||
decimal_separator = l(:general_csv_decimal_separator)
|
||||
encoding = l(:general_csv_encoding)
|
||||
def export_csv_encoding_select_tag
|
||||
return if l(:general_csv_encoding).casecmp('UTF-8') == 0
|
||||
options = [l(:general_csv_encoding), 'UTF-8']
|
||||
content_tag(:p) do
|
||||
concat(
|
||||
content_tag(:label) do
|
||||
concat l(:label_encoding) + ' '
|
||||
concat select_tag('encoding', options_for_select(options, l(:general_csv_encoding)))
|
||||
end
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns an array of error messages for bulk edited items (issues, time entries)
|
||||
def bulk_edit_error_messages(items)
|
||||
messages = {}
|
||||
items.each do |item|
|
||||
item.errors.full_messages.each do |message|
|
||||
messages[message] ||= []
|
||||
messages[message] << item
|
||||
end
|
||||
end
|
||||
messages.map { |message, items|
|
||||
"#{message}: " + items.map {|i| "##{i.id}"}.join(', ')
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
|
@ -33,13 +33,12 @@ module AttachmentsHelper
|
|||
# :thumbails -- display thumbnails if enabled in settings
|
||||
def link_to_attachments(container, options = {})
|
||||
options.assert_valid_keys(:author, :thumbnails)
|
||||
|
||||
attachments = if container.attachments.loaded?
|
||||
container.attachments
|
||||
else
|
||||
container.attachments.preload(:author).to_a
|
||||
end
|
||||
|
||||
attachments =
|
||||
if container.attachments.loaded?
|
||||
container.attachments
|
||||
else
|
||||
container.attachments.preload(:author).to_a
|
||||
end
|
||||
if attachments.any?
|
||||
options = {
|
||||
:editable => container.attachments_editable?,
|
||||
|
@ -56,6 +55,14 @@ module AttachmentsHelper
|
|||
end
|
||||
end
|
||||
|
||||
def render_pagination
|
||||
pagination_links_each @paginator do |text, parameters, options|
|
||||
if att = @attachments[parameters[:page] - 1]
|
||||
link_to text, named_attachment_path(att, att.filename)
|
||||
end
|
||||
end if @paginator
|
||||
end
|
||||
|
||||
def render_api_attachment(attachment, api, options={})
|
||||
api.attachment do
|
||||
render_api_attachment_attributes(attachment, api)
|
||||
|
@ -78,4 +85,14 @@ module AttachmentsHelper
|
|||
end
|
||||
api.created_on attachment.created_on
|
||||
end
|
||||
|
||||
def render_file_content(attachment, content)
|
||||
if attachment.is_markdown?
|
||||
render :partial => 'common/markup', :locals => {:markup_text_formatting => 'markdown', :markup_text => content}
|
||||
elsif attachment.is_textile?
|
||||
render :partial => 'common/markup', :locals => {:markup_text_formatting => 'textile', :markup_text => content}
|
||||
else
|
||||
render :partial => 'common/file', :locals => {:content => content, :filename => attachment.filename}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
|
|
75
app/helpers/avatars_helper.rb
Normal file
75
app/helpers/avatars_helper.rb
Normal file
|
@ -0,0 +1,75 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2019 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
module AvatarsHelper
|
||||
include GravatarHelper::PublicMethods
|
||||
|
||||
def assignee_avatar(user, options={})
|
||||
return '' unless user
|
||||
|
||||
options.merge!(:title => l(:field_assigned_to) + ": " + user.name)
|
||||
avatar(user, options).to_s.html_safe
|
||||
end
|
||||
|
||||
def author_avatar(user, options={})
|
||||
return '' unless user
|
||||
|
||||
options.merge!(:title => l(:field_author) + ": " + user.name)
|
||||
avatar(user, options).to_s.html_safe
|
||||
end
|
||||
|
||||
# Returns the avatar image tag for the given +user+ if avatars are enabled
|
||||
# +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
|
||||
def avatar(user, options = { })
|
||||
if Setting.gravatar_enabled?
|
||||
options.merge!(:default => Setting.gravatar_default)
|
||||
options[:class] = GravatarHelper::DEFAULT_OPTIONS[:class] + " " + options[:class] if options[:class]
|
||||
email = nil
|
||||
if user.respond_to?(:mail)
|
||||
email = user.mail
|
||||
options[:title] = user.name unless options[:title]
|
||||
elsif user.to_s =~ %r{<(.+?)>}
|
||||
email = $1
|
||||
end
|
||||
if email.present?
|
||||
gravatar(email.to_s.downcase, options) rescue nil
|
||||
elsif user.is_a?(AnonymousUser)
|
||||
anonymous_avatar(options)
|
||||
else
|
||||
nil
|
||||
end
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a link to edit user's avatar if avatars are enabled
|
||||
def avatar_edit_link(user, options={})
|
||||
if Setting.gravatar_enabled?
|
||||
url = Redmine::Configuration['avatar_server_url']
|
||||
link_to avatar(user, {:title => l(:button_edit)}.merge(options)), url, :target => '_blank'
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def anonymous_avatar(options={})
|
||||
image_tag 'anonymous.png', GravatarHelper::DEFAULT_OPTIONS.except(:default, :rating, :ssl).merge(options)
|
||||
end
|
||||
end
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,41 +18,12 @@
|
|||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
module CalendarsHelper
|
||||
def link_to_previous_month(year, month, options={})
|
||||
target_year, target_month = if month == 1
|
||||
[year - 1, 12]
|
||||
else
|
||||
[year, month - 1]
|
||||
end
|
||||
include Redmine::Utils::DateCalculation
|
||||
|
||||
name = if target_month == 12
|
||||
"#{month_name(target_month)} #{target_year}"
|
||||
else
|
||||
"#{month_name(target_month)}"
|
||||
end
|
||||
|
||||
# \xc2\xab(utf-8) = «
|
||||
link_to_month(("\xc2\xab " + name), target_year, target_month, options)
|
||||
end
|
||||
|
||||
def link_to_next_month(year, month, options={})
|
||||
target_year, target_month = if month == 12
|
||||
[year + 1, 1]
|
||||
else
|
||||
[year, month + 1]
|
||||
end
|
||||
|
||||
name = if target_month == 1
|
||||
"#{month_name(target_month)} #{target_year}"
|
||||
else
|
||||
"#{month_name(target_month)}"
|
||||
end
|
||||
|
||||
# \xc2\xbb(utf-8) = »
|
||||
link_to_month((name + " \xc2\xbb"), target_year, target_month, options)
|
||||
end
|
||||
|
||||
def link_to_month(link_name, year, month, options={})
|
||||
link_to(link_name, {:params => request.query_parameters.merge(:year => year, :month => month)}, options)
|
||||
def calendar_day_css_classes(calendar, day)
|
||||
css = day.month==calendar.month ? +'even' : +'odd'
|
||||
css << " today" if User.current.today == day
|
||||
css << " nwday" if non_working_week_days.include?(day.cwday)
|
||||
css
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,30 +21,38 @@ module ContextMenusHelper
|
|||
def context_menu_link(name, url, options={})
|
||||
options[:class] ||= ''
|
||||
if options.delete(:selected)
|
||||
options[:class] << ' icon-checked disabled'
|
||||
options[:class] += ' icon-checked disabled'
|
||||
options[:disabled] = true
|
||||
end
|
||||
if options.delete(:disabled)
|
||||
options.delete(:method)
|
||||
options.delete(:data)
|
||||
options[:onclick] = 'return false;'
|
||||
options[:class] << ' disabled'
|
||||
options[:class] += ' disabled'
|
||||
url = '#'
|
||||
end
|
||||
link_to h(name), url, options
|
||||
end
|
||||
|
||||
def bulk_update_custom_field_context_menu_link(field, text, value)
|
||||
context_menu_link h(text),
|
||||
bulk_update_issues_path(:ids => @issue_ids, :issue => {'custom_field_values' => {field.id => value}}, :back_url => @back),
|
||||
context_menu_link(
|
||||
h(text),
|
||||
bulk_update_issues_path(:ids => @issue_ids,
|
||||
:issue => {'custom_field_values' => {field.id => value}},
|
||||
:back_url => @back),
|
||||
:method => :post,
|
||||
:selected => (@issue && @issue.custom_field_value(field) == value)
|
||||
)
|
||||
end
|
||||
|
||||
def bulk_update_time_entry_custom_field_context_menu_link(field, text, value)
|
||||
context_menu_link h(text),
|
||||
bulk_update_time_entries_path(:ids => @time_entries.map(&:id).sort, :time_entry => {'custom_field_values' => {field.id => value}}, :back_url => @back),
|
||||
context_menu_link(
|
||||
h(text),
|
||||
bulk_update_time_entries_path(:ids => @time_entries.map(&:id).sort,
|
||||
:time_entry => {'custom_field_values' => {field.id => value}},
|
||||
:back_url => @back),
|
||||
:method => :post,
|
||||
:selected => (@time_entry && @time_entry.custom_field_value(field) == value)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
|
@ -55,7 +55,7 @@ module CustomFieldsHelper
|
|||
items = []
|
||||
items << [l(:label_custom_field_plural), custom_fields_path]
|
||||
items << [l(custom_field.type_name), custom_fields_path(:tab => custom_field.class.name)] if custom_field
|
||||
items << (custom_field.nil? || custom_field.new_record? ? l(:label_custom_field_new) : custom_field.name)
|
||||
items << (custom_field.nil? || custom_field.new_record? ? l(:label_custom_field_new) : custom_field.name)
|
||||
|
||||
title(*items)
|
||||
end
|
||||
|
@ -69,7 +69,7 @@ module CustomFieldsHelper
|
|||
|
||||
def custom_field_tag_name(prefix, custom_field)
|
||||
name = "#{prefix}[custom_field_values][#{custom_field.id}]"
|
||||
name << "[]" if custom_field.multiple?
|
||||
name += "[]" if custom_field.multiple?
|
||||
name
|
||||
end
|
||||
|
||||
|
@ -79,11 +79,22 @@ module CustomFieldsHelper
|
|||
|
||||
# Return custom field html tag corresponding to its format
|
||||
def custom_field_tag(prefix, custom_value)
|
||||
custom_value.custom_field.format.edit_tag self,
|
||||
css = "#{custom_value.custom_field.field_format}_cf"
|
||||
data = nil
|
||||
if custom_value.custom_field.full_text_formatting?
|
||||
css += ' wiki-edit'
|
||||
data = {
|
||||
:auto_complete => true,
|
||||
:issues_url => auto_complete_issues_path(:project_id => custom_value.customized.project, :q => '')
|
||||
} if custom_value.customized&.try(:project)
|
||||
end
|
||||
custom_value.custom_field.format.edit_tag(
|
||||
self,
|
||||
custom_field_tag_id(prefix, custom_value.custom_field),
|
||||
custom_field_tag_name(prefix, custom_value.custom_field),
|
||||
custom_value,
|
||||
:class => "#{custom_value.custom_field.field_format}_cf"
|
||||
:class => css,
|
||||
:data => data)
|
||||
end
|
||||
|
||||
# Return custom field name tag
|
||||
|
@ -92,16 +103,16 @@ module CustomFieldsHelper
|
|||
css = title ? "field-description" : nil
|
||||
content_tag 'span', custom_field.name, :title => title, :class => css
|
||||
end
|
||||
|
||||
|
||||
# Return custom field label tag
|
||||
def custom_field_label_tag(name, custom_value, options={})
|
||||
required = options[:required] || custom_value.custom_field.is_required?
|
||||
for_tag_id = options.fetch(:for_tag_id, "#{name}_custom_field_values_#{custom_value.custom_field.id}")
|
||||
content = custom_field_name_tag custom_value.custom_field
|
||||
|
||||
content_tag "label", content +
|
||||
content_tag(
|
||||
"label", content +
|
||||
(required ? " <span class=\"required\">*</span>".html_safe : ""),
|
||||
:for => for_tag_id
|
||||
:for => for_tag_id)
|
||||
end
|
||||
|
||||
# Return custom field tag with its label tag
|
||||
|
@ -117,13 +128,25 @@ module CustomFieldsHelper
|
|||
|
||||
# Returns the custom field tag for when bulk editing objects
|
||||
def custom_field_tag_for_bulk_edit(prefix, custom_field, objects=nil, value='')
|
||||
custom_field.format.bulk_edit_tag self,
|
||||
custom_field.format.bulk_edit_tag(
|
||||
self,
|
||||
custom_field_tag_id(prefix, custom_field),
|
||||
custom_field_tag_name(prefix, custom_field),
|
||||
custom_field,
|
||||
objects,
|
||||
value,
|
||||
:class => "#{custom_field.field_format}_cf"
|
||||
:class => "#{custom_field.field_format}_cf")
|
||||
end
|
||||
|
||||
# Returns custom field value tag
|
||||
def custom_field_value_tag(value)
|
||||
attr_value = show_value(value)
|
||||
|
||||
if !attr_value.blank? && value.custom_field.full_text_formatting?
|
||||
content_tag('div', attr_value, :class => 'wiki')
|
||||
else
|
||||
attr_value
|
||||
end
|
||||
end
|
||||
|
||||
# Return a string used to display a custom value
|
||||
|
@ -180,4 +203,15 @@ module CustomFieldsHelper
|
|||
end
|
||||
form.select :edit_tag_style, select_options, :label => :label_display
|
||||
end
|
||||
|
||||
def select_type_radio_buttons(default_type)
|
||||
if CUSTOM_FIELDS_TABS.none? {|tab| tab[:name] == default_type}
|
||||
default_type = 'IssueCustomField'
|
||||
end
|
||||
custom_field_type_options.map do |name, type|
|
||||
content_tag(:label, :style => 'display:block;') do
|
||||
radio_button_tag('type', type, type == default_type) + name
|
||||
end
|
||||
end.join("\n").html_safe
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,17 +22,19 @@ module EmailAddressesHelper
|
|||
# Returns a link to enable or disable notifications for the address
|
||||
def toggle_email_address_notify_link(address)
|
||||
if address.notify?
|
||||
link_to l(:label_disable_notifications),
|
||||
link_to(
|
||||
l(:label_disable_notifications),
|
||||
user_email_address_path(address.user, address, :notify => '0'),
|
||||
:method => :put, :remote => true,
|
||||
:title => l(:label_disable_notifications),
|
||||
:class => 'icon-only icon-email'
|
||||
:class => 'icon-only icon-email')
|
||||
else
|
||||
link_to l(:label_enable_notifications),
|
||||
link_to(
|
||||
l(:label_enable_notifications),
|
||||
user_email_address_path(address.user, address, :notify => '1'),
|
||||
:method => :put, :remote => true,
|
||||
:title => l(:label_enable_notifications),
|
||||
:class => 'icon-only icon-email-disabled'
|
||||
:class => 'icon-only icon-email-disabled')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,18 +23,20 @@ module GanttHelper
|
|||
case in_or_out
|
||||
when :in
|
||||
if gantt.zoom < 4
|
||||
link_to l(:text_zoom_in),
|
||||
link_to(
|
||||
l(:text_zoom_in),
|
||||
{:params => request.query_parameters.merge(gantt.params.merge(:zoom => (gantt.zoom + 1)))},
|
||||
:class => 'icon icon-zoom-in'
|
||||
:class => 'icon icon-zoom-in')
|
||||
else
|
||||
content_tag(:span, l(:text_zoom_in), :class => 'icon icon-zoom-in').html_safe
|
||||
end
|
||||
|
||||
when :out
|
||||
if gantt.zoom > 1
|
||||
link_to l(:text_zoom_out),
|
||||
link_to(
|
||||
l(:text_zoom_out),
|
||||
{:params => request.query_parameters.merge(gantt.params.merge(:zoom => (gantt.zoom - 1)))},
|
||||
:class => 'icon icon-zoom-out'
|
||||
:class => 'icon icon-zoom-out')
|
||||
else
|
||||
content_tag(:span, l(:text_zoom_out), :class => 'icon icon-zoom-out').html_safe
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,16 +31,14 @@ module GroupsHelper
|
|||
principal_count = scope.count
|
||||
principal_pages = Redmine::Pagination::Paginator.new principal_count, limit, params['page']
|
||||
principals = scope.offset(principal_pages.offset).limit(principal_pages.per_page).to_a
|
||||
|
||||
s = content_tag('div',
|
||||
s = content_tag(
|
||||
'div',
|
||||
content_tag('div', principals_check_box_tags('user_ids[]', principals), :id => 'principals'),
|
||||
:class => 'objects-selection'
|
||||
)
|
||||
|
||||
links = pagination_links_full(principal_pages, principal_count, :per_page_links => false) {|text, parameters, options|
|
||||
link_to text, autocomplete_for_user_group_path(group, parameters.merge(:q => params[:q], :format => 'js')), :remote => true
|
||||
}
|
||||
|
||||
s + content_tag('span', links, :class => 'pagination')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,6 +18,14 @@
|
|||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
module ImportsHelper
|
||||
def import_title
|
||||
l(:"label_import_#{import_partial_prefix}")
|
||||
end
|
||||
|
||||
def import_partial_prefix
|
||||
@import.class.name.sub('Import', '').underscore.pluralize
|
||||
end
|
||||
|
||||
def options_for_mapping_select(import, field, options={})
|
||||
tags = "".html_safe
|
||||
blank_text = options[:required] ? "-- #{l(:actionview_instancetag_blank_option)} --" : " ".html_safe
|
||||
|
@ -25,7 +33,7 @@ module ImportsHelper
|
|||
tags << options_for_select(import.columns_options, import.mapping[field])
|
||||
if values = options[:values]
|
||||
tags << content_tag('option', '--', :disabled => true)
|
||||
tags << options_for_select(values.map {|text, value| [text, "value:#{value}"]}, import.mapping[field])
|
||||
tags << options_for_select(values.map {|text, value| [text, "value:#{value}"]}, import.mapping[field] || options[:default_value])
|
||||
end
|
||||
tags
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,6 +20,6 @@
|
|||
module IssueRelationsHelper
|
||||
def collection_for_relation_type_select
|
||||
values = IssueRelation::TYPES
|
||||
values.keys.sort{|x,y| values[x][:order] <=> values[y][:order]}.collect{|k| [l(values[k][:name]), k]}
|
||||
values.keys.sort_by{|k| values[k][:order]}.collect{|k| [l(values[k][:name]), k]}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,7 +24,8 @@ module IssuesHelper
|
|||
def issue_list(issues, &block)
|
||||
ancestors = []
|
||||
issues.each do |issue|
|
||||
while (ancestors.any? && !issue.is_descendant_of?(ancestors.last))
|
||||
while ancestors.any? &&
|
||||
!issue.is_descendant_of?(ancestors.last)
|
||||
ancestors.pop
|
||||
end
|
||||
yield issue, ancestors.size
|
||||
|
@ -35,7 +36,8 @@ module IssuesHelper
|
|||
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))
|
||||
while ancestors.any? &&
|
||||
!issue.is_descendant_of?(ancestors.last)
|
||||
ancestors.pop
|
||||
end
|
||||
yield issue, ancestors.size, group_name, group_count, group_totals
|
||||
|
@ -62,10 +64,10 @@ module IssuesHelper
|
|||
|
||||
link_to_issue(issue) + "<br /><br />".html_safe +
|
||||
"<strong>#{@cached_label_project}</strong>: #{link_to_project(issue.project)}<br />".html_safe +
|
||||
"<strong>#{@cached_label_status}</strong>: #{h(issue.status.name)}<br />".html_safe +
|
||||
"<strong>#{@cached_label_status}</strong>: #{h(issue.status.name) + (" (#{format_date(issue.closed_on)})" if issue.closed?)}<br />".html_safe +
|
||||
"<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />".html_safe +
|
||||
"<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />".html_safe +
|
||||
"<strong>#{@cached_label_assigned_to}</strong>: #{h(issue.assigned_to)}<br />".html_safe +
|
||||
"<strong>#{@cached_label_assigned_to}</strong>: #{avatar(issue.assigned_to, :size => '13', :title => l(:field_assigned_to)) if issue.assigned_to} #{h(issue.assigned_to)}<br />".html_safe +
|
||||
"<strong>#{@cached_label_priority}</strong>: #{h(issue.priority.name)}".html_safe
|
||||
end
|
||||
|
||||
|
@ -74,7 +76,7 @@ module IssuesHelper
|
|||
end
|
||||
|
||||
def render_issue_subject_with_tree(issue)
|
||||
s = ''
|
||||
s = +''
|
||||
ancestors = issue.root? ? [] : issue.ancestors.visible.to_a
|
||||
ancestors.each do |ancestor|
|
||||
s << '<div>' + content_tag('p', link_to_issue(ancestor, :project => (issue.project_id != ancestor.project_id)))
|
||||
|
@ -82,7 +84,7 @@ module IssuesHelper
|
|||
s << '<div>'
|
||||
subject = h(issue.subject)
|
||||
if issue.is_private?
|
||||
subject = content_tag('span', l(:field_is_private), :class => 'private') + ' ' + subject
|
||||
subject = subject + ' ' + content_tag('span', l(:field_is_private), :class => 'badge badge-private private')
|
||||
end
|
||||
s << content_tag('h3', subject)
|
||||
s << '</div>' * (ancestors.size + 1)
|
||||
|
@ -90,17 +92,52 @@ module IssuesHelper
|
|||
end
|
||||
|
||||
def render_descendants_tree(issue)
|
||||
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}"
|
||||
manage_relations = User.current.allowed_to?(:manage_subtasks, issue.project)
|
||||
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
|
||||
s << content_tag('tr',
|
||||
content_tag('td', check_box_tag("ids[]", child.id, false, :id => nil), :class => 'checkbox') +
|
||||
content_tag('td', link_to_issue(child, :project => (issue.project_id != child.project_id)), :class => 'subject', :style => 'width: 50%') +
|
||||
buttons =
|
||||
if manage_relations
|
||||
link_to(l(:label_delete_link_to_subtask),
|
||||
issue_path(
|
||||
{:id => child.id, :issue => {:parent_issue_id => ''},
|
||||
:back_url => issue_path(issue.id), :no_flash => '1'}),
|
||||
:method => :put,
|
||||
:data => {:confirm => l(:text_are_you_sure)},
|
||||
:title => l(:label_delete_link_to_subtask),
|
||||
:class => 'icon-only icon-link-break'
|
||||
)
|
||||
else
|
||||
"".html_safe
|
||||
end
|
||||
buttons << link_to_context_menu
|
||||
s <<
|
||||
content_tag(
|
||||
'tr',
|
||||
content_tag('td', check_box_tag("ids[]", child.id, false, :id => nil),
|
||||
:class => 'checkbox') +
|
||||
content_tag('td',
|
||||
link_to_issue(
|
||||
child,
|
||||
:project => (issue.project_id != child.project_id)),
|
||||
:class => 'subject') +
|
||||
content_tag('td', h(child.status), :class => 'status') +
|
||||
content_tag('td', link_to_user(child.assigned_to), :class => 'assigned_to') +
|
||||
content_tag('td', child.disabled_core_fields.include?('done_ratio') ? '' : progress_bar(child.done_ratio), :class=> 'done_ratio'),
|
||||
:class => css)
|
||||
content_tag('td', format_date(child.start_date), :class => 'start_date') +
|
||||
content_tag('td', format_date(child.due_date), :class => 'due_date') +
|
||||
content_tag('td',
|
||||
(if child.disabled_core_fields.include?('done_ratio')
|
||||
''
|
||||
else
|
||||
progress_bar(child.done_ratio)
|
||||
end),
|
||||
:class=> 'done_ratio') +
|
||||
content_tag('td', buttons, :class => 'buttons'),
|
||||
:class => css)
|
||||
end
|
||||
s << '</table>'
|
||||
s.html_safe
|
||||
|
@ -109,32 +146,55 @@ module IssuesHelper
|
|||
# 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%') +
|
||||
buttons =
|
||||
if 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'
|
||||
)
|
||||
else
|
||||
"".html_safe
|
||||
end
|
||||
buttons << link_to_context_menu
|
||||
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') +
|
||||
content_tag('td', other_issue.status, :class => 'status') +
|
||||
content_tag('td', link_to_user(other_issue.assigned_to), :class => 'assigned_to') +
|
||||
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)
|
||||
content_tag('td',
|
||||
(if other_issue.disabled_core_fields.include?('done_ratio')
|
||||
''
|
||||
else
|
||||
progress_bar(other_issue.done_ratio)
|
||||
end),
|
||||
:class=> 'done_ratio') +
|
||||
content_tag('td', buttons, :class => 'buttons'),
|
||||
:id => "relation-#{relation.id}",
|
||||
:class => css)
|
||||
end
|
||||
|
||||
content_tag('table', s, :class => 'list issues odd-even')
|
||||
end
|
||||
|
||||
|
@ -144,7 +204,7 @@ module IssuesHelper
|
|||
l_hours_short(issue.estimated_hours)
|
||||
else
|
||||
s = issue.estimated_hours.present? ? l_hours_short(issue.estimated_hours) : ""
|
||||
s << " (#{l(:label_total)}: #{l_hours_short(issue.total_estimated_hours)})"
|
||||
s += " (#{l(:label_total)}: #{l_hours_short(issue.total_estimated_hours)})"
|
||||
s.html_safe
|
||||
end
|
||||
end
|
||||
|
@ -158,25 +218,18 @@ module IssuesHelper
|
|||
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), path})"
|
||||
s += " (#{l(:label_total)}: #{link_to l_hours_short(issue.total_spent_hours), path})"
|
||||
s.html_safe
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns an array of error messages for bulk edited issues
|
||||
def bulk_edit_error_messages(issues)
|
||||
messages = {}
|
||||
issues.each do |issue|
|
||||
issue.errors.full_messages.each do |message|
|
||||
messages[message] ||= []
|
||||
messages[message] << issue
|
||||
end
|
||||
end
|
||||
messages.map { |message, issues|
|
||||
"#{message}: " + issues.map {|i| "##{i.id}"}.join(', ')
|
||||
}
|
||||
end
|
||||
def issue_due_date_details(issue)
|
||||
return if issue&.due_date.nil?
|
||||
s = format_date(issue.due_date)
|
||||
s += " (#{due_date_distance_in_words(issue.due_date)})" unless issue.closed?
|
||||
s
|
||||
end
|
||||
|
||||
# Returns a link for adding a new subtask to the given issue
|
||||
def link_to_new_subtask(issue)
|
||||
|
@ -188,13 +241,18 @@ module IssuesHelper
|
|||
end
|
||||
|
||||
def trackers_options_for_select(issue)
|
||||
trackers = trackers_for_select(issue)
|
||||
trackers.collect {|t| [t.name, t.id]}
|
||||
end
|
||||
|
||||
def trackers_for_select(issue)
|
||||
trackers = issue.allowed_target_trackers
|
||||
if issue.new_record? && issue.parent_issue_id.present?
|
||||
trackers = trackers.reject do |tracker|
|
||||
issue.tracker_id != tracker.id && tracker.disabled_core_fields.include?('parent_issue_id')
|
||||
end
|
||||
end
|
||||
trackers.collect {|t| [t.name, t.id]}
|
||||
trackers
|
||||
end
|
||||
|
||||
class IssueFieldsRows
|
||||
|
@ -227,9 +285,11 @@ module IssuesHelper
|
|||
|
||||
def cells(label, text, options={})
|
||||
options[:class] = [options[:class] || "", 'attribute'].join(' ')
|
||||
content_tag 'div',
|
||||
content_tag('div', label + ":", :class => 'label') + content_tag('div', text, :class => 'value'),
|
||||
options
|
||||
content_tag(
|
||||
'div',
|
||||
content_tag('div', label + ":", :class => 'label') +
|
||||
content_tag('div', text, :class => 'value'),
|
||||
options)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -245,13 +305,8 @@ module IssuesHelper
|
|||
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), attr_value, :class => css
|
||||
rows.send m, custom_field_name_tag(value.custom_field), custom_field_value_tag(value), :class => value.custom_field.css_classes
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -259,21 +314,15 @@ module IssuesHelper
|
|||
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
|
||||
|
||||
attr_value_tag = custom_field_value_tag(value)
|
||||
next if attr_value_tag.blank?
|
||||
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")
|
||||
content_tag('hr') +
|
||||
content_tag('p', content_tag('strong', custom_field_name_tag(value.custom_field) )) +
|
||||
content_tag('div', attr_value_tag, class: 'value')
|
||||
s << content_tag('div', content, class: "#{value.custom_field.css_classes} attribute")
|
||||
end
|
||||
s
|
||||
end
|
||||
|
@ -323,20 +372,24 @@ module IssuesHelper
|
|||
|
||||
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")
|
||||
%w(author status priority assigned_to category fixed_version start_date due_date).each do |attribute|
|
||||
if issue.disabled_core_fields.grep(/^#{attribute}(_id)?$/).empty?
|
||||
attr_value = (issue.send attribute).to_s
|
||||
next if attr_value.blank?
|
||||
if html
|
||||
items << content_tag('strong', "#{l("field_#{attribute}")}: ") + (issue.send attribute)
|
||||
items << content_tag('strong', "#{l("field_#{attribute}")}: ") + attr_value
|
||||
else
|
||||
items << "#{l("field_#{attribute}")}: #{issue.send attribute}"
|
||||
items << "#{l("field_#{attribute}")}: #{attr_value}"
|
||||
end
|
||||
end
|
||||
end
|
||||
issue.visible_custom_field_values(user).each do |value|
|
||||
cf_value = show_value(value, false)
|
||||
next if cf_value.blank?
|
||||
if html
|
||||
items << content_tag('strong', "#{value.custom_field.name}: ") + show_value(value, false)
|
||||
items << content_tag('strong', "#{value.custom_field.name}: ") + cf_value
|
||||
else
|
||||
items << "#{value.custom_field.name}: #{show_value(value, false)}"
|
||||
items << "#{value.custom_field.name}: #{cf_value}"
|
||||
end
|
||||
end
|
||||
items
|
||||
|
@ -351,10 +404,12 @@ module IssuesHelper
|
|||
end
|
||||
end
|
||||
|
||||
MultipleValuesDetail = Struct.new(:property, :prop_key, :custom_field, :old_value, :value)
|
||||
|
||||
# Returns the textual representation of a journal details
|
||||
# as an array of strings
|
||||
def details_to_strings(details, no_html=false, options={})
|
||||
options[:only_path] = (options[:only_path] == false ? false : true)
|
||||
options[:only_path] = !(options[:only_path] == false)
|
||||
strings = []
|
||||
values_by_field = {}
|
||||
details.each do |detail|
|
||||
|
@ -374,15 +429,14 @@ module IssuesHelper
|
|||
strings << show_detail(detail, no_html, options)
|
||||
end
|
||||
if values_by_field.present?
|
||||
multiple_values_detail = Struct.new(:property, :prop_key, :custom_field, :old_value, :value)
|
||||
values_by_field.each do |field, changes|
|
||||
if changes[:added].any?
|
||||
detail = multiple_values_detail.new('cf', field.id.to_s, field)
|
||||
detail = MultipleValuesDetail.new('cf', field.id.to_s, field)
|
||||
detail.value = changes[:added]
|
||||
strings << show_detail(detail, no_html, options)
|
||||
end
|
||||
if changes[:deleted].any?
|
||||
detail = multiple_values_detail.new('cf', field.id.to_s, field)
|
||||
detail = MultipleValuesDetail.new('cf', field.id.to_s, field)
|
||||
detail.old_value = changes[:deleted]
|
||||
strings << show_detail(detail, no_html, options)
|
||||
end
|
||||
|
@ -446,12 +500,20 @@ module IssuesHelper
|
|||
when 'relation'
|
||||
if detail.value && !detail.old_value
|
||||
rel_issue = Issue.visible.find_by_id(detail.value)
|
||||
value = rel_issue.nil? ? "#{l(:label_issue)} ##{detail.value}" :
|
||||
(no_html ? rel_issue : link_to_issue(rel_issue, :only_path => options[:only_path]))
|
||||
value =
|
||||
if rel_issue.nil?
|
||||
"#{l(:label_issue)} ##{detail.value}"
|
||||
else
|
||||
(no_html ? rel_issue : link_to_issue(rel_issue, :only_path => options[:only_path]))
|
||||
end
|
||||
elsif detail.old_value && !detail.value
|
||||
rel_issue = Issue.visible.find_by_id(detail.old_value)
|
||||
old_value = rel_issue.nil? ? "#{l(:label_issue)} ##{detail.old_value}" :
|
||||
(no_html ? rel_issue : link_to_issue(rel_issue, :only_path => options[:only_path]))
|
||||
old_value =
|
||||
if rel_issue.nil?
|
||||
"#{l(:label_issue)} ##{detail.old_value}"
|
||||
else
|
||||
(no_html ? rel_issue : link_to_issue(rel_issue, :only_path => options[:only_path]))
|
||||
end
|
||||
end
|
||||
relation_type = IssueRelation::TYPES[detail.prop_key]
|
||||
label = l(relation_type[:name]) if relation_type
|
||||
|
@ -487,10 +549,13 @@ module IssuesHelper
|
|||
elsif show_diff
|
||||
s = l(:text_journal_changed_no_detail, :label => label)
|
||||
unless no_html
|
||||
diff_link = link_to 'diff',
|
||||
diff_journal_url(detail.journal_id, :detail_id => detail.id, :only_path => options[:only_path]),
|
||||
:title => l(:label_view_diff)
|
||||
s << " (#{ diff_link })"
|
||||
diff_link =
|
||||
link_to(
|
||||
'diff',
|
||||
diff_journal_url(detail.journal_id, :detail_id => detail.id,
|
||||
:only_path => options[:only_path]),
|
||||
:title => l(:label_view_diff))
|
||||
s << " (#{diff_link})"
|
||||
end
|
||||
s.html_safe
|
||||
elsif detail.value.present?
|
||||
|
@ -513,9 +578,7 @@ module IssuesHelper
|
|||
|
||||
# Find the name of an associated record stored in the field attribute
|
||||
def find_name_by_reflection(field, id)
|
||||
unless id.present?
|
||||
return nil
|
||||
end
|
||||
return nil if id.blank?
|
||||
@detail_value_name_by_reflection ||= Hash.new do |hash, key|
|
||||
association = Issue.reflect_on_association(key.first.to_sym)
|
||||
name = nil
|
||||
|
@ -543,4 +606,36 @@ module IssuesHelper
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Issue history tabs
|
||||
def issue_history_tabs
|
||||
tabs = []
|
||||
if @journals.present?
|
||||
journals_without_notes = @journals.select{|value| value.notes.blank?}
|
||||
journals_with_notes = @journals.reject{|value| value.notes.blank?}
|
||||
|
||||
tabs << {:name => 'history', :label => :label_history, :onclick => 'showIssueHistory("history", this.href)', :partial => 'issues/tabs/history', :locals => {:issue => @issue, :journals => @journals}}
|
||||
tabs << {:name => 'notes', :label => :label_issue_history_notes, :onclick => 'showIssueHistory("notes", this.href)'} if journals_with_notes.any?
|
||||
tabs << {:name => 'properties', :label => :label_issue_history_properties, :onclick => 'showIssueHistory("properties", this.href)'} if journals_without_notes.any?
|
||||
end
|
||||
tabs << {:name => 'time_entries', :label => :label_time_entry_plural, :remote => true, :onclick => "getRemoteTab('time_entries', '#{tab_issue_path(@issue, :name => 'time_entries')}', '#{issue_path(@issue, :tab => 'time_entries')}')"} if User.current.allowed_to?(:view_time_entries, @project) && @issue.spent_hours > 0
|
||||
tabs << {:name => 'changesets', :label => :label_associated_revisions, :remote => true, :onclick => "getRemoteTab('changesets', '#{tab_issue_path(@issue, :name => 'changesets')}', '#{issue_path(@issue, :tab => 'changesets')}')"} if @has_changesets
|
||||
tabs
|
||||
end
|
||||
|
||||
def issue_history_default_tab
|
||||
# tab params overrides user default tab preference
|
||||
return params[:tab] if params[:tab].present?
|
||||
user_default_tab = User.current.pref.history_default_tab
|
||||
|
||||
case user_default_tab
|
||||
when 'last_tab_visited'
|
||||
cookies['history_last_tab'].presence || 'notes'
|
||||
when ''
|
||||
'notes'
|
||||
else
|
||||
user_default_tab
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,19 +25,20 @@ module JournalsHelper
|
|||
ids.any? ? Attachment.where(:id => ids).select(&:thumbnailable?) : []
|
||||
end
|
||||
|
||||
def render_notes(issue, journal, options={})
|
||||
content = ''
|
||||
css_classes = "wiki"
|
||||
# Returns the action links for an issue journal
|
||||
def render_journal_actions(issue, journal, options={})
|
||||
links = []
|
||||
if journal.notes.present?
|
||||
links << link_to(l(:button_quote),
|
||||
quoted_issue_path(issue, :journal_id => journal),
|
||||
:remote => true,
|
||||
:method => 'post',
|
||||
:title => l(:button_quote),
|
||||
:class => 'icon-only icon-comment'
|
||||
) if options[:reply_links]
|
||||
|
||||
if options[:reply_links]
|
||||
indice = journal.indice || @journal.issue.visible_journals_with_index.find{|j| j.id == @journal.id}.indice
|
||||
links << link_to(l(:button_quote),
|
||||
quoted_issue_path(issue, :journal_id => journal, :journal_indice => indice),
|
||||
:remote => true,
|
||||
:method => 'post',
|
||||
:title => l(:button_quote),
|
||||
:class => 'icon-only icon-comment'
|
||||
)
|
||||
end
|
||||
if journal.editable_by?(User.current)
|
||||
links << link_to(l(:button_edit),
|
||||
edit_journal_path(journal),
|
||||
|
@ -49,21 +50,22 @@ module JournalsHelper
|
|||
links << link_to(l(:button_delete),
|
||||
journal_path(journal, :journal => {:notes => ""}),
|
||||
:remote => true,
|
||||
:method => 'put', :data => {:confirm => l(:text_are_you_sure)},
|
||||
: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)
|
||||
content_tag('div', content.html_safe, :id => "journal-#{journal.id}-notes", :class => css_classes)
|
||||
safe_join(links, ' ')
|
||||
end
|
||||
|
||||
def render_notes(issue, journal, options={})
|
||||
content_tag('div', textilizable(journal, :notes), :id => "journal-#{journal.id}-notes", :class => "wiki")
|
||||
end
|
||||
|
||||
def render_private_notes_indicator(journal)
|
||||
content = journal.private_notes? ? l(:field_is_private) : ''
|
||||
css_classes = journal.private_notes? ? 'private' : ''
|
||||
css_classes = journal.private_notes? ? 'badge badge-private 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
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,16 +23,40 @@ module MembersHelper
|
|||
principal_count = scope.count
|
||||
principal_pages = Redmine::Pagination::Paginator.new principal_count, limit, params['page']
|
||||
principals = scope.offset(principal_pages.offset).limit(principal_pages.per_page).to_a
|
||||
|
||||
s = content_tag('div',
|
||||
content_tag('div', principals_check_box_tags('membership[user_ids][]', principals), :id => 'principals'),
|
||||
:class => 'objects-selection'
|
||||
s = content_tag(
|
||||
'div',
|
||||
content_tag(
|
||||
'div',
|
||||
principals_check_box_tags('membership[user_ids][]', principals), :id => 'principals'),
|
||||
:class => 'objects-selection'
|
||||
)
|
||||
|
||||
links = pagination_links_full(principal_pages, principal_count, :per_page_links => false) {|text, parameters, options|
|
||||
link_to text, autocomplete_project_memberships_path(project, parameters.merge(:q => params[:q], :format => 'js')), :remote => true
|
||||
}
|
||||
|
||||
links =
|
||||
pagination_links_full(principal_pages,
|
||||
principal_count,
|
||||
:per_page_links => false) {|text, parameters, options|
|
||||
link_to(
|
||||
text,
|
||||
autocomplete_project_memberships_path(
|
||||
project,
|
||||
parameters.merge(:q => params[:q], :format => 'js')
|
||||
),
|
||||
:remote => true)
|
||||
}
|
||||
s + content_tag('span', links, :class => 'pagination')
|
||||
end
|
||||
|
||||
# Returns inheritance information for an inherited member role
|
||||
def render_role_inheritance(member, role)
|
||||
content = member.role_inheritance(role).map do |h|
|
||||
if h.is_a?(Project)
|
||||
l(:label_inherited_from_parent_project)
|
||||
elsif h.is_a?(Group)
|
||||
l(:label_inherited_from_group, :name => h.name.to_s)
|
||||
end
|
||||
end.compact.uniq
|
||||
|
||||
if content.present?
|
||||
content_tag('em', content.join(", "), :class => "info")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,7 @@ module MyHelper
|
|||
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))
|
||||
handle = content_tag('span', '', :class => 'icon-only icon-sort-handle sort-handle', :title => l(:button_move))
|
||||
close = link_to(l(:button_delete),
|
||||
{:action => "remove_block", :block => block},
|
||||
:remote => true, :method => 'post',
|
||||
|
@ -78,7 +78,7 @@ module MyHelper
|
|||
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(:project => User.current.projects).
|
||||
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).
|
||||
|
@ -96,6 +96,7 @@ module MyHelper
|
|||
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.add_filter 'project.status', '=', ["#{Project::STATUS_ACTIVE}"]
|
||||
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)
|
||||
|
@ -106,6 +107,18 @@ module MyHelper
|
|||
def render_issuesreportedbyme_block(block, settings)
|
||||
query = IssueQuery.new(:name => l(:label_reported_issues), :user => User.current)
|
||||
query.add_filter 'author_id', '=', ['me']
|
||||
query.add_filter 'project.status', '=', ["#{Project::STATUS_ACTIVE}"]
|
||||
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 render_issuesupdatedbyme_block(block, settings)
|
||||
query = IssueQuery.new(:name => l(:label_updated_issues), :user => User.current)
|
||||
query.add_filter 'updated_by', '=', ['me']
|
||||
query.add_filter 'project.status', '=', ["#{Project::STATUS_ACTIVE}"]
|
||||
query.column_names = settings[:columns].presence || ['project', 'tracker', 'status', 'subject']
|
||||
query.sort_criteria = settings[:sort].presence || [['updated_on', 'desc']]
|
||||
issues = query.issues(:limit => 10)
|
||||
|
@ -116,6 +129,7 @@ module MyHelper
|
|||
def render_issueswatched_block(block, settings)
|
||||
query = IssueQuery.new(:name => l(:label_watched_issues), :user => User.current)
|
||||
query.add_filter 'watcher_id', '=', ['me']
|
||||
query.add_filter 'project.status', '=', ["#{Project::STATUS_ACTIVE}"]
|
||||
query.column_names = settings[:columns].presence || ['project', 'tracker', 'status', 'subject']
|
||||
query.sort_criteria = settings[:sort].presence || [['updated_on', 'desc']]
|
||||
issues = query.issues(:limit => 10)
|
||||
|
@ -139,7 +153,7 @@ module MyHelper
|
|||
|
||||
def render_news_block(block, settings)
|
||||
news = News.visible.
|
||||
where(:project_id => User.current.projects.map(&:id)).
|
||||
where(:project => User.current.projects).
|
||||
limit(10).
|
||||
includes(:project, :author).
|
||||
references(:project, :author).
|
||||
|
@ -164,4 +178,10 @@ module MyHelper
|
|||
|
||||
render :partial => 'my/blocks/timelog', :locals => {:block => block, :entries => entries, :entries_by_day => entries_by_day, :days => days}
|
||||
end
|
||||
|
||||
def render_activity_block(block, settings)
|
||||
events_by_day = Redmine::Activity::Fetcher.new(User.current, :author => User.current).events(nil, nil, :limit => 10).group_by {|event| User.current.time_to_date(event.event_datetime)}
|
||||
|
||||
render :partial => 'my/blocks/activity', :locals => {:events_by_day => events_by_day}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,18 +19,31 @@
|
|||
|
||||
module ProjectsHelper
|
||||
def project_settings_tabs
|
||||
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,
|
||||
: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},
|
||||
{:name => 'boards', :action => :manage_boards, :partial => 'projects/settings/boards', :label => :label_board_plural},
|
||||
{:name => 'activities', :action => :manage_project_activities, :partial => 'projects/settings/activities', :label => :enumeration_activities}
|
||||
]
|
||||
tabs.select {|tab| User.current.allowed_to?(tab[:action], @project)}
|
||||
tabs =
|
||||
[
|
||||
{:name => 'info', :action => :edit_project,
|
||||
:partial => 'projects/edit', :label => :label_project},
|
||||
{:name => 'members', :action => :manage_members,
|
||||
:partial => 'projects/settings/members', :label => :label_member_plural},
|
||||
{:name => 'issues', :action => :edit_project, :module => :issue_tracking,
|
||||
:partial => 'projects/settings/issues', :label => :label_issue_tracking},
|
||||
{: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 => 'repositories', :action => :manage_repository,
|
||||
:partial => 'projects/settings/repositories', :label => :label_repository_plural},
|
||||
{:name => 'boards', :action => :manage_boards,
|
||||
:partial => 'projects/settings/boards', :label => :label_board_plural},
|
||||
{:name => 'activities', :action => :manage_project_activities,
|
||||
:partial => 'projects/settings/activities', :label => :label_time_tracking}
|
||||
]
|
||||
tabs.
|
||||
select {|tab| User.current.allowed_to?(tab[:action], @project)}.
|
||||
select {|tab| tab[:module].nil? || @project.module_enabled?(tab[:module])}
|
||||
end
|
||||
|
||||
def parent_project_select_tag(project)
|
||||
|
@ -41,24 +54,27 @@ module ProjectsHelper
|
|||
selected = (parent_id.blank? ? nil : Project.find(parent_id))
|
||||
end
|
||||
|
||||
options = ''
|
||||
options = +''
|
||||
options << "<option value=''> </option>" if project.allowed_parents.include?(nil)
|
||||
options << project_tree_options_for_select(project.allowed_parents.compact, :selected => selected)
|
||||
content_tag('select', options.html_safe, :name => 'project[parent_id]', :id => 'project_parent_id')
|
||||
end
|
||||
|
||||
def render_project_action_links
|
||||
links = "".html_safe
|
||||
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.admin?
|
||||
links << link_to(l(:label_administration), admin_projects_path, :class => 'icon icon-settings')
|
||||
end
|
||||
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) ? 'icon icon-fav my-project' : nil}")
|
||||
s = link_to_project(project, {}, :class => "#{project.css_classes} #{User.current.member_of?(project) ? 'icon icon-user my-project' : nil}")
|
||||
if project.description.present?
|
||||
s << content_tag('div', textilizable(project.short_description, :project => project), :class => 'wiki description')
|
||||
end
|
||||
|
@ -137,4 +153,33 @@ module ProjectsHelper
|
|||
end
|
||||
end if include_in_api_response?('enabled_modules')
|
||||
end
|
||||
|
||||
def bookmark_link(project, user = User.current)
|
||||
return '' unless user && user.logged?
|
||||
@jump_box ||= Redmine::ProjectJumpBox.new user
|
||||
bookmarked = @jump_box.bookmark?(project)
|
||||
css = +"icon bookmark "
|
||||
|
||||
if bookmarked
|
||||
css << "icon-bookmark"
|
||||
method = "delete"
|
||||
text = l(:button_project_bookmark_delete)
|
||||
else
|
||||
css << "icon-bookmark-off"
|
||||
method = "post"
|
||||
text = l(:button_project_bookmark)
|
||||
end
|
||||
|
||||
url = bookmark_project_path(project)
|
||||
link_to text, url, remote: true, method: method, class: css
|
||||
end
|
||||
|
||||
def grouped_project_list(projects, query, &block)
|
||||
ancestors = []
|
||||
grouped_query_results(projects, query) do |project, group_name, group_count, group_totals|
|
||||
ancestors.pop while ancestors.any? && !project.is_descendant_of?(ancestors.last)
|
||||
yield project, ancestors.size, group_name, group_count, group_totals
|
||||
ancestors << project unless project.leaf?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
62
app/helpers/projects_queries_helper.rb
Normal file
62
app/helpers/projects_queries_helper.rb
Normal file
|
@ -0,0 +1,62 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
module ProjectsQueriesHelper
|
||||
include ApplicationHelper
|
||||
|
||||
def column_value(column, item, value)
|
||||
if item.is_a?(Project)
|
||||
case column.name
|
||||
when :name
|
||||
link_to_project(item) + (content_tag('span', '', :class => 'icon icon-user my-project', :title => l(:label_my_projects)) if User.current.member_of?(item))
|
||||
when :short_description
|
||||
item.description? ? content_tag('div', textilizable(item, :short_description), :class => "wiki") : ''
|
||||
when :homepage
|
||||
item.homepage? ? content_tag('div', textilizable(item, :homepage), :class => "wiki") : ''
|
||||
when :status
|
||||
get_project_status_label[column.value_object(item)]
|
||||
when :parent_id
|
||||
link_to_project(item.parent) unless item.parent.nil?
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def csv_value(column, object, value)
|
||||
if object.is_a?(Project)
|
||||
case column.name
|
||||
when :status
|
||||
get_project_status_label[column.value_object(object)]
|
||||
when :parent_id
|
||||
object.parent.name unless object.parent.nil?
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_project_status_label
|
||||
{
|
||||
Project::STATUS_ACTIVE => l(:project_status_active),
|
||||
Project::STATUS_CLOSED => l(:project_status_closed)
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,6 +17,8 @@
|
|||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
require 'redmine/export/csv'
|
||||
|
||||
module QueriesHelper
|
||||
include ApplicationHelper
|
||||
|
||||
|
@ -28,7 +30,7 @@ module QueriesHelper
|
|||
group = :label_relations
|
||||
elsif field_options[:type] == :tree
|
||||
group = query.is_a?(IssueQuery) ? :label_relations : nil
|
||||
elsif field =~ /^cf_\d+\./
|
||||
elsif /^cf_\d+\./.match?(field)
|
||||
group = (field_options[:through] || field_options[:field]).try(:name)
|
||||
elsif field =~ /^(.+)\./
|
||||
# association filters
|
||||
|
@ -37,6 +39,8 @@ module QueriesHelper
|
|||
group = :field_assigned_to
|
||||
elsif field_options[:type] == :date_past || field_options[:type] == :date
|
||||
group = :label_date
|
||||
elsif %w(estimated_hours spent_time).include?(field)
|
||||
group = :label_time_tracking
|
||||
end
|
||||
if group
|
||||
(grouped[group] ||= []) << [field_options[:name], field]
|
||||
|
@ -93,12 +97,13 @@ module QueriesHelper
|
|||
tags
|
||||
end
|
||||
|
||||
def available_totalable_columns_tags(query)
|
||||
def available_totalable_columns_tags(query, options={})
|
||||
tag_name = (options[:name] || 't') + '[]'
|
||||
tags = ''.html_safe
|
||||
query.available_totalable_columns.each do |column|
|
||||
tags << content_tag('label', check_box_tag('t[]', column.name.to_s, query.totalable_columns.include?(column), :id => nil) + " #{column.caption}", :class => 'inline')
|
||||
tags << content_tag('label', check_box_tag(tag_name, column.name.to_s, query.totalable_columns.include?(column), :id => nil) + " #{column.caption}", :class => 'inline')
|
||||
end
|
||||
tags << hidden_field_tag('t[]', '')
|
||||
tags << hidden_field_tag(tag_name, '')
|
||||
tags
|
||||
end
|
||||
|
||||
|
@ -115,6 +120,15 @@ module QueriesHelper
|
|||
render :partial => 'queries/columns', :locals => {:query => query, :tag_name => tag_name}
|
||||
end
|
||||
|
||||
def available_display_types_tags(query)
|
||||
tags = ''.html_safe
|
||||
query.available_display_types.each do |t|
|
||||
tags << radio_button_tag('display_type', t, @query.display_type == t, :id => "display_type_#{t}") +
|
||||
content_tag('label', l(:"label_display_type_#{t}"), :for => "display_type_#{t}", :class => "inline")
|
||||
end
|
||||
tags
|
||||
end
|
||||
|
||||
def grouped_query_results(items, query, &block)
|
||||
result_count_by_group = query.result_count_by_group
|
||||
previous_group, first = false, true
|
||||
|
@ -125,7 +139,7 @@ module QueriesHelper
|
|||
items.each do |item|
|
||||
group_name = group_count = nil
|
||||
if query.grouped?
|
||||
group = query.group_by_column.value(item)
|
||||
group = query.group_by_column.group_value(item)
|
||||
if first || group != previous_group
|
||||
if group.blank? && group != false
|
||||
group_name = "(#{l(:label_blank_value)})"
|
||||
|
@ -152,11 +166,12 @@ module QueriesHelper
|
|||
|
||||
def total_tag(column, value)
|
||||
label = content_tag('span', "#{column.caption}:")
|
||||
value = if [:hours, :spent_hours, :total_spent_hours, :estimated_hours].include? column.name
|
||||
format_hours(value)
|
||||
else
|
||||
format_object(value)
|
||||
end
|
||||
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
|
||||
|
@ -166,18 +181,16 @@ module QueriesHelper
|
|||
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'
|
||||
css = 'sort asc icon icon-sorted-desc'
|
||||
order = 'desc'
|
||||
else
|
||||
css = 'sort desc'
|
||||
css = 'sort desc icon icon-sorted-asc'
|
||||
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
|
||||
sort_param = {param_key => query.sort_criteria.add(column.name, order).to_param}
|
||||
sort_param = {$1 => {$2 => sort_param.values.first}} while sort_param.keys.first.to_s =~ /^(.+)\[(.+)\]$/
|
||||
link_options = {
|
||||
:title => l(:label_sort_by, "\"#{column.caption}\""),
|
||||
:class => css
|
||||
|
@ -185,14 +198,15 @@ module QueriesHelper
|
|||
if options[:sort_link_options]
|
||||
link_options.merge! options[:sort_link_options]
|
||||
end
|
||||
content = link_to(column.caption,
|
||||
content = link_to(
|
||||
column.caption,
|
||||
{:params => request.query_parameters.deep_merge(sort_param)},
|
||||
link_options
|
||||
)
|
||||
else
|
||||
content = column.caption
|
||||
end
|
||||
content_tag('th', content)
|
||||
content_tag('th', content, :class => column.css_classes)
|
||||
end
|
||||
|
||||
def column_content(column, item)
|
||||
|
@ -220,7 +234,8 @@ module QueriesHelper
|
|||
when :done_ratio
|
||||
progress_bar(value)
|
||||
when :relations
|
||||
content_tag('span',
|
||||
content_tag(
|
||||
'span',
|
||||
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
|
||||
|
@ -258,7 +273,7 @@ module QueriesHelper
|
|||
value.to_s(object)
|
||||
when 'Issue'
|
||||
if object.is_a?(TimeEntry)
|
||||
"#{value.tracker} ##{value.id}: #{value.subject}"
|
||||
value.visible? ? "#{value.tracker} ##{value.id}: #{value.subject}" : "##{value.id}"
|
||||
else
|
||||
value.id
|
||||
end
|
||||
|
@ -272,7 +287,7 @@ module QueriesHelper
|
|||
def query_to_csv(items, query, options={})
|
||||
columns = query.columns
|
||||
|
||||
Redmine::Export::CSV.generate do |csv|
|
||||
Redmine::Export::CSV.generate(:encoding => params[:encoding]) do |csv|
|
||||
# csv header fields
|
||||
csv << columns.map {|c| c.caption.to_s}
|
||||
# csv lines
|
||||
|
@ -283,20 +298,20 @@ module QueriesHelper
|
|||
end
|
||||
|
||||
# Retrieve query from session or build a new query
|
||||
def retrieve_query(klass=IssueQuery, use_session=true)
|
||||
def retrieve_query(klass=IssueQuery, use_session=true, options={})
|
||||
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 = klass.where(cond).find(params[:query_id])
|
||||
scope = klass.where(:project_id => nil)
|
||||
scope = scope.or(klass.where(:project_id => @project)) if @project
|
||||
@query = scope.find(params[:query_id])
|
||||
raise ::Unauthorized unless @query.visible?
|
||||
@query.project = @project
|
||||
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 = klass.new(:name => "_", :project => @project)
|
||||
@query.build_from_params(params)
|
||||
@query.build_from_params(params, options[:defaults])
|
||||
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
|
||||
|
@ -367,7 +382,7 @@ module QueriesHelper
|
|||
|
||||
tags
|
||||
end
|
||||
|
||||
|
||||
def query_hidden_sort_tag(query)
|
||||
hidden_field_tag("sort", query.sort_criteria.to_param, :id => nil)
|
||||
end
|
||||
|
@ -381,19 +396,41 @@ module QueriesHelper
|
|||
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} : {}
|
||||
|
||||
url_params =
|
||||
if controller_name == 'issues'
|
||||
{:controller => 'issues', :action => 'index', :project_id => @project}
|
||||
else
|
||||
{}
|
||||
end
|
||||
content_tag('h3', title) + "\n" +
|
||||
content_tag('ul',
|
||||
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,
|
||||
css = +'query'
|
||||
clear_link = +''
|
||||
if query == @query
|
||||
css << ' selected'
|
||||
clear_link += link_to_clear_query
|
||||
end
|
||||
content_tag('li',
|
||||
link_to(query.name,
|
||||
url_params.merge(:query_id => query),
|
||||
:class => css) +
|
||||
clear_link.html_safe)
|
||||
}.join("\n").html_safe,
|
||||
:class => 'queries'
|
||||
) + "\n"
|
||||
end
|
||||
|
||||
def link_to_clear_query
|
||||
link_to(
|
||||
l(:button_clear),
|
||||
{:set_filter => 1, :sort => '', :project_id => @project},
|
||||
:class => 'icon-only icon-clear-query',
|
||||
:title => l(:button_clear)
|
||||
)
|
||||
end
|
||||
|
||||
# Renders the list of queries for the sidebar
|
||||
def render_sidebar_queries(klass, project)
|
||||
queries = sidebar_queries(klass, project)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,9 +32,18 @@ module RepositoriesHelper
|
|||
end
|
||||
end
|
||||
|
||||
def render_pagination
|
||||
pagination_links_each @paginator do |text, parameters, options|
|
||||
if entry = @entries[parameters[:page] - 1]
|
||||
ent_path = Redmine::CodesetUtil.replace_invalid_utf8(entry.path)
|
||||
link_to text, {action: 'entry', id: @project, repository_id: @repository.identifier_param, path: to_path_param(ent_path), rev: @rev}
|
||||
end
|
||||
end if @paginator
|
||||
end
|
||||
|
||||
def render_properties(properties)
|
||||
unless properties.nil? || properties.empty?
|
||||
content = ''
|
||||
content = +''
|
||||
properties.keys.sort.each do |property|
|
||||
content << content_tag('li', "<b>#{h property}</b>: <span>#{h properties[property]}</span>".html_safe)
|
||||
end
|
||||
|
@ -49,7 +58,7 @@ module RepositoriesHelper
|
|||
# Detects moved/copied files
|
||||
if !change.from_path.blank?
|
||||
change.action =
|
||||
@changeset.filechanges.detect {|c| c.action == 'D' && c.path == change.from_path} ? 'R' : 'C'
|
||||
@changeset.filechanges.detect {|c| c.action == 'D' && c.path == change.from_path} ? 'R' : 'C'
|
||||
end
|
||||
change
|
||||
when 'D'
|
||||
|
@ -78,10 +87,10 @@ module RepositoriesHelper
|
|||
|
||||
def render_changes_tree(tree)
|
||||
return '' if tree.nil?
|
||||
output = ''
|
||||
output = +''
|
||||
output << '<ul>'
|
||||
tree.keys.sort.each do |file|
|
||||
style = 'change'
|
||||
style = +'change'
|
||||
text = File.basename(h(file))
|
||||
if s = tree[file][:s]
|
||||
style << ' folder'
|
||||
|
@ -130,7 +139,7 @@ module RepositoriesHelper
|
|||
def scm_select_tag(repository)
|
||||
scm_options = [["--- #{l(:actionview_instancetag_blank_option)} ---", '']]
|
||||
Redmine::Scm::Base.all.each do |scm|
|
||||
if Setting.enabled_scm.include?(scm) ||
|
||||
if Setting.enabled_scm.include?(scm) ||
|
||||
(repository && repository.class.name.demodulize == scm)
|
||||
scm_options << ["Repository::#{scm}".constantize.scm_name, scm]
|
||||
end
|
||||
|
@ -157,15 +166,6 @@ module RepositoriesHelper
|
|||
:onchange => "this.name='repository[password]';"))
|
||||
end
|
||||
|
||||
def darcs_field_tags(form, repository)
|
||||
content_tag('p', form.text_field(
|
||||
:url, :label => l(:field_path_to_repository),
|
||||
:size => 60, :required => true,
|
||||
:disabled => !repository.safe_attribute?('url')) +
|
||||
scm_path_info_tag(repository)) +
|
||||
scm_log_encoding_tag(form, repository)
|
||||
end
|
||||
|
||||
def mercurial_field_tags(form, repository)
|
||||
content_tag('p', form.text_field(
|
||||
:url, :label => l(:field_path_to_repository),
|
||||
|
@ -278,8 +278,8 @@ module RepositoriesHelper
|
|||
:href => block_given? ? yield(commit.scmid) : commit.scmid
|
||||
}
|
||||
end
|
||||
heads.sort! { |head1, head2| head1.to_s <=> head2.to_s }
|
||||
space = nil
|
||||
heads.sort_by!(&:to_s)
|
||||
space = nil
|
||||
heads.each do |head|
|
||||
if commits_by_scmid.include? head.scmid
|
||||
space = index_head((space || -1) + 1, head, commits_by_scmid)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,14 +18,11 @@
|
|||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
module SearchHelper
|
||||
include ActionView::Helpers::SanitizeHelper
|
||||
|
||||
def highlight_tokens(text, tokens)
|
||||
return text unless text && tokens && !tokens.empty?
|
||||
re_tokens = tokens.collect {|t| Regexp.escape(t)}
|
||||
regexp = Regexp.new "(#{re_tokens.join('|')})", Regexp::IGNORECASE
|
||||
result = ''
|
||||
text = strip_tags(text)
|
||||
result = +''
|
||||
text.split(regexp).each_with_index do |words, i|
|
||||
if result.length > 1200
|
||||
# maximum length of the preview reached
|
||||
|
@ -58,7 +55,7 @@ module SearchHelper
|
|||
def render_results_by_type(results_by_type)
|
||||
links = []
|
||||
# Sorts types by results count
|
||||
results_by_type.keys.sort {|a, b| results_by_type[b] <=> results_by_type[a]}.each do |t|
|
||||
results_by_type.keys.sort_by {|k| results_by_type[k]}.reverse_each do |t|
|
||||
c = results_by_type[t]
|
||||
next if c == 0
|
||||
text = "#{type_label(t)} (#{c})"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,6 +24,7 @@ module SettingsHelper
|
|||
{:name => 'authentication', :partial => 'settings/authentication', :label => :label_authentication},
|
||||
{:name => 'api', :partial => 'settings/api', :label => :label_api},
|
||||
{:name => 'projects', :partial => 'settings/projects', :label => :label_project_plural},
|
||||
{:name => 'users', :partial => 'settings/users', :label => :label_user_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},
|
||||
|
@ -112,20 +113,16 @@ module SettingsHelper
|
|||
tag_data = notifiable.parent.present? ?
|
||||
{:parent_notifiable => notifiable.parent} :
|
||||
{:disables => "input[data-parent-notifiable=#{notifiable.name}]"}
|
||||
|
||||
tag = check_box_tag('settings[notified_events][]',
|
||||
notifiable.name,
|
||||
setting_value('notified_events').include?(notifiable.name),
|
||||
:id => nil,
|
||||
:data => tag_data)
|
||||
|
||||
notifiable.name,
|
||||
setting_value('notified_events').include?(notifiable.name),
|
||||
:id => nil,
|
||||
:data => tag_data)
|
||||
text = l_or_humanize(notifiable.name, :prefix => 'label_')
|
||||
|
||||
options = {}
|
||||
if notifiable.parent.present?
|
||||
options[:class] = "parent"
|
||||
end
|
||||
|
||||
content_tag(:label, tag + text, options)
|
||||
end
|
||||
|
||||
|
@ -207,4 +204,13 @@ module SettingsHelper
|
|||
["#{today} (#{format})", f]
|
||||
end
|
||||
end
|
||||
|
||||
def gravatar_default_setting_options
|
||||
[['Identicons', 'identicon'],
|
||||
['Monster ids', 'monsterid'],
|
||||
['Mystery man', 'mm'],
|
||||
['Retro', 'retro'],
|
||||
['Robohash', 'robohash'],
|
||||
['Wavatars', 'wavatar']]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Helpers to sort tables using clickable column headers.
|
||||
#
|
||||
# Author: Stuart Rackham <srackham@methods.co.nz>, March 2005.
|
||||
|
@ -96,7 +96,7 @@ module SortHelper
|
|||
# Returns an SQL sort clause corresponding to the current sort state.
|
||||
# Use this to sort the controller's table items collection.
|
||||
#
|
||||
def sort_clause()
|
||||
def sort_clause
|
||||
@sort_criteria.sort_clause(@sortable_columns)
|
||||
end
|
||||
|
||||
|
@ -115,10 +115,10 @@ module SortHelper
|
|||
|
||||
if column.to_s == @sort_criteria.first_key
|
||||
if @sort_criteria.first_asc?
|
||||
css = 'sort asc'
|
||||
css = 'sort asc icon icon-sorted-desc'
|
||||
order = 'desc'
|
||||
else
|
||||
css = 'sort desc'
|
||||
css = 'sort desc icon icon-sorted-asc'
|
||||
order = 'asc'
|
||||
end
|
||||
end
|
||||
|
@ -160,4 +160,3 @@ module SortHelper
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
|
@ -42,6 +42,12 @@ module TimelogHelper
|
|||
collection
|
||||
end
|
||||
|
||||
def user_collection_for_select_options(time_entry)
|
||||
collection = time_entry.assignable_users
|
||||
collection << time_entry.user unless time_entry.user.nil? && !collection.include?(time_entry.user)
|
||||
principals_options_for_select(collection, time_entry.user_id.to_s)
|
||||
end
|
||||
|
||||
def select_hours(data, criteria, value)
|
||||
if value.to_s.empty?
|
||||
data.select {|row| row[criteria].blank? }
|
||||
|
@ -58,15 +64,19 @@ module TimelogHelper
|
|||
sum
|
||||
end
|
||||
|
||||
def format_criteria_value(criteria_options, value)
|
||||
def format_criteria_value(criteria_options, value, html=true)
|
||||
if value.blank?
|
||||
"[#{l(:label_none)}]"
|
||||
elsif k = criteria_options[:klass]
|
||||
obj = k.find_by_id(value.to_i)
|
||||
if obj.is_a?(Issue)
|
||||
obj.visible? ? "#{obj.tracker} ##{obj.id}: #{obj.subject}" : "##{obj.id}"
|
||||
if obj.visible?
|
||||
html ? link_to_issue(obj) : "#{obj.tracker} ##{obj.id}: #{obj.subject}"
|
||||
else
|
||||
"##{obj.id}"
|
||||
end
|
||||
else
|
||||
obj
|
||||
format_object(obj, html)
|
||||
end
|
||||
elsif cf = criteria_options[:custom_field]
|
||||
format_value(value, cf)
|
||||
|
@ -76,9 +86,9 @@ module TimelogHelper
|
|||
end
|
||||
|
||||
def report_to_csv(report)
|
||||
Redmine::Export::CSV.generate do |csv|
|
||||
Redmine::Export::CSV.generate(:encoding => params[:encoding]) do |csv|
|
||||
# Column headers
|
||||
headers = report.criteria.collect {|criteria| l(report.available_criteria[criteria][:label]) }
|
||||
headers = report.criteria.collect {|criteria| l_or_humanize(report.available_criteria[criteria][:label]) }
|
||||
headers += report.periods
|
||||
headers << l(:label_total_time)
|
||||
csv << headers
|
||||
|
@ -103,7 +113,7 @@ module TimelogHelper
|
|||
hours_for_value = select_hours(hours, criteria[level], value)
|
||||
next if hours_for_value.empty?
|
||||
row = [''] * level
|
||||
row << format_criteria_value(available_criteria[criteria[level]], value).to_s
|
||||
row << format_criteria_value(available_criteria[criteria[level]], value, false).to_s
|
||||
row += [''] * (criteria.length - level - 1)
|
||||
total = 0
|
||||
periods.each do |period|
|
||||
|
@ -118,4 +128,10 @@ module TimelogHelper
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
def cancel_button_tag_for_time_entry(project)
|
||||
fallback_path = project ? project_time_entries_path(project) : time_entries_path
|
||||
cancel_button_tag(fallback_path)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,4 +18,10 @@
|
|||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
module TrackersHelper
|
||||
|
||||
def tracker_name_tag(tracker)
|
||||
title = tracker.description.presence
|
||||
css = title ? "field-description" : nil
|
||||
content_tag 'span', tracker.name, :class => css, :title => title
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,12 +18,11 @@
|
|||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
module UsersHelper
|
||||
include ApplicationHelper
|
||||
|
||||
def users_status_options_for_select(selected)
|
||||
user_count_by_status = User.group('status').count.to_hash
|
||||
options_for_select([[l(:label_all), ''],
|
||||
["#{l(:status_active)} (#{user_count_by_status[1].to_i})", '1'],
|
||||
["#{l(:status_registered)} (#{user_count_by_status[2].to_i})", '2'],
|
||||
["#{l(:status_locked)} (#{user_count_by_status[3].to_i})", '3']], selected.to_s)
|
||||
options_for_select([[l(:label_all), '']] + (User.valid_statuses.map {|c| ["#{l('status_' + User::LABEL_BY_STATUS[c])} (#{user_count_by_status[c].to_i})", c]}), selected.to_s)
|
||||
end
|
||||
|
||||
def user_mail_notification_options(user)
|
||||
|
@ -34,6 +33,15 @@ module UsersHelper
|
|||
[[l(:label_font_default), '']] + UserPreference::TEXTAREA_FONT_OPTIONS.map {|o| [l("label_font_#{o}"), o]}
|
||||
end
|
||||
|
||||
def history_default_tab_options
|
||||
[[l('label_issue_history_notes'), 'notes'],
|
||||
[l('label_history'), 'history'],
|
||||
[l('label_issue_history_properties'), 'properties'],
|
||||
[l('label_time_entry_plural'), 'time_entries'],
|
||||
[l('label_associated_revisions'), 'changesets'],
|
||||
[l('label_last_tab_visited'), 'last_tab_visited']]
|
||||
end
|
||||
|
||||
def change_status_link(user)
|
||||
url = {:controller => 'users', :action => 'update', :id => user, :page => params[:page], :status => params[:status], :tab => nil}
|
||||
|
||||
|
@ -61,4 +69,32 @@ module UsersHelper
|
|||
end
|
||||
tabs
|
||||
end
|
||||
|
||||
def users_to_csv(users)
|
||||
Redmine::Export::CSV.generate(:encoding => params[:encoding]) do |csv|
|
||||
columns = [
|
||||
'login',
|
||||
'firstname',
|
||||
'lastname',
|
||||
'mail',
|
||||
'admin',
|
||||
'created_on',
|
||||
'last_login_on',
|
||||
'status'
|
||||
]
|
||||
|
||||
# csv header fields
|
||||
csv << columns.map{|column| l('field_' + column)}
|
||||
# csv lines
|
||||
users.each do |user|
|
||||
csv << columns.map do |column|
|
||||
if column == 'status'
|
||||
l(("status_#{User::LABEL_BY_STATUS[user.status]}"))
|
||||
else
|
||||
format_object(user.send(column), false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,7 +29,8 @@ module VersionsHelper
|
|||
|
||||
def version_filtered_issues_path(version, options = {})
|
||||
options = {:fixed_version_id => version, :set_filter => 1}.merge(options)
|
||||
project = case version.sharing
|
||||
project =
|
||||
case version.sharing
|
||||
when 'hierarchy', 'tree'
|
||||
if version.project && version.project.root.visible?
|
||||
version.project.root
|
||||
|
@ -40,8 +41,7 @@ module VersionsHelper
|
|||
nil
|
||||
else
|
||||
version.project
|
||||
end
|
||||
|
||||
end
|
||||
if project
|
||||
project_issues_path(project, options)
|
||||
else
|
||||
|
@ -53,24 +53,55 @@ module VersionsHelper
|
|||
|
||||
def render_issue_status_by(version, criteria)
|
||||
criteria = 'tracker' unless STATUS_BY_CRITERIAS.include?(criteria)
|
||||
|
||||
h = Hash.new {|k,v| k[v] = [0, 0]}
|
||||
begin
|
||||
# Total issue count
|
||||
version.fixed_issues.group(criteria).count.each {|c,s| h[c][0] = s}
|
||||
version.visible_fixed_issues.group(criteria).count.each {|c,s| h[c][0] = s}
|
||||
# Open issues count
|
||||
version.fixed_issues.open.group(criteria).count.each {|c,s| h[c][1] = s}
|
||||
version.visible_fixed_issues.open.group(criteria).count.each {|c,s| h[c][1] = s}
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
# When grouping by an association, Rails throws this exception if there's no result (bug)
|
||||
# When grouping by an association, Rails throws this exception if there's no result (bug)
|
||||
end
|
||||
# Sort with nil keys in last position
|
||||
counts = h.keys.sort {|a,b| a.nil? ? 1 : (b.nil? ? -1 : a <=> b)}.collect {|k| {:group => k, :total => h[k][0], :open => h[k][1], :closed => (h[k][0] - h[k][1])}}
|
||||
sorted_keys =
|
||||
h.keys.sort {|a, b|
|
||||
if a.nil?
|
||||
1
|
||||
else
|
||||
b.nil? ? -1 : a <=> b
|
||||
end
|
||||
}
|
||||
counts =
|
||||
sorted_keys.collect {|k|
|
||||
{:group => k, :total => h[k][0], :open => h[k][1], :closed => (h[k][0] - h[k][1])}
|
||||
}
|
||||
max = counts.collect {|c| c[:total]}.max
|
||||
|
||||
render :partial => 'issue_counts', :locals => {:version => version, :criteria => criteria, :counts => counts, :max => max}
|
||||
end
|
||||
|
||||
def status_by_options_for_select(value)
|
||||
options_for_select(STATUS_BY_CRITERIAS.collect {|criteria| [l("field_#{criteria}".to_sym), criteria]}, value)
|
||||
end
|
||||
|
||||
def link_to_new_issue(version, project)
|
||||
if version.open? && User.current.allowed_to?(:add_issues, project)
|
||||
trackers = Issue.allowed_target_trackers(project)
|
||||
|
||||
unless trackers.empty?
|
||||
issue = Issue.new(:project => project)
|
||||
new_issue_tracker = trackers.detect do |tracker|
|
||||
issue.tracker = tracker
|
||||
issue.safe_attribute?('fixed_version_id')
|
||||
end
|
||||
end
|
||||
|
||||
if new_issue_tracker
|
||||
attrs = {
|
||||
:tracker_id => new_issue_tracker,
|
||||
:fixed_version_id => version.id
|
||||
}
|
||||
link_to l(:label_issue_new), new_project_issue_path(project, :issue => attrs, :back_url => version_path(version)), :class => 'icon icon-add'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,10 +22,10 @@ module WikiHelper
|
|||
|
||||
def wiki_page_options_for_select(pages, selected = nil, parent = nil, level = 0)
|
||||
pages = pages.group_by(&:parent) unless pages.is_a?(Hash)
|
||||
s = ''.html_safe
|
||||
s = (+'').html_safe
|
||||
if pages.has_key?(parent)
|
||||
pages[parent].each do |page|
|
||||
attrs = "value='#{page.id}'"
|
||||
attrs = +"value='#{page.id}'"
|
||||
attrs << " selected='selected'" if selected == page
|
||||
indent = (level > 0) ? (' ' * level * 2 + '» ') : ''
|
||||
|
||||
|
@ -64,4 +64,8 @@ module WikiHelper
|
|||
project_wiki_page_path(page.project, page.title)
|
||||
end
|
||||
end
|
||||
|
||||
def wiki_content_update_info(content)
|
||||
l(:label_updated_time_by, :author => link_to_user(content.author), :age => time_tag(content.updated_on)).html_safe
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
#
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
# Copyright (C) 2006-2019 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,7 +21,7 @@ module WorkflowsHelper
|
|||
def options_for_workflow_select(name, objects, selected, options={})
|
||||
option_tags = ''.html_safe
|
||||
multiple = false
|
||||
if selected
|
||||
if selected
|
||||
if selected.size == objects.size
|
||||
selected = 'all'
|
||||
else
|
||||
|
@ -51,7 +51,7 @@ module WorkflowsHelper
|
|||
options = [["", ""], [l(:label_readonly), "readonly"]]
|
||||
options << [l(:label_required), "required"] unless field_required?(field)
|
||||
html_options = {}
|
||||
|
||||
|
||||
if perm = permissions[status.id][name]
|
||||
if perm.uniq.size > 1 || perm.size < @roles.size * @trackers.size
|
||||
options << [l(:label_no_change_option), "no_change"]
|
||||
|
@ -74,22 +74,27 @@ module WorkflowsHelper
|
|||
select_tag("permissions[#{status.id}][#{name}]", options_for_select(options, selected), html_options)
|
||||
end
|
||||
|
||||
def transition_tag(workflows, old_status, new_status, name)
|
||||
w = workflows.select {|w| w.old_status == old_status && w.new_status == new_status}.size
|
||||
|
||||
def transition_tag(transition_count, old_status, new_status, name)
|
||||
w = transition_count
|
||||
tag_name = "transitions[#{ old_status.try(:id) || 0 }][#{new_status.id}][#{name}]"
|
||||
if w == 0 || w == @roles.size * @trackers.size
|
||||
|
||||
if old_status == new_status
|
||||
check_box_tag(tag_name, "1", true,
|
||||
{:disabled => true, :class => "old-status-#{old_status.try(:id) || 0} new-status-#{new_status.id}"})
|
||||
elsif w == 0 || w == @roles.size * @trackers.size
|
||||
hidden_field_tag(tag_name, "0", :id => nil) +
|
||||
check_box_tag(tag_name, "1", w != 0,
|
||||
:class => "old-status-#{old_status.try(:id) || 0} new-status-#{new_status.id}")
|
||||
:class => "old-status-#{old_status.try(:id) || 0} new-status-#{new_status.id}")
|
||||
else
|
||||
select_tag tag_name,
|
||||
options_for_select([
|
||||
select_tag(
|
||||
tag_name,
|
||||
options_for_select(
|
||||
[
|
||||
[l(:general_text_Yes), "1"],
|
||||
[l(:general_text_No), "0"],
|
||||
[l(:label_no_change_option), "no_change"]
|
||||
], "no_change")
|
||||
],
|
||||
"no_change")
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue