Compare commits
60 Commits
Author | SHA1 | Date |
---|---|---|
Jean-Philippe Lang | b0bf728118 | |
Jean-Philippe Lang | 92dce4f80d | |
Jean-Philippe Lang | d05cbf4df8 | |
Jean-Philippe Lang | e9e0ffb6dd | |
Jean-Philippe Lang | 40abbfa82d | |
Toshi MARUYAMA | 8d7fd10f90 | |
Toshi MARUYAMA | dac4f38eb4 | |
Toshi MARUYAMA | 5e031bce47 | |
Toshi MARUYAMA | dc431edbc8 | |
Jean-Philippe Lang | 8980f7a01a | |
Jean-Philippe Lang | c47aacd6b0 | |
Jean-Philippe Lang | 287bcacd81 | |
Jean-Philippe Lang | d6b718b43c | |
Jean-Philippe Lang | b8ffc995c1 | |
Jean-Philippe Lang | f52253c2ef | |
Jean-Philippe Lang | a11aec0d4c | |
Toshi MARUYAMA | a9f44b323f | |
Toshi MARUYAMA | fbeba062cf | |
Toshi MARUYAMA | 37a07789d1 | |
Jean-Philippe Lang | 742ab6f6b3 | |
Jean-Philippe Lang | aacaa9da8e | |
Jean-Philippe Lang | 9ebcb1e734 | |
Jean-Philippe Lang | 36c35080b2 | |
Jean-Philippe Lang | beed3c5746 | |
Jean-Philippe Lang | fa2b39bf73 | |
Jean-Philippe Lang | c295f67329 | |
Jean-Philippe Lang | 624b0071ad | |
Jean-Philippe Lang | 8ea273d427 | |
Toshi MARUYAMA | d05042fbb5 | |
Toshi MARUYAMA | 00ff3361d6 | |
Toshi MARUYAMA | 4f937b42ac | |
Toshi MARUYAMA | 64a72f2ea2 | |
Toshi MARUYAMA | 260d6d2c78 | |
Toshi MARUYAMA | 5b1073095a | |
Toshi MARUYAMA | 6d33524888 | |
Toshi MARUYAMA | b8a93cd445 | |
Toshi MARUYAMA | 3c0114f020 | |
Toshi MARUYAMA | cfe0ff70fb | |
Toshi MARUYAMA | afab881b01 | |
Toshi MARUYAMA | f9d083a94b | |
Toshi MARUYAMA | 5ae3de8c06 | |
Jean-Philippe Lang | 931c198de4 | |
Jean-Philippe Lang | bf3d9f0851 | |
Jean-Philippe Lang | a74ffeff07 | |
Toshi MARUYAMA | 1cf93f6e32 | |
Toshi MARUYAMA | cd13c5e6d1 | |
Toshi MARUYAMA | 9bae5a7695 | |
Toshi MARUYAMA | 496abc8ee3 | |
Toshi MARUYAMA | caed8500a3 | |
Jean-Philippe Lang | 80aa5f4058 | |
Jean-Philippe Lang | 6ff53935a2 | |
Jean-Philippe Lang | b5a605c808 | |
Jean-Philippe Lang | 4710cd8e6f | |
Jean-Philippe Lang | a435ca7278 | |
Jean-Philippe Lang | c462a8790e | |
Toshi MARUYAMA | 42a2dbd4ab | |
Jean-Philippe Lang | 6628610ed6 | |
Jean-Philippe Lang | ddef51599b | |
Jean-Philippe Lang | 84100235e0 | |
Jean-Philippe Lang | 000ad0ac6f |
38
.travis.yml
38
.travis.yml
|
@ -1,38 +0,0 @@
|
|||
# Redmine runs tests on own continuous integration server.
|
||||
# http://www.redmine.org/projects/redmine/wiki/Continuous_integration
|
||||
# You can also run tests on your environment.
|
||||
language: ruby
|
||||
rvm:
|
||||
- 1.8.7
|
||||
- 1.9.3
|
||||
- 2.0
|
||||
- 2.1
|
||||
- jruby
|
||||
matrix:
|
||||
allow_failures:
|
||||
# SCM tests fail randomly due to IO.popen().
|
||||
# https://github.com/jruby/jruby/issues/779
|
||||
- rvm: jruby
|
||||
env:
|
||||
- "TEST_SUITE=units DATABASE_ADAPTER=postgresql"
|
||||
- "TEST_SUITE=functionals DATABASE_ADAPTER=postgresql"
|
||||
- "TEST_SUITE=integration DATABASE_ADAPTER=postgresql"
|
||||
- "TEST_SUITE=units DATABASE_ADAPTER=mysql"
|
||||
- "TEST_SUITE=functionals DATABASE_ADAPTER=mysql"
|
||||
- "TEST_SUITE=integration DATABASE_ADAPTER=mysql"
|
||||
- "TEST_SUITE=units DATABASE_ADAPTER=sqlite3"
|
||||
- "TEST_SUITE=functionals DATABASE_ADAPTER=sqlite3"
|
||||
- "TEST_SUITE=integration DATABASE_ADAPTER=sqlite3"
|
||||
before_install:
|
||||
- "sudo apt-get update -qq"
|
||||
- "sudo apt-get --no-install-recommends install bzr cvs git mercurial subversion"
|
||||
script:
|
||||
- "SCMS=bazaar,cvs,subversion,git,mercurial,filesystem"
|
||||
- "export SCMS"
|
||||
- "git --version"
|
||||
- "bundle install"
|
||||
- "RUN_ON_NOT_OFFICIAL='' RUBY_VER=1.9 BRANCH=trunk bundle exec rake config/database.yml"
|
||||
- "bundle install"
|
||||
- "JRUBY_OPTS=-J-Xmx1024m bundle exec rake ci"
|
||||
notifications:
|
||||
email: false
|
16
Gemfile
16
Gemfile
|
@ -1,13 +1,10 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
gem "rails", "3.2.18"
|
||||
gem "rake", "~> 10.1.1"
|
||||
gem "rails", "3.2.17"
|
||||
gem "jquery-rails", "~> 2.0.2"
|
||||
gem "coderay", "~> 1.1.0"
|
||||
gem "fastercsv", "~> 1.5.0", :platforms => [:mri_18, :mingw_18, :jruby]
|
||||
gem "builder", "3.0.0"
|
||||
gem "request_store"
|
||||
gem "mime-types"
|
||||
|
||||
# Optional gem for LDAP authentication
|
||||
group :ldap do
|
||||
|
@ -20,20 +17,14 @@ group :openid do
|
|||
gem "rack-openid"
|
||||
end
|
||||
|
||||
# Optional gem for exporting the gantt to a PNG file, not supported with jruby
|
||||
platforms :mri, :mingw do
|
||||
# Optional gem for exporting the gantt to a PNG file, not supported with jruby
|
||||
group :rmagick do
|
||||
# RMagick 2 supports ruby 1.9
|
||||
# RMagick 1 would be fine for ruby 1.8 but Bundler does not support
|
||||
# different requirements for the same gem on different platforms
|
||||
gem "rmagick", ">= 2.0.0"
|
||||
end
|
||||
|
||||
# Optional Markdown support, not for JRuby
|
||||
group :markdown do
|
||||
# TODO: upgrade to redcarpet 3.x when ruby1.8 support is dropped
|
||||
gem "redcarpet", "~> 2.3.0"
|
||||
end
|
||||
end
|
||||
|
||||
platforms :jruby do
|
||||
|
@ -86,10 +77,11 @@ end
|
|||
|
||||
group :test do
|
||||
gem "shoulda", "~> 3.3.2"
|
||||
gem "mocha", "~> 1.0.0", :require => 'mocha/api'
|
||||
gem "mocha", ">= 0.14", :require => 'mocha/api'
|
||||
if RUBY_VERSION >= '1.9.3'
|
||||
gem "capybara", "~> 2.1.0"
|
||||
gem "selenium-webdriver"
|
||||
gem "database_cleaner"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ class AccountController < ApplicationController
|
|||
def login
|
||||
if request.get?
|
||||
if User.current.logged?
|
||||
redirect_back_or_default home_url, :referer => true
|
||||
redirect_to home_url
|
||||
end
|
||||
else
|
||||
authenticate_user
|
||||
|
|
|
@ -44,7 +44,6 @@ class ApplicationController < ActionController::Base
|
|||
unless api_request?
|
||||
super
|
||||
cookies.delete(autologin_cookie_name)
|
||||
self.logged_user = nil
|
||||
render_error :status => 422, :message => "Invalid form authenticity token."
|
||||
end
|
||||
end
|
||||
|
@ -120,7 +119,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
|
||||
else
|
||||
# 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)
|
||||
|
@ -202,7 +201,7 @@ class ApplicationController < ActionController::Base
|
|||
if User.current.logged?
|
||||
lang = find_language(User.current.language)
|
||||
end
|
||||
if lang.nil? && !Setting.force_default_language_for_anonymous? && request.env['HTTP_ACCEPT_LANGUAGE']
|
||||
if lang.nil? && request.env['HTTP_ACCEPT_LANGUAGE']
|
||||
accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first
|
||||
if !accept_lang.blank?
|
||||
accept_lang = accept_lang.downcase
|
||||
|
@ -374,13 +373,13 @@ class ApplicationController < ActionController::Base
|
|||
url
|
||||
end
|
||||
|
||||
def redirect_back_or_default(default, options={})
|
||||
def redirect_back_or_default(default)
|
||||
back_url = params[:back_url].to_s
|
||||
if back_url.present?
|
||||
begin
|
||||
uri = URI.parse(back_url)
|
||||
# do not redirect user to another host or to the login or register page
|
||||
if ((uri.relative? && back_url.match(%r{\A/(\w.*)?\z})) || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
|
||||
if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
|
||||
redirect_to(back_url)
|
||||
return
|
||||
end
|
||||
|
@ -388,9 +387,6 @@ class ApplicationController < ActionController::Base
|
|||
logger.warn("Could not redirect to invalid URL #{back_url}")
|
||||
# redirect to default
|
||||
end
|
||||
elsif options[:referer]
|
||||
redirect_to_referer_or default
|
||||
return
|
||||
end
|
||||
redirect_to default
|
||||
false
|
||||
|
@ -558,7 +554,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)} ? ERB::Util.url_encode(name) : name
|
||||
request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
|
||||
end
|
||||
|
||||
def api_request?
|
||||
|
|
|
@ -55,10 +55,12 @@ class ContextMenusController < ApplicationController
|
|||
|
||||
@options_by_custom_field = {}
|
||||
if @can[:edit]
|
||||
custom_fields = @issues.map(&:editable_custom_fields).reduce(:&).reject(&:multiple?)
|
||||
custom_fields = @issues.map(&:available_custom_fields).reduce(:&).select do |f|
|
||||
%w(bool list user version).include?(f.field_format) && !f.multiple?
|
||||
end
|
||||
custom_fields.each do |field|
|
||||
values = field.possible_values_options(@projects)
|
||||
if values.present?
|
||||
if values.any?
|
||||
@options_by_custom_field[field] = values
|
||||
end
|
||||
end
|
||||
|
|
|
@ -27,6 +27,7 @@ class CustomFieldsController < ApplicationController
|
|||
respond_to do |format|
|
||||
format.html {
|
||||
@custom_fields_by_type = CustomField.all.group_by {|f| f.class.name }
|
||||
@tab = params[:tab] || 'IssueCustomField'
|
||||
}
|
||||
format.api {
|
||||
@custom_fields = CustomField.all
|
||||
|
@ -35,8 +36,6 @@ class CustomFieldsController < ApplicationController
|
|||
end
|
||||
|
||||
def new
|
||||
@custom_field.field_format = 'string' if @custom_field.field_format.blank?
|
||||
@custom_field.default_value = nil
|
||||
end
|
||||
|
||||
def create
|
||||
|
@ -76,7 +75,9 @@ class CustomFieldsController < ApplicationController
|
|||
def build_new_custom_field
|
||||
@custom_field = CustomField.new_subclass_instance(params[:type], params[:custom_field])
|
||||
if @custom_field.nil?
|
||||
render :action => 'select_type'
|
||||
render_404
|
||||
else
|
||||
@custom_field.default_value = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ class GroupsController < ApplicationController
|
|||
end
|
||||
|
||||
def add_users
|
||||
@users = User.where(:id => (params[:user_id] || params[:user_ids])).all
|
||||
@users = User.find_all_by_id(params[:user_id] || params[:user_ids])
|
||||
@group.users << @users if request.post?
|
||||
respond_to do |format|
|
||||
format.html { redirect_to edit_group_path(@group, :tab => 'users') }
|
||||
|
|
|
@ -29,7 +29,7 @@ class IssueStatusesController < ApplicationController
|
|||
render :action => "index", :layout => false if request.xhr?
|
||||
}
|
||||
format.api {
|
||||
@issue_statuses = IssueStatus.order('position').all
|
||||
@issue_statuses = IssueStatus.all(:order => 'position')
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -62,14 +62,10 @@ class IssuesController < ApplicationController
|
|||
case params[:format]
|
||||
when 'csv', 'pdf'
|
||||
@limit = Setting.issues_export_limit.to_i
|
||||
if params[:columns] == 'all'
|
||||
@query.column_names = @query.available_inline_columns.map(&:name)
|
||||
end
|
||||
when 'atom'
|
||||
@limit = Setting.feeds_limit.to_i
|
||||
when 'xml', 'json'
|
||||
@offset, @limit = api_offset_and_limit
|
||||
@query.column_names = %w(author)
|
||||
else
|
||||
@limit = per_page_option
|
||||
end
|
||||
|
@ -301,7 +297,7 @@ class IssuesController < ApplicationController
|
|||
else
|
||||
@saved_issues = @issues
|
||||
@unsaved_issues = unsaved_issues
|
||||
@issues = Issue.visible.where(:id => @unsaved_issues.map(&:id)).all
|
||||
@issues = Issue.visible.find_all_by_id(@unsaved_issues.map(&:id))
|
||||
bulk_edit
|
||||
render :action => 'bulk_edit'
|
||||
end
|
||||
|
@ -314,15 +310,14 @@ class IssuesController < ApplicationController
|
|||
when 'destroy'
|
||||
# nothing to do
|
||||
when 'nullify'
|
||||
TimeEntry.where(['issue_id IN (?)', @issues]).update_all('issue_id = NULL')
|
||||
TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
|
||||
when 'reassign'
|
||||
reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
|
||||
if reassign_to.nil?
|
||||
flash.now[:error] = l(:error_issue_not_found_in_project)
|
||||
return
|
||||
else
|
||||
TimeEntry.where(['issue_id IN (?)', @issues]).
|
||||
update_all("issue_id = #{reassign_to.id}")
|
||||
TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
|
||||
end
|
||||
else
|
||||
# display the destroy form if it's a user request
|
||||
|
@ -432,10 +427,7 @@ class IssuesController < ApplicationController
|
|||
|
||||
@priorities = IssuePriority.active
|
||||
@allowed_statuses = @issue.new_statuses_allowed_to(User.current, @issue.new_record?)
|
||||
@available_watchers = @issue.watcher_users
|
||||
if @issue.project.users.count <= 20
|
||||
@available_watchers = (@available_watchers + @issue.project.users.sort).uniq
|
||||
end
|
||||
@available_watchers = (@issue.project.users.sort + @issue.watcher_users).uniq
|
||||
end
|
||||
|
||||
def check_for_default_issue_status
|
||||
|
|
|
@ -66,7 +66,7 @@ class JournalsController < ApplicationController
|
|||
text = @issue.description
|
||||
end
|
||||
# Replaces pre blocks with [...]
|
||||
text = text.to_s.strip.gsub(%r{<pre>(.*?)</pre>}m, '[...]')
|
||||
text = text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</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
|
||||
|
|
|
@ -28,11 +28,12 @@ class MembersController < ApplicationController
|
|||
@member_count = @project.member_principals.count
|
||||
@member_pages = Paginator.new @member_count, @limit, params['page']
|
||||
@offset ||= @member_pages.offset
|
||||
@members = @project.member_principals.
|
||||
order("#{Member.table_name}.id").
|
||||
limit(@limit).
|
||||
offset(@offset).
|
||||
all
|
||||
@members = @project.member_principals.all(
|
||||
:order => "#{Member.table_name}.id",
|
||||
:limit => @limit,
|
||||
:offset => @offset
|
||||
)
|
||||
|
||||
respond_to do |format|
|
||||
format.html { head 406 }
|
||||
format.api
|
||||
|
|
|
@ -113,7 +113,7 @@ class MessagesController < ApplicationController
|
|||
@subject = "RE: #{@subject}" unless @subject.starts_with?('RE:')
|
||||
|
||||
@content = "#{ll(Setting.default_language, :text_user_wrote, @message.author)}\n> "
|
||||
@content << @message.content.to_s.strip.gsub(%r{<pre>(.*?)</pre>}m, '[...]').gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n"
|
||||
@content << @message.content.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n"
|
||||
end
|
||||
|
||||
def preview
|
||||
|
@ -126,14 +126,14 @@ class MessagesController < ApplicationController
|
|||
private
|
||||
def find_message
|
||||
return unless find_board
|
||||
@message = @board.messages.includes(:parent).find(params[:id])
|
||||
@message = @board.messages.find(params[:id], :include => :parent)
|
||||
@topic = @message.root
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def find_board
|
||||
@board = Board.includes(:project).find(params[:board_id])
|
||||
@board = Board.find(params[:board_id], :include => :project)
|
||||
@project = @board.project
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
|
|
|
@ -139,7 +139,7 @@ class MyController < ApplicationController
|
|||
@blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup
|
||||
@block_options = []
|
||||
BLOCKS.each do |k, v|
|
||||
unless @blocks.values.flatten.include?(k)
|
||||
unless %w(top left right).detect {|f| (@blocks[f] ||= []).include?(k)}
|
||||
@block_options << [l("my.blocks.#{v}", :default => [v, v.to_s.humanize]), k.dasherize]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -42,11 +42,11 @@ class NewsController < ApplicationController
|
|||
@news_count = scope.count
|
||||
@news_pages = Paginator.new @news_count, @limit, params['page']
|
||||
@offset ||= @news_pages.offset
|
||||
@newss = scope.includes([:author, :project]).
|
||||
order("#{News.table_name}.created_on DESC").
|
||||
limit(@limit).
|
||||
offset(@offset).
|
||||
all
|
||||
@newss = scope.all(:include => [:author, :project],
|
||||
:order => "#{News.table_name}.created_on DESC",
|
||||
:offset => @offset,
|
||||
:limit => @limit)
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
@news = News.new # for adding news inline
|
||||
|
|
|
@ -151,8 +151,8 @@ class ProjectsController < ApplicationController
|
|||
|
||||
cond = @project.project_condition(Setting.display_subprojects_issues?)
|
||||
|
||||
@open_issues_by_tracker = Issue.visible.open.where(cond).group(:tracker).count
|
||||
@total_issues_by_tracker = Issue.visible.where(cond).group(:tracker).count
|
||||
@open_issues_by_tracker = Issue.visible.open.where(cond).count(:group => :tracker)
|
||||
@total_issues_by_tracker = Issue.visible.where(cond).count(:group => :tracker)
|
||||
|
||||
if User.current.allowed_to?(:view_time_entries, @project)
|
||||
@total_hours = TimeEntry.visible.where(cond).sum(:hours).to_f
|
||||
|
|
|
@ -31,13 +31,11 @@ class QueriesController < ApplicationController
|
|||
else
|
||||
@limit = per_page_option
|
||||
end
|
||||
|
||||
@query_count = IssueQuery.visible.count
|
||||
@query_pages = Paginator.new @query_count, @limit, params['page']
|
||||
@queries = IssueQuery.visible.
|
||||
order("#{Query.table_name}.name").
|
||||
limit(@limit).
|
||||
offset(@offset).
|
||||
all
|
||||
@queries = IssueQuery.visible.all(:limit => @limit, :offset => @offset, :order => "#{Query.table_name}.name")
|
||||
|
||||
respond_to do |format|
|
||||
format.api
|
||||
end
|
||||
|
|
|
@ -94,7 +94,7 @@ class RepositoriesController < ApplicationController
|
|||
@committers = @repository.committers
|
||||
@users = @project.users
|
||||
additional_user_ids = @committers.collect(&:last).collect(&:to_i) - @users.collect(&:id)
|
||||
@users += User.where(:id => additional_user_ids).all unless additional_user_ids.empty?
|
||||
@users += User.find_all_by_id(additional_user_ids) unless additional_user_ids.empty?
|
||||
@users.compact!
|
||||
@users.sort!
|
||||
if request.post? && params[:committers].is_a?(Hash)
|
||||
|
@ -411,7 +411,7 @@ class RepositoriesController < ApplicationController
|
|||
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
|
||||
|
||||
# Remove email address in usernames
|
||||
# Remove email adress in usernames
|
||||
fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
|
||||
|
||||
graph = SVG::Graph::BarHorizontal.new(
|
||||
|
|
|
@ -232,7 +232,7 @@ private
|
|||
end
|
||||
|
||||
def find_time_entries
|
||||
@time_entries = TimeEntry.where(:id => params[:id] || params[:ids]).all
|
||||
@time_entries = TimeEntry.find_all_by_id(params[:id] || params[:ids])
|
||||
raise ActiveRecord::RecordNotFound if @time_entries.empty?
|
||||
@projects = @time_entries.collect(&:project).compact.uniq
|
||||
@project = @projects.first if @projects.size == 1
|
||||
|
|
|
@ -90,9 +90,11 @@ class UsersController < ApplicationController
|
|||
@user.admin = params[:user][:admin] || false
|
||||
@user.login = params[:user][:login]
|
||||
@user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id
|
||||
@user.pref.attributes = params[:pref]
|
||||
|
||||
if @user.save
|
||||
@user.pref.attributes = params[:pref]
|
||||
@user.pref.save
|
||||
|
||||
Mailer.account_information(@user, @user.password).deliver if params[:send_information]
|
||||
|
||||
respond_to do |format|
|
||||
|
|
|
@ -30,7 +30,6 @@ class WatchersController < ApplicationController
|
|||
accept_api_auth :create, :destroy
|
||||
|
||||
def new
|
||||
@users = users_for_new_watcher
|
||||
end
|
||||
|
||||
def create
|
||||
|
@ -45,7 +44,7 @@ class WatchersController < ApplicationController
|
|||
end
|
||||
respond_to do |format|
|
||||
format.html { redirect_to_referer_or {render :text => 'Watcher added.', :layout => true}}
|
||||
format.js { @users = users_for_new_watcher }
|
||||
format.js
|
||||
format.api { render_api_ok }
|
||||
end
|
||||
end
|
||||
|
@ -53,10 +52,7 @@ class WatchersController < ApplicationController
|
|||
def append
|
||||
if params[:watcher].is_a?(Hash)
|
||||
user_ids = params[:watcher][:user_ids] || [params[:watcher][:user_id]]
|
||||
@users = User.active.where(:id => user_ids).all
|
||||
end
|
||||
if @users.blank?
|
||||
render :nothing => true
|
||||
@users = User.active.find_all_by_id(user_ids)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -70,7 +66,10 @@ class WatchersController < ApplicationController
|
|||
end
|
||||
|
||||
def autocomplete_for_user
|
||||
@users = users_for_new_watcher
|
||||
@users = User.active.sorted.like(params[:q]).limit(100).all
|
||||
if @watched
|
||||
@users -= @watched.watcher_users
|
||||
end
|
||||
render :layout => false
|
||||
end
|
||||
|
||||
|
@ -92,14 +91,8 @@ class WatchersController < ApplicationController
|
|||
def find_watchables
|
||||
klass = Object.const_get(params[:object_type].camelcase) rescue nil
|
||||
if klass && klass.respond_to?('watched_by')
|
||||
@watchables = klass.where(:id => Array.wrap(params[:object_id])).all
|
||||
raise Unauthorized if @watchables.any? {|w|
|
||||
if w.respond_to?(:visible?)
|
||||
!w.visible?
|
||||
elsif w.respond_to?(:project) && w.project
|
||||
!w.project.visible?
|
||||
end
|
||||
}
|
||||
@watchables = klass.find_all_by_id(Array.wrap(params[:object_id]))
|
||||
raise Unauthorized if @watchables.any? {|w| w.respond_to?(:visible?) && !w.visible?}
|
||||
end
|
||||
render_404 unless @watchables.present?
|
||||
end
|
||||
|
@ -113,17 +106,4 @@ class WatchersController < ApplicationController
|
|||
format.js { render :partial => 'set_watcher', :locals => {:user => user, :watched => watchables} }
|
||||
end
|
||||
end
|
||||
|
||||
def users_for_new_watcher
|
||||
users = []
|
||||
if params[:q].blank? && @project.present?
|
||||
users = @project.users.sorted
|
||||
else
|
||||
users = User.active.sorted.like(params[:q]).limit(100)
|
||||
end
|
||||
if @watched
|
||||
users -= @watched.watcher_users
|
||||
end
|
||||
users
|
||||
end
|
||||
end
|
||||
|
|
|
@ -277,19 +277,14 @@ class WikiController < ApplicationController
|
|||
|
||||
# Export wiki to a single pdf or html file
|
||||
def export
|
||||
@pages = @wiki.pages.
|
||||
order('title').
|
||||
includes([:content, {:attachments => :author}]).
|
||||
all
|
||||
@pages = @wiki.pages.all(:order => 'title', :include => [:content, {:attachments => :author}])
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
export = render_to_string :action => 'export_multiple', :layout => false
|
||||
send_data(export, :type => 'text/html', :filename => "wiki.html")
|
||||
}
|
||||
format.pdf {
|
||||
send_data(wiki_pages_to_pdf(@pages, @project),
|
||||
:type => 'application/pdf',
|
||||
:filename => "#{@project.identifier}.pdf")
|
||||
send_data(wiki_pages_to_pdf(@pages, @project), :type => 'application/pdf', :filename => "#{@project.identifier}.pdf")
|
||||
}
|
||||
end
|
||||
end
|
||||
|
@ -356,10 +351,6 @@ private
|
|||
end
|
||||
|
||||
def load_pages_for_index
|
||||
@pages = @wiki.pages.with_updated_on.
|
||||
reorder("#{WikiPage.table_name}.title").
|
||||
includes(:wiki => :project).
|
||||
includes(:parent).
|
||||
all
|
||||
@pages = @wiki.pages.with_updated_on.reorder("#{WikiPage.table_name}.title").includes(:wiki => :project).includes(:parent).all
|
||||
end
|
||||
end
|
||||
|
|
|
@ -18,30 +18,39 @@
|
|||
class WorkflowsController < ApplicationController
|
||||
layout 'admin'
|
||||
|
||||
before_filter :require_admin
|
||||
before_filter :require_admin, :find_roles, :find_trackers
|
||||
|
||||
def index
|
||||
@workflow_counts = WorkflowTransition.count_by_tracker_and_role
|
||||
end
|
||||
|
||||
def edit
|
||||
find_trackers_roles_and_statuses_for_edit
|
||||
@role = Role.find_by_id(params[:role_id]) if params[:role_id]
|
||||
@tracker = Tracker.find_by_id(params[:tracker_id]) if params[:tracker_id]
|
||||
|
||||
if request.post? && @roles && @trackers && params[:transitions]
|
||||
transitions = params[:transitions].deep_dup
|
||||
transitions.each do |old_status_id, transitions_by_new_status|
|
||||
transitions_by_new_status.each do |new_status_id, transition_by_rule|
|
||||
transition_by_rule.reject! {|rule, transition| transition == 'no_change'}
|
||||
end
|
||||
if request.post?
|
||||
WorkflowTransition.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id])
|
||||
(params[:issue_status] || []).each { |status_id, transitions|
|
||||
transitions.each { |new_status_id, options|
|
||||
author = options.is_a?(Array) && options.include?('author') && !options.include?('always')
|
||||
assignee = options.is_a?(Array) && options.include?('assignee') && !options.include?('always')
|
||||
WorkflowTransition.create(:role_id => @role.id, :tracker_id => @tracker.id, :old_status_id => status_id, :new_status_id => new_status_id, :author => author, :assignee => assignee)
|
||||
}
|
||||
}
|
||||
if @role.save
|
||||
redirect_to workflows_edit_path(:role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only])
|
||||
return
|
||||
end
|
||||
WorkflowTransition.replace_transitions(@trackers, @roles, transitions)
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to_referer_or workflows_edit_path
|
||||
return
|
||||
end
|
||||
|
||||
if @trackers && @roles && @statuses.any?
|
||||
workflows = WorkflowTransition.where(:role_id => @roles.map(&:id), :tracker_id => @trackers.map(&:id))
|
||||
@used_statuses_only = (params[:used_statuses_only] == '0' ? false : true)
|
||||
if @tracker && @used_statuses_only && @tracker.issue_statuses.any?
|
||||
@statuses = @tracker.issue_statuses
|
||||
end
|
||||
@statuses ||= IssueStatus.sorted.all
|
||||
|
||||
if @tracker && @role && @statuses.any?
|
||||
workflows = WorkflowTransition.where(:role_id => @role.id, :tracker_id => @tracker.id).all
|
||||
@workflows = {}
|
||||
@workflows['always'] = workflows.select {|w| !w.author && !w.assignee}
|
||||
@workflows['author'] = workflows.select {|w| w.author}
|
||||
|
@ -50,30 +59,35 @@ class WorkflowsController < ApplicationController
|
|||
end
|
||||
|
||||
def permissions
|
||||
find_trackers_roles_and_statuses_for_edit
|
||||
@role = Role.find_by_id(params[:role_id]) if params[:role_id]
|
||||
@tracker = Tracker.find_by_id(params[:tracker_id]) if params[:tracker_id]
|
||||
|
||||
if request.post? && @roles && @trackers && params[:permissions]
|
||||
permissions = params[:permissions].deep_dup
|
||||
permissions.each { |field, rule_by_status_id|
|
||||
rule_by_status_id.reject! {|status_id, rule| rule == 'no_change'}
|
||||
}
|
||||
WorkflowPermission.replace_permissions(@trackers, @roles, permissions)
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to_referer_or workflows_permissions_path
|
||||
if request.post? && @role && @tracker
|
||||
WorkflowPermission.replace_permissions(@tracker, @role, params[:permissions] || {})
|
||||
redirect_to workflows_permissions_path(:role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only])
|
||||
return
|
||||
end
|
||||
|
||||
if @roles && @trackers
|
||||
@fields = (Tracker::CORE_FIELDS_ALL - @trackers.map(&:disabled_core_fields).reduce(:&)).map {|field| [field, l("field_"+field.sub(/_id$/, ''))]}
|
||||
@custom_fields = @trackers.map(&:custom_fields).flatten.uniq.sort
|
||||
@permissions = WorkflowPermission.rules_by_status_id(@trackers, @roles)
|
||||
@used_statuses_only = (params[:used_statuses_only] == '0' ? false : true)
|
||||
if @tracker && @used_statuses_only && @tracker.issue_statuses.any?
|
||||
@statuses = @tracker.issue_statuses
|
||||
end
|
||||
@statuses ||= IssueStatus.sorted.all
|
||||
|
||||
if @role && @tracker
|
||||
@fields = (Tracker::CORE_FIELDS_ALL - @tracker.disabled_core_fields).map {|field| [field, l("field_"+field.sub(/_id$/, ''))]}
|
||||
@custom_fields = @tracker.custom_fields
|
||||
|
||||
@permissions = WorkflowPermission.where(:tracker_id => @tracker.id, :role_id => @role.id).all.inject({}) do |h, w|
|
||||
h[w.old_status_id] ||= {}
|
||||
h[w.old_status_id][w.field_name] = w.rule
|
||||
h
|
||||
end
|
||||
@statuses.each {|status| @permissions[status.id] ||= {}}
|
||||
end
|
||||
end
|
||||
|
||||
def copy
|
||||
@roles = Role.sorted
|
||||
@trackers = Tracker.sorted
|
||||
|
||||
if params[:source_tracker_id].blank? || params[:source_tracker_id] == 'any'
|
||||
@source_tracker = nil
|
||||
|
@ -85,10 +99,10 @@ class WorkflowsController < ApplicationController
|
|||
else
|
||||
@source_role = Role.find_by_id(params[:source_role_id].to_i)
|
||||
end
|
||||
@target_trackers = params[:target_tracker_ids].blank? ?
|
||||
nil : Tracker.where(:id => params[:target_tracker_ids]).all
|
||||
@target_roles = params[:target_role_ids].blank? ?
|
||||
nil : Role.where(:id => params[:target_role_ids]).all
|
||||
|
||||
@target_trackers = params[:target_tracker_ids].blank? ? nil : Tracker.find_all_by_id(params[:target_tracker_ids])
|
||||
@target_roles = params[:target_role_ids].blank? ? nil : Role.find_all_by_id(params[:target_role_ids])
|
||||
|
||||
if request.post?
|
||||
if params[:source_tracker_id].blank? || params[:source_role_id].blank? || (@source_tracker.nil? && @source_role.nil?)
|
||||
flash.now[:error] = l(:error_workflow_copy_source)
|
||||
|
@ -104,37 +118,11 @@ class WorkflowsController < ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def find_trackers_roles_and_statuses_for_edit
|
||||
find_roles
|
||||
find_trackers
|
||||
find_statuses
|
||||
end
|
||||
|
||||
def find_roles
|
||||
ids = Array.wrap(params[:role_id])
|
||||
if ids == ['all']
|
||||
@roles = Role.sorted.all
|
||||
elsif ids.present?
|
||||
@roles = Role.where(:id => ids).all
|
||||
end
|
||||
@roles = nil if @roles.blank?
|
||||
@roles = Role.sorted.all
|
||||
end
|
||||
|
||||
def find_trackers
|
||||
ids = Array.wrap(params[:tracker_id])
|
||||
if ids == ['all']
|
||||
@trackers = Tracker.sorted.all
|
||||
elsif ids.present?
|
||||
@trackers = Tracker.where(:id => ids).all
|
||||
end
|
||||
@trackers = nil if @trackers.blank?
|
||||
end
|
||||
|
||||
def find_statuses
|
||||
@used_statuses_only = (params[:used_statuses_only] == '0' ? false : true)
|
||||
if @trackers && @used_statuses_only
|
||||
@statuses = @trackers.map(&:issue_statuses).flatten.uniq.sort.presence
|
||||
end
|
||||
@statuses ||= IssueStatus.sorted.all
|
||||
@trackers = Tracker.sorted.all
|
||||
end
|
||||
end
|
||||
|
|
|
@ -24,12 +24,4 @@ module AdminHelper
|
|||
[l(:project_status_closed), '5'],
|
||||
[l(:project_status_archived), '9']], selected.to_s)
|
||||
end
|
||||
|
||||
def plugin_data_for_updates(plugins)
|
||||
data = {"v" => Redmine::VERSION.to_s, "p" => {}}
|
||||
plugins.each do |plugin|
|
||||
data["p"].merge! plugin.id => {"v" => plugin.version, "n" => plugin.name, "a" => plugin.author}
|
||||
end
|
||||
data
|
||||
end
|
||||
end
|
||||
|
|
|
@ -72,16 +72,15 @@ module ApplicationHelper
|
|||
subject = nil
|
||||
text = options[:tracker] == false ? "##{issue.id}" : "#{issue.tracker} ##{issue.id}"
|
||||
if options[:subject] == false
|
||||
title = issue.subject.truncate(60)
|
||||
title = truncate(issue.subject, :length => 60)
|
||||
else
|
||||
subject = issue.subject
|
||||
if truncate_length = options[:truncate]
|
||||
subject = subject.truncate(truncate_length)
|
||||
if options[:truncate]
|
||||
subject = truncate(subject, :length => options[:truncate])
|
||||
end
|
||||
end
|
||||
only_path = options[:only_path].nil? ? true : options[:only_path]
|
||||
s = link_to(text, issue_path(issue, :only_path => only_path),
|
||||
:class => issue.css_classes, :title => title)
|
||||
s = link_to text, issue_path(issue, :only_path => only_path), :class => issue.css_classes, :title => title
|
||||
s << h(": #{subject}") if subject
|
||||
s = h("#{issue.project} - ") + s if options[:project]
|
||||
s
|
||||
|
@ -118,7 +117,7 @@ module ApplicationHelper
|
|||
# Generates a link to a message
|
||||
def link_to_message(message, options={}, html_options = nil)
|
||||
link_to(
|
||||
message.subject.truncate(60),
|
||||
truncate(message.subject, :length => 60),
|
||||
board_message_path(message.board_id, message.parent_id || message.id, {
|
||||
:r => (message.parent_id && message.id),
|
||||
:anchor => (message.parent_id ? "message-#{message.id}" : nil)
|
||||
|
@ -157,50 +156,6 @@ module ApplicationHelper
|
|||
end
|
||||
end
|
||||
|
||||
# Helper that formats object for html or text rendering
|
||||
def format_object(object, html=true, &block)
|
||||
if block_given?
|
||||
object = yield object
|
||||
end
|
||||
case object.class.name
|
||||
when 'Array'
|
||||
object.map {|o| format_object(o, html)}.join(', ').html_safe
|
||||
when 'Time'
|
||||
format_time(object)
|
||||
when 'Date'
|
||||
format_date(object)
|
||||
when 'Fixnum'
|
||||
object.to_s
|
||||
when 'Float'
|
||||
sprintf "%.2f", object
|
||||
when 'User'
|
||||
html ? link_to_user(object) : object.to_s
|
||||
when 'Project'
|
||||
html ? link_to_project(object) : object.to_s
|
||||
when 'Version'
|
||||
html ? link_to(object.name, version_path(object)) : object.to_s
|
||||
when 'TrueClass'
|
||||
l(:general_text_Yes)
|
||||
when 'FalseClass'
|
||||
l(:general_text_No)
|
||||
when 'Issue'
|
||||
object.visible? && html ? link_to_issue(object) : "##{object.id}"
|
||||
when 'CustomValue', 'CustomFieldValue'
|
||||
if object.custom_field
|
||||
f = object.custom_field.format.formatted_custom_value(self, object, html)
|
||||
if f.nil? || f.is_a?(String)
|
||||
f
|
||||
else
|
||||
format_object(f, html, &block)
|
||||
end
|
||||
else
|
||||
object.value.to_s
|
||||
end
|
||||
else
|
||||
html ? h(object) : object.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def wiki_page_path(page, options={})
|
||||
url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options))
|
||||
end
|
||||
|
@ -227,7 +182,7 @@ module ApplicationHelper
|
|||
end
|
||||
|
||||
def format_activity_title(text)
|
||||
h(truncate_single_line_raw(text, 100))
|
||||
h(truncate_single_line(text, :length => 100))
|
||||
end
|
||||
|
||||
def format_activity_day(date)
|
||||
|
@ -235,7 +190,7 @@ module ApplicationHelper
|
|||
end
|
||||
|
||||
def format_activity_description(text)
|
||||
h(text.to_s.truncate(120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
|
||||
h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
|
||||
).gsub(/[\r\n]+/, "<br />").html_safe
|
||||
end
|
||||
|
||||
|
@ -312,13 +267,9 @@ module ApplicationHelper
|
|||
end
|
||||
|
||||
# Renders tabs and their content
|
||||
def render_tabs(tabs, selected=params[:tab])
|
||||
def render_tabs(tabs)
|
||||
if tabs.any?
|
||||
unless tabs.detect {|tab| tab[:name] == selected}
|
||||
selected = nil
|
||||
end
|
||||
selected ||= tabs.first[:name]
|
||||
render :partial => 'common/tabs', :locals => {:tabs => tabs, :selected_tab => selected}
|
||||
render :partial => 'common/tabs', :locals => {:tabs => tabs}
|
||||
else
|
||||
content_tag 'p', l(:label_no_data), :class => "nodata"
|
||||
end
|
||||
|
@ -404,17 +355,9 @@ module ApplicationHelper
|
|||
|
||||
# Truncates and returns the string as a single line
|
||||
def truncate_single_line(string, *args)
|
||||
ActiveSupport::Deprecation.warn(
|
||||
"ApplicationHelper#truncate_single_line is deprecated and will be removed in Rails 4 poring")
|
||||
# Rails 4 ActionView::Helpers::TextHelper#truncate escapes.
|
||||
# So, result is broken.
|
||||
truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
|
||||
end
|
||||
|
||||
def truncate_single_line_raw(string, length)
|
||||
string.truncate(length).gsub(%r{[\r\n]+}m, ' ')
|
||||
end
|
||||
|
||||
# Truncates at line break after 250 characters or options[:length]
|
||||
def truncate_lines(string, options={})
|
||||
length = options[:length] || 250
|
||||
|
@ -765,30 +708,21 @@ module ApplicationHelper
|
|||
repository = project.repository
|
||||
end
|
||||
# project.changesets.visible raises an SQL error because of a double join on repositories
|
||||
if repository &&
|
||||
(changeset = Changeset.visible.
|
||||
find_by_repository_id_and_revision(repository.id, identifier))
|
||||
link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"),
|
||||
{:only_path => only_path, :controller => 'repositories',
|
||||
:action => 'revision', :id => project,
|
||||
:repository_id => repository.identifier_param,
|
||||
:rev => changeset.revision},
|
||||
:class => 'changeset',
|
||||
:title => truncate_single_line_raw(changeset.comments, 100))
|
||||
if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
|
||||
link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision},
|
||||
:class => 'changeset',
|
||||
:title => truncate_single_line(changeset.comments, :length => 100))
|
||||
end
|
||||
end
|
||||
elsif sep == '#'
|
||||
oid = identifier.to_i
|
||||
case prefix
|
||||
when nil
|
||||
if oid.to_s == identifier &&
|
||||
issue = Issue.visible.includes(:status).find_by_id(oid)
|
||||
if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status)
|
||||
anchor = comment_id ? "note-#{comment_id}" : nil
|
||||
link = link_to(h("##{oid}#{comment_suffix}"),
|
||||
{:only_path => only_path, :controller => 'issues',
|
||||
:action => 'show', :id => oid, :anchor => anchor},
|
||||
:class => issue.css_classes,
|
||||
:title => "#{issue.subject.truncate(100)} (#{issue.status.name})")
|
||||
link = link_to(h("##{oid}#{comment_suffix}"), {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
|
||||
:class => issue.css_classes,
|
||||
:title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
|
||||
end
|
||||
when 'document'
|
||||
if document = Document.visible.find_by_id(oid)
|
||||
|
@ -801,7 +735,7 @@ module ApplicationHelper
|
|||
:class => 'version'
|
||||
end
|
||||
when 'message'
|
||||
if message = Message.visible.includes(:parent).find_by_id(oid)
|
||||
if message = Message.visible.find_by_id(oid, :include => :parent)
|
||||
link = link_to_message(message, {:only_path => only_path}, :class => 'message')
|
||||
end
|
||||
when 'forum'
|
||||
|
@ -822,7 +756,6 @@ module ApplicationHelper
|
|||
elsif sep == ':'
|
||||
# removes the double quotes if any
|
||||
name = identifier.gsub(%r{^"(.*)"$}, "\\1")
|
||||
name = CGI.unescapeHTML(name)
|
||||
case prefix
|
||||
when 'document'
|
||||
if project && document = project.documents.visible.find_by_title(name)
|
||||
|
@ -857,7 +790,7 @@ module ApplicationHelper
|
|||
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)
|
||||
:title => truncate_single_line(changeset.comments, :length => 100)
|
||||
end
|
||||
else
|
||||
if repository && User.current.allowed_to?(:browse_repository, project)
|
||||
|
@ -873,8 +806,7 @@ module ApplicationHelper
|
|||
repo_prefix = nil
|
||||
end
|
||||
when 'attachment'
|
||||
attachments = options[:attachments] || []
|
||||
attachments += obj.attachments if obj.respond_to?(:attachments)
|
||||
attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
|
||||
if attachments && attachment = Attachment.latest_attach(attachments, name)
|
||||
link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment')
|
||||
end
|
||||
|
@ -982,20 +914,19 @@ module ApplicationHelper
|
|||
end
|
||||
end
|
||||
|
||||
TOC_RE = /<p>\{\{((<|<)|(>|>))?toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
|
||||
TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
|
||||
|
||||
# Renders the TOC with given headings
|
||||
def replace_toc(text, headings)
|
||||
text.gsub!(TOC_RE) do
|
||||
left_align, right_align = $2, $3
|
||||
# Keep only the 4 first levels
|
||||
headings = headings.select{|level, anchor, item| level <= 4}
|
||||
if headings.empty?
|
||||
''
|
||||
else
|
||||
div_class = 'toc'
|
||||
div_class << ' right' if right_align
|
||||
div_class << ' left' if left_align
|
||||
div_class << ' right' if $1 == '>'
|
||||
div_class << ' left' if $1 == '<'
|
||||
out = "<ul class=\"#{div_class}\"><li>"
|
||||
root = headings.map(&:first).min
|
||||
current = root
|
||||
|
@ -1296,21 +1227,7 @@ module ApplicationHelper
|
|||
end
|
||||
|
||||
def favicon
|
||||
"<link rel='shortcut icon' href='#{favicon_path}' />".html_safe
|
||||
end
|
||||
|
||||
# Returns the path to the favicon
|
||||
def favicon_path
|
||||
icon = (current_theme && current_theme.favicon?) ? current_theme.favicon_path : '/favicon.ico'
|
||||
image_path(icon)
|
||||
end
|
||||
|
||||
# Returns the full URL to the favicon
|
||||
def favicon_url
|
||||
# TODO: use #image_url introduced in Rails4
|
||||
path = favicon_path
|
||||
base = url_for(:controller => 'welcome', :action => 'index', :only_path => false)
|
||||
base.sub(%r{/+$},'') + '/' + path.sub(%r{^/+},'')
|
||||
"<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
|
||||
end
|
||||
|
||||
def robot_exclusion_tag
|
||||
|
@ -1332,7 +1249,7 @@ module ApplicationHelper
|
|||
def api_meta(options)
|
||||
if params[:nometa].present? || request.headers['X-Redmine-Nometa']
|
||||
# compatibility mode for activeresource clients that raise
|
||||
# an error when deserializing an array with attributes
|
||||
# an error when unserializing an array with attributes
|
||||
nil
|
||||
else
|
||||
options
|
||||
|
|
|
@ -40,48 +40,55 @@ module CustomFieldsHelper
|
|||
:label => DocumentCategory::OptionName}
|
||||
]
|
||||
|
||||
def render_custom_fields_tabs(types)
|
||||
tabs = CUSTOM_FIELDS_TABS.select {|h| types.include?(h[:name]) }
|
||||
render_tabs tabs
|
||||
end
|
||||
|
||||
def custom_field_type_options
|
||||
CUSTOM_FIELDS_TABS.map {|h| [l(h[:label]), h[:name]]}
|
||||
end
|
||||
|
||||
def render_custom_field_format_partial(form, custom_field)
|
||||
partial = custom_field.format.form_partial
|
||||
if partial
|
||||
render :partial => custom_field.format.form_partial, :locals => {:f => form, :custom_field => custom_field}
|
||||
end
|
||||
end
|
||||
|
||||
def custom_field_tag_name(prefix, custom_field)
|
||||
name = "#{prefix}[custom_field_values][#{custom_field.id}]"
|
||||
name << "[]" if custom_field.multiple?
|
||||
name
|
||||
end
|
||||
|
||||
def custom_field_tag_id(prefix, custom_field)
|
||||
"#{prefix}_custom_field_values_#{custom_field.id}"
|
||||
def custom_fields_tabs
|
||||
CUSTOM_FIELDS_TABS
|
||||
end
|
||||
|
||||
# Return custom field html tag corresponding to its format
|
||||
def custom_field_tag(prefix, custom_value)
|
||||
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"
|
||||
def custom_field_tag(name, custom_value)
|
||||
custom_field = custom_value.custom_field
|
||||
field_name = "#{name}[custom_field_values][#{custom_field.id}]"
|
||||
field_name << "[]" if custom_field.multiple?
|
||||
field_id = "#{name}_custom_field_values_#{custom_field.id}"
|
||||
|
||||
tag_options = {:id => field_id, :class => "#{custom_field.field_format}_cf"}
|
||||
|
||||
field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
|
||||
case field_format.try(:edit_as)
|
||||
when "date"
|
||||
text_field_tag(field_name, custom_value.value, tag_options.merge(:size => 10)) +
|
||||
calendar_for(field_id)
|
||||
when "text"
|
||||
text_area_tag(field_name, custom_value.value, tag_options.merge(:rows => 3))
|
||||
when "bool"
|
||||
hidden_field_tag(field_name, '0') + check_box_tag(field_name, '1', custom_value.true?, tag_options)
|
||||
when "list"
|
||||
blank_option = ''.html_safe
|
||||
unless custom_field.multiple?
|
||||
if custom_field.is_required?
|
||||
unless custom_field.default_value.present?
|
||||
blank_option = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---", :value => '')
|
||||
end
|
||||
else
|
||||
blank_option = content_tag('option')
|
||||
end
|
||||
end
|
||||
s = select_tag(field_name, blank_option + options_for_select(custom_field.possible_values_options(custom_value.customized), custom_value.value),
|
||||
tag_options.merge(:multiple => custom_field.multiple?))
|
||||
if custom_field.multiple?
|
||||
s << hidden_field_tag(field_name, '')
|
||||
end
|
||||
s
|
||||
else
|
||||
text_field_tag(field_name, custom_value.value, tag_options)
|
||||
end
|
||||
end
|
||||
|
||||
# Return custom field label tag
|
||||
def custom_field_label_tag(name, custom_value, options={})
|
||||
required = options[:required] || custom_value.custom_field.is_required?
|
||||
title = custom_value.custom_field.description.presence
|
||||
content = content_tag 'span', custom_value.custom_field.name, :title => title
|
||||
|
||||
content_tag "label", content +
|
||||
content_tag "label", h(custom_value.custom_field.name) +
|
||||
(required ? " <span class=\"required\">*</span>".html_safe : ""),
|
||||
:for => "#{name}_custom_field_values_#{custom_value.custom_field.id}"
|
||||
end
|
||||
|
@ -91,30 +98,65 @@ module CustomFieldsHelper
|
|||
custom_field_label_tag(name, custom_value, options) + custom_field_tag(name, custom_value)
|
||||
end
|
||||
|
||||
# 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_tag_id(prefix, custom_field),
|
||||
custom_field_tag_name(prefix, custom_field),
|
||||
custom_field,
|
||||
objects,
|
||||
value,
|
||||
:class => "#{custom_field.field_format}_cf"
|
||||
def custom_field_tag_for_bulk_edit(name, custom_field, projects=nil, value='')
|
||||
field_name = "#{name}[custom_field_values][#{custom_field.id}]"
|
||||
field_name << "[]" if custom_field.multiple?
|
||||
field_id = "#{name}_custom_field_values_#{custom_field.id}"
|
||||
|
||||
tag_options = {:id => field_id, :class => "#{custom_field.field_format}_cf"}
|
||||
|
||||
unset_tag = ''
|
||||
unless custom_field.is_required?
|
||||
unset_tag = content_tag('label',
|
||||
check_box_tag(field_name, '__none__', (value == '__none__'), :id => nil, :data => {:disables => "##{field_id}"}) + l(:button_clear),
|
||||
:class => 'inline'
|
||||
)
|
||||
end
|
||||
|
||||
field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
|
||||
case field_format.try(:edit_as)
|
||||
when "date"
|
||||
text_field_tag(field_name, value, tag_options.merge(:size => 10)) +
|
||||
calendar_for(field_id) +
|
||||
unset_tag
|
||||
when "text"
|
||||
text_area_tag(field_name, value, tag_options.merge(:rows => 3)) +
|
||||
'<br />'.html_safe +
|
||||
unset_tag
|
||||
when "bool"
|
||||
select_tag(field_name, options_for_select([[l(:label_no_change_option), ''],
|
||||
[l(:general_text_yes), '1'],
|
||||
[l(:general_text_no), '0']], value), tag_options)
|
||||
when "list"
|
||||
options = []
|
||||
options << [l(:label_no_change_option), ''] unless custom_field.multiple?
|
||||
options << [l(:label_none), '__none__'] unless custom_field.is_required?
|
||||
options += custom_field.possible_values_options(projects)
|
||||
select_tag(field_name, options_for_select(options, value), tag_options.merge(:multiple => custom_field.multiple?))
|
||||
else
|
||||
text_field_tag(field_name, value, tag_options) +
|
||||
unset_tag
|
||||
end
|
||||
end
|
||||
|
||||
# Return a string used to display a custom value
|
||||
def show_value(custom_value, html=true)
|
||||
format_object(custom_value, html)
|
||||
def show_value(custom_value)
|
||||
return "" unless custom_value
|
||||
format_value(custom_value.value, custom_value.custom_field.field_format)
|
||||
end
|
||||
|
||||
# Return a string used to display a custom value
|
||||
def format_value(value, custom_field)
|
||||
format_object(custom_field.format.formatted_value(self, custom_field, value, false), false)
|
||||
def format_value(value, field_format)
|
||||
if value.is_a?(Array)
|
||||
value.collect {|v| format_value(v, field_format)}.compact.sort.join(', ')
|
||||
else
|
||||
Redmine::CustomFieldFormat.format_value(value, field_format)
|
||||
end
|
||||
end
|
||||
|
||||
# Return an array of custom field formats which can be used in select_tag
|
||||
def custom_field_formats_for_select(custom_field)
|
||||
Redmine::FieldFormat.as_select(custom_field.class.customized_class.name)
|
||||
Redmine::CustomFieldFormat.as_select(custom_field.class.customized_class.name)
|
||||
end
|
||||
|
||||
# Renders the custom_values in api views
|
||||
|
@ -137,8 +179,4 @@ module CustomFieldsHelper
|
|||
end
|
||||
end unless custom_values.empty?
|
||||
end
|
||||
|
||||
def edit_tag_style_tag(form)
|
||||
form.select :edit_tag_style, [[l(:label_drop_down_list), ''], [l(:label_checkboxes), 'check_box']], :label => :label_display
|
||||
end
|
||||
end
|
||||
|
|
|
@ -171,9 +171,8 @@ module IssuesHelper
|
|||
s = "<tr>\n"
|
||||
n = 0
|
||||
ordered_values.compact.each do |value|
|
||||
css = "cf_#{value.custom_field.id}"
|
||||
s << "</tr>\n<tr>\n" if n > 0 && (n % 2) == 0
|
||||
s << "\t<th class=\"#{css}\">#{ h(value.custom_field.name) }:</th><td class=\"#{css}\">#{ h(show_value(value)) }</td>\n"
|
||||
s << "\t<th>#{ h(value.custom_field.name) }:</th><td>#{ simple_format_without_paragraph(h(show_value(value))) }</td>\n"
|
||||
n += 1
|
||||
end
|
||||
s << "</tr>\n"
|
||||
|
@ -240,7 +239,7 @@ module IssuesHelper
|
|||
end
|
||||
end
|
||||
issue.visible_custom_field_values(user).each do |value|
|
||||
items << "#{value.custom_field.name}: #{show_value(value, false)}"
|
||||
items << "#{value.custom_field.name}: #{show_value(value)}"
|
||||
end
|
||||
items
|
||||
end
|
||||
|
@ -325,8 +324,8 @@ module IssuesHelper
|
|||
if custom_field
|
||||
multiple = custom_field.multiple?
|
||||
label = custom_field.name
|
||||
value = format_value(detail.value, custom_field) if detail.value
|
||||
old_value = format_value(detail.old_value, custom_field) if detail.old_value
|
||||
value = format_value(detail.value, custom_field.field_format) if detail.value
|
||||
old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value
|
||||
end
|
||||
when 'attachment'
|
||||
label = l(:label_attachment)
|
||||
|
@ -340,8 +339,7 @@ module IssuesHelper
|
|||
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]))
|
||||
end
|
||||
relation_type = IssueRelation::TYPES[detail.prop_key]
|
||||
label = l(relation_type[:name]) if relation_type
|
||||
label = l(detail.prop_key.to_sym)
|
||||
end
|
||||
call_hook(:helper_issues_show_detail_after_setting,
|
||||
{:detail => detail, :label => label, :value => value, :old_value => old_value })
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
module ProjectsHelper
|
||||
def link_to_version(version, options = {})
|
||||
return '' unless version && version.is_a?(Version)
|
||||
link_to_if version.visible?, format_version_name(version), version_path(version), options
|
||||
link_to_if version.visible?, format_version_name(version), { :controller => 'versions', :action => 'show', :id => version }, options
|
||||
end
|
||||
|
||||
def project_settings_tabs
|
||||
|
@ -51,21 +51,6 @@ module ProjectsHelper
|
|||
content_tag('select', options.html_safe, :name => 'project[parent_id]', :id => 'project_parent_id')
|
||||
end
|
||||
|
||||
def render_project_action_links
|
||||
links = []
|
||||
if User.current.allowed_to?(:add_project, nil, :global => true)
|
||||
links << link_to(l(:label_project_new), new_project_path, :class => 'icon icon-add')
|
||||
end
|
||||
if User.current.allowed_to?(:view_issues, nil, :global => true)
|
||||
links << link_to(l(:label_issue_view_all), issues_path)
|
||||
end
|
||||
if User.current.allowed_to?(:view_time_entries, nil, :global => true)
|
||||
links << link_to(l(:label_overall_spent_time), time_entries_path)
|
||||
end
|
||||
links << link_to(l(:label_overall_activity), activity_path)
|
||||
links.join(" | ").html_safe
|
||||
end
|
||||
|
||||
# Renders the projects index
|
||||
def render_project_hierarchy(projects)
|
||||
render_project_nested_lists(projects) do |project|
|
||||
|
|
|
@ -18,8 +18,6 @@
|
|||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
module QueriesHelper
|
||||
include ApplicationHelper
|
||||
|
||||
def filters_options_for_select(query)
|
||||
options_for_select(filters_options(query))
|
||||
end
|
||||
|
@ -58,7 +56,7 @@ module QueriesHelper
|
|||
def available_block_columns_tags(query)
|
||||
tags = ''.html_safe
|
||||
query.available_block_columns.each do |column|
|
||||
tags << content_tag('label', check_box_tag('c[]', column.name.to_s, query.has_column?(column), :id => nil) + " #{column.caption}", :class => 'inline')
|
||||
tags << content_tag('label', check_box_tag('c[]', column.name.to_s, query.has_column?(column)) + " #{column.caption}", :class => 'inline')
|
||||
end
|
||||
tags
|
||||
end
|
||||
|
@ -83,7 +81,7 @@ module QueriesHelper
|
|||
end
|
||||
|
||||
def column_content(column, issue)
|
||||
value = column.value_object(issue)
|
||||
value = column.value(issue)
|
||||
if value.is_a?(Array)
|
||||
value.collect {|v| column_value(column, issue, v)}.compact.join(', ').html_safe
|
||||
else
|
||||
|
@ -92,27 +90,53 @@ module QueriesHelper
|
|||
end
|
||||
|
||||
def column_value(column, issue, value)
|
||||
case column.name
|
||||
when :id
|
||||
link_to value, issue_path(issue)
|
||||
when :subject
|
||||
link_to value, issue_path(issue)
|
||||
when :description
|
||||
issue.description? ? content_tag('div', textilizable(issue, :description), :class => "wiki") : ''
|
||||
when :done_ratio
|
||||
progress_bar(value, :width => '80px')
|
||||
when :relations
|
||||
case value.class.name
|
||||
when 'String'
|
||||
if column.name == :subject
|
||||
link_to(h(value), :controller => 'issues', :action => 'show', :id => issue)
|
||||
elsif column.name == :description
|
||||
issue.description? ? content_tag('div', textilizable(issue, :description), :class => "wiki") : ''
|
||||
else
|
||||
h(value)
|
||||
end
|
||||
when 'Time'
|
||||
format_time(value)
|
||||
when 'Date'
|
||||
format_date(value)
|
||||
when 'Fixnum'
|
||||
if column.name == :id
|
||||
link_to value, issue_path(issue)
|
||||
elsif column.name == :done_ratio
|
||||
progress_bar(value, :width => '80px')
|
||||
else
|
||||
value.to_s
|
||||
end
|
||||
when 'Float'
|
||||
sprintf "%.2f", value
|
||||
when 'User'
|
||||
link_to_user value
|
||||
when 'Project'
|
||||
link_to_project value
|
||||
when 'Version'
|
||||
link_to(h(value), :controller => 'versions', :action => 'show', :id => value)
|
||||
when 'TrueClass'
|
||||
l(:general_text_Yes)
|
||||
when 'FalseClass'
|
||||
l(:general_text_No)
|
||||
when 'Issue'
|
||||
value.visible? ? link_to_issue(value) : "##{value.id}"
|
||||
when 'IssueRelation'
|
||||
other = value.other_issue(issue)
|
||||
content_tag('span',
|
||||
(l(value.label_for(issue)) + " " + link_to_issue(other, :subject => false, :tracker => false)).html_safe,
|
||||
:class => value.css_classes_for(issue))
|
||||
else
|
||||
format_object(value)
|
||||
h(value)
|
||||
end
|
||||
end
|
||||
|
||||
def csv_content(column, issue)
|
||||
value = column.value_object(issue)
|
||||
value = column.value(issue)
|
||||
if value.is_a?(Array)
|
||||
value.collect {|v| csv_value(column, issue, v)}.compact.join(', ')
|
||||
else
|
||||
|
@ -121,16 +145,22 @@ module QueriesHelper
|
|||
end
|
||||
|
||||
def csv_value(column, issue, value)
|
||||
format_object(value, false) do |value|
|
||||
case value.class.name
|
||||
when 'Float'
|
||||
sprintf("%.2f", value).gsub('.', l(:general_csv_decimal_separator))
|
||||
when 'IssueRelation'
|
||||
other = value.other_issue(issue)
|
||||
l(value.label_for(issue)) + " ##{other.id}"
|
||||
else
|
||||
value
|
||||
end
|
||||
case value.class.name
|
||||
when 'Time'
|
||||
format_time(value)
|
||||
when 'Date'
|
||||
format_date(value)
|
||||
when 'Float'
|
||||
sprintf("%.2f", value).gsub('.', l(:general_csv_decimal_separator))
|
||||
when 'IssueRelation'
|
||||
other = value.other_issue(issue)
|
||||
l(value.label_for(issue)) + " ##{other.id}"
|
||||
when 'TrueClass'
|
||||
l(:general_text_Yes)
|
||||
when 'FalseClass'
|
||||
l(:general_text_No)
|
||||
else
|
||||
value.to_s
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ module RepositoriesHelper
|
|||
end
|
||||
|
||||
def render_changeset_changes
|
||||
changes = @changeset.filechanges.limit(1000).reorder('path').collect do |change|
|
||||
changes = @changeset.filechanges.limit(1000).reorder('path').all.collect do |change|
|
||||
case change.action
|
||||
when 'A'
|
||||
# Detects moved/copied files
|
||||
|
|
|
@ -79,29 +79,17 @@ module SettingsHelper
|
|||
|
||||
def setting_label(setting, options={})
|
||||
label = options.delete(:label)
|
||||
label != false ? label_tag("settings_#{setting}", l(label || "setting_#{setting}"), options[:label_options]).html_safe : ''
|
||||
label != false ? label_tag("settings_#{setting}", l(label || "setting_#{setting}")).html_safe : ''
|
||||
end
|
||||
|
||||
# Renders a notification field for a Redmine::Notifiable option
|
||||
def notification_field(notifiable)
|
||||
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.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)
|
||||
return content_tag(:label,
|
||||
check_box_tag('settings[notified_events][]',
|
||||
notifiable.name,
|
||||
Setting.notified_events.include?(notifiable.name), :id => nil).html_safe +
|
||||
l_or_humanize(notifiable.name, :prefix => 'label_').html_safe,
|
||||
:class => notifiable.parent.present? ? "parent" : '').html_safe
|
||||
end
|
||||
|
||||
def cross_project_subtasks_options
|
||||
|
|
|
@ -96,10 +96,8 @@ module TimelogHelper
|
|||
else
|
||||
obj
|
||||
end
|
||||
elsif cf = criteria_options[:custom_field]
|
||||
format_value(value, cf)
|
||||
else
|
||||
value.to_s
|
||||
format_value(value, criteria_options[:format])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
module UsersHelper
|
||||
def users_status_options_for_select(selected)
|
||||
user_count_by_status = User.group('status').count.to_hash
|
||||
user_count_by_status = User.count(:group => 'status').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'],
|
||||
|
|
|
@ -27,7 +27,6 @@ module WatchersHelper
|
|||
def watcher_link(objects, user)
|
||||
return '' unless user && user.logged?
|
||||
objects = Array.wrap(objects)
|
||||
return '' unless objects.any?
|
||||
|
||||
watched = Watcher.any_watched?(objects, user)
|
||||
css = [watcher_css(objects), watched ? 'icon icon-fav' : 'icon icon-fav-off'].join(' ')
|
||||
|
|
|
@ -18,78 +18,24 @@
|
|||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
module WorkflowsHelper
|
||||
def options_for_workflow_select(name, objects, selected, options={})
|
||||
option_tags = ''.html_safe
|
||||
multiple = false
|
||||
if selected
|
||||
if selected.size == objects.size
|
||||
selected = 'all'
|
||||
else
|
||||
selected = selected.map(&:id)
|
||||
if selected.size > 1
|
||||
multiple = true
|
||||
end
|
||||
end
|
||||
else
|
||||
selected = objects.first.try(:id)
|
||||
end
|
||||
all_tag_options = {:value => 'all', :selected => (selected == 'all')}
|
||||
if multiple
|
||||
all_tag_options.merge!(:style => "display:none;")
|
||||
end
|
||||
option_tags << content_tag('option', l(:label_all), all_tag_options)
|
||||
option_tags << options_from_collection_for_select(objects, "id", "name", selected)
|
||||
select_tag name, option_tags, {:multiple => multiple}.merge(options)
|
||||
end
|
||||
|
||||
def field_required?(field)
|
||||
field.is_a?(CustomField) ? field.is_required? : %w(project_id tracker_id subject priority_id is_private).include?(field)
|
||||
end
|
||||
|
||||
def field_permission_tag(permissions, status, field, roles)
|
||||
def field_permission_tag(permissions, status, field, role)
|
||||
name = field.is_a?(CustomField) ? field.id.to_s : field
|
||||
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"]
|
||||
selected = 'no_change'
|
||||
else
|
||||
selected = perm.first
|
||||
end
|
||||
end
|
||||
|
||||
hidden = field.is_a?(CustomField) &&
|
||||
!field.visible? &&
|
||||
!roles.detect {|role| role.custom_fields.to_a.include?(field)}
|
||||
selected = permissions[status.id][name]
|
||||
|
||||
hidden = field.is_a?(CustomField) && !field.visible? && !role.custom_fields.to_a.include?(field)
|
||||
if hidden
|
||||
options[0][0] = l(:label_hidden)
|
||||
selected = ''
|
||||
html_options[:disabled] = true
|
||||
end
|
||||
|
||||
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_id == old_status.id && w.new_status_id == new_status.id}.size
|
||||
|
||||
tag_name = "transitions[#{ old_status.id }][#{new_status.id}][#{name}]"
|
||||
if w == 0 || w == @roles.size * @trackers.size
|
||||
|
||||
hidden_field_tag(tag_name, "0") +
|
||||
check_box_tag(tag_name, "1", w != 0,
|
||||
:class => "old-status-#{old_status.id} new-status-#{new_status.id}")
|
||||
else
|
||||
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")
|
||||
end
|
||||
select_tag("permissions[#{name}][#{status.id}]", options_for_select(options, selected), html_options)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -77,7 +77,7 @@ class AuthSource < ActiveRecord::Base
|
|||
|
||||
# Try to authenticate a user not yet registered against available sources
|
||||
def self.authenticate(login, password)
|
||||
AuthSource.where(:onthefly_register => true).each do |source|
|
||||
AuthSource.where(:onthefly_register => true).all.each do |source|
|
||||
begin
|
||||
logger.debug "Authenticating '#{login}' against '#{source.name}'" if logger && logger.debug?
|
||||
attrs = source.authenticate(login, password)
|
||||
|
|
|
@ -60,10 +60,10 @@ class Board < ActiveRecord::Base
|
|||
# Updates topics_count, messages_count and last_message_id attributes for +board_id+
|
||||
def self.reset_counters!(board_id)
|
||||
board_id = board_id.to_i
|
||||
where(["id = ?", board_id]).
|
||||
update_all("topics_count = (SELECT COUNT(*) FROM #{Message.table_name} WHERE board_id=#{board_id} AND parent_id IS NULL)," +
|
||||
update_all("topics_count = (SELECT COUNT(*) FROM #{Message.table_name} WHERE board_id=#{board_id} AND parent_id IS NULL)," +
|
||||
" messages_count = (SELECT COUNT(*) FROM #{Message.table_name} WHERE board_id=#{board_id})," +
|
||||
" last_message_id = (SELECT MAX(id) FROM #{Message.table_name} WHERE board_id=#{board_id})")
|
||||
" last_message_id = (SELECT MAX(id) FROM #{Message.table_name} WHERE board_id=#{board_id})",
|
||||
["id = ?", board_id])
|
||||
end
|
||||
|
||||
def self.board_tree(boards, parent_id=nil, level=0)
|
||||
|
|
|
@ -198,7 +198,7 @@ class Changeset < ActiveRecord::Base
|
|||
# Finds an issue that can be referenced by the commit message
|
||||
def find_referenced_issue_by_id(id)
|
||||
return nil if id.blank?
|
||||
issue = Issue.includes(:project).where(:id => id.to_i).first
|
||||
issue = Issue.find_by_id(id.to_i, :include => :project)
|
||||
if Setting.commit_cross_project_ref?
|
||||
# all issues can be referenced/fixed
|
||||
elsif issue
|
||||
|
|
|
@ -22,18 +22,14 @@ class CustomField < ActiveRecord::Base
|
|||
has_and_belongs_to_many :roles, :join_table => "#{table_name_prefix}custom_fields_roles#{table_name_suffix}", :foreign_key => "custom_field_id"
|
||||
acts_as_list :scope => 'type = \'#{self.class}\''
|
||||
serialize :possible_values
|
||||
store :format_store
|
||||
|
||||
validates_presence_of :name, :field_format
|
||||
validates_uniqueness_of :name, :scope => :type
|
||||
validates_length_of :name, :maximum => 30
|
||||
validates_inclusion_of :field_format, :in => Proc.new { Redmine::FieldFormat.available_formats }
|
||||
validates_inclusion_of :field_format, :in => Proc.new { Redmine::CustomFieldFormat.available_formats }
|
||||
validate :validate_custom_field
|
||||
|
||||
before_validation :set_searchable
|
||||
before_save do |field|
|
||||
field.format.before_custom_field_save(field)
|
||||
end
|
||||
after_save :handle_multiplicity_change
|
||||
after_save do |field|
|
||||
if field.visible_changed? && field.visible
|
||||
|
@ -61,29 +57,23 @@ class CustomField < ActiveRecord::Base
|
|||
visible? || user.admin?
|
||||
end
|
||||
|
||||
def format
|
||||
@format ||= Redmine::FieldFormat.find(field_format)
|
||||
end
|
||||
|
||||
def field_format=(arg)
|
||||
# cannot change format of a saved custom field
|
||||
if new_record?
|
||||
@format = nil
|
||||
super
|
||||
end
|
||||
super if new_record?
|
||||
end
|
||||
|
||||
def set_searchable
|
||||
# make sure these fields are not searchable
|
||||
self.searchable = false unless format.class.searchable_supported
|
||||
self.searchable = false if %w(int float date bool).include?(field_format)
|
||||
# make sure only these fields can have multiple values
|
||||
self.multiple = false unless format.class.multiple_supported
|
||||
self.multiple = false unless %w(list user version).include?(field_format)
|
||||
true
|
||||
end
|
||||
|
||||
def validate_custom_field
|
||||
format.validate_custom_field(self).each do |attribute, message|
|
||||
errors.add attribute, message
|
||||
if self.field_format == "list"
|
||||
errors.add(:possible_values, :blank) if self.possible_values.nil? || self.possible_values.empty?
|
||||
errors.add(:possible_values, :invalid) unless self.possible_values.is_a? Array
|
||||
end
|
||||
|
||||
if regexp.present?
|
||||
|
@ -94,49 +84,80 @@ class CustomField < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
if default_value.present?
|
||||
validate_field_value(default_value).each do |message|
|
||||
errors.add :default_value, message
|
||||
end
|
||||
if default_value.present? && !valid_field_value?(default_value)
|
||||
errors.add(:default_value, :invalid)
|
||||
end
|
||||
end
|
||||
|
||||
def possible_custom_value_options(custom_value)
|
||||
format.possible_custom_value_options(custom_value)
|
||||
end
|
||||
|
||||
def possible_values_options(object=nil)
|
||||
if object.is_a?(Array)
|
||||
object.map {|o| format.possible_values_options(self, o)}.reduce(:&) || []
|
||||
def possible_values_options(obj=nil)
|
||||
case field_format
|
||||
when 'user', 'version'
|
||||
if obj.respond_to?(:project) && obj.project
|
||||
case field_format
|
||||
when 'user'
|
||||
obj.project.users.sort.collect {|u| [u.to_s, u.id.to_s]}
|
||||
when 'version'
|
||||
obj.project.shared_versions.sort.collect {|u| [u.to_s, u.id.to_s]}
|
||||
end
|
||||
elsif obj.is_a?(Array)
|
||||
obj.collect {|o| possible_values_options(o)}.reduce(:&)
|
||||
else
|
||||
[]
|
||||
end
|
||||
when 'bool'
|
||||
[[l(:general_text_Yes), '1'], [l(:general_text_No), '0']]
|
||||
else
|
||||
format.possible_values_options(self, object) || []
|
||||
possible_values || []
|
||||
end
|
||||
end
|
||||
|
||||
def possible_values
|
||||
values = read_attribute(:possible_values)
|
||||
if values.is_a?(Array)
|
||||
values.each do |value|
|
||||
value.force_encoding('UTF-8') if value.respond_to?(:force_encoding)
|
||||
end
|
||||
values
|
||||
def possible_values(obj=nil)
|
||||
case field_format
|
||||
when 'user', 'version'
|
||||
possible_values_options(obj).collect(&:last)
|
||||
when 'bool'
|
||||
['1', '0']
|
||||
else
|
||||
[]
|
||||
values = super()
|
||||
if values.is_a?(Array)
|
||||
values.each do |value|
|
||||
value.force_encoding('UTF-8') if value.respond_to?(:force_encoding)
|
||||
end
|
||||
values
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Makes possible_values accept a multiline string
|
||||
def possible_values=(arg)
|
||||
if arg.is_a?(Array)
|
||||
values = arg.compact.collect(&:strip).select {|v| !v.blank?}
|
||||
write_attribute(:possible_values, values)
|
||||
super(arg.compact.collect(&:strip).select {|v| !v.blank?})
|
||||
else
|
||||
self.possible_values = arg.to_s.split(/[\n\r]+/)
|
||||
end
|
||||
end
|
||||
|
||||
def cast_value(value)
|
||||
format.cast_value(self, value)
|
||||
casted = nil
|
||||
unless value.blank?
|
||||
case field_format
|
||||
when 'string', 'text', 'list'
|
||||
casted = value
|
||||
when 'date'
|
||||
casted = begin; value.to_date; rescue; nil end
|
||||
when 'bool'
|
||||
casted = (value == '1' ? true : false)
|
||||
when 'int'
|
||||
casted = value.to_i
|
||||
when 'float'
|
||||
casted = value.to_f
|
||||
when 'user', 'version'
|
||||
casted = (value.blank? ? nil : field_format.classify.constantize.find_by_id(value.to_i))
|
||||
end
|
||||
end
|
||||
casted
|
||||
end
|
||||
|
||||
def value_from_keyword(keyword, customized)
|
||||
|
@ -160,32 +181,96 @@ class CustomField < ActiveRecord::Base
|
|||
# Returns nil if the custom field can not be used for sorting.
|
||||
def order_statement
|
||||
return nil if multiple?
|
||||
format.order_statement(self)
|
||||
case field_format
|
||||
when 'string', 'text', 'list', 'date', 'bool'
|
||||
# COALESCE is here to make sure that blank and NULL values are sorted equally
|
||||
"COALESCE(#{join_alias}.value, '')"
|
||||
when 'int', 'float'
|
||||
# Make the database cast values into numeric
|
||||
# Postgresql will raise an error if a value can not be casted!
|
||||
# CustomValue validations should ensure that it doesn't occur
|
||||
"CAST(CASE #{join_alias}.value WHEN '' THEN '0' ELSE #{join_alias}.value END AS decimal(30,3))"
|
||||
when 'user', 'version'
|
||||
value_class.fields_for_order_statement(value_join_alias)
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a GROUP BY clause that can used to group by custom value
|
||||
# Returns nil if the custom field can not be used for grouping.
|
||||
def group_statement
|
||||
return nil if multiple?
|
||||
format.group_statement(self)
|
||||
case field_format
|
||||
when 'list', 'date', 'bool', 'int'
|
||||
order_statement
|
||||
when 'user', 'version'
|
||||
"COALESCE(#{join_alias}.value, '')"
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def join_for_order_statement
|
||||
format.join_for_order_statement(self)
|
||||
case field_format
|
||||
when 'user', 'version'
|
||||
"LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
|
||||
" ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
|
||||
" AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
|
||||
" AND #{join_alias}.custom_field_id = #{id}" +
|
||||
" AND (#{visibility_by_project_condition})" +
|
||||
" AND #{join_alias}.value <> ''" +
|
||||
" AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
|
||||
" WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
|
||||
" AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
|
||||
" AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)" +
|
||||
" LEFT OUTER JOIN #{value_class.table_name} #{value_join_alias}" +
|
||||
" ON CAST(CASE #{join_alias}.value WHEN '' THEN '0' ELSE #{join_alias}.value END AS decimal(30,0)) = #{value_join_alias}.id"
|
||||
when 'int', 'float'
|
||||
"LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
|
||||
" ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
|
||||
" AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
|
||||
" AND #{join_alias}.custom_field_id = #{id}" +
|
||||
" AND (#{visibility_by_project_condition})" +
|
||||
" AND #{join_alias}.value <> ''" +
|
||||
" AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
|
||||
" WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
|
||||
" AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
|
||||
" AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)"
|
||||
when 'string', 'text', 'list', 'date', 'bool'
|
||||
"LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
|
||||
" ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
|
||||
" AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
|
||||
" AND #{join_alias}.custom_field_id = #{id}" +
|
||||
" AND (#{visibility_by_project_condition})" +
|
||||
" AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
|
||||
" WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
|
||||
" AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
|
||||
" AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)"
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def visibility_by_project_condition(project_key=nil, user=User.current, id_column=nil)
|
||||
def join_alias
|
||||
"cf_#{id}"
|
||||
end
|
||||
|
||||
def value_join_alias
|
||||
join_alias + "_" + field_format
|
||||
end
|
||||
|
||||
def visibility_by_project_condition(project_key=nil, user=User.current)
|
||||
if visible? || user.admin?
|
||||
"1=1"
|
||||
elsif user.anonymous?
|
||||
"1=0"
|
||||
else
|
||||
project_key ||= "#{self.class.customized_class.table_name}.project_id"
|
||||
id_column ||= id
|
||||
"#{project_key} IN (SELECT DISTINCT m.project_id FROM #{Member.table_name} m" +
|
||||
" INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" +
|
||||
" INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" +
|
||||
" WHERE m.user_id = #{user.id} AND cfr.custom_field_id = #{id_column})"
|
||||
" WHERE m.user_id = #{user.id} AND cfr.custom_field_id = #{id})"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -208,7 +293,12 @@ class CustomField < ActiveRecord::Base
|
|||
|
||||
# Returns the class that values represent
|
||||
def value_class
|
||||
format.target_class if format.respond_to?(:target_class)
|
||||
case field_format
|
||||
when 'user', 'version'
|
||||
field_format.classify.constantize
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def self.customized_class
|
||||
|
@ -227,8 +317,7 @@ class CustomField < ActiveRecord::Base
|
|||
|
||||
# Returns the error messages for the given value
|
||||
# or an empty array if value is a valid value for the custom field
|
||||
def validate_custom_value(custom_value)
|
||||
value = custom_value.value
|
||||
def validate_field_value(value)
|
||||
errs = []
|
||||
if value.is_a?(Array)
|
||||
if !multiple?
|
||||
|
@ -237,20 +326,16 @@ class CustomField < ActiveRecord::Base
|
|||
if is_required? && value.detect(&:present?).nil?
|
||||
errs << ::I18n.t('activerecord.errors.messages.blank')
|
||||
end
|
||||
value.each {|v| errs += validate_field_value_format(v)}
|
||||
else
|
||||
if is_required? && value.blank?
|
||||
errs << ::I18n.t('activerecord.errors.messages.blank')
|
||||
end
|
||||
errs += validate_field_value_format(value)
|
||||
end
|
||||
errs += format.validate_custom_value(custom_value)
|
||||
errs
|
||||
end
|
||||
|
||||
# Returns the error messages for the default custom field value
|
||||
def validate_field_value(value)
|
||||
validate_custom_value(CustomValue.new(:custom_field => self, :value => value))
|
||||
end
|
||||
|
||||
# Returns true if value is a valid value for the custom field
|
||||
def valid_field_value?(value)
|
||||
validate_field_value(value).empty?
|
||||
|
@ -262,6 +347,29 @@ class CustomField < ActiveRecord::Base
|
|||
|
||||
protected
|
||||
|
||||
# Returns the error message for the given value regarding its format
|
||||
def validate_field_value_format(value)
|
||||
errs = []
|
||||
unless value.to_s == ''
|
||||
errs << ::I18n.t('activerecord.errors.messages.invalid') unless regexp.blank? or value =~ Regexp.new(regexp)
|
||||
errs << ::I18n.t('activerecord.errors.messages.too_short', :count => min_length) if min_length > 0 and value.length < min_length
|
||||
errs << ::I18n.t('activerecord.errors.messages.too_long', :count => max_length) if max_length > 0 and value.length > max_length
|
||||
|
||||
# Format specific validations
|
||||
case field_format
|
||||
when 'int'
|
||||
errs << ::I18n.t('activerecord.errors.messages.not_a_number') unless value =~ /^[+-]?\d+$/
|
||||
when 'float'
|
||||
begin; Kernel.Float(value); rescue; errs << ::I18n.t('activerecord.errors.messages.invalid') end
|
||||
when 'date'
|
||||
errs << ::I18n.t('activerecord.errors.messages.not_a_date') unless value =~ /^\d{4}-\d{2}-\d{2}$/ && begin; value.to_date; rescue; false end
|
||||
when 'list'
|
||||
errs << ::I18n.t('activerecord.errors.messages.inclusion') unless possible_values.include?(value)
|
||||
end
|
||||
end
|
||||
errs
|
||||
end
|
||||
|
||||
# Removes multiple values for the custom field after setting the multiple attribute to false
|
||||
# We kepp the value with the highest id for each customized object
|
||||
def handle_multiplicity_change
|
||||
|
@ -278,5 +386,3 @@ class CustomField < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
require_dependency 'redmine/field_format'
|
||||
|
|
|
@ -16,13 +16,7 @@
|
|||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
class CustomFieldValue
|
||||
attr_accessor :custom_field, :customized, :value, :value_was
|
||||
|
||||
def initialize(attributes={})
|
||||
attributes.each do |name, v|
|
||||
send "#{name}=", v
|
||||
end
|
||||
end
|
||||
attr_accessor :custom_field, :customized, :value
|
||||
|
||||
def custom_field_id
|
||||
custom_field.id
|
||||
|
@ -49,7 +43,7 @@ class CustomFieldValue
|
|||
end
|
||||
|
||||
def validate_value
|
||||
custom_field.validate_custom_value(self).each do |message|
|
||||
custom_field.validate_field_value(value).each do |message|
|
||||
customized.errors.add(:base, custom_field.name + ' ' + message)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
class EnabledModule < ActiveRecord::Base
|
||||
belongs_to :project
|
||||
acts_as_watchable
|
||||
|
||||
validates_presence_of :name
|
||||
validates_uniqueness_of :name, :scope => :project_id
|
||||
|
|
|
@ -60,7 +60,7 @@ class Enumeration < ActiveRecord::Base
|
|||
|
||||
def check_default
|
||||
if is_default? && is_default_changed?
|
||||
Enumeration.where({:type => type}).update_all({:is_default => false})
|
||||
Enumeration.update_all({:is_default => false}, {:type => type})
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -73,7 +73,7 @@ class Enumeration < ActiveRecord::Base
|
|||
self.objects_count != 0
|
||||
end
|
||||
|
||||
# Is this enumeration overriding a system level enumeration?
|
||||
# Is this enumeration overiding a system level enumeration?
|
||||
def is_override?
|
||||
!self.parent.nil?
|
||||
end
|
||||
|
@ -103,14 +103,8 @@ class Enumeration < ActiveRecord::Base
|
|||
subclasses
|
||||
end
|
||||
|
||||
# TODO: remove in Redmine 3.0
|
||||
def self.overridding_change?(new, previous)
|
||||
ActiveSupport::Deprecation.warn "Enumeration#overridding_change? is deprecated and will be removed in Redmine 3.0. Please use #overriding_change?."
|
||||
overriding_change?(new, previous)
|
||||
end
|
||||
|
||||
# Does the +new+ Hash override the previous Enumeration?
|
||||
def self.overriding_change?(new, previous)
|
||||
def self.overridding_change?(new, previous)
|
||||
if (same_active_state?(new['active'], previous.active)) && same_custom_values?(new,previous)
|
||||
return false
|
||||
else
|
||||
|
|
|
@ -18,10 +18,8 @@
|
|||
class Group < Principal
|
||||
include Redmine::SafeAttributes
|
||||
|
||||
has_and_belongs_to_many :users,
|
||||
:join_table => "#{table_name_prefix}groups_users#{table_name_suffix}",
|
||||
:after_add => :user_added,
|
||||
:after_remove => :user_removed
|
||||
has_and_belongs_to_many :users, :after_add => :user_added,
|
||||
:after_remove => :user_removed
|
||||
|
||||
acts_as_customizable
|
||||
|
||||
|
@ -68,6 +66,7 @@ class Group < Principal
|
|||
MemberRole.
|
||||
includes(:member).
|
||||
where("#{Member.table_name}.user_id = ? AND #{MemberRole.table_name}.inherited_from IN (?)", user.id, member.member_role_ids).
|
||||
all.
|
||||
each(&:destroy)
|
||||
end
|
||||
end
|
||||
|
@ -86,6 +85,6 @@ class Group < Principal
|
|||
def remove_references_before_destroy
|
||||
return if self.id.nil?
|
||||
|
||||
Issue.where(['assigned_to_id = ?', id]).update_all('assigned_to_id = NULL')
|
||||
Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -33,12 +33,12 @@ class Issue < ActiveRecord::Base
|
|||
has_many :visible_journals,
|
||||
:class_name => 'Journal',
|
||||
:as => :journalized,
|
||||
:conditions => Proc.new {
|
||||
:conditions => Proc.new {
|
||||
["(#{Journal.table_name}.private_notes = ? OR (#{Project.allowed_to_condition(User.current, :view_private_notes)}))", false]
|
||||
},
|
||||
:readonly => true
|
||||
|
||||
has_many :time_entries, :dependent => :destroy
|
||||
has_many :time_entries, :dependent => :delete_all
|
||||
has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
|
||||
|
||||
has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
|
||||
|
@ -94,7 +94,7 @@ class Issue < ActiveRecord::Base
|
|||
before_create :default_assign
|
||||
before_save :close_duplicates, :update_done_ratio_from_issue_status,
|
||||
:force_updated_on_change, :update_closed_on, :set_assigned_to_was
|
||||
after_save {|issue| issue.send :after_project_change if !issue.id_changed? && issue.project_id_changed?}
|
||||
after_save {|issue| issue.send :after_project_change if !issue.id_changed? && issue.project_id_changed?}
|
||||
after_save :reschedule_following_issues, :update_nested_set_attributes,
|
||||
:update_parent_attributes, :create_journal
|
||||
# Should be after_create but would be called before previous after_save callbacks
|
||||
|
@ -200,7 +200,7 @@ class Issue < ActiveRecord::Base
|
|||
|
||||
# Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
|
||||
def available_custom_fields
|
||||
(project && tracker) ? (project.all_issue_custom_fields & tracker.custom_fields) : []
|
||||
(project && tracker) ? (project.all_issue_custom_fields & tracker.custom_fields.all) : []
|
||||
end
|
||||
|
||||
def visible_custom_field_values(user=nil)
|
||||
|
@ -218,7 +218,7 @@ class Issue < ActiveRecord::Base
|
|||
self.status = issue.status
|
||||
self.author = User.current
|
||||
unless options[:attachments] == false
|
||||
self.attachments = issue.attachments.map do |attachement|
|
||||
self.attachments = issue.attachments.map do |attachement|
|
||||
attachement.copy(:container => self)
|
||||
end
|
||||
end
|
||||
|
@ -394,10 +394,10 @@ class Issue < ActiveRecord::Base
|
|||
:if => lambda {|issue, user| user.allowed_to?(:add_issue_notes, issue.project)}
|
||||
|
||||
safe_attributes 'private_notes',
|
||||
:if => lambda {|issue, user| !issue.new_record? && user.allowed_to?(:set_notes_private, issue.project)}
|
||||
:if => lambda {|issue, user| !issue.new_record? && user.allowed_to?(:set_notes_private, issue.project)}
|
||||
|
||||
safe_attributes 'watcher_user_ids',
|
||||
:if => lambda {|issue, user| issue.new_record? && user.allowed_to?(:add_issue_watchers, issue.project)}
|
||||
:if => lambda {|issue, user| issue.new_record? && user.allowed_to?(:add_issue_watchers, issue.project)}
|
||||
|
||||
safe_attributes 'is_private',
|
||||
:if => lambda {|issue, user|
|
||||
|
@ -483,11 +483,6 @@ class Issue < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
# Returns the custom fields that can be edited by the given user
|
||||
def editable_custom_fields(user=nil)
|
||||
editable_custom_field_values(user).map(&:custom_field).uniq
|
||||
end
|
||||
|
||||
# Returns the names of attributes that are read-only for user or the current user
|
||||
# For users with multiple roles, the read-only fields are the intersection of
|
||||
# read-only fields of each role
|
||||
|
@ -529,7 +524,7 @@ class Issue < ActiveRecord::Base
|
|||
return {} if roles.empty?
|
||||
|
||||
result = {}
|
||||
workflow_permissions = WorkflowPermission.where(:tracker_id => tracker_id, :old_status_id => status_id, :role_id => roles.map(&:id))
|
||||
workflow_permissions = WorkflowPermission.where(:tracker_id => tracker_id, :old_status_id => status_id, :role_id => roles.map(&:id)).all
|
||||
if workflow_permissions.any?
|
||||
workflow_rules = workflow_permissions.inject({}) do |h, wp|
|
||||
h[wp.field_name] ||= []
|
||||
|
@ -767,7 +762,7 @@ class Issue < ActiveRecord::Base
|
|||
initial_status ||= status
|
||||
|
||||
initial_assigned_to_id = assigned_to_id_changed? ? assigned_to_id_was : assigned_to_id
|
||||
assignee_transitions_allowed = initial_assigned_to_id.present? &&
|
||||
assignee_transitions_allowed = initial_assigned_to_id.present? &&
|
||||
(user.id == initial_assigned_to_id || user.group_ids.include?(initial_assigned_to_id))
|
||||
|
||||
statuses = initial_status.find_new_statuses_allowed_to(
|
||||
|
@ -844,10 +839,8 @@ class Issue < ActiveRecord::Base
|
|||
# spent_hours => 0.0
|
||||
# spent_hours => 50.2
|
||||
def total_spent_hours
|
||||
@total_spent_hours ||=
|
||||
self_and_descendants.
|
||||
joins("LEFT JOIN #{TimeEntry.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id").
|
||||
sum("#{TimeEntry.table_name}.hours").to_f || 0.0
|
||||
@total_spent_hours ||= self_and_descendants.sum("#{TimeEntry.table_name}.hours",
|
||||
:joins => "LEFT JOIN #{TimeEntry.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id").to_f || 0.0
|
||||
end
|
||||
|
||||
def relations
|
||||
|
@ -1061,7 +1054,7 @@ class Issue < ActiveRecord::Base
|
|||
if leaf.start_date
|
||||
# Only move subtask if it starts at the same date as the parent
|
||||
# or if it starts before the given date
|
||||
if start_date == leaf.start_date || date > leaf.start_date
|
||||
if start_date == leaf.start_date || date > leaf.start_date
|
||||
leaf.reschedule_on!(date)
|
||||
end
|
||||
else
|
||||
|
@ -1112,10 +1105,7 @@ class Issue < ActiveRecord::Base
|
|||
def self.update_versions_from_hierarchy_change(project)
|
||||
moved_project_ids = project.self_and_descendants.reload.collect(&:id)
|
||||
# Update issues of the moved projects and issues assigned to a version of a moved project
|
||||
Issue.update_versions(
|
||||
["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)",
|
||||
moved_project_ids, moved_project_ids]
|
||||
)
|
||||
Issue.update_versions(["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)", moved_project_ids, moved_project_ids])
|
||||
end
|
||||
|
||||
def parent_issue_id=(arg)
|
||||
|
@ -1199,13 +1189,13 @@ class Issue < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def self.by_subproject(project)
|
||||
ActiveRecord::Base.connection.select_all("select s.id as status_id,
|
||||
s.is_closed as closed,
|
||||
ActiveRecord::Base.connection.select_all("select s.id as status_id,
|
||||
s.is_closed as closed,
|
||||
#{Issue.table_name}.project_id as project_id,
|
||||
count(#{Issue.table_name}.id) as total
|
||||
from
|
||||
count(#{Issue.table_name}.id) as total
|
||||
from
|
||||
#{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s
|
||||
where
|
||||
where
|
||||
#{Issue.table_name}.status_id=s.id
|
||||
and #{Issue.table_name}.project_id = #{Project.table_name}.id
|
||||
and #{visible_condition(User.current, :project => project, :with_subprojects => true)}
|
||||
|
@ -1232,7 +1222,7 @@ class Issue < ActiveRecord::Base
|
|||
|
||||
def after_project_change
|
||||
# Update project_id on related time entries
|
||||
TimeEntry.where({:issue_id => id}).update_all(["project_id = ?", project_id])
|
||||
TimeEntry.update_all(["project_id = ?", project_id], {:issue_id => id})
|
||||
|
||||
# Delete issue relations
|
||||
unless Setting.cross_project_issue_relations?
|
||||
|
@ -1295,7 +1285,8 @@ class Issue < ActiveRecord::Base
|
|||
if root_id.nil?
|
||||
# issue was just created
|
||||
self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id)
|
||||
Issue.where(["id = ?", id]).update_all(["root_id = ?", root_id])
|
||||
set_default_left_and_right
|
||||
Issue.update_all(["root_id = ?, lft = ?, rgt = ?", root_id, lft, rgt], ["id = ?", id])
|
||||
if @parent_issue
|
||||
move_to_child_of(@parent_issue)
|
||||
end
|
||||
|
@ -1318,18 +1309,13 @@ class Issue < ActiveRecord::Base
|
|||
move_to_right_of(root)
|
||||
end
|
||||
old_root_id = root_id
|
||||
in_tenacious_transaction do
|
||||
@parent_issue.reload_nested_set if @parent_issue
|
||||
self.reload_nested_set
|
||||
self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id)
|
||||
cond = ["root_id = ? AND lft >= ? AND rgt <= ? ", old_root_id, lft, rgt]
|
||||
self.class.base_class.select('id').lock(true).where(cond)
|
||||
offset = right_most_bound + 1 - lft
|
||||
Issue.where(cond).
|
||||
update_all(["root_id = ?, lft = lft + ?, rgt = rgt + ?", root_id, offset, offset])
|
||||
self[left_column_name] = lft + offset
|
||||
self[right_column_name] = rgt + offset
|
||||
end
|
||||
self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id )
|
||||
target_maxright = nested_set_scope.maximum(right_column_name) || 0
|
||||
offset = target_maxright + 1 - lft
|
||||
Issue.update_all(["root_id = ?, lft = lft + ?, rgt = rgt + ?", root_id, offset, offset],
|
||||
["root_id = ? AND lft >= ? AND rgt <= ? ", old_root_id, lft, rgt])
|
||||
self[left_column_name] = lft + offset
|
||||
self[right_column_name] = rgt + offset
|
||||
if @parent_issue
|
||||
move_to_child_of(@parent_issue)
|
||||
end
|
||||
|
@ -1351,7 +1337,7 @@ class Issue < ActiveRecord::Base
|
|||
def recalculate_attributes_for(issue_id)
|
||||
if issue_id && p = Issue.find_by_id(issue_id)
|
||||
# priority = highest priority of children
|
||||
if priority_position = p.children.joins(:priority).maximum("#{IssuePriority.table_name}.position")
|
||||
if priority_position = p.children.maximum("#{IssuePriority.table_name}.position", :joins => :priority)
|
||||
p.priority = IssuePriority.find_by_position(priority_position)
|
||||
end
|
||||
|
||||
|
@ -1370,9 +1356,8 @@ class Issue < ActiveRecord::Base
|
|||
if average == 0
|
||||
average = 1
|
||||
end
|
||||
done = p.leaves.joins(:status).
|
||||
sum("COALESCE(CASE WHEN estimated_hours > 0 THEN estimated_hours ELSE NULL END, #{average}) " +
|
||||
"* (CASE WHEN is_closed = #{connection.quoted_true} THEN 100 ELSE COALESCE(done_ratio, 0) END)").to_f
|
||||
done = p.leaves.sum("COALESCE(CASE WHEN estimated_hours > 0 THEN estimated_hours ELSE NULL END, #{average}) " +
|
||||
"* (CASE WHEN is_closed = #{connection.quoted_true} THEN 100 ELSE COALESCE(done_ratio, 0) END)", :joins => :status).to_f
|
||||
progress = done / (average * leaves_count)
|
||||
p.done_ratio = progress.round
|
||||
end
|
||||
|
@ -1441,7 +1426,7 @@ class Issue < ActiveRecord::Base
|
|||
def close_duplicates
|
||||
if closing?
|
||||
duplicates.each do |duplicate|
|
||||
# Reload is needed in case the duplicate was updated by a previous duplicate
|
||||
# Reload is need in case the duplicate was updated by a previous duplicate
|
||||
duplicate.reload
|
||||
# Don't re-close it if it's already closed
|
||||
next if duplicate.closed?
|
||||
|
@ -1496,11 +1481,11 @@ class Issue < ActiveRecord::Base
|
|||
before = @custom_values_before_change[c.custom_field_id]
|
||||
after = c.value
|
||||
next if before == after || (before.blank? && after.blank?)
|
||||
|
||||
|
||||
if before.is_a?(Array) || after.is_a?(Array)
|
||||
before = [before] unless before.is_a?(Array)
|
||||
after = [after] unless after.is_a?(Array)
|
||||
|
||||
|
||||
# values removed
|
||||
(before - after).reject(&:blank?).each do |value|
|
||||
@current_journal.details << JournalDetail.new(:property => 'cf',
|
||||
|
@ -1561,14 +1546,14 @@ class Issue < ActiveRecord::Base
|
|||
|
||||
where = "#{Issue.table_name}.#{select_field}=j.id"
|
||||
|
||||
ActiveRecord::Base.connection.select_all("select s.id as status_id,
|
||||
s.is_closed as closed,
|
||||
ActiveRecord::Base.connection.select_all("select s.id as status_id,
|
||||
s.is_closed as closed,
|
||||
j.id as #{select_field},
|
||||
count(#{Issue.table_name}.id) as total
|
||||
from
|
||||
count(#{Issue.table_name}.id) as total
|
||||
from
|
||||
#{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s, #{joins} j
|
||||
where
|
||||
#{Issue.table_name}.status_id=s.id
|
||||
where
|
||||
#{Issue.table_name}.status_id=s.id
|
||||
and #{where}
|
||||
and #{Issue.table_name}.project_id=#{Project.table_name}.id
|
||||
and #{visible_condition(User.current, :project => project)}
|
||||
|
|
|
@ -35,7 +35,7 @@ class IssueCategory < ActiveRecord::Base
|
|||
# If a category is specified, issues are reassigned to this category
|
||||
def destroy(reassign_to = nil)
|
||||
if reassign_to && reassign_to.is_a?(IssueCategory) && reassign_to.project == self.project
|
||||
Issue.where({:category_id => id}).update_all({:category_id => reassign_to.id})
|
||||
Issue.update_all({:category_id => reassign_to.id}, {:category_id => id})
|
||||
end
|
||||
destroy_without_reassign
|
||||
end
|
||||
|
|
|
@ -28,14 +28,13 @@ class IssueCustomField < CustomField
|
|||
super || (roles & user.roles_for_project(project)).present?
|
||||
end
|
||||
|
||||
def visibility_by_project_condition(project_key=nil, user=User.current, id_column=nil)
|
||||
def visibility_by_project_condition(*args)
|
||||
sql = super
|
||||
id_column ||= id
|
||||
tracker_condition = "#{Issue.table_name}.tracker_id IN (SELECT tracker_id FROM #{table_name_prefix}custom_fields_trackers#{table_name_suffix} WHERE custom_field_id = #{id_column})"
|
||||
project_condition = "EXISTS (SELECT 1 FROM #{CustomField.table_name} ifa WHERE ifa.is_for_all = #{connection.quoted_true} AND ifa.id = #{id_column})" +
|
||||
" OR #{Issue.table_name}.project_id IN (SELECT project_id FROM #{table_name_prefix}custom_fields_projects#{table_name_suffix} WHERE custom_field_id = #{id_column})"
|
||||
|
||||
"((#{sql}) AND (#{tracker_condition}) AND (#{project_condition}))"
|
||||
additional_sql = "#{Issue.table_name}.tracker_id IN (SELECT tracker_id FROM #{table_name_prefix}custom_fields_trackers#{table_name_suffix} WHERE custom_field_id = #{id})"
|
||||
unless is_for_all?
|
||||
additional_sql << " AND #{Issue.table_name}.project_id IN (SELECT project_id FROM #{table_name_prefix}custom_fields_projects#{table_name_suffix} WHERE custom_field_id = #{id})"
|
||||
end
|
||||
"((#{sql}) AND (#{additional_sql}))"
|
||||
end
|
||||
|
||||
def validate_custom_field
|
||||
|
|
|
@ -48,7 +48,7 @@ class IssuePriority < Enumeration
|
|||
# Updates position_name for active priorities
|
||||
# Called from migration 20121026003537_populate_enumerations_position_name
|
||||
def self.compute_position_names
|
||||
priorities = where(:active => true).sort_by(&:position)
|
||||
priorities = where(:active => true).all.sort_by(&:position)
|
||||
if priorities.any?
|
||||
default = priorities.detect(&:is_default?) || priorities[(priorities.size - 1) / 2]
|
||||
priorities.each_with_index do |priority, index|
|
||||
|
@ -61,7 +61,7 @@ class IssuePriority < Enumeration
|
|||
index == (priorities.size - 1) ? "highest" : "high#{priorities.size - index}"
|
||||
end
|
||||
|
||||
where(:id => priority.id).update_all({:position_name => name})
|
||||
update_all({:position_name => name}, :id => priority.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -142,7 +142,7 @@ class IssueQuery < Query
|
|||
if all_projects.any?
|
||||
principals += Principal.member_of(all_projects)
|
||||
end
|
||||
versions = Version.visible.where(:sharing => 'system').all
|
||||
versions = Version.visible.find_all_by_sharing('system')
|
||||
issue_custom_fields = IssueCustomField.where(:is_for_all => true)
|
||||
end
|
||||
principals.uniq!
|
||||
|
@ -150,7 +150,7 @@ class IssueQuery < Query
|
|||
users = principals.select {|p| p.is_a?(User)}
|
||||
|
||||
add_available_filter "status_id",
|
||||
:type => :list_status, :values => IssueStatus.sorted.collect{|s| [s.name, s.id.to_s] }
|
||||
:type => :list_status, :values => IssueStatus.sorted.all.collect{|s| [s.name, s.id.to_s] }
|
||||
|
||||
if project.nil?
|
||||
project_values = []
|
||||
|
@ -333,9 +333,8 @@ class IssueQuery < Query
|
|||
limit(options[:limit]).
|
||||
offset(options[:offset])
|
||||
|
||||
scope = scope.preload(:custom_values)
|
||||
if has_column?(:author)
|
||||
scope = scope.preload(:author)
|
||||
if has_custom_field_column?
|
||||
scope = scope.preload(:custom_values)
|
||||
end
|
||||
|
||||
issues = scope.all
|
||||
|
@ -410,7 +409,7 @@ class IssueQuery < Query
|
|||
groups = Group.all
|
||||
operator = '!' # Override the operator since we want to find by assigned_to
|
||||
else
|
||||
groups = Group.where(:id => value).all
|
||||
groups = Group.find_all_by_id(value)
|
||||
end
|
||||
groups ||= []
|
||||
|
||||
|
|
|
@ -185,12 +185,12 @@ class IssueRelation < ActiveRecord::Base
|
|||
def create_journal_after_create
|
||||
journal = issue_from.init_journal(User.current)
|
||||
journal.details << JournalDetail.new(:property => 'relation',
|
||||
:prop_key => relation_type_for(issue_from),
|
||||
:prop_key => label_for(issue_from).to_s,
|
||||
:value => issue_to.id)
|
||||
journal.save
|
||||
journal = issue_to.init_journal(User.current)
|
||||
journal.details << JournalDetail.new(:property => 'relation',
|
||||
:prop_key => relation_type_for(issue_to),
|
||||
:prop_key => label_for(issue_to).to_s,
|
||||
:value => issue_from.id)
|
||||
journal.save
|
||||
end
|
||||
|
@ -198,12 +198,12 @@ class IssueRelation < ActiveRecord::Base
|
|||
def create_journal_after_delete
|
||||
journal = issue_from.init_journal(User.current)
|
||||
journal.details << JournalDetail.new(:property => 'relation',
|
||||
:prop_key => relation_type_for(issue_from),
|
||||
:prop_key => label_for(issue_from).to_s,
|
||||
:old_value => issue_to.id)
|
||||
journal.save
|
||||
journal = issue_to.init_journal(User.current)
|
||||
journal.details << JournalDetail.new(:property => 'relation',
|
||||
:prop_key => relation_type_for(issue_to),
|
||||
:prop_key => label_for(issue_to).to_s,
|
||||
:old_value => issue_from.id)
|
||||
journal.save
|
||||
end
|
||||
|
|
|
@ -32,7 +32,7 @@ class IssueStatus < ActiveRecord::Base
|
|||
scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)}
|
||||
|
||||
def update_default
|
||||
IssueStatus.where(['id <> ?', id]).update_all({:is_default => false}) if self.is_default?
|
||||
IssueStatus.update_all({:is_default => false}, ['id <> ?', id]) if self.is_default?
|
||||
end
|
||||
|
||||
# Returns the default status for new issues
|
||||
|
@ -43,8 +43,8 @@ class IssueStatus < ActiveRecord::Base
|
|||
# Update all the +Issues+ setting their done_ratio to the value of their +IssueStatus+
|
||||
def self.update_issue_done_ratios
|
||||
if Issue.use_status_for_done_ratio?
|
||||
IssueStatus.where("default_done_ratio >= 0").each do |status|
|
||||
Issue.where({:status_id => status.id}).update_all({:done_ratio => status.default_done_ratio})
|
||||
IssueStatus.where("default_done_ratio >= 0").all.each do |status|
|
||||
Issue.update_all({:done_ratio => status.default_done_ratio}, {:status_id => status.id})
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -80,20 +80,15 @@ class Journal < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
# Returns the JournalDetail for the given attribute, or nil if the attribute
|
||||
# was not updated
|
||||
def detail_for_attribute(attribute)
|
||||
details.detect {|detail| detail.prop_key == attribute}
|
||||
end
|
||||
|
||||
# Returns the new status if the journal contains a status change, otherwise nil
|
||||
def new_status
|
||||
s = new_value_for('status_id')
|
||||
s ? IssueStatus.find_by_id(s.to_i) : nil
|
||||
c = details.detect {|detail| detail.prop_key == 'status_id'}
|
||||
(c && c.value) ? IssueStatus.find_by_id(c.value.to_i) : nil
|
||||
end
|
||||
|
||||
def new_value_for(prop)
|
||||
detail_for_attribute(prop).try(:value)
|
||||
c = details.detect {|detail| detail.prop_key == prop}
|
||||
c ? c.value : nil
|
||||
end
|
||||
|
||||
def editable_by?(usr)
|
||||
|
@ -153,7 +148,7 @@ class Journal < ActiveRecord::Base
|
|||
def self.preload_journals_details_custom_fields(journals)
|
||||
field_ids = journals.map(&:details).flatten.select {|d| d.property == 'cf'}.map(&:prop_key).uniq
|
||||
if field_ids.any?
|
||||
fields_by_id = CustomField.where(:id => field_ids).inject({}) {|h, f| h[f.id] = f; h}
|
||||
fields_by_id = CustomField.find_all_by_id(field_ids).inject({}) {|h, f| h[f.id] = f; h}
|
||||
journals.each do |journal|
|
||||
journal.details.each do |detail|
|
||||
if detail.property == 'cf'
|
||||
|
@ -190,7 +185,6 @@ class Journal < ActiveRecord::Base
|
|||
if notify? && (Setting.notified_events.include?('issue_updated') ||
|
||||
(Setting.notified_events.include?('issue_note_added') && notes.present?) ||
|
||||
(Setting.notified_events.include?('issue_status_updated') && new_status.present?) ||
|
||||
(Setting.notified_events.include?('issue_assigned_to_updated') && detail_for_attribute('assigned_to_id').present?) ||
|
||||
(Setting.notified_events.include?('issue_priority_updated') && new_value_for('priority_id').present?)
|
||||
)
|
||||
Mailer.deliver_issue_edit(self)
|
||||
|
|
|
@ -46,14 +46,6 @@ class MailHandler < ActionMailer::Base
|
|||
super(email)
|
||||
end
|
||||
|
||||
# Receives an email and rescues any exception
|
||||
def self.safe_receive(*args)
|
||||
receive(*args)
|
||||
rescue => e
|
||||
logger.error "An unexpected error occurred when receiving email: #{e.message}" if logger
|
||||
return false
|
||||
end
|
||||
|
||||
# Extracts MailHandler options from environment variables
|
||||
# Use when receiving emails with rake tasks
|
||||
def self.extract_options_from_env(env)
|
||||
|
@ -198,7 +190,6 @@ class MailHandler < ActionMailer::Base
|
|||
issue.subject = '(no subject)'
|
||||
end
|
||||
issue.description = cleaned_up_text_body
|
||||
issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
|
||||
|
||||
# add To and Cc as watchers before saving so the watchers can reply to Redmine
|
||||
add_watchers(issue)
|
||||
|
@ -305,9 +296,8 @@ class MailHandler < ActionMailer::Base
|
|||
if user.allowed_to?("add_#{obj.class.name.underscore}_watchers".to_sym, obj.project)
|
||||
addresses = [email.to, email.cc].flatten.compact.uniq.collect {|a| a.strip.downcase}
|
||||
unless addresses.empty?
|
||||
User.active.where('LOWER(mail) IN (?)', addresses).each do |w|
|
||||
obj.add_watcher(w)
|
||||
end
|
||||
watchers = User.active.where('LOWER(mail) IN (?)', addresses).all
|
||||
watchers.each {|w| obj.add_watcher(w)}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -420,11 +410,7 @@ class MailHandler < ActionMailer::Base
|
|||
part.header[:content_disposition].try(:disposition_type) == 'attachment'
|
||||
end
|
||||
|
||||
@plain_text_body = parts.map do |p|
|
||||
body_charset = p.charset.respond_to?(:force_encoding) ?
|
||||
Mail::RubyVer.pick_encoding(p.charset).to_s : p.charset
|
||||
Redmine::CodesetUtil.to_utf8(p.body.decoded, body_charset)
|
||||
end.join("\r\n")
|
||||
@plain_text_body = parts.map {|p| Redmine::CodesetUtil.to_utf8(p.body.decoded, p.charset)}.join("\r\n")
|
||||
|
||||
# strip html tags and remove doctype directive
|
||||
if parts.any? {|p| p.mime_type == 'text/html'}
|
||||
|
|
|
@ -80,7 +80,7 @@ class Mailer < ActionMailer::Base
|
|||
def self.deliver_issue_edit(journal)
|
||||
issue = journal.journalized.reload
|
||||
to = journal.notified_users
|
||||
cc = journal.notified_watchers - to
|
||||
cc = journal.notified_watchers
|
||||
journal.each_notification(to + cc) do |users|
|
||||
issue.each_notification(users) do |users2|
|
||||
Mailer.issue_edit(journal, to & users2, cc & users2).deliver
|
||||
|
@ -158,7 +158,6 @@ class Mailer < ActionMailer::Base
|
|||
@news = news
|
||||
@news_url = url_for(:controller => 'news', :action => 'show', :id => news)
|
||||
mail :to => news.recipients,
|
||||
:cc => news.cc_for_added_news,
|
||||
:subject => "[#{news.project.name}] #{l(:label_news)}: #{news.title}"
|
||||
end
|
||||
|
||||
|
@ -267,7 +266,7 @@ class Mailer < ActionMailer::Base
|
|||
# Mailer.account_activation_request(user).deliver => sends an email to all active administrators
|
||||
def account_activation_request(user)
|
||||
# Send the email to all active administrators
|
||||
recipients = User.active.where(:admin => true).collect { |u| u.mail }.compact
|
||||
recipients = User.active.where(:admin => true).all.collect { |u| u.mail }.compact
|
||||
@user = user
|
||||
@url = url_for(:controller => 'users', :action => 'index',
|
||||
:status => User::STATUS_REGISTERED,
|
||||
|
@ -331,8 +330,8 @@ class Mailer < ActionMailer::Base
|
|||
scope = scope.where(:assigned_to_id => user_ids) if user_ids.present?
|
||||
scope = scope.where(:project_id => project.id) if project
|
||||
scope = scope.where(:tracker_id => tracker.id) if tracker
|
||||
issues_by_assignee = scope.includes(:status, :assigned_to, :project, :tracker).
|
||||
group_by(&:assigned_to)
|
||||
|
||||
issues_by_assignee = scope.includes(:status, :assigned_to, :project, :tracker).all.group_by(&:assigned_to)
|
||||
issues_by_assignee.keys.each do |assignee|
|
||||
if assignee.is_a?(Group)
|
||||
assignee.users.each do |user|
|
||||
|
@ -464,7 +463,7 @@ class Mailer < ActionMailer::Base
|
|||
if rand
|
||||
hash << Redmine::Utils.random_hex(8)
|
||||
end
|
||||
host = Setting.mail_from.to_s.strip.gsub(%r{^.*@|>}, '')
|
||||
host = Setting.mail_from.to_s.gsub(%r{^.*@}, '')
|
||||
host = "#{::Socket.gethostname}.redmine" if host.empty?
|
||||
"#{hash.join('.')}@#{host}"
|
||||
end
|
||||
|
|
|
@ -84,12 +84,11 @@ class Member < ActiveRecord::Base
|
|||
def set_issue_category_nil
|
||||
if user
|
||||
# remove category based auto assignments for this member
|
||||
IssueCategory.where(["project_id = ? AND assigned_to_id = ?", project.id, user.id]).
|
||||
update_all("assigned_to_id = NULL")
|
||||
IssueCategory.update_all "assigned_to_id = NULL", ["project_id = ? AND assigned_to_id = ?", project.id, user.id]
|
||||
end
|
||||
end
|
||||
|
||||
# Find or initialize a Member with an id, attributes, and for a Principal
|
||||
# Find or initilize a Member with an id, attributes, and for a Principal
|
||||
def self.edit_membership(id, new_attributes, principal=nil)
|
||||
@membership = id.present? ? Member.find(id) : Member.new(:principal => principal)
|
||||
@membership.attributes = new_attributes
|
||||
|
|
|
@ -64,8 +64,7 @@ class MemberRole < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def remove_inherited_roles
|
||||
MemberRole.where(:inherited_from => id).group_by(&:member).
|
||||
each do |member, member_roles|
|
||||
MemberRole.where(:inherited_from => id).all.group_by(&:member).each do |member, member_roles|
|
||||
member_roles.each(&:destroy)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -68,7 +68,7 @@ class Message < ActiveRecord::Base
|
|||
|
||||
def update_messages_board
|
||||
if board_id_changed?
|
||||
Message.where(["id = ? OR parent_id = ?", root.id, root.id]).update_all({:board_id => board_id})
|
||||
Message.update_all({:board_id => board_id}, ["id = ? OR parent_id = ?", root.id, root.id])
|
||||
Board.reset_counters!(board_id_was)
|
||||
Board.reset_counters!(board_id)
|
||||
end
|
||||
|
@ -76,7 +76,7 @@ class Message < ActiveRecord::Base
|
|||
|
||||
def reset_counters!
|
||||
if parent && parent.id
|
||||
Message.where({:id => parent.id}).update_all({:last_reply_id => parent.children.maximum(:id)})
|
||||
Message.update_all({:last_reply_id => parent.children.maximum(:id)}, {:id => parent.id})
|
||||
end
|
||||
board.reset_counters!
|
||||
end
|
||||
|
|
|
@ -51,19 +51,7 @@ class News < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def recipients
|
||||
project.users.select {|user| user.notify_about?(self) && user.allowed_to?(:view_news, project)}.map(&:mail)
|
||||
end
|
||||
|
||||
# Returns the email addresses that should be cc'd when a new news is added
|
||||
def cc_for_added_news
|
||||
cc = []
|
||||
if m = project.enabled_module('news')
|
||||
cc = m.notified_watchers
|
||||
unless project.is_public?
|
||||
cc = cc.select {|user| project.users.include?(user)}
|
||||
end
|
||||
end
|
||||
cc.map(&:mail)
|
||||
project.users.select {|user| user.notify_about?(self)}.map(&:mail)
|
||||
end
|
||||
|
||||
# returns latest news for projects visible by user
|
||||
|
|
|
@ -25,11 +25,7 @@ class Principal < ActiveRecord::Base
|
|||
STATUS_LOCKED = 3
|
||||
|
||||
has_many :members, :foreign_key => 'user_id', :dependent => :destroy
|
||||
has_many :memberships, :class_name => 'Member',
|
||||
:foreign_key => 'user_id',
|
||||
:include => [:project, :roles],
|
||||
:conditions => "#{Project.table_name}.status<>#{Project::STATUS_ARCHIVED}",
|
||||
:order => "#{Project.table_name}.name"
|
||||
has_many :memberships, :class_name => 'Member', :foreign_key => 'user_id', :include => [ :project, :roles ], :conditions => "#{Project.table_name}.status<>#{Project::STATUS_ARCHIVED}", :order => "#{Project.table_name}.name"
|
||||
has_many :projects, :through => :memberships
|
||||
has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ class Project < ActiveRecord::Base
|
|||
# Maximum length for project identifiers
|
||||
IDENTIFIER_MAX_LENGTH = 100
|
||||
|
||||
# Specific overridden Activities
|
||||
# Specific overidden Activities
|
||||
has_many :time_entry_activities
|
||||
has_many :members, :include => [:principal, :roles], :conditions => "#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{Principal::STATUS_ACTIVE}"
|
||||
has_many :memberships, :class_name => 'Member'
|
||||
|
@ -39,7 +39,7 @@ class Project < ActiveRecord::Base
|
|||
has_many :issues, :dependent => :destroy, :include => [:status, :tracker]
|
||||
has_many :issue_changes, :through => :issues, :source => :journals
|
||||
has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
|
||||
has_many :time_entries, :dependent => :destroy
|
||||
has_many :time_entries, :dependent => :delete_all
|
||||
has_many :queries, :class_name => 'IssueQuery', :dependent => :delete_all
|
||||
has_many :documents, :dependent => :destroy
|
||||
has_many :news, :dependent => :destroy, :include => :author
|
||||
|
@ -56,7 +56,7 @@ class Project < ActiveRecord::Base
|
|||
:join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
|
||||
:association_foreign_key => 'custom_field_id'
|
||||
|
||||
acts_as_nested_set :dependent => :destroy
|
||||
acts_as_nested_set :order => 'name', :dependent => :destroy
|
||||
acts_as_attachable :view_permission => :view_files,
|
||||
:delete_permission => :manage_files
|
||||
|
||||
|
@ -74,7 +74,7 @@ class Project < ActiveRecord::Base
|
|||
validates_length_of :name, :maximum => 255
|
||||
validates_length_of :homepage, :maximum => 255
|
||||
validates_length_of :identifier, :in => 1..IDENTIFIER_MAX_LENGTH
|
||||
# downcase letters, digits, dashes but not digits only
|
||||
# donwcase letters, digits, dashes but not digits only
|
||||
validates_format_of :identifier, :with => /\A(?!\d+$)[a-z0-9\-_]*\z/, :if => Proc.new { |p| p.identifier_changed? }
|
||||
# reserved words
|
||||
validates_exclusion_of :identifier, :in => %w( new )
|
||||
|
@ -249,17 +249,18 @@ class Project < ActiveRecord::Base
|
|||
# does not successfully save.
|
||||
def create_time_entry_activity_if_needed(activity)
|
||||
if activity['parent_id']
|
||||
|
||||
parent_activity = TimeEntryActivity.find(activity['parent_id'])
|
||||
activity['name'] = parent_activity.name
|
||||
activity['position'] = parent_activity.position
|
||||
if Enumeration.overriding_change?(activity, parent_activity)
|
||||
|
||||
if Enumeration.overridding_change?(activity, parent_activity)
|
||||
project_activity = self.time_entry_activities.create(activity)
|
||||
|
||||
if project_activity.new_record?
|
||||
raise ActiveRecord::Rollback, "Overriding TimeEntryActivity was not successfully saved"
|
||||
raise ActiveRecord::Rollback, "Overridding TimeEntryActivity was not successfully saved"
|
||||
else
|
||||
self.time_entries.
|
||||
where(["activity_id = ?", parent_activity.id]).
|
||||
update_all("activity_id = #{project_activity.id}")
|
||||
self.time_entries.update_all("activity_id = #{project_activity.id}", ["activity_id = ?", parent_activity.id])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -421,7 +422,6 @@ class Project < ActiveRecord::Base
|
|||
transaction do
|
||||
update_all "lft = NULL, rgt = NULL"
|
||||
rebuild!(false)
|
||||
all.each { |p| p.set_or_update_position_under(p.parent) }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -440,7 +440,7 @@ class Project < ActiveRecord::Base
|
|||
# Closes open and locked project versions that are completed
|
||||
def close_completed_versions
|
||||
Version.transaction do
|
||||
versions.where(:status => %w(open locked)).each do |version|
|
||||
versions.where(:status => %w(open locked)).all.each do |version|
|
||||
if version.completed?
|
||||
version.update_attribute(:status, 'closed')
|
||||
end
|
||||
|
@ -480,7 +480,7 @@ class Project < ActiveRecord::Base
|
|||
|
||||
# Returns a hash of project users grouped by role
|
||||
def users_by_role
|
||||
members.includes(:user, :roles).inject({}) do |h, m|
|
||||
members.includes(:user, :roles).all.inject({}) do |h, m|
|
||||
m.roles.each do |r|
|
||||
h[r] ||= []
|
||||
h[r] << m.user
|
||||
|
@ -502,7 +502,7 @@ class Project < ActiveRecord::Base
|
|||
assignable.select {|m| m.roles.detect {|role| role.assignable?}}.collect {|m| m.principal}.sort
|
||||
end
|
||||
|
||||
# Returns the mail addresses of users that should be always notified on project events
|
||||
# Returns the mail adresses of users that should be always notified on project events
|
||||
def recipients
|
||||
notified_users.collect {|user| user.mail}
|
||||
end
|
||||
|
@ -514,7 +514,7 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
|
||||
# Returns a scope of all custom fields enabled for project issues
|
||||
# (explicitly associated custom fields and custom fields enabled for all projects)
|
||||
# (explictly associated custom fields and custom fields enabled for all projects)
|
||||
def all_issue_custom_fields
|
||||
@all_issue_custom_fields ||= IssueCustomField.
|
||||
sorted.
|
||||
|
@ -622,16 +622,9 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
# Return the enabled module with the given name
|
||||
# or nil if the module is not enabled for the project
|
||||
def enabled_module(name)
|
||||
name = name.to_s
|
||||
enabled_modules.detect {|m| m.name == name}
|
||||
end
|
||||
|
||||
# Return true if the module with the given name is enabled
|
||||
def module_enabled?(name)
|
||||
enabled_module(name).present?
|
||||
def module_enabled?(module_name)
|
||||
module_name = module_name.to_s
|
||||
enabled_modules.detect {|m| m.name == module_name}
|
||||
end
|
||||
|
||||
def enabled_module_names=(module_names)
|
||||
|
@ -847,7 +840,7 @@ class Project < ActiveRecord::Base
|
|||
# Copies issues from +project+
|
||||
def copy_issues(project)
|
||||
# Stores the source issue id as a key and the copied issues as the
|
||||
# value. Used to map the two together for issue relations.
|
||||
# value. Used to map the two togeather for issue relations.
|
||||
issues_map = {}
|
||||
|
||||
# Store status and reopen locked/closed versions
|
||||
|
@ -858,7 +851,7 @@ class Project < ActiveRecord::Base
|
|||
|
||||
# Get issues sorted by root_id, lft so that parent issues
|
||||
# get copied before their children
|
||||
project.issues.reorder('root_id, lft').each do |issue|
|
||||
project.issues.reorder('root_id, lft').all.each do |issue|
|
||||
new_issue = Issue.new
|
||||
new_issue.copy_from(issue, :subtasks => false, :link => false)
|
||||
new_issue.project = self
|
||||
|
@ -1002,15 +995,15 @@ class Project < ActiveRecord::Base
|
|||
|
||||
# Returns the systemwide active activities merged with the project specific overrides
|
||||
def system_activities_and_project_overrides(include_inactive=false)
|
||||
t = TimeEntryActivity.table_name
|
||||
scope = TimeEntryActivity.where(
|
||||
"(#{t}.project_id IS NULL AND #{t}.id NOT IN (?)) OR (#{t}.project_id = ?)",
|
||||
time_entry_activities.map(&:parent_id), id
|
||||
)
|
||||
unless include_inactive
|
||||
scope = scope.active
|
||||
if include_inactive
|
||||
return TimeEntryActivity.shared.
|
||||
where("id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)).all +
|
||||
self.time_entry_activities
|
||||
else
|
||||
return TimeEntryActivity.shared.active.
|
||||
where("id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)).all +
|
||||
self.time_entry_activities.active
|
||||
end
|
||||
scope
|
||||
end
|
||||
|
||||
# Archives subprojects recursively
|
||||
|
@ -1025,8 +1018,6 @@ class Project < ActiveRecord::Base
|
|||
set_or_update_position_under(parent)
|
||||
end
|
||||
|
||||
public
|
||||
|
||||
# Inserts/moves the project so that target's children or root projects stay alphabetically sorted
|
||||
def set_or_update_position_under(target_parent)
|
||||
parent_was = parent
|
||||
|
|
|
@ -57,10 +57,6 @@ class QueryColumn
|
|||
object.send name
|
||||
end
|
||||
|
||||
def value_object(object)
|
||||
object.send name
|
||||
end
|
||||
|
||||
def css_classes
|
||||
name
|
||||
end
|
||||
|
@ -84,21 +80,10 @@ class QueryCustomFieldColumn < QueryColumn
|
|||
@cf
|
||||
end
|
||||
|
||||
def value_object(object)
|
||||
if custom_field.visible_by?(object.project, User.current)
|
||||
cv = object.custom_values.select {|v| v.custom_field_id == @cf.id}
|
||||
cv.size > 1 ? cv.sort {|a,b| a.value.to_s <=> b.value.to_s} : cv.first
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def value(object)
|
||||
raw = value_object(object)
|
||||
if raw.is_a?(Array)
|
||||
raw.map {|r| @cf.cast_value(r.value)}
|
||||
elsif raw
|
||||
@cf.cast_value(raw.value)
|
||||
if custom_field.visible_by?(object.project, User.current)
|
||||
cv = object.custom_values.select {|v| v.custom_field_id == @cf.id}.collect {|v| @cf.cast_value(v.value)}
|
||||
cv.size > 1 ? cv.sort {|a,b| a.to_s <=> b.to_s} : cv.first
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
@ -120,7 +105,7 @@ class QueryAssociationCustomFieldColumn < QueryCustomFieldColumn
|
|||
@association = association
|
||||
end
|
||||
|
||||
def value_object(object)
|
||||
def value(object)
|
||||
if assoc = object.send(@association)
|
||||
super(assoc)
|
||||
end
|
||||
|
@ -159,8 +144,8 @@ class Query < ActiveRecord::Base
|
|||
|
||||
after_save do |query|
|
||||
if query.visibility_changed? && query.visibility != VISIBILITY_ROLES
|
||||
query.roles.clear
|
||||
end
|
||||
query.roles.clear
|
||||
end
|
||||
end
|
||||
|
||||
class_attribute :operators
|
||||
|
@ -257,9 +242,7 @@ class Query < ActiveRecord::Base
|
|||
when :date, :date_past
|
||||
case operator_for(field)
|
||||
when "=", ">=", "<=", "><"
|
||||
add_filter_error(field, :invalid) if values_for(field).detect {|v|
|
||||
v.present? && (!v.match(/\A\d{4}-\d{2}-\d{2}(T\d{2}((:)?\d{2}){0,2}(Z|\d{2}:?\d{2})?)?\z/) || parse_date(v).nil?)
|
||||
}
|
||||
add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && (!v.match(/^\d{4}-\d{2}-\d{2}$/) || (Date.parse(v) rescue nil).nil?) }
|
||||
when ">t-", "<t-", "t-", ">t+", "<t+", "t+", "><t+", "><t-"
|
||||
add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) }
|
||||
end
|
||||
|
@ -556,7 +539,7 @@ class Query < ActiveRecord::Base
|
|||
next unless v and !v.empty?
|
||||
operator = operator_for(field)
|
||||
|
||||
# "me" value substitution
|
||||
# "me" value subsitution
|
||||
if %w(assigned_to_id author_id user_id watcher_id).include?(field)
|
||||
if v.delete("me")
|
||||
if User.current.logged?
|
||||
|
@ -604,7 +587,7 @@ class Query < ActiveRecord::Base
|
|||
db_field = 'value'
|
||||
filter = @available_filters[field]
|
||||
return nil unless filter
|
||||
if filter[:field].format.target_class && filter[:field].format.target_class <= User
|
||||
if filter[:format] == 'user'
|
||||
if value.delete('me')
|
||||
value.push User.current.id.to_s
|
||||
end
|
||||
|
@ -641,7 +624,7 @@ class Query < ActiveRecord::Base
|
|||
if value.any?
|
||||
case type_for(field)
|
||||
when :date, :date_past
|
||||
sql = date_clause(db_table, db_field, parse_date(value.first), parse_date(value.first))
|
||||
sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), (Date.parse(value.first) rescue nil))
|
||||
when :integer
|
||||
if is_custom_filter
|
||||
sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) = #{value.first.to_i})"
|
||||
|
@ -676,7 +659,7 @@ class Query < ActiveRecord::Base
|
|||
sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
|
||||
when ">="
|
||||
if [:date, :date_past].include?(type_for(field))
|
||||
sql = date_clause(db_table, db_field, parse_date(value.first), nil)
|
||||
sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), nil)
|
||||
else
|
||||
if is_custom_filter
|
||||
sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) >= #{value.first.to_f})"
|
||||
|
@ -686,7 +669,7 @@ class Query < ActiveRecord::Base
|
|||
end
|
||||
when "<="
|
||||
if [:date, :date_past].include?(type_for(field))
|
||||
sql = date_clause(db_table, db_field, nil, parse_date(value.first))
|
||||
sql = date_clause(db_table, db_field, nil, (Date.parse(value.first) rescue nil))
|
||||
else
|
||||
if is_custom_filter
|
||||
sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) <= #{value.first.to_f})"
|
||||
|
@ -696,7 +679,7 @@ class Query < ActiveRecord::Base
|
|||
end
|
||||
when "><"
|
||||
if [:date, :date_past].include?(type_for(field))
|
||||
sql = date_clause(db_table, db_field, parse_date(value[0]), parse_date(value[1]))
|
||||
sql = date_clause(db_table, db_field, (Date.parse(value[0]) rescue nil), (Date.parse(value[1]) rescue nil))
|
||||
else
|
||||
if is_custom_filter
|
||||
sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) BETWEEN #{value[0].to_f} AND #{value[1].to_f})"
|
||||
|
@ -781,13 +764,29 @@ class Query < ActiveRecord::Base
|
|||
|
||||
# Adds a filter for the given custom field
|
||||
def add_custom_field_filter(field, assoc=nil)
|
||||
options = field.format.query_filter_options(field, self)
|
||||
if field.format.target_class && field.format.target_class <= User
|
||||
if options[:values].is_a?(Array) && User.current.logged?
|
||||
options[:values].unshift ["<< #{l(:label_me)} >>", "me"]
|
||||
case field.field_format
|
||||
when "text"
|
||||
options = { :type => :text }
|
||||
when "list"
|
||||
options = { :type => :list_optional, :values => field.possible_values }
|
||||
when "date"
|
||||
options = { :type => :date }
|
||||
when "bool"
|
||||
options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]] }
|
||||
when "int"
|
||||
options = { :type => :integer }
|
||||
when "float"
|
||||
options = { :type => :float }
|
||||
when "user", "version"
|
||||
return unless project
|
||||
values = field.possible_values_options(project)
|
||||
if User.current.logged? && field.field_format == 'user'
|
||||
values.unshift ["<< #{l(:label_me)} >>", "me"]
|
||||
end
|
||||
options = { :type => :list_optional, :values => values }
|
||||
else
|
||||
options = { :type => :string }
|
||||
end
|
||||
|
||||
filter_id = "cf_#{field.id}"
|
||||
filter_name = field.name
|
||||
if assoc.present?
|
||||
|
@ -796,6 +795,7 @@ class Query < ActiveRecord::Base
|
|||
end
|
||||
add_available_filter filter_id, options.merge({
|
||||
:name => filter_name,
|
||||
:format => field.field_format,
|
||||
:field => field
|
||||
})
|
||||
end
|
||||
|
@ -826,24 +826,19 @@ class Query < ActiveRecord::Base
|
|||
def date_clause(table, field, from, to)
|
||||
s = []
|
||||
if from
|
||||
if from.is_a?(Date)
|
||||
from = Time.local(from.year, from.month, from.day).yesterday.end_of_day
|
||||
else
|
||||
from = from - 1 # second
|
||||
end
|
||||
from_yesterday = from - 1
|
||||
from_yesterday_time = Time.local(from_yesterday.year, from_yesterday.month, from_yesterday.day)
|
||||
if self.class.default_timezone == :utc
|
||||
from = from.utc
|
||||
from_yesterday_time = from_yesterday_time.utc
|
||||
end
|
||||
s << ("#{table}.#{field} > '%s'" % [connection.quoted_date(from)])
|
||||
s << ("#{table}.#{field} > '%s'" % [connection.quoted_date(from_yesterday_time.end_of_day)])
|
||||
end
|
||||
if to
|
||||
if to.is_a?(Date)
|
||||
to = Time.local(to.year, to.month, to.day).end_of_day
|
||||
end
|
||||
to_time = Time.local(to.year, to.month, to.day)
|
||||
if self.class.default_timezone == :utc
|
||||
to = to.utc
|
||||
to_time = to_time.utc
|
||||
end
|
||||
s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to)])
|
||||
s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to_time.end_of_day)])
|
||||
end
|
||||
s.join(' AND ')
|
||||
end
|
||||
|
@ -853,15 +848,6 @@ class Query < ActiveRecord::Base
|
|||
date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil))
|
||||
end
|
||||
|
||||
# Returns a Date or Time from the given filter value
|
||||
def parse_date(arg)
|
||||
if arg.to_s =~ /\A\d{4}-\d{2}-\d{2}T/
|
||||
Time.parse(arg) rescue nil
|
||||
else
|
||||
Date.parse(arg) rescue nil
|
||||
end
|
||||
end
|
||||
|
||||
# Additional joins required for the given sort options
|
||||
def joins_for_order_statement(order_options)
|
||||
joins = []
|
||||
|
|
|
@ -40,7 +40,7 @@ class Repository < ActiveRecord::Base
|
|||
validates_length_of :identifier, :maximum => IDENTIFIER_MAX_LENGTH, :allow_blank => true
|
||||
validates_presence_of :identifier, :unless => Proc.new { |r| r.is_default? || r.set_as_default? }
|
||||
validates_uniqueness_of :identifier, :scope => :project_id, :allow_blank => true
|
||||
validates_exclusion_of :identifier, :in => %w(browse show entry raw changes annotate diff statistics graph revisions revision)
|
||||
validates_exclusion_of :identifier, :in => %w(show entry raw changes annotate diff show stats graph)
|
||||
# donwcase letters, digits, dashes, underscores but not digits only
|
||||
validates_format_of :identifier, :with => /\A(?!\d+$)[a-z0-9\-_]*\z/, :allow_blank => true
|
||||
# Checks if the SCM is enabled when creating a repository
|
||||
|
@ -194,13 +194,8 @@ class Repository < ActiveRecord::Base
|
|||
scm.entry(path, identifier)
|
||||
end
|
||||
|
||||
def scm_entries(path=nil, identifier=nil)
|
||||
scm.entries(path, identifier)
|
||||
end
|
||||
protected :scm_entries
|
||||
|
||||
def entries(path=nil, identifier=nil)
|
||||
entries = scm_entries(path, identifier)
|
||||
entries = scm.entries(path, identifier)
|
||||
load_entries_changesets(entries)
|
||||
entries
|
||||
end
|
||||
|
@ -292,8 +287,9 @@ class Repository < ActiveRecord::Base
|
|||
new_user_id = h[committer]
|
||||
if new_user_id && (new_user_id.to_i != user_id.to_i)
|
||||
new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
|
||||
Changeset.where(["repository_id = ? AND committer = ?", id, committer]).
|
||||
update_all("user_id = #{new_user_id.nil? ? 'NULL' : new_user_id}")
|
||||
Changeset.update_all(
|
||||
"user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }",
|
||||
["repository_id = ? AND committer = ?", id, committer])
|
||||
end
|
||||
end
|
||||
@committers = nil
|
||||
|
@ -412,7 +408,7 @@ class Repository < ActiveRecord::Base
|
|||
self.is_default = true
|
||||
end
|
||||
if is_default? && is_default_changed?
|
||||
Repository.where(["project_id = ?", project_id]).update_all(["is_default = ?", false])
|
||||
Repository.update_all(["is_default = ?", false], ["project_id = ?", project_id])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ class Repository::Bazaar < Repository
|
|||
scm.diff(path, rev, rev_to)
|
||||
end
|
||||
|
||||
def scm_entries(path=nil, identifier=nil)
|
||||
def entries(path=nil, identifier=nil)
|
||||
scm.bzr_path_encodig = log_encoding
|
||||
entries = scm.entries(path, identifier)
|
||||
if entries
|
||||
|
@ -80,9 +80,9 @@ class Repository::Bazaar < Repository
|
|||
end
|
||||
end
|
||||
end
|
||||
load_entries_changesets(entries)
|
||||
entries
|
||||
end
|
||||
protected :scm_entries
|
||||
|
||||
def fetch_changesets
|
||||
scm.bzr_path_encodig = log_encoding
|
||||
|
|
|
@ -47,7 +47,7 @@ class Repository::Cvs < Repository
|
|||
scm.entry(path, rev.nil? ? nil : rev.committed_on)
|
||||
end
|
||||
|
||||
def scm_entries(path=nil, identifier=nil)
|
||||
def entries(path=nil, identifier=nil)
|
||||
rev = nil
|
||||
if ! identifier.nil?
|
||||
rev = changesets.find_by_revision(identifier)
|
||||
|
@ -69,9 +69,9 @@ class Repository::Cvs < Repository
|
|||
end
|
||||
end
|
||||
end
|
||||
load_entries_changesets(entries)
|
||||
entries
|
||||
end
|
||||
protected :scm_entries
|
||||
|
||||
def cat(path, identifier=nil)
|
||||
rev = nil
|
||||
|
@ -138,9 +138,9 @@ class Repository::Cvs < Repository
|
|||
# is not exclusive at all.
|
||||
tmp_time = revision.time.clone
|
||||
unless filechanges.find_by_path_and_revision(
|
||||
scm.with_leading_slash(revision.paths[0][:path]),
|
||||
revision.paths[0][:revision]
|
||||
)
|
||||
scm.with_leading_slash(revision.paths[0][:path]),
|
||||
revision.paths[0][:revision]
|
||||
)
|
||||
cmt = Changeset.normalize_comments(revision.message, repo_log_encoding)
|
||||
author_utf8 = Changeset.to_utf8(revision.author, repo_log_encoding)
|
||||
cs = changesets.where(
|
||||
|
@ -150,7 +150,7 @@ class Repository::Cvs < Repository
|
|||
).first
|
||||
# create a new changeset....
|
||||
unless cs
|
||||
# we use a temporary revision number here (just for inserting)
|
||||
# we use a temporaray revision number here (just for inserting)
|
||||
# later on, we calculate a continous positive number
|
||||
tmp_time2 = tmp_time.clone.gmtime
|
||||
branch = revision.paths[0][:branch]
|
||||
|
@ -186,7 +186,7 @@ class Repository::Cvs < Repository
|
|||
order('committed_on ASC, id ASC').
|
||||
where("repository_id = ? AND revision LIKE 'tmp%'", id).
|
||||
each do |changeset|
|
||||
changeset.update_attribute :revision, next_revision_number
|
||||
changeset.update_attribute :revision, next_revision_number
|
||||
end
|
||||
end # transaction
|
||||
@current_revision_number = nil
|
||||
|
|
|
@ -45,7 +45,7 @@ class Repository::Darcs < Repository
|
|||
scm.entry(path, patch.nil? ? nil : patch.scmid)
|
||||
end
|
||||
|
||||
def scm_entries(path=nil, identifier=nil)
|
||||
def entries(path=nil, identifier=nil)
|
||||
patch = nil
|
||||
if ! identifier.nil?
|
||||
patch = changesets.find_by_revision(identifier)
|
||||
|
@ -66,9 +66,9 @@ class Repository::Darcs < Repository
|
|||
end
|
||||
end
|
||||
end
|
||||
load_entries_changesets(entries)
|
||||
entries
|
||||
end
|
||||
protected :scm_entries
|
||||
|
||||
def cat(path, identifier=nil)
|
||||
patch = identifier.nil? ? nil : changesets.find_by_revision(identifier.to_s)
|
||||
|
|
|
@ -93,10 +93,11 @@ class Repository::Git < Repository
|
|||
end
|
||||
end
|
||||
|
||||
def scm_entries(path=nil, identifier=nil)
|
||||
scm.entries(path, identifier, :report_last_commit => extra_report_last_commit)
|
||||
def entries(path=nil, identifier=nil)
|
||||
entries = scm.entries(path, identifier, :report_last_commit => extra_report_last_commit)
|
||||
load_entries_changesets(entries)
|
||||
entries
|
||||
end
|
||||
protected :scm_entries
|
||||
|
||||
# With SCMs that have a sequential commit numbering,
|
||||
# such as Subversion and Mercurial,
|
||||
|
@ -179,13 +180,10 @@ class Repository::Git < Repository
|
|||
# So, Redmine needs to scan revisions and database every time.
|
||||
#
|
||||
# This is replacing the one-after-one queries.
|
||||
# Find all revisions, that are in the database, and then remove them
|
||||
# from the revision array.
|
||||
# Find all revisions, that are in the database, and then remove them from the revision array.
|
||||
# Then later we won't need any conditions for db existence.
|
||||
# Query for several revisions at once, and remove them
|
||||
# from the revisions array, if they are there.
|
||||
# Do this in chunks, to avoid eventual memory problems
|
||||
# (in case of tens of thousands of commits).
|
||||
# Query for several revisions at once, and remove them from the revisions array, if they are there.
|
||||
# Do this in chunks, to avoid eventual memory problems (in case of tens of thousands of commits).
|
||||
# If there are no revisions (because the original code's algorithm filtered them),
|
||||
# then this part will be stepped over.
|
||||
# We make queries, just if there is any revision.
|
||||
|
@ -194,12 +192,13 @@ class Repository::Git < Repository
|
|||
revisions_copy = revisions.clone # revisions will change
|
||||
while offset < revisions_copy.size
|
||||
scmids = revisions_copy.slice(offset, limit).map{|x| x.scmid}
|
||||
recent_changesets_slice = changesets.where(:scmid => scmids)
|
||||
recent_changesets_slice = changesets.where(:scmid => scmids).all
|
||||
# Subtract revisions that redmine already knows about
|
||||
recent_revisions = recent_changesets_slice.map{|c| c.scmid}
|
||||
revisions.reject!{|r| recent_revisions.include?(r.scmid)}
|
||||
offset += limit
|
||||
end
|
||||
|
||||
revisions.each do |rev|
|
||||
transaction do
|
||||
# There is no search in the db for this revision, because above we ensured,
|
||||
|
@ -241,6 +240,7 @@ class Repository::Git < Repository
|
|||
def latest_changesets(path,rev,limit=10)
|
||||
revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false)
|
||||
return [] if revisions.nil? || revisions.empty?
|
||||
|
||||
changesets.where(:scmid => revisions.map {|c| c.scmid}).all
|
||||
end
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ class Repository::Mercurial < Repository
|
|||
|
||||
# Returns the readable identifier for the given mercurial changeset
|
||||
def self.format_changeset_identifier(changeset)
|
||||
"#{changeset.revision}:#{changeset.scmid[0, 12]}"
|
||||
"#{changeset.revision}:#{changeset.scmid}"
|
||||
end
|
||||
|
||||
# Returns the identifier for the given Mercurial changeset
|
||||
|
@ -71,28 +71,6 @@ class Repository::Mercurial < Repository
|
|||
super(cs, cs_to, ' ')
|
||||
end
|
||||
|
||||
def modify_entry_lastrev_identifier(entry)
|
||||
if entry.lastrev && entry.lastrev.identifier
|
||||
entry.lastrev.identifier = scmid_for_inserting_db(entry.lastrev.identifier)
|
||||
end
|
||||
end
|
||||
private :modify_entry_lastrev_identifier
|
||||
|
||||
def entry(path=nil, identifier=nil)
|
||||
entry = scm.entry(path, identifier)
|
||||
return nil if entry.nil?
|
||||
modify_entry_lastrev_identifier(entry)
|
||||
entry
|
||||
end
|
||||
|
||||
def scm_entries(path=nil, identifier=nil)
|
||||
entries = scm.entries(path, identifier)
|
||||
return nil if entries.nil?
|
||||
entries.each {|entry| modify_entry_lastrev_identifier(entry)}
|
||||
entries
|
||||
end
|
||||
protected :scm_entries
|
||||
|
||||
# Finds and returns a revision with a number or the beginning of a hash
|
||||
def find_changeset_by_name(name)
|
||||
return nil if name.blank?
|
||||
|
@ -122,28 +100,6 @@ class Repository::Mercurial < Repository
|
|||
all
|
||||
end
|
||||
|
||||
def is_short_id_in_db?
|
||||
return @is_short_id_in_db unless @is_short_id_in_db.nil?
|
||||
cs = changesets.first
|
||||
@is_short_id_in_db = (!cs.nil? && cs.scmid.length != 40)
|
||||
end
|
||||
private :is_short_id_in_db?
|
||||
|
||||
def scmid_for_inserting_db(scmid)
|
||||
is_short_id_in_db? ? scmid[0, 12] : scmid
|
||||
end
|
||||
|
||||
def nodes_in_branch(rev, branch_limit)
|
||||
scm.nodes_in_branch(rev, :limit => branch_limit).collect do |b|
|
||||
scmid_for_inserting_db(b)
|
||||
end
|
||||
end
|
||||
|
||||
def tag_scmid(rev)
|
||||
scmid = scm.tagmap[rev]
|
||||
scmid.nil? ? nil : scmid_for_inserting_db(scmid)
|
||||
end
|
||||
|
||||
def latest_changesets_cond(path, rev, limit)
|
||||
cond, args = [], []
|
||||
if scm.branchmap.member? rev
|
||||
|
@ -155,11 +111,11 @@ class Repository::Mercurial < Repository
|
|||
# Revisions in root directory and sub directory are not equal.
|
||||
# So, in order to get correct limit, we need to get all revisions.
|
||||
# But, it is very heavy.
|
||||
# Mercurial does not treat directory.
|
||||
# Mercurial does not treat direcotry.
|
||||
# So, "hg log DIR" is very heavy.
|
||||
branch_limit = path.blank? ? limit : ( limit * 5 )
|
||||
args << nodes_in_branch(rev, branch_limit)
|
||||
elsif last = rev ? find_changeset_by_name(tag_scmid(rev) || rev) : nil
|
||||
args << scm.nodes_in_branch(rev, :limit => branch_limit)
|
||||
elsif last = rev ? find_changeset_by_name(scm.tagmap[rev] || rev) : nil
|
||||
cond << "#{Changeset.table_name}.id <= ?"
|
||||
args << last.id
|
||||
end
|
||||
|
@ -185,23 +141,16 @@ class Repository::Mercurial < Repository
|
|||
(db_rev + 1).step(scm_rev, FETCH_AT_ONCE) do |i|
|
||||
scm.each_revision('', i, [i + FETCH_AT_ONCE - 1, scm_rev].min) do |re|
|
||||
transaction do
|
||||
parents = (re.parents || []).collect do |rp|
|
||||
find_changeset_by_name(scmid_for_inserting_db(rp))
|
||||
end.compact
|
||||
parents = (re.parents || []).collect{|rp| find_changeset_by_name(rp)}.compact
|
||||
cs = Changeset.create(:repository => self,
|
||||
:revision => re.revision,
|
||||
:scmid => scmid_for_inserting_db(re.scmid),
|
||||
:scmid => re.scmid,
|
||||
:committer => re.author,
|
||||
:committed_on => re.time,
|
||||
:comments => re.message,
|
||||
:parents => parents)
|
||||
unless cs.new_record?
|
||||
re.paths.each do |e|
|
||||
if from_revision = e[:from_revision]
|
||||
e[:from_revision] = scmid_for_inserting_db(from_revision)
|
||||
end
|
||||
cs.create_change(e)
|
||||
end
|
||||
re.paths.each { |e| cs.create_change(e) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -90,13 +90,12 @@ class Repository::Subversion < Repository
|
|||
|
||||
def load_entries_changesets(entries)
|
||||
return unless entries
|
||||
entries_with_identifier =
|
||||
entries.select {|entry| entry.lastrev && entry.lastrev.identifier.present?}
|
||||
|
||||
entries_with_identifier = entries.select {|entry| entry.lastrev && entry.lastrev.identifier.present?}
|
||||
identifiers = entries_with_identifier.map {|entry| entry.lastrev.identifier}.compact.uniq
|
||||
|
||||
if identifiers.any?
|
||||
changesets_by_identifier =
|
||||
changesets.where(:revision => identifiers).
|
||||
includes(:user, :repository).group_by(&:revision)
|
||||
changesets_by_identifier = changesets.where(:revision => identifiers).includes(:user, :repository).all.group_by(&:revision)
|
||||
entries_with_identifier.each do |entry|
|
||||
if m = changesets_by_identifier[entry.lastrev.identifier]
|
||||
entry.changeset = m.first
|
||||
|
|
|
@ -83,9 +83,7 @@ class Setting < ActiveRecord::Base
|
|||
|
||||
validates_uniqueness_of :name
|
||||
validates_inclusion_of :name, :in => @@available_settings.keys
|
||||
validates_numericality_of :value, :only_integer => true, :if => Proc.new { |setting|
|
||||
(s = @@available_settings[setting.name]) && s['format'] == 'int'
|
||||
}
|
||||
validates_numericality_of :value, :only_integer => true, :if => Proc.new { |setting| @@available_settings[setting.name]['format'] == 'int' }
|
||||
|
||||
# Hash used to cache setting values
|
||||
@cached_settings = {}
|
||||
|
@ -242,10 +240,9 @@ private
|
|||
def self.find_or_default(name)
|
||||
name = name.to_s
|
||||
raise "There's no setting named #{name}" unless @@available_settings.has_key?(name)
|
||||
setting = where(:name => name).first
|
||||
setting = find_by_name(name)
|
||||
unless setting
|
||||
setting = new
|
||||
setting.name = name
|
||||
setting = new(:name => name)
|
||||
setting.value = @@available_settings[name]['default']
|
||||
end
|
||||
setting
|
||||
|
|
|
@ -77,16 +77,6 @@ class TimeEntry < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def safe_attributes=(attrs, user=User.current)
|
||||
attrs = super
|
||||
if !new_record? && issue && issue.project_id != project_id
|
||||
if user.allowed_to?(:log_time, issue.project)
|
||||
self.project_id = issue.project_id
|
||||
end
|
||||
end
|
||||
attrs
|
||||
end
|
||||
|
||||
def set_project_if_nil
|
||||
self.project = issue.project if issue && project.nil?
|
||||
end
|
||||
|
|
|
@ -91,10 +91,8 @@ class TimeEntryQuery < Query
|
|||
def available_columns
|
||||
return @available_columns if @available_columns
|
||||
@available_columns = self.class.available_columns.dup
|
||||
@available_columns += TimeEntryCustomField.visible.
|
||||
map {|cf| QueryCustomFieldColumn.new(cf) }
|
||||
@available_columns += IssueCustomField.visible.
|
||||
map {|cf| QueryAssociationCustomFieldColumn.new(:issue, cf) }
|
||||
@available_columns += TimeEntryCustomField.visible.all.map {|cf| QueryCustomFieldColumn.new(cf) }
|
||||
@available_columns += IssueCustomField.visible.all.map {|cf| QueryAssociationCustomFieldColumn.new(:issue, cf) }
|
||||
@available_columns
|
||||
end
|
||||
|
||||
|
|
|
@ -63,7 +63,8 @@ class Tracker < ActiveRecord::Base
|
|||
connection.select_rows("SELECT DISTINCT old_status_id, new_status_id FROM #{WorkflowTransition.table_name} WHERE tracker_id = #{id} AND type = 'WorkflowTransition'").
|
||||
flatten.
|
||||
uniq
|
||||
@issue_statuses = IssueStatus.where(:id => ids).all.sort
|
||||
|
||||
@issue_statuses = IssueStatus.find_all_by_id(ids).sort
|
||||
end
|
||||
|
||||
def disabled_core_fields
|
||||
|
|
|
@ -32,11 +32,6 @@ class User < Principal
|
|||
:order => %w(firstname lastname id),
|
||||
:setting_order => 2
|
||||
},
|
||||
:firstinitial_lastname => {
|
||||
:string => '#{firstname.to_s.gsub(/(([[:alpha:]])[[:alpha:]]*\.?)/, \'\2.\')} #{lastname}',
|
||||
:order => %w(firstname lastname id),
|
||||
:setting_order => 2
|
||||
},
|
||||
:firstname => {
|
||||
:string => '#{firstname}',
|
||||
:order => %w(firstname id),
|
||||
|
@ -73,10 +68,8 @@ class User < Principal
|
|||
['none', :label_user_mail_option_none]
|
||||
]
|
||||
|
||||
has_and_belongs_to_many :groups,
|
||||
:join_table => "#{table_name_prefix}groups_users#{table_name_suffix}",
|
||||
:after_add => Proc.new {|user, group| group.user_added(user)},
|
||||
:after_remove => Proc.new {|user, group| group.user_removed(user)}
|
||||
has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
|
||||
:after_remove => Proc.new {|user, group| group.user_removed(user)}
|
||||
has_many :changesets, :dependent => :nullify
|
||||
has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
|
||||
has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'"
|
||||
|
@ -314,18 +307,6 @@ class User < Principal
|
|||
@time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
|
||||
end
|
||||
|
||||
def force_default_language?
|
||||
Setting.force_default_language_for_loggedin?
|
||||
end
|
||||
|
||||
def language
|
||||
if force_default_language?
|
||||
Setting.default_language
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def wants_comments_in_reverse_order?
|
||||
self.pref[:comments_sorting] == 'desc'
|
||||
end
|
||||
|
@ -384,10 +365,10 @@ class User < Principal
|
|||
# Find a user account by matching the exact login and then a case-insensitive
|
||||
# version. Exact matches will be given priority.
|
||||
def self.find_by_login(login)
|
||||
login = Redmine::CodesetUtil.replace_invalid_utf8(login.to_s)
|
||||
if login.present?
|
||||
login = login.to_s
|
||||
# First look for an exact match
|
||||
user = where(:login => login).detect {|u| u.login == login}
|
||||
user = where(:login => login).all.detect {|u| u.login == login}
|
||||
unless user
|
||||
# Fail over to case-insensitive if none was found
|
||||
user = where("LOWER(login) = ?", login.downcase).first
|
||||
|
@ -626,11 +607,11 @@ class User < Principal
|
|||
end
|
||||
|
||||
def self.current=(user)
|
||||
RequestStore.store[:current_user] = user
|
||||
Thread.current[:current_user] = user
|
||||
end
|
||||
|
||||
def self.current
|
||||
RequestStore.store[:current_user] ||= User.anonymous
|
||||
Thread.current[:current_user] ||= User.anonymous
|
||||
end
|
||||
|
||||
# Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
|
||||
|
@ -683,27 +664,23 @@ class User < Principal
|
|||
return if self.id.nil?
|
||||
|
||||
substitute = User.anonymous
|
||||
Attachment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
|
||||
Comment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
|
||||
Issue.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
|
||||
Issue.where(['assigned_to_id = ?', id]).update_all('assigned_to_id = NULL')
|
||||
Journal.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
|
||||
JournalDetail.
|
||||
where(["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]).
|
||||
update_all(['old_value = ?', substitute.id.to_s])
|
||||
JournalDetail.
|
||||
where(["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]).
|
||||
update_all(['value = ?', substitute.id.to_s])
|
||||
Message.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
|
||||
News.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
|
||||
Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
|
||||
Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
|
||||
Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
|
||||
Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
|
||||
Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
|
||||
JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]
|
||||
JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]
|
||||
Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
|
||||
News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
|
||||
# Remove private queries and keep public ones
|
||||
::Query.delete_all ['user_id = ? AND visibility = ?', id, ::Query::VISIBILITY_PRIVATE]
|
||||
::Query.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
|
||||
TimeEntry.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
|
||||
::Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
|
||||
TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
|
||||
Token.delete_all ['user_id = ?', id]
|
||||
Watcher.delete_all ['user_id = ?', id]
|
||||
WikiContent.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
|
||||
WikiContent::Version.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
|
||||
WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
|
||||
WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
|
||||
end
|
||||
|
||||
# Return password digest
|
||||
|
|
|
@ -203,7 +203,7 @@ class Version < ActiveRecord::Base
|
|||
["(CASE WHEN #{table}.effective_date IS NULL THEN 1 ELSE 0 END)", "#{table}.effective_date", "#{table}.name", "#{table}.id"]
|
||||
end
|
||||
|
||||
scope :sorted, lambda { order(fields_for_order_statement) }
|
||||
scope :sorted, order(fields_for_order_statement)
|
||||
|
||||
# Returns the sharings that +user+ can set the version to
|
||||
def allowed_sharings(user = User.current)
|
||||
|
@ -232,7 +232,7 @@ class Version < ActiveRecord::Base
|
|||
unless @issue_count
|
||||
@open_issues_count = 0
|
||||
@closed_issues_count = 0
|
||||
fixed_issues.group(:status).count.each do |status, count|
|
||||
fixed_issues.count(:all, :group => :status).each do |status, count|
|
||||
if status.is_closed?
|
||||
@closed_issues_count += count
|
||||
else
|
||||
|
@ -256,7 +256,7 @@ class Version < ActiveRecord::Base
|
|||
|
||||
# Returns the average estimated time of assigned issues
|
||||
# or 1 if no issue has an estimated time
|
||||
# Used to weight unestimated issues in progress calculation
|
||||
# Used to weigth unestimated issues in progress calculation
|
||||
def estimated_average
|
||||
if @estimated_average.nil?
|
||||
average = fixed_issues.average(:estimated_hours).to_f
|
||||
|
|
|
@ -42,7 +42,7 @@ class Watcher < ActiveRecord::Base
|
|||
prune_single_user(options[:user], options)
|
||||
else
|
||||
pruned = 0
|
||||
User.where("id IN (SELECT DISTINCT user_id FROM #{table_name})").each do |user|
|
||||
User.where("id IN (SELECT DISTINCT user_id FROM #{table_name})").all.each do |user|
|
||||
pruned += prune_single_user(user, options)
|
||||
end
|
||||
pruned
|
||||
|
@ -60,14 +60,13 @@ class Watcher < ActiveRecord::Base
|
|||
def self.prune_single_user(user, options={})
|
||||
return unless user.is_a?(User)
|
||||
pruned = 0
|
||||
where(:user_id => user.id).each do |watcher|
|
||||
where(:user_id => user.id).all.each do |watcher|
|
||||
next if watcher.watchable.nil?
|
||||
|
||||
if options.has_key?(:project)
|
||||
unless watcher.watchable.respond_to?(:project) &&
|
||||
watcher.watchable.project == options[:project]
|
||||
next
|
||||
end
|
||||
next unless watcher.watchable.respond_to?(:project) && watcher.watchable.project == options[:project]
|
||||
end
|
||||
|
||||
if watcher.watchable.respond_to?(:visible?)
|
||||
unless watcher.watchable.visible?(user)
|
||||
watcher.destroy
|
||||
|
|
|
@ -40,7 +40,7 @@ class WikiContent < ActiveRecord::Base
|
|||
page.nil? ? [] : page.attachments
|
||||
end
|
||||
|
||||
# Returns the mail addresses of users that should be notified
|
||||
# Returns the mail adresses of users that should be notified
|
||||
def recipients
|
||||
notified = project.notified_users
|
||||
notified.reject! {|user| !visible?(user)}
|
||||
|
|
|
@ -82,12 +82,12 @@ class WikiPage < ActiveRecord::Base
|
|||
# Manage redirects if the title has changed
|
||||
if !@previous_title.blank? && (@previous_title != title) && !new_record?
|
||||
# Update redirects that point to the old title
|
||||
wiki.redirects.where(:redirects_to => @previous_title).each do |r|
|
||||
wiki.redirects.find_all_by_redirects_to(@previous_title).each do |r|
|
||||
r.redirects_to = title
|
||||
r.title == r.redirects_to ? r.destroy : r.save
|
||||
end
|
||||
# Remove redirects for the new title
|
||||
wiki.redirects.where(:title => title).each(&:destroy)
|
||||
wiki.redirects.find_all_by_title(title).each(&:destroy)
|
||||
# Create a redirect to the new title
|
||||
wiki.redirects << WikiRedirect.new(:title => @previous_title, :redirects_to => title) unless redirect_existing_links == "0"
|
||||
@previous_title = nil
|
||||
|
@ -96,7 +96,7 @@ class WikiPage < ActiveRecord::Base
|
|||
|
||||
def remove_redirects
|
||||
# Remove redirects to this page
|
||||
wiki.redirects.where(:redirects_to => title).each(&:destroy)
|
||||
wiki.redirects.find_all_by_redirects_to(title).each(&:destroy)
|
||||
end
|
||||
|
||||
def pretty_title
|
||||
|
@ -104,11 +104,9 @@ class WikiPage < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def content_for_version(version=nil)
|
||||
if content
|
||||
result = content.versions.find_by_version(version.to_i) if version
|
||||
result ||= content
|
||||
result
|
||||
end
|
||||
result = content.versions.find_by_version(version.to_i) if version
|
||||
result ||= content
|
||||
result
|
||||
end
|
||||
|
||||
def diff(version_to=nil, version_from=nil)
|
||||
|
|
|
@ -19,43 +19,20 @@ class WorkflowPermission < WorkflowRule
|
|||
validates_inclusion_of :rule, :in => %w(readonly required)
|
||||
validate :validate_field_name
|
||||
|
||||
# Returns the workflow permissions for the given trackers and roles
|
||||
# grouped by status_id
|
||||
# Replaces the workflow permissions for the given tracker and role
|
||||
#
|
||||
# Example:
|
||||
# WorkflowPermission.rules_by_status_id trackers, roles
|
||||
# # => {1 => {'start_date' => 'required', 'due_date' => 'readonly'}}
|
||||
def self.rules_by_status_id(trackers, roles)
|
||||
WorkflowPermission.where(:tracker_id => trackers.map(&:id), :role_id => roles.map(&:id)).inject({}) do |h, w|
|
||||
h[w.old_status_id] ||= {}
|
||||
h[w.old_status_id][w.field_name] ||= []
|
||||
h[w.old_status_id][w.field_name] << w.rule
|
||||
h
|
||||
end
|
||||
end
|
||||
# WorkflowPermission.replace_permissions role, tracker, {'due_date' => {'1' => 'readonly', '2' => 'required'}}
|
||||
def self.replace_permissions(tracker, role, permissions)
|
||||
destroy_all(:tracker_id => tracker.id, :role_id => role.id)
|
||||
|
||||
# Replaces the workflow permissions for the given trackers and roles
|
||||
#
|
||||
# Example:
|
||||
# WorkflowPermission.replace_permissions trackers, roles, {'1' => {'start_date' => 'required', 'due_date' => 'readonly'}}
|
||||
def self.replace_permissions(trackers, roles, permissions)
|
||||
trackers = Array.wrap trackers
|
||||
roles = Array.wrap roles
|
||||
|
||||
transaction do
|
||||
permissions.each { |status_id, rule_by_field|
|
||||
rule_by_field.each { |field, rule|
|
||||
destroy_all(:tracker_id => trackers.map(&:id), :role_id => roles.map(&:id), :old_status_id => status_id, :field_name => field)
|
||||
if rule.present?
|
||||
trackers.each do |tracker|
|
||||
roles.each do |role|
|
||||
WorkflowPermission.create(:role_id => role.id, :tracker_id => tracker.id, :old_status_id => status_id, :field_name => field, :rule => rule)
|
||||
end
|
||||
end
|
||||
end
|
||||
}
|
||||
permissions.each { |field, rule_by_status_id|
|
||||
rule_by_status_id.each { |status_id, rule|
|
||||
if rule.present?
|
||||
WorkflowPermission.create(:role_id => role.id, :tracker_id => tracker.id, :old_status_id => status_id, :field_name => field, :rule => rule)
|
||||
end
|
||||
}
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
protected
|
||||
|
|
|
@ -21,8 +21,9 @@ class WorkflowTransition < WorkflowRule
|
|||
# Returns workflow transitions count by tracker and role
|
||||
def self.count_by_tracker_and_role
|
||||
counts = connection.select_all("SELECT role_id, tracker_id, count(id) AS c FROM #{table_name} WHERE type = 'WorkflowTransition' GROUP BY role_id, tracker_id")
|
||||
roles = Role.sorted
|
||||
trackers = Tracker.sorted
|
||||
roles = Role.sorted.all
|
||||
trackers = Tracker.sorted.all
|
||||
|
||||
result = []
|
||||
trackers.each do |tracker|
|
||||
t = []
|
||||
|
@ -32,71 +33,7 @@ class WorkflowTransition < WorkflowRule
|
|||
end
|
||||
result << [tracker, t]
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def self.replace_transitions(trackers, roles, transitions)
|
||||
trackers = Array.wrap trackers
|
||||
roles = Array.wrap roles
|
||||
|
||||
transaction do
|
||||
records = WorkflowTransition.where(:tracker_id => trackers.map(&:id), :role_id => roles.map(&:id)).all
|
||||
|
||||
transitions.each do |old_status_id, transitions_by_new_status|
|
||||
transitions_by_new_status.each do |new_status_id, transition_by_rule|
|
||||
transition_by_rule.each do |rule, transition|
|
||||
trackers.each do |tracker|
|
||||
roles.each do |role|
|
||||
w = records.select {|r|
|
||||
r.old_status_id == old_status_id.to_i &&
|
||||
r.new_status_id == new_status_id.to_i &&
|
||||
r.tracker_id == tracker.id &&
|
||||
r.role_id == role.id &&
|
||||
!r.destroyed?
|
||||
}
|
||||
|
||||
if rule == 'always'
|
||||
w = w.select {|r| !r.author && !r.assignee}
|
||||
else
|
||||
w = w.select {|r| r.author || r.assignee}
|
||||
end
|
||||
if w.size > 1
|
||||
w[1..-1].each(&:destroy)
|
||||
end
|
||||
w = w.first
|
||||
|
||||
if transition == "1" || transition == true
|
||||
unless w
|
||||
w = WorkflowTransition.new(:old_status_id => old_status_id, :new_status_id => new_status_id, :tracker_id => tracker.id, :role_id => role.id)
|
||||
records << w
|
||||
end
|
||||
w.author = true if rule == "author"
|
||||
w.assignee = true if rule == "assignee"
|
||||
w.save if w.changed?
|
||||
elsif w
|
||||
if rule == 'always'
|
||||
w.destroy
|
||||
elsif rule == 'author'
|
||||
if w.assignee
|
||||
w.author = false
|
||||
w.save if w.changed?
|
||||
else
|
||||
w.destroy
|
||||
end
|
||||
elsif rule == 'assignee'
|
||||
if w.author
|
||||
w.assignee = false
|
||||
w.save if w.changed?
|
||||
else
|
||||
w.destroy
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,10 +16,7 @@
|
|||
<p><%= f.text_field :firstname, :required => true %></p>
|
||||
<p><%= f.text_field :lastname, :required => true %></p>
|
||||
<p><%= f.text_field :mail, :required => true %></p>
|
||||
|
||||
<% unless @user.force_default_language? %>
|
||||
<p><%= f.select :language, lang_options_for_select %></p>
|
||||
<% end %>
|
||||
|
||||
<% if Setting.openid? %>
|
||||
<p><%= f.text_field :identity_url %></p>
|
||||
|
|
|
@ -9,55 +9,11 @@
|
|||
<%= content_tag('span', link_to(h(plugin.url), plugin.url), :class => 'url') unless plugin.url.blank? %>
|
||||
</td>
|
||||
<td class="author"><%= plugin.author_url.blank? ? h(plugin.author) : link_to(h(plugin.author), plugin.author_url) %></td>
|
||||
<td class="version"><span class="icon"><%= plugin.version %></span></td>
|
||||
<td class="version"><%=h plugin.version %></td>
|
||||
<td class="configure"><%= link_to(l(:button_configure), plugin_settings_path(plugin)) if plugin.configurable? %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</table>
|
||||
<p><a href="#" id="check-for-updates"><%= l(:label_check_for_updates) %></a></p>
|
||||
<% else %>
|
||||
<p class="nodata"><%= l(:label_no_data) %></p>
|
||||
<% end %>
|
||||
|
||||
<%= javascript_tag do %>
|
||||
$(document).ready(function(){
|
||||
$("#check-for-updates").click(function(e){
|
||||
e.preventDefault();
|
||||
$.ajax({
|
||||
dataType: "jsonp",
|
||||
url: "http://www.redmine.org/plugins/check_updates",
|
||||
data: <%= raw_json plugin_data_for_updates(@plugins) %>,
|
||||
timeout: 3000,
|
||||
beforeSend: function(){
|
||||
$('#ajax-indicator').show();
|
||||
},
|
||||
success: function(data){
|
||||
$('#ajax-indicator').hide();
|
||||
$("table.plugins td.version span").addClass("unknown");
|
||||
$.each(data, function(plugin_id, plugin_data){
|
||||
var s = $("tr#plugin-"+plugin_id+" td.version span");
|
||||
s.removeClass("icon-checked icon-warning unknown");
|
||||
if (plugin_data.url) {
|
||||
if (s.parent("a").length>0) {
|
||||
s.unwrap();
|
||||
}
|
||||
s.addClass("found");
|
||||
s.wrap($("<a></a>").attr("href", plugin_data.url).attr("target", "_blank"));
|
||||
}
|
||||
if (plugin_data.c == s.text()) {
|
||||
s.addClass("icon-checked");
|
||||
} else if (plugin_data.c) {
|
||||
s.addClass("icon-warning");
|
||||
s.attr("title", "<%= escape_javascript l(:label_latest_compatible_version) %>: "+plugin_data.c);
|
||||
}
|
||||
});
|
||||
$("table.plugins td.version span.unknown").addClass("icon-help").attr("title", "<%= escape_javascript l(:label_unknown_plugin) %>");
|
||||
},
|
||||
error: function(){
|
||||
$('#ajax-indicator').hide();
|
||||
alert("Unable to retrieve plugin informations from www.redmine.org");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
<% end if @plugins.any? %>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<%= raw @issues.map {|issue| {
|
||||
'id' => issue.id,
|
||||
'label' => "#{issue.tracker} ##{issue.id}: #{issue.subject.to_s.truncate(60)}",
|
||||
'label' => "#{issue.tracker} ##{issue.id}: #{truncate issue.subject.to_s, :length => 60}",
|
||||
'value' => issue.id
|
||||
}
|
||||
}.to_json
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<% selected_tab = params[:tab] ? params[:tab].to_s : tabs.first[:name] %>
|
||||
|
||||
<div class="tabs">
|
||||
<ul>
|
||||
<% tabs.each do |tab| -%>
|
||||
|
@ -8,8 +10,8 @@
|
|||
<% end -%>
|
||||
</ul>
|
||||
<div class="tabs-buttons" style="display:none;">
|
||||
<button class="tab-left" onclick="moveTabLeft(this); return false;"></button>
|
||||
<button class="tab-right" onclick="moveTabRight(this); return false;"></button>
|
||||
<button class="tab-left" onclick="moveTabLeft(this);"></button>
|
||||
<button class="tab-right" onclick="moveTabRight(this);"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
xml.instruct!
|
||||
xml.feed "xmlns" => "http://www.w3.org/2005/Atom" do
|
||||
xml.title truncate_single_line_raw(@title, 100)
|
||||
xml.title truncate_single_line(@title, :length => 100)
|
||||
xml.link "rel" => "self", "href" => url_for(params.merge(:only_path => false))
|
||||
xml.link "rel" => "alternate", "href" => url_for(params.merge(:only_path => false, :format => nil, :key => nil))
|
||||
xml.id url_for(:controller => 'welcome', :only_path => false)
|
||||
xml.icon favicon_url
|
||||
xml.updated((@items.first ? @items.first.event_datetime : Time.now).xmlschema)
|
||||
xml.author { xml.name "#{Setting.app_title}" }
|
||||
xml.generator(:uri => Redmine::Info.url) { xml.text! Redmine::Info.app_name; }
|
||||
|
@ -12,9 +11,9 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom" do
|
|||
xml.entry do
|
||||
url = url_for(item.event_url(:only_path => false))
|
||||
if @project
|
||||
xml.title truncate_single_line_raw(item.event_title, 100)
|
||||
xml.title truncate_single_line(item.event_title, :length => 100)
|
||||
else
|
||||
xml.title truncate_single_line_raw("#{item.project} - #{item.event_title}", 100)
|
||||
xml.title truncate_single_line("#{item.project} - #{item.event_title}", :length => 100)
|
||||
end
|
||||
xml.link "rel" => "alternate", "href" => url
|
||||
xml.id url
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
<%= error_messages_for 'custom_field' %>
|
||||
|
||||
<% if @custom_field.is_a?(IssueCustomField) %>
|
||||
<div class="splitcontentleft">
|
||||
<div class="box tabular">
|
||||
<p><%= f.select :field_format, custom_field_formats_for_select(@custom_field), {}, :disabled => !@custom_field.new_record? %></p>
|
||||
<p><%= f.text_field :name, :required => true %></p>
|
||||
<p><%= f.text_area :description, :rows => 7 %></p>
|
||||
<% end %>
|
||||
|
||||
<% if @custom_field.format.multiple_supported %>
|
||||
<div class="box tabular">
|
||||
<p><%= f.text_field :name, :required => true %></p>
|
||||
<p><%= f.select :field_format, custom_field_formats_for_select(@custom_field), {}, :disabled => !@custom_field.new_record? %></p>
|
||||
|
||||
<% if @custom_field.format_in? 'list', 'user', 'version' %>
|
||||
<p>
|
||||
<%= f.check_box :multiple %>
|
||||
<% if !@custom_field.new_record? && @custom_field.multiple %>
|
||||
|
@ -15,38 +17,56 @@
|
|||
</p>
|
||||
<% end %>
|
||||
|
||||
<%= render_custom_field_format_partial f, @custom_field %>
|
||||
<% unless @custom_field.format_in? 'list', 'bool', 'date', 'user', 'version' %>
|
||||
<p><label for="custom_field_min_length"><%=l(:label_min_max_length)%></label>
|
||||
<%= f.text_field :min_length, :size => 5, :no_label => true %> -
|
||||
<%= f.text_field :max_length, :size => 5, :no_label => true %><br />(<%=l(:text_min_max_length_info)%>)</p>
|
||||
<p><%= f.text_field :regexp, :size => 50 %><br />(<%=l(:text_regexp_info)%>)</p>
|
||||
<% end %>
|
||||
|
||||
<% if @custom_field.format_in? 'list' %>
|
||||
<p>
|
||||
<%= f.text_area :possible_values, :value => @custom_field.possible_values.to_a.join("\n"), :rows => 15 %>
|
||||
<em class="info"><%= l(:text_custom_field_possible_values_info) %></em>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<% case @custom_field.field_format %>
|
||||
<% when 'bool' %>
|
||||
<p><%= f.check_box(:default_value) %></p>
|
||||
<% when 'text' %>
|
||||
<p><%= f.text_area(:default_value, :rows => 8) %></p>
|
||||
<% when 'date' %>
|
||||
<p><%= f.text_field(:default_value, :size => 10) %></p>
|
||||
<%= calendar_for('custom_field_default_value') %>
|
||||
<% when 'user', 'version' %>
|
||||
<% else %>
|
||||
<p><%= f.text_field(:default_value) %></p>
|
||||
<% end %>
|
||||
|
||||
<%= call_hook(:view_custom_fields_form_upper_box, :custom_field => @custom_field, :form => f) %>
|
||||
</div>
|
||||
<p><%= submit_tag l(:button_save) %></p>
|
||||
</div>
|
||||
|
||||
<div class="splitcontentright">
|
||||
<div class="box tabular">
|
||||
<% case @custom_field.class.name
|
||||
when "IssueCustomField" %>
|
||||
<p><%= f.check_box :is_required %></p>
|
||||
<p><%= f.check_box :is_for_all, :data => {:disables => '#custom_field_project_ids input'} %></p>
|
||||
<p><%= f.check_box :is_for_all %></p>
|
||||
<p><%= f.check_box :is_filter %></p>
|
||||
<% if @custom_field.format.searchable_supported %>
|
||||
<p><%= f.check_box :searchable %></p>
|
||||
<% end %>
|
||||
<p>
|
||||
<label><%= l(:field_visible) %></label>
|
||||
<label class="block">
|
||||
<%= radio_button_tag 'custom_field[visible]', 1, @custom_field.visible?, :id => 'custom_field_visible_on',
|
||||
:data => {:disables => '.custom_field_role input'} %>
|
||||
<%= radio_button_tag 'custom_field[visible]', 1, @custom_field.visible?, :id => 'custom_field_visible_on' %>
|
||||
<%= l(:label_visibility_public) %>
|
||||
</label>
|
||||
<label class="block">
|
||||
<%= radio_button_tag 'custom_field[visible]', 0, !@custom_field.visible?, :id => 'custom_field_visible_off',
|
||||
:data => {:enables => '.custom_field_role input'} %>
|
||||
<%= radio_button_tag 'custom_field[visible]', 0, !@custom_field.visible?, :id => 'custom_field_visible_off' %>
|
||||
<%= l(:label_visibility_roles) %>:
|
||||
</label>
|
||||
<% Role.givable.sorted.each do |role| %>
|
||||
<label class="block custom_field_role" style="padding-left:2em;">
|
||||
<%= check_box_tag 'custom_field[role_ids][]', role.id, @custom_field.roles.include?(role), :id => nil %>
|
||||
<%= check_box_tag 'custom_field[role_ids][]', role.id, @custom_field.roles.include?(role) %>
|
||||
<%= role.name %>
|
||||
</label>
|
||||
<% end %>
|
||||
|
@ -62,9 +82,7 @@ when "IssueCustomField" %>
|
|||
<% when "ProjectCustomField" %>
|
||||
<p><%= f.check_box :is_required %></p>
|
||||
<p><%= f.check_box :visible %></p>
|
||||
<% if @custom_field.format.searchable_supported %>
|
||||
<p><%= f.check_box :searchable %></p>
|
||||
<% end %>
|
||||
<p><%= f.check_box :is_filter %></p>
|
||||
|
||||
<% when "VersionCustomField" %>
|
||||
|
@ -85,10 +103,13 @@ when "IssueCustomField" %>
|
|||
<% end %>
|
||||
<%= call_hook(:"view_custom_fields_form_#{@custom_field.type.to_s.underscore}", :custom_field => @custom_field, :form => f) %>
|
||||
</div>
|
||||
<%= submit_tag l(:button_save) %>
|
||||
|
||||
<% if @custom_field.is_a?(IssueCustomField) %>
|
||||
<fieldset class="box" id="custom_field_tracker_ids"><legend><%=l(:label_tracker_plural)%></legend>
|
||||
<% Tracker.sorted.each do |tracker| %>
|
||||
</div>
|
||||
<div class="splitcontentright">
|
||||
<fieldset class="box"><legend><%=l(:label_tracker_plural)%></legend>
|
||||
<% Tracker.sorted.all.each do |tracker| %>
|
||||
<%= check_box_tag "custom_field[tracker_ids][]",
|
||||
tracker.id,
|
||||
(@custom_field.trackers.include? tracker),
|
||||
|
@ -98,7 +119,6 @@ when "IssueCustomField" %>
|
|||
</label>
|
||||
<% end %>
|
||||
<%= hidden_field_tag "custom_field[tracker_ids][]", '' %>
|
||||
<p><%= check_all_links 'custom_field_tracker_ids' %></p>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="box" id="custom_field_project_ids"><legend><%= l(:label_project_plural) %></legend>
|
||||
|
@ -108,7 +128,20 @@ when "IssueCustomField" %>
|
|||
<%= hidden_field_tag('custom_field[project_ids][]', '', :id => nil) %>
|
||||
<p><%= check_all_links 'custom_field_project_ids' %></p>
|
||||
</fieldset>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% include_calendar_headers_tags %>
|
||||
|
||||
<%= javascript_tag do %>
|
||||
function toggleCustomFieldRoles(){
|
||||
var checked = $("#custom_field_visible_on").is(':checked');
|
||||
$('.custom_field_role input').attr('disabled', checked);
|
||||
}
|
||||
$("#custom_field_visible_on, #custom_field_visible_off").change(toggleCustomFieldRoles);
|
||||
$(document).ready(toggleCustomFieldRoles);
|
||||
|
||||
$("#custom_field_is_for_all").change(function(){
|
||||
$("#custom_field_project_ids input").attr("disabled", $(this).is(":checked"));
|
||||
}).trigger('change');
|
||||
<% end %>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<% (@custom_fields_by_type[tab[:name]] || []).sort.each do |custom_field| -%>
|
||||
<tr class="<%= cycle("odd", "even") %>">
|
||||
<td class="name"><%= link_to h(custom_field.name), edit_custom_field_path(custom_field) %></td>
|
||||
<td><%= l(custom_field.format.label) %></td>
|
||||
<td><%= l(Redmine::CustomFieldFormat.label_for(custom_field.field_format)) %></td>
|
||||
<td><%= checked_image custom_field.is_required? %></td>
|
||||
<% if tab[:name] == 'IssueCustomField' %>
|
||||
<td><%= checked_image custom_field.is_for_all? %></td>
|
||||
|
@ -28,3 +28,5 @@
|
|||
<% end; reset_cycle %>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><%= link_to l(:label_custom_field_new), new_custom_field_path(:type => tab[:name]), :class => 'icon icon-add' %></p>
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
<p><%= f.select :default_value, [[]]+@custom_field.possible_values_options %></p>
|
||||
<p><%= f.text_field :url_pattern, :size => 50, :label => :label_link_values_to %></p>
|
||||
<p><%= edit_tag_style_tag f %></p>
|
|
@ -1,3 +0,0 @@
|
|||
<p><%= f.text_field(:default_value, :size => 10) %></p>
|
||||
<%= calendar_for('custom_field_default_value') %>
|
||||
<p><%= f.text_field :url_pattern, :size => 50, :label => :label_link_values_to %></p>
|
|
@ -1,3 +0,0 @@
|
|||
<%= render :partial => 'custom_fields/formats/regexp', :locals => {:f => f, :custom_field => custom_field} %>
|
||||
<p><%= f.text_field :url_pattern, :size => 50, :label => :field_url %></p>
|
||||
<p><%= f.text_field(:default_value) %></p>
|
|
@ -1,7 +0,0 @@
|
|||
<p>
|
||||
<%= f.text_area :possible_values, :value => @custom_field.possible_values.to_a.join("\n"), :rows => 15 %>
|
||||
<em class="info"><%= l(:text_custom_field_possible_values_info) %></em>
|
||||
</p>
|
||||
<p><%= f.text_field(:default_value) %></p>
|
||||
<p><%= f.text_field :url_pattern, :size => 50, :label => :label_link_values_to %></p>
|
||||
<p><%= edit_tag_style_tag f %></p>
|
|
@ -1,3 +0,0 @@
|
|||
<%= render :partial => 'custom_fields/formats/regexp', :locals => {:f => f, :custom_field => custom_field} %>
|
||||
<p><%= f.text_field(:default_value) %></p>
|
||||
<p><%= f.text_field :url_pattern, :size => 50, :label => :label_link_values_to %></p>
|
|
@ -1,9 +0,0 @@
|
|||
<p>
|
||||
<label for="custom_field_min_length"><%=l(:label_min_max_length)%></label>
|
||||
<%= f.text_field :min_length, :size => 5, :no_label => true %> -
|
||||
<%= f.text_field :max_length, :size => 5, :no_label => true %>
|
||||
</p>
|
||||
<p>
|
||||
<%= f.text_field :regexp, :size => 50 %>
|
||||
<em class="info"><%= l(:text_regexp_info) %></em>
|
||||
</p>
|
|
@ -1,4 +0,0 @@
|
|||
<%= render :partial => 'custom_fields/formats/regexp', :locals => {:f => f, :custom_field => custom_field} %>
|
||||
<p><%= f.check_box :text_formatting, {:label => :setting_text_formatting, :data => {:disables => '#custom_field_url_pattern'}}, 'full', '' %></p>
|
||||
<p><%= f.text_field(:default_value) %></p>
|
||||
<p><%= f.text_field :url_pattern, :size => 50, :label => :label_link_values_to %></p>
|
|
@ -1,3 +0,0 @@
|
|||
<%= render :partial => 'custom_fields/formats/regexp', :locals => {:f => f, :custom_field => custom_field} %>
|
||||
<p><%= f.check_box :text_formatting, {:label => :setting_text_formatting}, 'full', '' %></p>
|
||||
<p><%= f.text_area(:default_value, :rows => 5) %></p>
|
|
@ -1,24 +0,0 @@
|
|||
<p>
|
||||
<label><%= l(:label_role) %></label>
|
||||
<label class="block">
|
||||
<%= radio_button_tag 'status', 1, custom_field.user_role.blank?, :id => 'custom_field_user_role_all',
|
||||
:data => {:disables => '.custom_field_user_role input'} %>
|
||||
<%= l(:label_all) %>
|
||||
</label>
|
||||
<label class="block">
|
||||
<%= radio_button_tag 'status', 0, custom_field.user_role.present?, :id => 'custom_field_user_role_only',
|
||||
:data => {:enables => '.custom_field_user_role input'} %>
|
||||
<%= l(:label_only) %>:
|
||||
</label>
|
||||
<% Role.givable.sorted.each do |role| %>
|
||||
<label class="block custom_field_user_role" style="padding-left:2em;">
|
||||
<%= check_box_tag 'custom_field[user_role][]',
|
||||
role.id,
|
||||
custom_field.user_role.is_a?(Array) && custom_field.user_role.include?(role.id.to_s),
|
||||
:id => nil %>
|
||||
<%= role.name %>
|
||||
</label>
|
||||
<% end %>
|
||||
<%= hidden_field_tag 'custom_field[user_role][]', '' %>
|
||||
</p>
|
||||
<p><%= edit_tag_style_tag f %></p>
|
|
@ -1,24 +0,0 @@
|
|||
<p>
|
||||
<label><%= l(:field_status) %></label>
|
||||
<label class="block">
|
||||
<%= radio_button_tag 'status', 1, custom_field.version_status.blank?, :id => 'custom_field_version_status_all',
|
||||
:data => {:disables => '.custom_field_version_status input'} %>
|
||||
<%= l(:label_all) %>
|
||||
</label>
|
||||
<label class="block">
|
||||
<%= radio_button_tag 'status', 0, custom_field.version_status.present?, :id => 'custom_field_version_status_only',
|
||||
:data => {:enables => '.custom_field_version_status input'} %>
|
||||
<%= l(:label_only) %>:
|
||||
</label>
|
||||
<% Version::VERSION_STATUSES.each do |status| %>
|
||||
<label class="block custom_field_version_status" style="padding-left:2em;">
|
||||
<%= check_box_tag 'custom_field[version_status][]',
|
||||
status,
|
||||
custom_field.version_status.is_a?(Array) && custom_field.version_status.include?(status),
|
||||
:id => nil %>
|
||||
<%= l("version_status_#{status}") %>
|
||||
</label>
|
||||
<% end %>
|
||||
<%= hidden_field_tag 'custom_field[version_status][]', '' %>
|
||||
</p>
|
||||
<p><%= edit_tag_style_tag f %></p>
|
|
@ -6,8 +6,8 @@ api.array :custom_fields do
|
|||
api.customized_type field.class.customized_class.name.underscore if field.class.customized_class
|
||||
api.field_format field.field_format
|
||||
api.regexp field.regexp
|
||||
api.min_length field.min_length
|
||||
api.max_length field.max_length
|
||||
api.min_length (field.min_length == 0 ? nil : field.min_length)
|
||||
api.max_length (field.max_length == 0 ? nil : field.max_length)
|
||||
api.is_required field.is_required?
|
||||
api.is_filter field.is_filter?
|
||||
api.searchable field.searchable
|
||||
|
@ -15,24 +15,23 @@ api.array :custom_fields do
|
|||
api.default_value field.default_value
|
||||
api.visible field.visible?
|
||||
|
||||
values = field.possible_values_options
|
||||
if values.present?
|
||||
if field.field_format == 'list'
|
||||
api.array :possible_values do
|
||||
values.each do |label, value|
|
||||
field.possible_values.each do |v|
|
||||
api.possible_value do
|
||||
api.value value || label
|
||||
api.value v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if field.is_a?(IssueCustomField)
|
||||
api.array :trackers do
|
||||
api.trackers do
|
||||
field.trackers.each do |tracker|
|
||||
api.tracker :id => tracker.id, :name => tracker.name
|
||||
end
|
||||
end
|
||||
api.array :roles do
|
||||
api.roles do
|
||||
field.roles.each do |role|
|
||||
api.role :id => role.id, :name => role.name
|
||||
end
|
||||
|
|
|
@ -1,11 +1,3 @@
|
|||
<div class="contextual">
|
||||
<%= link_to l(:label_custom_field_new), new_custom_field_path, :class => 'icon icon-add' %>
|
||||
</div>
|
||||
|
||||
<%= title l(:label_custom_field_plural) %>
|
||||
|
||||
<% if @custom_fields_by_type.present? %>
|
||||
<%= render_custom_fields_tabs(@custom_fields_by_type.keys) %>
|
||||
<% else %>
|
||||
<p class="nodata"><%= l(:label_no_data) %></p>
|
||||
<% end %>
|
||||
<%= render_tabs custom_fields_tabs %>
|
||||
|
|
|
@ -12,8 +12,7 @@ $('#custom_field_field_format').change(function(){
|
|||
$.ajax({
|
||||
url: '<%= new_custom_field_path(:format => 'js') %>',
|
||||
type: 'get',
|
||||
data: $('#custom_field_form').serialize(),
|
||||
complete: toggleDisabledInit
|
||||
data: $('#custom_field_form').serialize()
|
||||
});
|
||||
});
|
||||
<% end %>
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
<%= title [l(:label_custom_field_plural), custom_fields_path],
|
||||
l(:label_custom_field_new) %>
|
||||
|
||||
<% selected = 0 %>
|
||||
<%= form_tag new_custom_field_path, :method => 'get' do %>
|
||||
<div class="box">
|
||||
<p><%= l(:label_custom_field_select_type) %>:</p>
|
||||
<p>
|
||||
<% custom_field_type_options.each do |name, type| %>
|
||||
<label style="display:block;"><%= radio_button_tag 'type', type, 1==selected+=1 %> <%= name %></label>
|
||||
<% end %>
|
||||
</p>
|
||||
</div>
|
||||
<p><%= submit_tag l(:label_next).html_safe + " »".html_safe, :name => nil %></p>
|
||||
<% end %>
|
|
@ -20,7 +20,7 @@
|
|||
:url => { :action => 'edit_membership', :id => @group, :membership_id => membership },
|
||||
:html => { :id => "member-#{membership.id}-roles-form", :style => 'display:none;'}) do %>
|
||||
<p><% roles.each do |role| %>
|
||||
<label><%= check_box_tag 'membership[role_ids][]', role.id, membership.roles.include?(role), :id => nil %> <%=h role %></label><br />
|
||||
<label><%= check_box_tag 'membership[role_ids][]', role.id, membership.roles.include?(role) %> <%=h role %></label><br />
|
||||
<% end %></p>
|
||||
<p><%= submit_tag l(:button_change) %>
|
||||
<%= link_to_function(
|
||||
|
@ -56,7 +56,7 @@
|
|||
<%= select_tag 'membership[project_id]', options_for_membership_project_select(@group, projects) %>
|
||||
<p><%= l(:label_role_plural) %>:
|
||||
<% roles.each do |role| %>
|
||||
<label><%= check_box_tag 'membership[role_ids][]', role.id, false, :id => nil %> <%=h role %></label>
|
||||
<label><%= check_box_tag 'membership[role_ids][]', role.id %> <%=h role %></label>
|
||||
<% end %></p>
|
||||
<p><%= submit_tag l(:button_add) %></p>
|
||||
<% end %>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<tbody>
|
||||
<% @group.users.sort.each do |user| %>
|
||||
<tr id="user-<%= user.id %>" class="<%= cycle 'odd', 'even' %>">
|
||||
<td class="name"><%= link_to_user user %></td>
|
||||
<td class="user"><%= link_to_user user %></td>
|
||||
<td class="buttons">
|
||||
<%= delete_link group_user_path(@group, :user_id => user), :remote => true %>
|
||||
</td>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue