Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
572fbb792d | ||
|
|
8cdd427a0e | ||
|
|
de2b7c2879 | ||
|
|
f0aad8c2a8 | ||
|
|
68b923640e | ||
|
|
4e2fbf440b | ||
|
|
5572b6c715 | ||
|
|
d63f0fa5b0 | ||
|
|
dff772b066 | ||
|
|
fb2cbdc6cc | ||
|
|
6062a0a89d | ||
|
|
0ebf95d819 | ||
|
|
14879e24c4 | ||
|
|
ca0b289479 | ||
|
|
e36a611f81 | ||
|
|
6c2eec59d8 | ||
|
|
905ed3aa75 | ||
|
|
bb4c530ba2 | ||
|
|
e3958ef577 | ||
|
|
c5f2dadb2c | ||
|
|
af8dcccb7b | ||
|
|
c0742ed9e6 | ||
|
|
8c7ae20402 | ||
|
|
06b68f1948 | ||
|
|
b51203bc83 | ||
|
|
f578a5c4f6 | ||
|
|
17d1907747 | ||
|
|
0e241bb857 | ||
|
|
9dcde53dab | ||
|
|
a44521527a | ||
|
|
f12590fa5c | ||
|
|
a1c6f710aa | ||
|
|
3ad82e4665 | ||
|
|
eef3b5b5ca | ||
|
|
ab69845ea2 | ||
|
|
af51a68b34 | ||
|
|
d5c08680f6 | ||
|
|
9b82b948e3 | ||
|
|
1c8510bc80 | ||
|
|
23861c7cc1 | ||
|
|
6550343cb9 | ||
|
|
a70cf2f833 | ||
|
|
0f219b973a | ||
|
|
d02e5e54a8 | ||
|
|
c050028102 | ||
|
|
012ffdf9e8 | ||
|
|
9e3aeeef88 | ||
|
|
21b162a624 | ||
|
|
b7e999d1aa | ||
|
|
284d03e1f8 | ||
|
|
3b4126e14f | ||
|
|
46a76ecb6e | ||
|
|
5a74edef89 | ||
|
|
2f9c2e6bd3 | ||
|
|
2df7c0ff3a | ||
|
|
f5d5077d2b | ||
|
|
94a1eb21a4 | ||
|
|
1f80a4b0d9 | ||
|
|
55220950d2 | ||
|
|
daea57a37c | ||
|
|
fe61739108 | ||
|
|
a4cd96e8b0 | ||
|
|
804302ce61 | ||
|
|
89ecbe9cdc | ||
|
|
aac3e1e51c | ||
|
|
595fef0d68 | ||
|
|
65a6e3985e | ||
|
|
f27a0eca5b | ||
|
|
2d34ab9235 | ||
|
|
03bff3308f | ||
|
|
006a1b4fd7 | ||
|
|
9095874a07 | ||
|
|
8ecfd1dcf8 | ||
|
|
35584bc53a | ||
|
|
21a16e0958 | ||
|
|
76d24e44bf | ||
|
|
efbdef9357 | ||
|
|
5eab4af70d | ||
|
|
96b2e00015 | ||
|
|
9939fbeef6 | ||
|
|
b369f82d15 | ||
|
|
0ad5dfaba0 | ||
|
|
d7d18689b7 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -23,7 +23,6 @@
|
||||
/tmp/sessions/*
|
||||
/tmp/sockets/*
|
||||
/tmp/test/*
|
||||
/tmp/thumbnails/*
|
||||
/vendor/cache
|
||||
/vendor/rails
|
||||
*.rbc
|
||||
|
||||
@@ -25,7 +25,6 @@ tmp/pdf/*
|
||||
tmp/sessions/*
|
||||
tmp/sockets/*
|
||||
tmp/test/*
|
||||
tmp/thumbnails/*
|
||||
vendor/cache
|
||||
vendor/rails
|
||||
*.rbc
|
||||
|
||||
9
Gemfile
9
Gemfile
@@ -1,6 +1,6 @@
|
||||
source 'http://rubygems.org'
|
||||
|
||||
gem 'rails', '3.2.12'
|
||||
gem 'rails', '3.2.11'
|
||||
gem "jquery-rails", "~> 2.0.2"
|
||||
gem "i18n", "~> 0.6.0"
|
||||
gem "coderay", "~> 1.0.6"
|
||||
@@ -74,11 +74,8 @@ end
|
||||
|
||||
group :test do
|
||||
gem "shoulda", "~> 2.11"
|
||||
# Shoulda does not work nice on Ruby 1.9.3 and JRuby 1.7.
|
||||
# It seems to need test-unit explicitely.
|
||||
platforms = [:mri_19]
|
||||
platforms << :jruby if defined?(JRUBY_VERSION) && JRUBY_VERSION >= "1.7"
|
||||
gem "test-unit", :platforms => platforms
|
||||
# Shoulda does not work nice on Ruby 1.9.3 and seems to need test-unit explicitely.
|
||||
gem "test-unit", :platforms => [:mri_19]
|
||||
gem "mocha", "0.12.3"
|
||||
end
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ class Unauthorized < Exception; end
|
||||
|
||||
class ApplicationController < ActionController::Base
|
||||
include Redmine::I18n
|
||||
|
||||
|
||||
class_attribute :accept_api_auth_actions
|
||||
class_attribute :accept_rss_auth_actions
|
||||
class_attribute :model_object
|
||||
@@ -90,7 +90,7 @@ class ApplicationController < ActionController::Base
|
||||
def find_current_user
|
||||
user = nil
|
||||
unless api_request?
|
||||
if session[:user_id]
|
||||
if session[:user_id]
|
||||
# existing session
|
||||
user = (User.active.find(session[:user_id]) rescue nil)
|
||||
elsif autologin_user = try_to_autologin
|
||||
@@ -110,16 +110,6 @@ class ApplicationController < ActionController::Base
|
||||
user = User.try_to_login(username, password) || User.find_by_api_key(username)
|
||||
end
|
||||
end
|
||||
# Switch user if requested by an admin user
|
||||
if user && user.admin? && (username = api_switch_user_from_request)
|
||||
su = User.find_by_login(username)
|
||||
if su && su.active?
|
||||
logger.info(" User switched by: #{user.login} (id=#{user.id})") if logger
|
||||
user = su
|
||||
else
|
||||
render_error :message => 'Invalid X-Redmine-Switch-User header', :status => 412
|
||||
end
|
||||
end
|
||||
end
|
||||
user
|
||||
end
|
||||
@@ -276,24 +266,14 @@ class ApplicationController < ActionController::Base
|
||||
self.model_object = model
|
||||
end
|
||||
|
||||
# Find the issue whose id is the :id parameter
|
||||
# Raises a Unauthorized exception if the issue is not visible
|
||||
def find_issue
|
||||
# Issue.visible.find(...) can not be used to redirect user to the login form
|
||||
# if the issue actually exists but requires authentication
|
||||
@issue = Issue.find(params[:id])
|
||||
raise Unauthorized unless @issue.visible?
|
||||
@project = @issue.project
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
# Find issues with a single :id param or :ids array param
|
||||
# Raises a Unauthorized exception if one of the issues is not visible
|
||||
# Filter for bulk issue operations
|
||||
def find_issues
|
||||
@issues = Issue.find_all_by_id(params[:id] || params[:ids])
|
||||
raise ActiveRecord::RecordNotFound if @issues.empty?
|
||||
raise Unauthorized unless @issues.all?(&:visible?)
|
||||
if @issues.detect {|issue| !issue.visible?}
|
||||
deny_access
|
||||
return
|
||||
end
|
||||
@projects = @issues.collect(&:project).compact.uniq
|
||||
@project = @projects.first if @projects.size == 1
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
@@ -422,7 +402,7 @@ class ApplicationController < ActionController::Base
|
||||
@items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
|
||||
@items = @items.slice(0, Setting.feeds_limit.to_i)
|
||||
@title = options[:title] || Setting.app_title
|
||||
render :template => "common/feed", :formats => [:atom], :layout => false,
|
||||
render :template => "common/feed.atom", :layout => false,
|
||||
:content_type => 'application/atom+xml'
|
||||
end
|
||||
|
||||
@@ -528,11 +508,6 @@ class ApplicationController < ActionController::Base
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the API 'switch user' value if present
|
||||
def api_switch_user_from_request
|
||||
request.headers["X-Redmine-Switch-User"].to_s.presence
|
||||
end
|
||||
|
||||
# Renders a warning flash if obj has unsaved attachments
|
||||
def render_attachment_warning_if_needed(obj)
|
||||
flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present?
|
||||
@@ -563,13 +538,8 @@ class ApplicationController < ActionController::Base
|
||||
|
||||
# Renders a 200 response for successfull updates or deletions via the API
|
||||
def render_api_ok
|
||||
render_api_head :ok
|
||||
end
|
||||
|
||||
# Renders a head API response
|
||||
def render_api_head(status)
|
||||
# #head would return a response body with one space
|
||||
render :text => '', :status => status, :layout => nil
|
||||
# head :ok would return a response body with one space
|
||||
render :text => '', :status => :ok, :layout => nil
|
||||
end
|
||||
|
||||
# Renders API response on validation failure
|
||||
|
||||
@@ -84,7 +84,7 @@ class AttachmentsController < ApplicationController
|
||||
|
||||
@attachment = Attachment.new(:file => request.raw_post)
|
||||
@attachment.author = User.current
|
||||
@attachment.filename = params[:filename].presence || Redmine::Utils.random_hex(16)
|
||||
@attachment.filename = Redmine::Utils.random_hex(16)
|
||||
|
||||
if @attachment.save
|
||||
respond_to do |format|
|
||||
|
||||
@@ -18,26 +18,13 @@
|
||||
class EnumerationsController < ApplicationController
|
||||
layout 'admin'
|
||||
|
||||
before_filter :require_admin, :except => :index
|
||||
before_filter :require_admin_or_api_request, :only => :index
|
||||
before_filter :require_admin
|
||||
before_filter :build_new_enumeration, :only => [:new, :create]
|
||||
before_filter :find_enumeration, :only => [:edit, :update, :destroy]
|
||||
accept_api_auth :index
|
||||
|
||||
helper :custom_fields
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.api {
|
||||
@klass = Enumeration.get_subclass(params[:type])
|
||||
if @klass
|
||||
@enumerations = @klass.shared.sorted.all
|
||||
else
|
||||
render_404
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def new
|
||||
@@ -46,7 +33,7 @@ class EnumerationsController < ApplicationController
|
||||
def create
|
||||
if request.post? && @enumeration.save
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
redirect_to :action => 'index'
|
||||
redirect_to :action => 'index', :type => @enumeration.type
|
||||
else
|
||||
render :action => 'new'
|
||||
end
|
||||
@@ -58,7 +45,7 @@ class EnumerationsController < ApplicationController
|
||||
def update
|
||||
if request.put? && @enumeration.update_attributes(params[:enumeration])
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to :action => 'index'
|
||||
redirect_to :action => 'index', :type => @enumeration.type
|
||||
else
|
||||
render :action => 'edit'
|
||||
end
|
||||
|
||||
@@ -23,7 +23,7 @@ class IssueCategoriesController < ApplicationController
|
||||
before_filter :find_project_by_project_id, :only => [:index, :new, :create]
|
||||
before_filter :authorize
|
||||
accept_api_auth :index, :show, :create, :update, :destroy
|
||||
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project }
|
||||
@@ -92,7 +92,7 @@ class IssueCategoriesController < ApplicationController
|
||||
|
||||
def destroy
|
||||
@issue_count = @category.issues.size
|
||||
if @issue_count == 0 || params[:todo] || api_request?
|
||||
if @issue_count == 0 || params[:todo] || api_request?
|
||||
reassign_to = nil
|
||||
if params[:reassign_to_id] && (params[:todo] == 'reassign' || params[:todo].blank?)
|
||||
reassign_to = @project.issue_categories.find_by_id(params[:reassign_to_id])
|
||||
|
||||
@@ -56,7 +56,6 @@ class IssuesController < ApplicationController
|
||||
retrieve_query
|
||||
sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
|
||||
sort_update(@query.sortable_columns)
|
||||
@query.sort_criteria = sort_criteria.to_a
|
||||
|
||||
if @query.valid?
|
||||
case params[:format]
|
||||
@@ -82,7 +81,7 @@ class IssuesController < ApplicationController
|
||||
respond_to do |format|
|
||||
format.html { render :template => 'issues/index', :layout => !request.xhr? }
|
||||
format.api {
|
||||
Issue.load_visible_relations(@issues) if include_in_api_response?('relations')
|
||||
Issue.load_relations(@issues) if include_in_api_response?('relations')
|
||||
}
|
||||
format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
|
||||
format.csv { send_data(issues_to_csv(@issues, @project, @query, params), :type => 'text/csv; header=present', :filename => 'export.csv') }
|
||||
@@ -100,9 +99,8 @@ class IssuesController < ApplicationController
|
||||
end
|
||||
|
||||
def show
|
||||
@journals = @issue.journals.includes(:user, :details).reorder("#{Journal.table_name}.id ASC").all
|
||||
@journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
|
||||
@journals.each_with_index {|j,i| j.indice = i+1}
|
||||
@journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
|
||||
@journals.reverse! if User.current.wants_comments_in_reverse_order?
|
||||
|
||||
@changesets = @issue.changesets.visible.all
|
||||
@@ -120,10 +118,7 @@ class IssuesController < ApplicationController
|
||||
}
|
||||
format.api
|
||||
format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
|
||||
format.pdf {
|
||||
pdf = issue_to_pdf(@issue, :journals => @journals)
|
||||
send_data(pdf, :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf")
|
||||
}
|
||||
format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -178,7 +173,6 @@ class IssuesController < ApplicationController
|
||||
@conflict = true
|
||||
if params[:last_journal_id]
|
||||
@conflict_journals = @issue.journals_after(params[:last_journal_id]).all
|
||||
@conflict_journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -313,7 +307,19 @@ class IssuesController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
def find_issue
|
||||
# Issue.visible.find(...) can not be used to redirect user to the login form
|
||||
# if the issue actually exists but requires authentication
|
||||
@issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
|
||||
unless @issue.visible?
|
||||
deny_access
|
||||
return
|
||||
end
|
||||
@project = @issue.project
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def find_project
|
||||
project_id = params[:project_id] || (params[:issue] && params[:issue][:project_id])
|
||||
@@ -348,7 +354,8 @@ class IssuesController < ApplicationController
|
||||
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
|
||||
@time_entry.attributes = params[:time_entry]
|
||||
|
||||
@issue.init_journal(User.current)
|
||||
@notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil)
|
||||
@issue.init_journal(User.current, @notes)
|
||||
|
||||
issue_attributes = params[:issue]
|
||||
if issue_attributes && params[:conflict_resolution]
|
||||
@@ -357,7 +364,7 @@ class IssuesController < ApplicationController
|
||||
issue_attributes = issue_attributes.dup
|
||||
issue_attributes.delete(:lock_version)
|
||||
when 'add_notes'
|
||||
issue_attributes = issue_attributes.slice(:notes)
|
||||
issue_attributes = {}
|
||||
when 'cancel'
|
||||
redirect_to issue_path(@issue)
|
||||
return false
|
||||
|
||||
@@ -57,10 +57,10 @@ class JournalsController < ApplicationController
|
||||
end
|
||||
|
||||
def new
|
||||
@journal = Journal.visible.find(params[:journal_id]) if params[:journal_id]
|
||||
if @journal
|
||||
user = @journal.user
|
||||
text = @journal.notes
|
||||
journal = Journal.find(params[:journal_id]) if params[:journal_id]
|
||||
if journal
|
||||
user = journal.user
|
||||
text = journal.notes
|
||||
else
|
||||
user = @issue.author
|
||||
text = @issue.description
|
||||
@@ -69,8 +69,6 @@ class JournalsController < ApplicationController
|
||||
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
|
||||
render_404
|
||||
end
|
||||
|
||||
def edit
|
||||
@@ -97,9 +95,17 @@ class JournalsController < ApplicationController
|
||||
private
|
||||
|
||||
def find_journal
|
||||
@journal = Journal.visible.find(params[:id])
|
||||
@journal = Journal.find(params[:id])
|
||||
@project = @journal.journalized.project
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
# TODO: duplicated in IssuesController
|
||||
def find_issue
|
||||
@issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
|
||||
@project = @issue.project
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
@@ -147,16 +147,15 @@ class MyController < ApplicationController
|
||||
# params[:block] : id of the block to add
|
||||
def add_block
|
||||
block = params[:block].to_s.underscore
|
||||
if block.present? && BLOCKS.key?(block)
|
||||
@user = User.current
|
||||
layout = @user.pref[:my_page_layout] || {}
|
||||
# remove if already present in a group
|
||||
%w(top left right).each {|f| (layout[f] ||= []).delete block }
|
||||
# add it on top
|
||||
layout['top'].unshift block
|
||||
@user.pref[:my_page_layout] = layout
|
||||
@user.pref.save
|
||||
end
|
||||
(render :nothing => true; return) unless block && (BLOCKS.keys.include? block)
|
||||
@user = User.current
|
||||
layout = @user.pref[:my_page_layout] || {}
|
||||
# remove if already present in a group
|
||||
%w(top left right).each {|f| (layout[f] ||= []).delete block }
|
||||
# add it on top
|
||||
layout['top'].unshift block
|
||||
@user.pref[:my_page_layout] = layout
|
||||
@user.pref.save
|
||||
redirect_to :action => 'page_layout'
|
||||
end
|
||||
|
||||
|
||||
@@ -35,10 +35,6 @@ class PreviewsController < ApplicationController
|
||||
end
|
||||
|
||||
def news
|
||||
if params[:id].present? && news = News.visible.find_by_id(params[:id])
|
||||
@previewed = news
|
||||
@attachments = news.attachments
|
||||
end
|
||||
@text = (params[:news] ? params[:news][:description] : nil)
|
||||
render :partial => 'common/preview'
|
||||
end
|
||||
|
||||
@@ -116,7 +116,11 @@ class ProjectsController < ApplicationController
|
||||
@source_project = Project.find(params[:id])
|
||||
if request.get?
|
||||
@project = Project.copy_from(@source_project)
|
||||
@project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
|
||||
if @project
|
||||
@project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
|
||||
else
|
||||
redirect_to :controller => 'admin', :action => 'projects'
|
||||
end
|
||||
else
|
||||
Mailer.with_deliveries(params[:notifications] == '1') do
|
||||
@project = Project.new
|
||||
@@ -135,8 +139,7 @@ class ProjectsController < ApplicationController
|
||||
end
|
||||
end
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
# source_project not found
|
||||
render_404
|
||||
redirect_to :controller => 'admin', :action => 'projects'
|
||||
end
|
||||
|
||||
# Show @project
|
||||
|
||||
@@ -242,7 +242,7 @@ class RepositoriesController < ApplicationController
|
||||
# DELETE /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues/:issue_id
|
||||
def remove_related_issue
|
||||
@issue = Issue.visible.find_by_id(params[:issue_id])
|
||||
if @issue
|
||||
if @issue
|
||||
@changeset.issues.delete(@issue)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -18,10 +18,10 @@
|
||||
class RolesController < ApplicationController
|
||||
layout 'admin'
|
||||
|
||||
before_filter :require_admin, :except => [:index, :show]
|
||||
before_filter :require_admin_or_api_request, :only => [:index, :show]
|
||||
before_filter :find_role, :only => [:show, :edit, :update, :destroy]
|
||||
accept_api_auth :index, :show
|
||||
before_filter :require_admin, :except => :index
|
||||
before_filter :require_admin_or_api_request, :only => :index
|
||||
before_filter :find_role, :only => [:edit, :update, :destroy]
|
||||
accept_api_auth :index
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
@@ -35,12 +35,6 @@ class RolesController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.api
|
||||
end
|
||||
end
|
||||
|
||||
def new
|
||||
# Prefills the form with 'Non member' role permissions by default
|
||||
@role = Role.new(params[:role] || {:permissions => Role.non_member.permissions})
|
||||
|
||||
@@ -39,8 +39,7 @@ class SettingsController < ApplicationController
|
||||
redirect_to :action => 'edit', :tab => params[:tab]
|
||||
else
|
||||
@options = {}
|
||||
user_format = User::USER_FORMATS.collect{|key, value| [key, value[:setting_order]]}.sort{|a, b| a[1] <=> b[1]}
|
||||
@options[:user_format] = user_format.collect{|f| [User.current.name(f[0]), f[0].to_s]}
|
||||
@options[:user_format] = User::USER_FORMATS.keys.collect {|f| [User.current.name(f), f.to_s] }
|
||||
@deliveries = ActionMailer::Base.perform_deliveries
|
||||
|
||||
@guessed_host_and_path = request.host_with_port.dup
|
||||
|
||||
@@ -138,7 +138,7 @@ class TimelogController < ApplicationController
|
||||
:time_entry => {:issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id},
|
||||
:back_url => params[:back_url]
|
||||
else
|
||||
redirect_to :action => 'new',
|
||||
redirect_to :action => 'new',
|
||||
:time_entry => {:project_id => @time_entry.project_id, :issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id},
|
||||
:back_url => params[:back_url]
|
||||
end
|
||||
@@ -308,9 +308,6 @@ private
|
||||
when 'last_week'
|
||||
@from = Date.today - 7 - (Date.today.cwday - 1)%7
|
||||
@to = @from + 6
|
||||
when 'last_2_weeks'
|
||||
@from = Date.today - 14 - (Date.today.cwday - 1)%7
|
||||
@to = @from + 13
|
||||
when '7_days'
|
||||
@from = Date.today - 7
|
||||
@to = Date.today
|
||||
|
||||
@@ -59,7 +59,7 @@ class TrackersController < ApplicationController
|
||||
@tracker ||= Tracker.find(params[:id])
|
||||
@projects = Project.find(:all)
|
||||
end
|
||||
|
||||
|
||||
def update
|
||||
@tracker = Tracker.find(params[:id])
|
||||
if request.put? and @tracker.update_attributes(params[:tracker])
|
||||
|
||||
@@ -20,7 +20,7 @@ class VersionsController < ApplicationController
|
||||
model_object Version
|
||||
before_filter :find_model_object, :except => [:index, :new, :create, :close_completed]
|
||||
before_filter :find_project_from_association, :except => [:index, :new, :create, :close_completed]
|
||||
before_filter :find_project_by_project_id, :only => [:index, :new, :create, :close_completed]
|
||||
before_filter :find_project, :only => [:index, :new, :create, :close_completed]
|
||||
before_filter :authorize
|
||||
|
||||
accept_api_auth :index, :show, :create, :update, :destroy
|
||||
@@ -169,7 +169,12 @@ class VersionsController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
def find_project
|
||||
@project = Project.find(params[:project_id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def retrieve_selected_tracker_ids(selectable_trackers, default_trackers=nil)
|
||||
if ids = params[:tracker_ids]
|
||||
@@ -178,4 +183,5 @@ class VersionsController < ApplicationController
|
||||
@selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s }
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -35,8 +35,7 @@ class WikiController < ApplicationController
|
||||
default_search_scope :wiki_pages
|
||||
before_filter :find_wiki, :authorize
|
||||
before_filter :find_existing_or_new_page, :only => [:show, :edit, :update]
|
||||
before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy, :destroy_version]
|
||||
accept_api_auth :index, :show, :update, :destroy
|
||||
before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy]
|
||||
|
||||
helper :attachments
|
||||
include AttachmentsHelper
|
||||
@@ -46,13 +45,7 @@ class WikiController < ApplicationController
|
||||
# List of pages, sorted alphabetically and by parent (hierarchy)
|
||||
def index
|
||||
load_pages_for_index
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
@pages_by_parent_id = @pages.group_by(&:parent_id)
|
||||
}
|
||||
format.api
|
||||
end
|
||||
@pages_by_parent_id = @pages.group_by(&:parent_id)
|
||||
end
|
||||
|
||||
# List of page, by last update
|
||||
@@ -64,7 +57,7 @@ class WikiController < ApplicationController
|
||||
# display a page (in editing mode if it doesn't exist)
|
||||
def show
|
||||
if @page.new_record?
|
||||
if User.current.allowed_to?(:edit_wiki_pages, @project) && editable? && !api_request?
|
||||
if User.current.allowed_to?(:edit_wiki_pages, @project) && editable?
|
||||
edit
|
||||
render :action => 'edit'
|
||||
else
|
||||
@@ -73,7 +66,8 @@ class WikiController < ApplicationController
|
||||
return
|
||||
end
|
||||
if params[:version] && !User.current.allowed_to?(:view_wiki_edits, @project)
|
||||
deny_access
|
||||
# Redirects user to the current version if he's not allowed to view previous versions
|
||||
redirect_to :version => nil
|
||||
return
|
||||
end
|
||||
@content = @page.content_for_version(params[:version])
|
||||
@@ -95,10 +89,7 @@ class WikiController < ApplicationController
|
||||
@content.current_version? &&
|
||||
Redmine::WikiFormatting.supports_section_edit?
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.api
|
||||
end
|
||||
render :action => 'show'
|
||||
end
|
||||
|
||||
# edit an existing page or a new one
|
||||
@@ -130,25 +121,22 @@ class WikiController < ApplicationController
|
||||
# Creates a new page or updates an existing one
|
||||
def update
|
||||
return render_403 unless editable?
|
||||
was_new_page = @page.new_record?
|
||||
@page.content = WikiContent.new(:page => @page) if @page.new_record?
|
||||
@page.safe_attributes = params[:wiki_page]
|
||||
|
||||
@content = @page.content
|
||||
content_params = params[:content]
|
||||
if content_params.nil? && params[:wiki_page].is_a?(Hash)
|
||||
content_params = params[:wiki_page].slice(:text, :comments, :version)
|
||||
end
|
||||
content_params ||= {}
|
||||
@content = @page.content_for_version(params[:version])
|
||||
@content.text = initial_page_content(@page) if @content.text.blank?
|
||||
# don't keep previous comment
|
||||
@content.comments = nil
|
||||
|
||||
@content.comments = content_params[:comments]
|
||||
@text = content_params[:text]
|
||||
@content.comments = params[:content][:comments]
|
||||
@text = params[:content][:text]
|
||||
if params[:section].present? && Redmine::WikiFormatting.supports_section_edit?
|
||||
@section = params[:section].to_i
|
||||
@section_hash = params[:section_hash]
|
||||
@content.text = Redmine::WikiFormatting.formatter.new(@content.text).update_section(params[:section].to_i, @text, @section_hash)
|
||||
else
|
||||
@content.version = content_params[:version] if content_params[:version]
|
||||
@content.version = params[:content][:version]
|
||||
@content.text = @text
|
||||
end
|
||||
@content.author = User.current
|
||||
@@ -157,38 +145,17 @@ class WikiController < ApplicationController
|
||||
attachments = Attachment.attach_files(@page, params[:attachments])
|
||||
render_attachment_warning_if_needed(@page)
|
||||
call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page})
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :action => 'show', :project_id => @project, :id => @page.title }
|
||||
format.api {
|
||||
if was_new_page
|
||||
render :action => 'show', :status => :created, :location => url_for(:controller => 'wiki', :action => 'show', :project_id => @project, :id => @page.title)
|
||||
else
|
||||
render_api_ok
|
||||
end
|
||||
}
|
||||
end
|
||||
redirect_to :action => 'show', :project_id => @project, :id => @page.title
|
||||
else
|
||||
respond_to do |format|
|
||||
format.html { render :action => 'edit' }
|
||||
format.api { render_validation_errors(@content) }
|
||||
end
|
||||
render :action => 'edit'
|
||||
end
|
||||
|
||||
rescue ActiveRecord::StaleObjectError, Redmine::WikiFormatting::StaleSectionError
|
||||
# Optimistic locking exception
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
flash.now[:error] = l(:notice_locking_conflict)
|
||||
render :action => 'edit'
|
||||
}
|
||||
format.api { render_api_head :conflict }
|
||||
end
|
||||
flash.now[:error] = l(:notice_locking_conflict)
|
||||
render :action => 'edit'
|
||||
rescue ActiveRecord::RecordNotSaved
|
||||
respond_to do |format|
|
||||
format.html { render :action => 'edit' }
|
||||
format.api { render_validation_errors(@content) }
|
||||
end
|
||||
render :action => 'edit'
|
||||
end
|
||||
|
||||
# rename a page
|
||||
@@ -211,7 +178,7 @@ class WikiController < ApplicationController
|
||||
# show page history
|
||||
def history
|
||||
@version_count = @page.content.versions.count
|
||||
@version_pages = Paginator.new self, @version_count, per_page_option, params['page']
|
||||
@version_pages = Paginator.new self, @version_count, per_page_option, params['p']
|
||||
# don't load text
|
||||
@versions = @page.content.versions.find :all,
|
||||
:select => "id, author_id, comments, updated_on, version",
|
||||
@@ -254,28 +221,16 @@ class WikiController < ApplicationController
|
||||
end
|
||||
else
|
||||
@reassignable_to = @wiki.pages - @page.self_and_descendants
|
||||
# display the destroy form if it's a user request
|
||||
return unless api_request?
|
||||
return
|
||||
end
|
||||
end
|
||||
@page.destroy
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :action => 'index', :project_id => @project }
|
||||
format.api { render_api_ok }
|
||||
end
|
||||
end
|
||||
|
||||
def destroy_version
|
||||
return render_403 unless editable?
|
||||
|
||||
@content = @page.content_for_version(params[:version])
|
||||
@content.destroy
|
||||
redirect_to_referer_or :action => 'history', :id => @page.title, :project_id => @project
|
||||
redirect_to :action => 'index', :project_id => @project
|
||||
end
|
||||
|
||||
# Export wiki to a single pdf or html file
|
||||
def export
|
||||
@pages = @wiki.pages.all(:order => 'title', :include => [:content, {:attachments => :author}])
|
||||
@pages = @wiki.pages.all(:order => 'title', :include => [:content, :attachments])
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
export = render_to_string :action => 'export_multiple', :layout => false
|
||||
@@ -349,6 +304,6 @@ private
|
||||
end
|
||||
|
||||
def load_pages_for_index
|
||||
@pages = @wiki.pages.with_updated_on.order("#{WikiPage.table_name}.title").includes(:wiki => :project).includes(:parent).all
|
||||
@pages = @wiki.pages.with_updated_on.all(:order => 'title', :include => {:wiki => :project})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -47,8 +47,8 @@ module ApplicationHelper
|
||||
def link_to_user(user, options={})
|
||||
if user.is_a?(User)
|
||||
name = h(user.name(options[:format]))
|
||||
if user.active? || (User.current.admin? && user.logged?)
|
||||
link_to name, user_path(user), :class => user.css_classes
|
||||
if user.active?
|
||||
link_to name, :controller => 'users', :action => 'show', :id => user
|
||||
else
|
||||
name
|
||||
end
|
||||
@@ -64,12 +64,10 @@ module ApplicationHelper
|
||||
# link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
|
||||
# link_to_issue(issue, :subject => false) # => Defect #6
|
||||
# link_to_issue(issue, :project => true) # => Foo - Defect #6
|
||||
# link_to_issue(issue, :subject => false, :tracker => false) # => #6
|
||||
#
|
||||
def link_to_issue(issue, options={})
|
||||
title = nil
|
||||
subject = nil
|
||||
text = options[:tracker] == false ? "##{issue.id}" : "#{issue.tracker} ##{issue.id}"
|
||||
if options[:subject] == false
|
||||
title = truncate(issue.subject, :length => 60)
|
||||
else
|
||||
@@ -78,7 +76,9 @@ module ApplicationHelper
|
||||
subject = truncate(subject, :length => options[:truncate])
|
||||
end
|
||||
end
|
||||
s = link_to text, issue_path(issue), :class => issue.css_classes, :title => title
|
||||
s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
|
||||
:class => issue.css_classes,
|
||||
:title => title
|
||||
s << h(": #{subject}") if subject
|
||||
s = h("#{issue.project} - ") + s if options[:project]
|
||||
s
|
||||
@@ -147,10 +147,6 @@ module ApplicationHelper
|
||||
end
|
||||
end
|
||||
|
||||
def wiki_page_path(page, options={})
|
||||
url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options))
|
||||
end
|
||||
|
||||
def thumbnail_tag(attachment)
|
||||
link_to image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment)),
|
||||
{:controller => 'attachments', :action => 'show', :id => attachment, :filename => attachment.filename},
|
||||
@@ -238,7 +234,7 @@ module ApplicationHelper
|
||||
content << "<ul class=\"pages-hierarchy\">\n"
|
||||
pages[node].each do |page|
|
||||
content << "<li>"
|
||||
content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title, :version => nil},
|
||||
content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
|
||||
:title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
|
||||
content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
|
||||
content << "</li>\n"
|
||||
@@ -331,15 +327,6 @@ module ApplicationHelper
|
||||
s.html_safe
|
||||
end
|
||||
|
||||
# Options for the new membership projects combo-box
|
||||
def options_for_membership_project_select(principal, projects)
|
||||
options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
|
||||
options << project_tree_options_for_select(projects) do |p|
|
||||
{:disabled => principal.projects.include?(p)}
|
||||
end
|
||||
options
|
||||
end
|
||||
|
||||
# Truncates and returns the string as a single line
|
||||
def truncate_single_line(string, *args)
|
||||
truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
|
||||
@@ -597,9 +584,8 @@ module ApplicationHelper
|
||||
|
||||
def parse_inline_attachments(text, project, obj, attr, only_path, options)
|
||||
# when using an image link, try to use an attachment, if possible
|
||||
attachments = options[:attachments] || []
|
||||
attachments += obj.attachments if obj.respond_to?(:attachments)
|
||||
if attachments.present?
|
||||
if options[:attachments] || (obj && obj.respond_to?(:attachments))
|
||||
attachments = options[:attachments] || obj.attachments
|
||||
text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
|
||||
filename, ext, alt, alttext = $1.downcase, $2, $3, $4
|
||||
# search for the picture in attachments
|
||||
@@ -658,7 +644,7 @@ module ApplicationHelper
|
||||
wiki_page_id = page.present? ? Wiki.titleize(page) : nil
|
||||
parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
|
||||
url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
|
||||
:id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent)
|
||||
:id => wiki_page_id, :anchor => anchor, :parent => parent)
|
||||
end
|
||||
end
|
||||
link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
|
||||
@@ -704,11 +690,10 @@ module ApplicationHelper
|
||||
# identifier:document:"Some document"
|
||||
# identifier:version:1.0.0
|
||||
# identifier:source:some/file
|
||||
def parse_redmine_links(text, default_project, obj, attr, only_path, options)
|
||||
text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-_]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
|
||||
def parse_redmine_links(text, project, obj, attr, only_path, options)
|
||||
text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
|
||||
leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17
|
||||
link = nil
|
||||
project = default_project
|
||||
if project_identifier
|
||||
project = Project.visible.find_by_identifier(project_identifier)
|
||||
end
|
||||
@@ -794,7 +779,7 @@ module ApplicationHelper
|
||||
when 'commit', 'source', 'export'
|
||||
if project
|
||||
repository = nil
|
||||
if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$}
|
||||
if name =~ %r{^(([a-z0-9\-]+)\|)(.+)$}
|
||||
repo_prefix, repo_identifier, name = $1, $2, $3
|
||||
repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
|
||||
else
|
||||
@@ -821,7 +806,7 @@ module ApplicationHelper
|
||||
end
|
||||
when 'attachment'
|
||||
attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
|
||||
if attachments && attachment = Attachment.latest_attach(attachments, name)
|
||||
if attachments && attachment = attachments.detect {|a| a.filename == name }
|
||||
link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
|
||||
:class => 'attachment'
|
||||
end
|
||||
@@ -973,7 +958,8 @@ module ApplicationHelper
|
||||
end
|
||||
|
||||
def lang_options_for_select(blank=true)
|
||||
(blank ? [["(auto)", ""]] : []) + languages_options
|
||||
(blank ? [["(auto)", ""]] : []) +
|
||||
valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
|
||||
end
|
||||
|
||||
def label_tag_for(name, option_tags = nil, options = {})
|
||||
|
||||
@@ -20,7 +20,16 @@
|
||||
module CustomFieldsHelper
|
||||
|
||||
def custom_fields_tabs
|
||||
CustomField::CUSTOM_FIELDS_TABS
|
||||
tabs = [{:name => 'IssueCustomField', :partial => 'custom_fields/index', :label => :label_issue_plural},
|
||||
{:name => 'TimeEntryCustomField', :partial => 'custom_fields/index', :label => :label_spent_time},
|
||||
{:name => 'ProjectCustomField', :partial => 'custom_fields/index', :label => :label_project_plural},
|
||||
{:name => 'VersionCustomField', :partial => 'custom_fields/index', :label => :label_version_plural},
|
||||
{:name => 'UserCustomField', :partial => 'custom_fields/index', :label => :label_user_plural},
|
||||
{:name => 'GroupCustomField', :partial => 'custom_fields/index', :label => :label_group_plural},
|
||||
{:name => 'TimeEntryActivityCustomField', :partial => 'custom_fields/index', :label => TimeEntryActivity::OptionName},
|
||||
{:name => 'IssuePriorityCustomField', :partial => 'custom_fields/index', :label => IssuePriority::OptionName},
|
||||
{:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index', :label => DocumentCategory::OptionName}
|
||||
]
|
||||
end
|
||||
|
||||
# Return custom field html tag corresponding to its format
|
||||
|
||||
@@ -18,6 +18,15 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
module GroupsHelper
|
||||
# Options for the new membership projects combo-box
|
||||
def options_for_membership_project_select(user, projects)
|
||||
options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
|
||||
options << project_tree_options_for_select(projects) do |p|
|
||||
{:disabled => (user.projects.include?(p))}
|
||||
end
|
||||
options
|
||||
end
|
||||
|
||||
def group_settings_tabs
|
||||
tabs = [{:name => 'general', :partial => 'groups/general', :label => :label_general},
|
||||
{:name => 'users', :partial => 'groups/users', :label => :label_user_plural},
|
||||
|
||||
@@ -65,7 +65,7 @@ module IssuesHelper
|
||||
s = ''
|
||||
ancestors = issue.root? ? [] : issue.ancestors.visible.all
|
||||
ancestors.each do |ancestor|
|
||||
s << '<div>' + content_tag('p', link_to_issue(ancestor, :project => (issue.project_id != ancestor.project_id)))
|
||||
s << '<div>' + content_tag('p', link_to_issue(ancestor))
|
||||
end
|
||||
s << '<div>'
|
||||
subject = h(issue.subject)
|
||||
@@ -80,29 +80,18 @@ module IssuesHelper
|
||||
def render_descendants_tree(issue)
|
||||
s = '<form><table class="list issues">'
|
||||
issue_list(issue.descendants.visible.sort_by(&:lft)) do |child, level|
|
||||
css = "issue issue-#{child.id} hascontextmenu"
|
||||
css << " idnt idnt-#{level}" if level > 0
|
||||
s << content_tag('tr',
|
||||
content_tag('td', check_box_tag("ids[]", child.id, false, :id => nil), :class => 'checkbox') +
|
||||
content_tag('td', link_to_issue(child, :truncate => 60, :project => (issue.project_id != child.project_id)), :class => 'subject') +
|
||||
content_tag('td', link_to_issue(child, :truncate => 60), :class => 'subject') +
|
||||
content_tag('td', h(child.status)) +
|
||||
content_tag('td', link_to_user(child.assigned_to)) +
|
||||
content_tag('td', progress_bar(child.done_ratio, :width => '80px')),
|
||||
:class => css)
|
||||
:class => "issue issue-#{child.id} hascontextmenu #{level > 0 ? "idnt idnt-#{level}" : nil}")
|
||||
end
|
||||
s << '</table></form>'
|
||||
s.html_safe
|
||||
end
|
||||
|
||||
# Returns a link for adding a new subtask to the given issue
|
||||
def link_to_new_subtask(issue)
|
||||
attrs = {
|
||||
:tracker_id => issue.tracker,
|
||||
:parent_issue_id => issue
|
||||
}
|
||||
link_to(l(:button_add), new_project_issue_path(issue.project, :issue => attrs))
|
||||
end
|
||||
|
||||
class IssueFieldsRows
|
||||
include ActionView::Helpers::TagHelper
|
||||
|
||||
@@ -371,16 +360,12 @@ module IssuesHelper
|
||||
def issues_to_csv(issues, project, query, options={})
|
||||
decimal_separator = l(:general_csv_decimal_separator)
|
||||
encoding = l(:general_csv_encoding)
|
||||
columns = (options[:columns] == 'all' ? query.available_inline_columns : query.inline_columns)
|
||||
if options[:description]
|
||||
if description = query.available_columns.detect {|q| q.name == :description}
|
||||
columns << description
|
||||
end
|
||||
end
|
||||
columns = (options[:columns] == 'all' ? query.available_columns : query.columns)
|
||||
|
||||
export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
|
||||
# csv header fields
|
||||
csv << [ "#" ] + columns.collect {|c| Redmine::CodesetUtil.from_utf8(c.caption.to_s, encoding) }
|
||||
csv << [ "#" ] + columns.collect {|c| Redmine::CodesetUtil.from_utf8(c.caption.to_s, encoding) } +
|
||||
(options[:description] ? [Redmine::CodesetUtil.from_utf8(l(:field_description), encoding)] : [])
|
||||
|
||||
# csv lines
|
||||
issues.each do |issue|
|
||||
@@ -402,7 +387,8 @@ module IssuesHelper
|
||||
end
|
||||
s.to_s
|
||||
end
|
||||
csv << [ issue.id.to_s ] + col_values.collect {|c| Redmine::CodesetUtil.from_utf8(c.to_s, encoding) }
|
||||
csv << [ issue.id.to_s ] + col_values.collect {|c| Redmine::CodesetUtil.from_utf8(c.to_s, encoding) } +
|
||||
(options[:description] ? [Redmine::CodesetUtil.from_utf8(issue.description, encoding)] : [])
|
||||
end
|
||||
end
|
||||
export
|
||||
|
||||
@@ -19,43 +19,11 @@
|
||||
|
||||
module QueriesHelper
|
||||
def filters_options_for_select(query)
|
||||
options_for_select(filters_options(query))
|
||||
end
|
||||
|
||||
def filters_options(query)
|
||||
options = [[]]
|
||||
sorted_options = query.available_filters.sort do |a, b|
|
||||
ord = 0
|
||||
if !(a[1][:order] == 20 && b[1][:order] == 20)
|
||||
ord = a[1][:order] <=> b[1][:order]
|
||||
else
|
||||
cn = (CustomField::CUSTOM_FIELDS_NAMES.index(a[1][:field].class.name) <=>
|
||||
CustomField::CUSTOM_FIELDS_NAMES.index(b[1][:field].class.name))
|
||||
if cn != 0
|
||||
ord = cn
|
||||
else
|
||||
f = (a[1][:field] <=> b[1][:field])
|
||||
if f != 0
|
||||
ord = f
|
||||
else
|
||||
# assigned_to or author
|
||||
ord = (a[0] <=> b[0])
|
||||
end
|
||||
end
|
||||
end
|
||||
ord
|
||||
end
|
||||
options += sorted_options.map do |field, field_options|
|
||||
options += query.available_filters.sort {|a,b| a[1][:order] <=> b[1][:order]}.map do |field, field_options|
|
||||
[field_options[:name], field]
|
||||
end
|
||||
end
|
||||
|
||||
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)) + " #{column.caption}", :class => 'inline')
|
||||
end
|
||||
tags
|
||||
options_for_select(options)
|
||||
end
|
||||
|
||||
def column_header(column)
|
||||
@@ -67,7 +35,7 @@ module QueriesHelper
|
||||
def column_content(column, issue)
|
||||
value = column.value(issue)
|
||||
if value.is_a?(Array)
|
||||
value.collect {|v| column_value(column, issue, v)}.compact.join(', ').html_safe
|
||||
value.collect {|v| column_value(column, issue, v)}.compact.sort.join(', ').html_safe
|
||||
else
|
||||
column_value(column, issue, value)
|
||||
end
|
||||
@@ -78,8 +46,6 @@ module QueriesHelper
|
||||
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
|
||||
@@ -87,14 +53,14 @@ module QueriesHelper
|
||||
format_time(value)
|
||||
when 'Date'
|
||||
format_date(value)
|
||||
when 'Fixnum'
|
||||
when 'Fixnum', 'Float'
|
||||
if column.name == :done_ratio
|
||||
progress_bar(value, :width => '80px')
|
||||
elsif column.name == :spent_hours
|
||||
sprintf "%.2f", value
|
||||
else
|
||||
value.to_s
|
||||
h(value.to_s)
|
||||
end
|
||||
when 'Float'
|
||||
sprintf "%.2f", value
|
||||
when 'User'
|
||||
link_to_user value
|
||||
when 'Project'
|
||||
@@ -107,11 +73,6 @@ module QueriesHelper
|
||||
l(:general_text_No)
|
||||
when 'Issue'
|
||||
link_to_issue(value, :subject => false)
|
||||
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
|
||||
h(value)
|
||||
end
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
require 'iconv'
|
||||
require 'redmine/codeset_util'
|
||||
|
||||
module RepositoriesHelper
|
||||
def format_revision(revision)
|
||||
if revision.respond_to? :format_identifier
|
||||
@@ -250,13 +253,16 @@ module RepositoriesHelper
|
||||
|
||||
def index_commits(commits, heads)
|
||||
return nil if commits.nil? or commits.first.parents.nil?
|
||||
|
||||
refs_map = {}
|
||||
heads.each do |head|
|
||||
refs_map[head.scmid] ||= []
|
||||
refs_map[head.scmid] << head
|
||||
end
|
||||
|
||||
commits_by_scmid = {}
|
||||
commits.reverse.each_with_index do |commit, commit_index|
|
||||
|
||||
commits_by_scmid[commit.scmid] = {
|
||||
:parent_scmids => commit.parents.collect { |parent| parent.scmid },
|
||||
:rdmid => commit_index,
|
||||
@@ -265,28 +271,38 @@ module RepositoriesHelper
|
||||
:href => block_given? ? yield(commit.scmid) : commit.scmid
|
||||
}
|
||||
end
|
||||
|
||||
heads.sort! { |head1, head2| head1.to_s <=> head2.to_s }
|
||||
|
||||
space = nil
|
||||
heads.each do |head|
|
||||
if commits_by_scmid.include? head.scmid
|
||||
space = index_head((space || -1) + 1, head, commits_by_scmid)
|
||||
end
|
||||
end
|
||||
|
||||
# when no head matched anything use first commit
|
||||
space ||= index_head(0, commits.first, commits_by_scmid)
|
||||
|
||||
return commits_by_scmid, space
|
||||
end
|
||||
|
||||
def index_head(space, commit, commits_by_scmid)
|
||||
|
||||
stack = [[space, commits_by_scmid[commit.scmid]]]
|
||||
max_space = space
|
||||
|
||||
until stack.empty?
|
||||
space, commit = stack.pop
|
||||
commit[:space] = space if commit[:space].nil?
|
||||
|
||||
space -= 1
|
||||
commit[:parent_scmids].each_with_index do |parent_scmid, parent_index|
|
||||
|
||||
parent_commit = commits_by_scmid[parent_scmid]
|
||||
|
||||
if parent_commit and parent_commit[:space].nil?
|
||||
|
||||
stack.unshift [space += 1, parent_commit]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -56,7 +56,7 @@ module SettingsHelper
|
||||
Setting.send(setting).include?(value),
|
||||
:id => nil
|
||||
) + text.to_s,
|
||||
:class => (options[:inline] ? 'inline' : 'block')
|
||||
:class => 'block'
|
||||
)
|
||||
end.join.html_safe
|
||||
end
|
||||
@@ -91,16 +91,4 @@ module SettingsHelper
|
||||
l_or_humanize(notifiable.name, :prefix => 'label_').html_safe,
|
||||
:class => notifiable.parent.present? ? "parent" : '').html_safe
|
||||
end
|
||||
|
||||
def cross_project_subtasks_options
|
||||
options = [
|
||||
[:label_disabled, ''],
|
||||
[:label_cross_project_system, 'system'],
|
||||
[:label_cross_project_tree, 'tree'],
|
||||
[:label_cross_project_hierarchy, 'hierarchy'],
|
||||
[:label_cross_project_descendants, 'descendants']
|
||||
]
|
||||
|
||||
options.map {|label, value| [l(label), value.to_s]}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -89,10 +89,6 @@ module SortHelper
|
||||
sql.blank? ? nil : sql
|
||||
end
|
||||
|
||||
def to_a
|
||||
@criteria.dup
|
||||
end
|
||||
|
||||
def add!(key, asc)
|
||||
@criteria.delete_if {|k,o| k == key}
|
||||
@criteria = [[key, asc]] + @criteria
|
||||
@@ -186,10 +182,6 @@ module SortHelper
|
||||
@sort_criteria.to_sql
|
||||
end
|
||||
|
||||
def sort_criteria
|
||||
@sort_criteria
|
||||
end
|
||||
|
||||
# Returns a link which sorts by the named column.
|
||||
#
|
||||
# - column is the name of an attribute in the sorted record collection.
|
||||
|
||||
@@ -77,7 +77,6 @@ module TimelogHelper
|
||||
[l(:label_yesterday), 'yesterday'],
|
||||
[l(:label_this_week), 'current_week'],
|
||||
[l(:label_last_week), 'last_week'],
|
||||
[l(:label_last_n_weeks, 2), 'last_2_weeks'],
|
||||
[l(:label_last_n_days, 7), '7_days'],
|
||||
[l(:label_this_month), 'current_month'],
|
||||
[l(:label_last_month), 'last_month'],
|
||||
|
||||
@@ -26,6 +26,15 @@ module UsersHelper
|
||||
["#{l(:status_locked)} (#{user_count_by_status[3].to_i})", '3']], selected.to_s)
|
||||
end
|
||||
|
||||
# Options for the new membership projects combo-box
|
||||
def options_for_membership_project_select(user, projects)
|
||||
options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
|
||||
options << project_tree_options_for_select(projects) do |p|
|
||||
{:disabled => (user.projects.include?(p))}
|
||||
end
|
||||
options
|
||||
end
|
||||
|
||||
def user_mail_notification_options(user)
|
||||
user.valid_notification_options.collect {|o| [l(o.last), o.first]}
|
||||
end
|
||||
|
||||
@@ -44,8 +44,7 @@ module VersionsHelper
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
# When grouping by an association, Rails throws this exception if there's no result (bug)
|
||||
end
|
||||
# Sort with nil keys in last position
|
||||
counts = h.keys.sort {|a,b| a.nil? ? 1 : (b.nil? ? -1 : a <=> b)}.collect {|k| {:group => k, :total => h[k][0], :open => h[k][1], :closed => (h[k][0] - h[k][1])}}
|
||||
counts = h.keys.compact.sort.collect {|k| {:group => k, :total => h[k][0], :open => h[k][1], :closed => (h[k][0] - h[k][1])}}
|
||||
max = counts.collect {|c| c[:total]}.max
|
||||
|
||||
render :partial => 'issue_counts', :locals => {:version => version, :criteria => criteria, :counts => counts, :max => max}
|
||||
|
||||
@@ -37,7 +37,7 @@ module WikiHelper
|
||||
|
||||
def wiki_page_breadcrumb(page)
|
||||
breadcrumb(page.ancestors.reverse.collect {|parent|
|
||||
link_to(h(parent.pretty_title), {:controller => 'wiki', :action => 'show', :id => parent.title, :project_id => parent.project, :version => nil})
|
||||
link_to(h(parent.pretty_title), {:controller => 'wiki', :action => 'show', :id => parent.title, :project_id => parent.project})
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -30,34 +30,6 @@ class CustomField < ActiveRecord::Base
|
||||
validate :validate_custom_field
|
||||
before_validation :set_searchable
|
||||
|
||||
CUSTOM_FIELDS_TABS = [
|
||||
{:name => 'IssueCustomField', :partial => 'custom_fields/index',
|
||||
:label => :label_issue_plural},
|
||||
{:name => 'TimeEntryCustomField', :partial => 'custom_fields/index',
|
||||
:label => :label_spent_time},
|
||||
{:name => 'ProjectCustomField', :partial => 'custom_fields/index',
|
||||
:label => :label_project_plural},
|
||||
{:name => 'VersionCustomField', :partial => 'custom_fields/index',
|
||||
:label => :label_version_plural},
|
||||
{:name => 'UserCustomField', :partial => 'custom_fields/index',
|
||||
:label => :label_user_plural},
|
||||
{:name => 'GroupCustomField', :partial => 'custom_fields/index',
|
||||
:label => :label_group_plural},
|
||||
{:name => 'TimeEntryActivityCustomField', :partial => 'custom_fields/index',
|
||||
:label => TimeEntryActivity::OptionName},
|
||||
{:name => 'IssuePriorityCustomField', :partial => 'custom_fields/index',
|
||||
:label => IssuePriority::OptionName},
|
||||
{:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index',
|
||||
:label => DocumentCategory::OptionName}
|
||||
]
|
||||
|
||||
CUSTOM_FIELDS_NAMES = CUSTOM_FIELDS_TABS.collect{|v| v[:name]}
|
||||
|
||||
def field_format=(arg)
|
||||
# cannot change format of a saved custom field
|
||||
super if new_record?
|
||||
end
|
||||
|
||||
def set_searchable
|
||||
# make sure these fields are not searchable
|
||||
self.searchable = false if %w(int float date bool).include?(field_format)
|
||||
|
||||
@@ -36,7 +36,6 @@ class Enumeration < ActiveRecord::Base
|
||||
validates_length_of :name, :maximum => 30
|
||||
|
||||
scope :shared, where(:project_id => nil)
|
||||
scope :sorted, order("#{table_name}.position ASC")
|
||||
scope :active, where(:active => true)
|
||||
scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
class Issue < ActiveRecord::Base
|
||||
include Redmine::SafeAttributes
|
||||
include Redmine::Utils::DateCalculation
|
||||
|
||||
belongs_to :project
|
||||
belongs_to :tracker
|
||||
@@ -29,14 +28,6 @@ class Issue < ActiveRecord::Base
|
||||
belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
|
||||
|
||||
has_many :journals, :as => :journalized, :dependent => :destroy
|
||||
has_many :visible_journals,
|
||||
:class_name => 'Journal',
|
||||
:as => :journalized,
|
||||
: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 => :delete_all
|
||||
has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
|
||||
|
||||
@@ -48,7 +39,7 @@ class Issue < ActiveRecord::Base
|
||||
acts_as_customizable
|
||||
acts_as_watchable
|
||||
acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"],
|
||||
:include => [:project, :visible_journals],
|
||||
:include => [:project, :journals],
|
||||
# sort by id so that limited eager loading doesn't break with postgresql
|
||||
:order_column => "#{table_name}.id"
|
||||
acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"},
|
||||
@@ -61,7 +52,6 @@ class Issue < ActiveRecord::Base
|
||||
DONE_RATIO_OPTIONS = %w(issue_field issue_status)
|
||||
|
||||
attr_reader :current_journal
|
||||
delegate :notes, :notes=, :private_notes, :private_notes=, :to => :current_journal, :allow_nil => true
|
||||
|
||||
validates_presence_of :subject, :priority, :project, :tracker, :author, :status
|
||||
|
||||
@@ -286,8 +276,7 @@ class Issue < ActiveRecord::Base
|
||||
if fixed_version && fixed_version.project != project && !project.shared_versions.include?(fixed_version)
|
||||
self.fixed_version = nil
|
||||
end
|
||||
# Clear the parent task if it's no longer valid
|
||||
unless valid_parent_project?
|
||||
if parent && parent.project_id != project_id
|
||||
self.parent_issue_id = nil
|
||||
end
|
||||
@custom_field_values = nil
|
||||
@@ -346,7 +335,6 @@ class Issue < ActiveRecord::Base
|
||||
'custom_field_values',
|
||||
'custom_fields',
|
||||
'lock_version',
|
||||
'notes',
|
||||
:if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) }
|
||||
|
||||
safe_attributes 'status_id',
|
||||
@@ -354,15 +342,8 @@ class Issue < ActiveRecord::Base
|
||||
'fixed_version_id',
|
||||
'done_ratio',
|
||||
'lock_version',
|
||||
'notes',
|
||||
:if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? }
|
||||
|
||||
safe_attributes 'notes',
|
||||
: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)}
|
||||
|
||||
safe_attributes 'watcher_user_ids',
|
||||
:if => lambda {|issue, user| issue.new_record? && user.allowed_to?(:add_issue_watchers, issue.project)}
|
||||
|
||||
@@ -417,10 +398,7 @@ class Issue < ActiveRecord::Base
|
||||
end
|
||||
|
||||
if attrs['parent_issue_id'].present?
|
||||
s = attrs['parent_issue_id'].to_s
|
||||
unless (m = s.match(%r{\A#?(\d+)\z})) && (m[1] == parent_id.to_s || Issue.visible(user).exists?(m[1]))
|
||||
@invalid_parent_issue_id = attrs.delete('parent_issue_id')
|
||||
end
|
||||
attrs.delete('parent_issue_id') unless Issue.visible(user).exists?(attrs['parent_issue_id'].to_i)
|
||||
end
|
||||
|
||||
if attrs['custom_field_values'].present?
|
||||
@@ -526,15 +504,11 @@ class Issue < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def validate_issue
|
||||
if due_date.nil? && @attributes['due_date'].present?
|
||||
if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
|
||||
errors.add :due_date, :not_a_date
|
||||
end
|
||||
|
||||
if start_date.nil? && @attributes['start_date'].present?
|
||||
errors.add :start_date, :not_a_date
|
||||
end
|
||||
|
||||
if due_date && start_date && due_date < start_date
|
||||
if self.due_date and self.start_date and self.due_date < self.start_date
|
||||
errors.add :due_date, :greater_than_start_date
|
||||
end
|
||||
|
||||
@@ -558,11 +532,9 @@ class Issue < ActiveRecord::Base
|
||||
end
|
||||
|
||||
# Checks parent issue assignment
|
||||
if @invalid_parent_issue_id.present?
|
||||
errors.add :parent_issue_id, :invalid
|
||||
elsif @parent_issue
|
||||
if !valid_parent_project?(@parent_issue)
|
||||
errors.add :parent_issue_id, :invalid
|
||||
if @parent_issue
|
||||
if @parent_issue.project_id != project_id
|
||||
errors.add :parent_issue_id, :not_same_project
|
||||
elsif !new_record?
|
||||
# moving an existing issue
|
||||
if @parent_issue.root_id != root_id
|
||||
@@ -570,7 +542,7 @@ class Issue < ActiveRecord::Base
|
||||
elsif move_possible?(@parent_issue)
|
||||
# move accepted inside tree
|
||||
else
|
||||
errors.add :parent_issue_id, :invalid
|
||||
errors.add :parent_issue_id, :not_a_valid_parent
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -743,8 +715,8 @@ class Issue < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the users that should be notified
|
||||
def notified_users
|
||||
# Returns the mail adresses of users that should be notified
|
||||
def recipients
|
||||
notified = []
|
||||
# Author and assignee are always notified unless they have been
|
||||
# locked or don't want to be notified
|
||||
@@ -761,12 +733,7 @@ class Issue < ActiveRecord::Base
|
||||
notified.uniq!
|
||||
# Remove users that can not view the issue
|
||||
notified.reject! {|user| !visible?(user)}
|
||||
notified
|
||||
end
|
||||
|
||||
# Returns the email addresses that should be notified
|
||||
def recipients
|
||||
notified_users.collect(&:mail)
|
||||
notified.collect(&:mail)
|
||||
end
|
||||
|
||||
# Returns the number of hours spent on this issue
|
||||
@@ -785,7 +752,7 @@ class Issue < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def relations
|
||||
@relations ||= IssueRelations.new(self, (relations_from + relations_to).sort)
|
||||
@relations ||= (relations_from + relations_to).sort
|
||||
end
|
||||
|
||||
# Preloads relations for a collection of issues
|
||||
@@ -808,25 +775,6 @@ class Issue < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
# Preloads visible relations for a collection of issues
|
||||
def self.load_visible_relations(issues, user=User.current)
|
||||
if issues.any?
|
||||
issue_ids = issues.map(&:id)
|
||||
# Relations with issue_from in given issues and visible issue_to
|
||||
relations_from = IssueRelation.includes(:issue_to => [:status, :project]).where(visible_condition(user)).where(:issue_from_id => issue_ids).all
|
||||
# Relations with issue_to in given issues and visible issue_from
|
||||
relations_to = IssueRelation.includes(:issue_from => [:status, :project]).where(visible_condition(user)).where(:issue_to_id => issue_ids).all
|
||||
|
||||
issues.each do |issue|
|
||||
relations =
|
||||
relations_from.select {|relation| relation.issue_from_id == issue.id} +
|
||||
relations_to.select {|relation| relation.issue_to_id == issue.id}
|
||||
|
||||
issue.instance_variable_set "@relations", IssueRelations.new(issue, relations.sort)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Finds an issue relation given its id.
|
||||
def find_relation(relation_id)
|
||||
IssueRelation.find(relation_id, :conditions => ["issue_to_id = ? OR issue_from_id = ?", id, id])
|
||||
@@ -864,58 +812,29 @@ class Issue < ActiveRecord::Base
|
||||
(start_date && due_date) ? due_date - start_date : 0
|
||||
end
|
||||
|
||||
# Returns the duration in working days
|
||||
def working_duration
|
||||
(start_date && due_date) ? working_days(start_date, due_date) : 0
|
||||
end
|
||||
|
||||
def soonest_start(reload=false)
|
||||
@soonest_start = nil if reload
|
||||
def soonest_start
|
||||
@soonest_start ||= (
|
||||
relations_to(reload).collect{|relation| relation.successor_soonest_start} +
|
||||
relations_to.collect{|relation| relation.successor_soonest_start} +
|
||||
ancestors.collect(&:soonest_start)
|
||||
).compact.max
|
||||
end
|
||||
|
||||
# Sets start_date on the given date or the next working day
|
||||
# and changes due_date to keep the same working duration.
|
||||
def reschedule_on(date)
|
||||
wd = working_duration
|
||||
date = next_working_date(date)
|
||||
self.start_date = date
|
||||
self.due_date = add_working_days(date, wd)
|
||||
end
|
||||
|
||||
# Reschedules the issue on the given date or the next working day and saves the record.
|
||||
# If the issue is a parent task, this is done by rescheduling its subtasks.
|
||||
def reschedule_on!(date)
|
||||
def reschedule_after(date)
|
||||
return if date.nil?
|
||||
if leaf?
|
||||
if start_date.nil? || start_date != date
|
||||
if start_date && start_date > date
|
||||
# Issue can not be moved earlier than its soonest start date
|
||||
date = [soonest_start(true), date].compact.max
|
||||
end
|
||||
reschedule_on(date)
|
||||
if start_date.nil? || start_date < date
|
||||
self.start_date, self.due_date = date, date + duration
|
||||
begin
|
||||
save
|
||||
rescue ActiveRecord::StaleObjectError
|
||||
reload
|
||||
reschedule_on(date)
|
||||
self.start_date, self.due_date = date, date + duration
|
||||
save
|
||||
end
|
||||
end
|
||||
else
|
||||
leaves.each do |leaf|
|
||||
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
|
||||
leaf.reschedule_on!(date)
|
||||
end
|
||||
else
|
||||
leaf.reschedule_on!(date)
|
||||
end
|
||||
leaf.reschedule_after(date)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -936,7 +855,7 @@ class Issue < ActiveRecord::Base
|
||||
|
||||
# Returns a string of css classes that apply to the issue
|
||||
def css_classes
|
||||
s = "issue status-#{status_id} #{priority.try(:css_classes)}"
|
||||
s = "issue status-#{status_id} priority-#{priority_id}"
|
||||
s << ' closed' if closed?
|
||||
s << ' overdue' if overdue?
|
||||
s << ' child' if child?
|
||||
@@ -986,44 +905,23 @@ class Issue < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def parent_issue_id=(arg)
|
||||
s = arg.to_s.strip.presence
|
||||
if s && (m = s.match(%r{\A#?(\d+)\z})) && (@parent_issue = Issue.find_by_id(m[1]))
|
||||
parent_issue_id = arg.blank? ? nil : arg.to_i
|
||||
if parent_issue_id && @parent_issue = Issue.find_by_id(parent_issue_id)
|
||||
@parent_issue.id
|
||||
else
|
||||
@parent_issue = nil
|
||||
@invalid_parent_issue_id = arg
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def parent_issue_id
|
||||
if @invalid_parent_issue_id
|
||||
@invalid_parent_issue_id
|
||||
elsif instance_variable_defined? :@parent_issue
|
||||
if instance_variable_defined? :@parent_issue
|
||||
@parent_issue.nil? ? nil : @parent_issue.id
|
||||
else
|
||||
parent_id
|
||||
end
|
||||
end
|
||||
|
||||
# Returns true if issue's project is a valid
|
||||
# parent issue project
|
||||
def valid_parent_project?(issue=parent)
|
||||
return true if issue.nil? || issue.project_id == project_id
|
||||
|
||||
case Setting.cross_project_subtasks
|
||||
when 'system'
|
||||
true
|
||||
when 'tree'
|
||||
issue.project.root == project.root
|
||||
when 'hierarchy'
|
||||
issue.project.is_or_is_ancestor_of?(project) || issue.project.is_descendant_of?(project)
|
||||
when 'descendants'
|
||||
issue.project.is_or_is_ancestor_of?(project)
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# Extracted from the ReportsController.
|
||||
def self.by_tracker(project)
|
||||
count_and_group_by(:project => project,
|
||||
@@ -1103,9 +1001,8 @@ class Issue < ActiveRecord::Base
|
||||
relations_to.clear
|
||||
end
|
||||
|
||||
# Move subtasks that were in the same project
|
||||
# Move subtasks
|
||||
children.each do |child|
|
||||
next unless child.project_id == project_id_was
|
||||
# Change project and keep project
|
||||
child.send :project=, project, true
|
||||
unless child.save
|
||||
@@ -1114,20 +1011,11 @@ class Issue < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
# Callback for after the creation of an issue by copy
|
||||
# * adds a "copied to" relation with the copied issue
|
||||
# * copies subtasks from the copied issue
|
||||
# Copies subtasks from the copied issue
|
||||
def after_create_from_copy
|
||||
return unless copy? && !@after_create_from_copy_handled
|
||||
return unless copy?
|
||||
|
||||
if (@copied_from.project_id == project_id || Setting.cross_project_issue_relations?) && @copy_options[:link] != false
|
||||
relation = IssueRelation.new(:issue_from => @copied_from, :issue_to => self, :relation_type => IssueRelation::TYPE_COPIED_TO)
|
||||
unless relation.save
|
||||
logger.error "Could not create relation while copying ##{@copied_from.id} to ##{id} due to validation errors: #{relation.errors.full_messages.join(', ')}" if logger
|
||||
end
|
||||
end
|
||||
|
||||
unless @copied_from.leaf? || @copy_options[:subtasks] == false
|
||||
unless @copied_from.leaf? || @copy_options[:subtasks] == false || @subtasks_copied
|
||||
@copied_from.children.each do |child|
|
||||
unless child.visible?
|
||||
# Do not copy subtasks that are not visible to avoid potential disclosure of private data
|
||||
@@ -1143,8 +1031,8 @@ class Issue < ActiveRecord::Base
|
||||
logger.error "Could not copy subtask ##{child.id} while copying ##{@copied_from.id} to ##{id} due to validation errors: #{copy.errors.full_messages.join(', ')}" if logger
|
||||
end
|
||||
end
|
||||
@subtasks_copied = true
|
||||
end
|
||||
@after_create_from_copy_handled = true
|
||||
end
|
||||
|
||||
def update_nested_set_attributes
|
||||
@@ -1255,7 +1143,7 @@ class Issue < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
# Callback on file attachment
|
||||
# Callback on attachment deletion
|
||||
def attachment_added(obj)
|
||||
if @current_journal && !obj.new_record?
|
||||
@current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => obj.id, :value => obj.filename)
|
||||
|
||||
@@ -27,7 +27,7 @@ class IssueCategory < ActiveRecord::Base
|
||||
|
||||
safe_attributes 'name', 'assigned_to_id'
|
||||
|
||||
scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)}
|
||||
scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}}
|
||||
|
||||
alias :destroy_without_reassign :destroy
|
||||
|
||||
|
||||
@@ -18,9 +18,6 @@
|
||||
class IssuePriority < Enumeration
|
||||
has_many :issues, :foreign_key => 'priority_id'
|
||||
|
||||
after_destroy {|priority| priority.class.compute_position_names}
|
||||
after_save {|priority| priority.class.compute_position_names if priority.position_changed? && priority.position}
|
||||
|
||||
OptionName = :enumeration_issue_priorities
|
||||
|
||||
def option_name
|
||||
@@ -34,35 +31,4 @@ class IssuePriority < Enumeration
|
||||
def transfer_relations(to)
|
||||
issues.update_all("priority_id = #{to.id}")
|
||||
end
|
||||
|
||||
def css_classes
|
||||
"priority-#{id} priority-#{position_name}"
|
||||
end
|
||||
|
||||
# Clears position_name for all priorities
|
||||
# Called from migration 20121026003537_populate_enumerations_position_name
|
||||
def self.clear_position_names
|
||||
update_all :position_name => nil
|
||||
end
|
||||
|
||||
# Updates position_name for active priorities
|
||||
# Called from migration 20121026003537_populate_enumerations_position_name
|
||||
def self.compute_position_names
|
||||
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|
|
||||
name = case
|
||||
when priority.position == default.position
|
||||
"default"
|
||||
when priority.position < default.position
|
||||
index == 0 ? "lowest" : "low#{index+1}"
|
||||
else
|
||||
index == (priorities.size - 1) ? "highest" : "high#{priorities.size - index}"
|
||||
end
|
||||
|
||||
update_all({:position_name => name}, :id => priority.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,20 +15,6 @@
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
# Class used to represent the relations of an issue
|
||||
class IssueRelations < Array
|
||||
include Redmine::I18n
|
||||
|
||||
def initialize(issue, *args)
|
||||
@issue = issue
|
||||
super(*args)
|
||||
end
|
||||
|
||||
def to_s(*args)
|
||||
map {|relation| "#{l(relation.label_for(@issue))} ##{relation.other_issue(@issue).id}"}.join(', ')
|
||||
end
|
||||
end
|
||||
|
||||
class IssueRelation < ActiveRecord::Base
|
||||
belongs_to :issue_from, :class_name => 'Issue', :foreign_key => 'issue_from_id'
|
||||
belongs_to :issue_to, :class_name => 'Issue', :foreign_key => 'issue_to_id'
|
||||
@@ -40,37 +26,25 @@ class IssueRelation < ActiveRecord::Base
|
||||
TYPE_BLOCKED = "blocked"
|
||||
TYPE_PRECEDES = "precedes"
|
||||
TYPE_FOLLOWS = "follows"
|
||||
TYPE_COPIED_TO = "copied_to"
|
||||
TYPE_COPIED_FROM = "copied_from"
|
||||
|
||||
TYPES = {
|
||||
TYPE_RELATES => { :name => :label_relates_to, :sym_name => :label_relates_to,
|
||||
:order => 1, :sym => TYPE_RELATES },
|
||||
TYPE_DUPLICATES => { :name => :label_duplicates, :sym_name => :label_duplicated_by,
|
||||
:order => 2, :sym => TYPE_DUPLICATED },
|
||||
TYPE_DUPLICATED => { :name => :label_duplicated_by, :sym_name => :label_duplicates,
|
||||
:order => 3, :sym => TYPE_DUPLICATES, :reverse => TYPE_DUPLICATES },
|
||||
TYPE_BLOCKS => { :name => :label_blocks, :sym_name => :label_blocked_by,
|
||||
:order => 4, :sym => TYPE_BLOCKED },
|
||||
TYPE_BLOCKED => { :name => :label_blocked_by, :sym_name => :label_blocks,
|
||||
:order => 5, :sym => TYPE_BLOCKS, :reverse => TYPE_BLOCKS },
|
||||
TYPE_PRECEDES => { :name => :label_precedes, :sym_name => :label_follows,
|
||||
:order => 6, :sym => TYPE_FOLLOWS },
|
||||
TYPE_FOLLOWS => { :name => :label_follows, :sym_name => :label_precedes,
|
||||
:order => 7, :sym => TYPE_PRECEDES, :reverse => TYPE_PRECEDES },
|
||||
TYPE_COPIED_TO => { :name => :label_copied_to, :sym_name => :label_copied_from,
|
||||
:order => 8, :sym => TYPE_COPIED_FROM },
|
||||
TYPE_COPIED_FROM => { :name => :label_copied_from, :sym_name => :label_copied_to,
|
||||
:order => 9, :sym => TYPE_COPIED_TO, :reverse => TYPE_COPIED_TO }
|
||||
}.freeze
|
||||
TYPES = { TYPE_RELATES => { :name => :label_relates_to, :sym_name => :label_relates_to, :order => 1, :sym => TYPE_RELATES },
|
||||
TYPE_DUPLICATES => { :name => :label_duplicates, :sym_name => :label_duplicated_by, :order => 2, :sym => TYPE_DUPLICATED },
|
||||
TYPE_DUPLICATED => { :name => :label_duplicated_by, :sym_name => :label_duplicates, :order => 3, :sym => TYPE_DUPLICATES, :reverse => TYPE_DUPLICATES },
|
||||
TYPE_BLOCKS => { :name => :label_blocks, :sym_name => :label_blocked_by, :order => 4, :sym => TYPE_BLOCKED },
|
||||
TYPE_BLOCKED => { :name => :label_blocked_by, :sym_name => :label_blocks, :order => 5, :sym => TYPE_BLOCKS, :reverse => TYPE_BLOCKS },
|
||||
TYPE_PRECEDES => { :name => :label_precedes, :sym_name => :label_follows, :order => 6, :sym => TYPE_FOLLOWS },
|
||||
TYPE_FOLLOWS => { :name => :label_follows, :sym_name => :label_precedes, :order => 7, :sym => TYPE_PRECEDES, :reverse => TYPE_PRECEDES }
|
||||
}.freeze
|
||||
|
||||
validates_presence_of :issue_from, :issue_to, :relation_type
|
||||
validates_inclusion_of :relation_type, :in => TYPES.keys
|
||||
validates_numericality_of :delay, :allow_nil => true
|
||||
validates_uniqueness_of :issue_to_id, :scope => :issue_from_id
|
||||
|
||||
validate :validate_issue_relation
|
||||
|
||||
attr_protected :issue_from_id, :issue_to_id
|
||||
|
||||
before_save :handle_issue_order
|
||||
|
||||
def visible?(user=User.current)
|
||||
@@ -95,19 +69,14 @@ class IssueRelation < ActiveRecord::Base
|
||||
def validate_issue_relation
|
||||
if issue_from && issue_to
|
||||
errors.add :issue_to_id, :invalid if issue_from_id == issue_to_id
|
||||
unless issue_from.project_id == issue_to.project_id ||
|
||||
Setting.cross_project_issue_relations?
|
||||
errors.add :issue_to_id, :not_same_project
|
||||
end
|
||||
# detect circular dependencies depending wether the relation should be reversed
|
||||
errors.add :issue_to_id, :not_same_project unless issue_from.project_id == issue_to.project_id || Setting.cross_project_issue_relations?
|
||||
#detect circular dependencies depending wether the relation should be reversed
|
||||
if TYPES.has_key?(relation_type) && TYPES[relation_type][:reverse]
|
||||
errors.add :base, :circular_dependency if issue_from.all_dependent_issues.include? issue_to
|
||||
else
|
||||
errors.add :base, :circular_dependency if issue_to.all_dependent_issues.include? issue_from
|
||||
end
|
||||
if issue_from.is_descendant_of?(issue_to) || issue_from.is_ancestor_of?(issue_to)
|
||||
errors.add :base, :cant_link_an_issue_with_a_descendant
|
||||
end
|
||||
errors.add :base, :cant_link_an_issue_with_a_descendant if issue_from.is_descendant_of?(issue_to) || issue_from.is_ancestor_of?(issue_to)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -127,13 +96,7 @@ class IssueRelation < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def label_for(issue)
|
||||
TYPES[relation_type] ?
|
||||
TYPES[relation_type][(self.issue_from_id == issue.id) ? :name : :sym_name] :
|
||||
:unknow
|
||||
end
|
||||
|
||||
def css_classes_for(issue)
|
||||
"rel-#{relation_type_for(issue)}"
|
||||
TYPES[relation_type] ? TYPES[relation_type][(self.issue_from_id == issue.id) ? :name : :sym_name] : :unknow
|
||||
end
|
||||
|
||||
def handle_issue_order
|
||||
@@ -150,20 +113,18 @@ class IssueRelation < ActiveRecord::Base
|
||||
def set_issue_to_dates
|
||||
soonest_start = self.successor_soonest_start
|
||||
if soonest_start && issue_to
|
||||
issue_to.reschedule_on!(soonest_start)
|
||||
issue_to.reschedule_after(soonest_start)
|
||||
end
|
||||
end
|
||||
|
||||
def successor_soonest_start
|
||||
if (TYPE_PRECEDES == self.relation_type) && delay && issue_from &&
|
||||
(issue_from.start_date || issue_from.due_date)
|
||||
if (TYPE_PRECEDES == self.relation_type) && delay && issue_from && (issue_from.start_date || issue_from.due_date)
|
||||
(issue_from.due_date || issue_from.start_date) + 1 + delay
|
||||
end
|
||||
end
|
||||
|
||||
def <=>(relation)
|
||||
r = TYPES[self.relation_type][:order] <=> TYPES[relation.relation_type][:order]
|
||||
r == 0 ? id <=> relation.id : r
|
||||
TYPES[self.relation_type][:order] <=> TYPES[relation.relation_type][:order]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -29,7 +29,7 @@ class IssueStatus < ActiveRecord::Base
|
||||
validates_inclusion_of :default_done_ratio, :in => 0..100, :allow_nil => true
|
||||
|
||||
scope :sorted, order("#{table_name}.position ASC")
|
||||
scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)}
|
||||
scope :named, lambda {|arg| where(["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip])}
|
||||
|
||||
def update_default
|
||||
IssueStatus.update_all({:is_default => false}, ['id <> ?', id]) if self.is_default?
|
||||
|
||||
@@ -37,15 +37,10 @@ class Journal < ActiveRecord::Base
|
||||
:conditions => "#{Journal.table_name}.journalized_type = 'Issue' AND" +
|
||||
" (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')"}
|
||||
|
||||
before_create :split_private_notes
|
||||
|
||||
scope :visible, lambda {|*args|
|
||||
user = args.shift || User.current
|
||||
|
||||
includes(:issue => :project).
|
||||
where(Issue.visible_condition(user, *args)).
|
||||
where("(#{Journal.table_name}.private_notes = ? OR (#{Project.allowed_to_condition(user, :view_private_notes, *args)}))", false)
|
||||
}
|
||||
scope :visible, lambda {|*args| {
|
||||
:include => {:issue => :project},
|
||||
:conditions => Issue.visible_condition(args.shift || User.current, *args)
|
||||
}}
|
||||
|
||||
def save(*args)
|
||||
# Do not save an empty journal
|
||||
@@ -80,7 +75,6 @@ class Journal < ActiveRecord::Base
|
||||
s = 'journal'
|
||||
s << ' has-notes' unless notes.blank?
|
||||
s << ' has-details' unless details.blank?
|
||||
s << ' private-notes' if private_notes?
|
||||
s
|
||||
end
|
||||
|
||||
@@ -91,41 +85,4 @@ class Journal < ActiveRecord::Base
|
||||
def notify=(arg)
|
||||
@notify = arg
|
||||
end
|
||||
|
||||
def recipients
|
||||
notified = journalized.notified_users
|
||||
if private_notes?
|
||||
notified = notified.select {|user| user.allowed_to?(:view_private_notes, journalized.project)}
|
||||
end
|
||||
notified.map(&:mail)
|
||||
end
|
||||
|
||||
def watcher_recipients
|
||||
notified = journalized.notified_watchers
|
||||
if private_notes?
|
||||
notified = notified.select {|user| user.allowed_to?(:view_private_notes, journalized.project)}
|
||||
end
|
||||
notified.map(&:mail)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def split_private_notes
|
||||
if private_notes?
|
||||
if notes.present?
|
||||
if details.any?
|
||||
# Split the journal (notes/changes) so we don't have half-private journals
|
||||
journal = Journal.new(:journalized => journalized, :user => user, :notes => nil, :private_notes => false)
|
||||
journal.details = details
|
||||
journal.save
|
||||
self.details = []
|
||||
self.created_on = journal.created_on
|
||||
end
|
||||
else
|
||||
# Blank notes should not be private
|
||||
self.private_notes = false
|
||||
end
|
||||
end
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
@@ -182,7 +182,7 @@ class MailHandler < ActionMailer::Base
|
||||
end
|
||||
|
||||
# Adds a note to an existing issue
|
||||
def receive_issue_reply(issue_id, from_journal=nil)
|
||||
def receive_issue_reply(issue_id)
|
||||
issue = Issue.find_by_id(issue_id)
|
||||
return unless issue
|
||||
# check permission
|
||||
@@ -197,10 +197,6 @@ class MailHandler < ActionMailer::Base
|
||||
@@handler_options[:issue].clear
|
||||
|
||||
journal = issue.init_journal(user)
|
||||
if from_journal && from_journal.private_notes?
|
||||
# If the received email was a reply to a private note, make the added note private
|
||||
issue.private_notes = true
|
||||
end
|
||||
issue.safe_attributes = issue_attributes_from_keywords(issue)
|
||||
issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
|
||||
journal.notes = cleaned_up_text_body
|
||||
@@ -216,7 +212,7 @@ class MailHandler < ActionMailer::Base
|
||||
def receive_journal_reply(journal_id)
|
||||
journal = Journal.find_by_id(journal_id)
|
||||
if journal && journal.journalized_type == 'Issue'
|
||||
receive_issue_reply(journal.journalized_id, journal)
|
||||
receive_issue_reply(journal.journalized_id)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -62,9 +62,9 @@ class Mailer < ActionMailer::Base
|
||||
message_id journal
|
||||
references issue
|
||||
@author = journal.user
|
||||
recipients = journal.recipients
|
||||
recipients = issue.recipients
|
||||
# Watchers in cc
|
||||
cc = journal.watcher_recipients - recipients
|
||||
cc = issue.watcher_recipients - recipients
|
||||
s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] "
|
||||
s << "(#{issue.status.name}) " if journal.new_value_for('status_id')
|
||||
s << issue.subject
|
||||
|
||||
@@ -27,20 +27,19 @@ class Principal < ActiveRecord::Base
|
||||
scope :active, :conditions => "#{Principal.table_name}.status = 1"
|
||||
|
||||
scope :like, lambda {|q|
|
||||
q = q.to_s
|
||||
if q.blank?
|
||||
where({})
|
||||
{}
|
||||
else
|
||||
q = q.to_s
|
||||
pattern = "%#{q}%"
|
||||
sql = %w(login firstname lastname mail).map {|column| "LOWER(#{table_name}.#{column}) LIKE LOWER(:p)"}.join(" OR ")
|
||||
sql = "LOWER(login) LIKE LOWER(:p) OR LOWER(firstname) LIKE LOWER(:p) OR LOWER(lastname) LIKE LOWER(:p) OR LOWER(mail) LIKE LOWER(:p)"
|
||||
params = {:p => pattern}
|
||||
if q =~ /^(.+)\s+(.+)$/
|
||||
a, b = "#{$1}%", "#{$2}%"
|
||||
sql << " OR (LOWER(#{table_name}.firstname) LIKE LOWER(:a) AND LOWER(#{table_name}.lastname) LIKE LOWER(:b))"
|
||||
sql << " OR (LOWER(#{table_name}.firstname) LIKE LOWER(:b) AND LOWER(#{table_name}.lastname) LIKE LOWER(:a))"
|
||||
sql << " OR (LOWER(firstname) LIKE LOWER(:a) AND LOWER(lastname) LIKE LOWER(:b)) OR (LOWER(firstname) LIKE LOWER(:b) AND LOWER(lastname) LIKE LOWER(:a))"
|
||||
params.merge!(:a => a, :b => b)
|
||||
end
|
||||
where(sql, params)
|
||||
{:conditions => [sql, params]}
|
||||
end
|
||||
}
|
||||
|
||||
@@ -48,20 +47,20 @@ class Principal < ActiveRecord::Base
|
||||
scope :member_of, lambda {|projects|
|
||||
projects = [projects] unless projects.is_a?(Array)
|
||||
if projects.empty?
|
||||
where("1=0")
|
||||
{:conditions => "1=0"}
|
||||
else
|
||||
ids = projects.map(&:id)
|
||||
where("#{Principal.table_name}.status = 1 AND #{Principal.table_name}.id IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids)
|
||||
{:conditions => ["#{Principal.table_name}.status = 1 AND #{Principal.table_name}.id IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids]}
|
||||
end
|
||||
}
|
||||
# Principals that are not members of projects
|
||||
scope :not_member_of, lambda {|projects|
|
||||
projects = [projects] unless projects.is_a?(Array)
|
||||
if projects.empty?
|
||||
where("1=0")
|
||||
{:conditions => "1=0"}
|
||||
else
|
||||
ids = projects.map(&:id)
|
||||
where("#{Principal.table_name}.id NOT IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids)
|
||||
{:conditions => ["#{Principal.table_name}.id NOT IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids]}
|
||||
end
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ class Project < ActiveRecord::Base
|
||||
|
||||
# Specific overidden Activities
|
||||
has_many :time_entry_activities
|
||||
has_many :members, :include => [:principal, :roles], :conditions => "#{User.table_name}.type='User' AND #{User.table_name}.status=#{User::STATUS_ACTIVE}"
|
||||
has_many :members, :include => [:user, :roles], :conditions => "#{User.table_name}.type='User' AND #{User.table_name}.status=#{User::STATUS_ACTIVE}"
|
||||
has_many :memberships, :class_name => 'Member'
|
||||
has_many :member_principals, :class_name => 'Member',
|
||||
:include => :principal,
|
||||
@@ -393,16 +393,6 @@ class Project < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
# Recalculates all lft and rgt values based on project names
|
||||
# Unlike Project.rebuild!, these values are recalculated even if the tree "looks" valid
|
||||
# Used in BuildProjectsTree migration
|
||||
def self.rebuild_tree!
|
||||
transaction do
|
||||
update_all "lft = NULL, rgt = NULL"
|
||||
rebuild!(false)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns an array of the trackers used by the project and its active sub projects
|
||||
def rolled_up_trackers
|
||||
@rolled_up_trackers ||=
|
||||
@@ -482,7 +472,7 @@ class Project < ActiveRecord::Base
|
||||
# Returns the users that should be notified on project events
|
||||
def notified_users
|
||||
# TODO: User part should be extracted to User#notify_about?
|
||||
members.select {|m| m.principal.present? && (m.mail_notification? || m.principal.mail_notification == 'all')}.collect {|m| m.principal}
|
||||
members.select {|m| m.mail_notification? || m.user.mail_notification == 'all'}.collect {|m| m.user}
|
||||
end
|
||||
|
||||
# Returns an array of all custom fields enabled for project issues
|
||||
@@ -731,7 +721,7 @@ class Project < ActiveRecord::Base
|
||||
def copy_wiki(project)
|
||||
# Check that the source project has a wiki first
|
||||
unless project.wiki.nil?
|
||||
wiki = self.wiki || Wiki.new
|
||||
self.wiki ||= Wiki.new
|
||||
wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
|
||||
wiki_pages_map = {}
|
||||
project.wiki.pages.each do |page|
|
||||
@@ -743,8 +733,6 @@ class Project < ActiveRecord::Base
|
||||
wiki.pages << new_wiki_page
|
||||
wiki_pages_map[page.id] = new_wiki_page
|
||||
end
|
||||
|
||||
self.wiki = wiki
|
||||
wiki.save
|
||||
# Reproduce page hierarchy
|
||||
project.wiki.pages.each do |page|
|
||||
@@ -790,7 +778,7 @@ class Project < ActiveRecord::Base
|
||||
# get copied before their children
|
||||
project.issues.find(:all, :order => 'root_id, lft').each do |issue|
|
||||
new_issue = Issue.new
|
||||
new_issue.copy_from(issue, :subtasks => false, :link => false)
|
||||
new_issue.copy_from(issue, :subtasks => false)
|
||||
new_issue.project = self
|
||||
# Reassign fixed_versions by name, since names are unique per project
|
||||
if issue.fixed_version && issue.fixed_version.project == project
|
||||
|
||||
@@ -27,7 +27,6 @@ class QueryColumn
|
||||
self.groupable = name.to_s
|
||||
end
|
||||
self.default_order = options[:default_order]
|
||||
@inline = options.key?(:inline) ? options[:inline] : true
|
||||
@caption_key = options[:caption] || "field_#{name}"
|
||||
end
|
||||
|
||||
@@ -39,15 +38,11 @@ class QueryColumn
|
||||
def sortable?
|
||||
!@sortable.nil?
|
||||
end
|
||||
|
||||
|
||||
def sortable
|
||||
@sortable.is_a?(Proc) ? @sortable.call : @sortable
|
||||
end
|
||||
|
||||
def inline?
|
||||
@inline
|
||||
end
|
||||
|
||||
def value(issue)
|
||||
issue.send name
|
||||
end
|
||||
@@ -63,7 +58,6 @@ class QueryCustomFieldColumn < QueryColumn
|
||||
self.name = "cf_#{custom_field.id}".to_sym
|
||||
self.sortable = custom_field.order_statement || false
|
||||
self.groupable = custom_field.group_statement || false
|
||||
@inline = true
|
||||
@cf = custom_field
|
||||
end
|
||||
|
||||
@@ -77,7 +71,7 @@ class QueryCustomFieldColumn < QueryColumn
|
||||
|
||||
def value(issue)
|
||||
cv = issue.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
|
||||
cv.size > 1 ? cv : cv.first
|
||||
end
|
||||
|
||||
def css_classes
|
||||
@@ -106,25 +100,20 @@ class Query < ActiveRecord::Base
|
||||
"o" => :label_open_issues,
|
||||
"c" => :label_closed_issues,
|
||||
"!*" => :label_none,
|
||||
"*" => :label_any,
|
||||
"*" => :label_all,
|
||||
">=" => :label_greater_or_equal,
|
||||
"<=" => :label_less_or_equal,
|
||||
"><" => :label_between,
|
||||
"<t+" => :label_in_less_than,
|
||||
">t+" => :label_in_more_than,
|
||||
"><t+"=> :label_in_the_next_days,
|
||||
"t+" => :label_in,
|
||||
"t" => :label_today,
|
||||
"w" => :label_this_week,
|
||||
">t-" => :label_less_than_ago,
|
||||
"<t-" => :label_more_than_ago,
|
||||
"><t-"=> :label_in_the_past_days,
|
||||
"t-" => :label_ago,
|
||||
"~" => :label_contains,
|
||||
"!~" => :label_not_contains,
|
||||
"=p" => :label_any_issues_in_project,
|
||||
"=!p" => :label_any_issues_not_in_project,
|
||||
"!p" => :label_no_issues_in_project}
|
||||
"!~" => :label_not_contains }
|
||||
|
||||
cattr_reader :operators
|
||||
|
||||
@@ -132,13 +121,12 @@ class Query < ActiveRecord::Base
|
||||
:list_status => [ "o", "=", "!", "c", "*" ],
|
||||
:list_optional => [ "=", "!", "!*", "*" ],
|
||||
:list_subprojects => [ "*", "!*", "=" ],
|
||||
:date => [ "=", ">=", "<=", "><", "<t+", ">t+", "><t+", "t+", "t", "w", ">t-", "<t-", "><t-", "t-", "!*", "*" ],
|
||||
:date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "><t-", "t-", "t", "w", "!*", "*" ],
|
||||
:date => [ "=", ">=", "<=", "><", "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-", "!*", "*" ],
|
||||
:date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "t-", "t", "w", "!*", "*" ],
|
||||
:string => [ "=", "~", "!", "!~", "!*", "*" ],
|
||||
:text => [ "~", "!~", "!*", "*" ],
|
||||
:integer => [ "=", ">=", "<=", "><", "!*", "*" ],
|
||||
:float => [ "=", ">=", "<=", "><", "!*", "*" ],
|
||||
:relation => ["=", "=p", "=!p", "!p", "!*", "*"]}
|
||||
:float => [ "=", ">=", "<=", "><", "!*", "*" ] }
|
||||
|
||||
cattr_reader :operators_by_filter_type
|
||||
|
||||
@@ -159,8 +147,6 @@ class Query < ActiveRecord::Base
|
||||
QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
|
||||
QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
|
||||
QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
|
||||
QueryColumn.new(:relations, :caption => :label_related_issues),
|
||||
QueryColumn.new(:description, :inline => false)
|
||||
]
|
||||
cattr_reader :available_columns
|
||||
|
||||
@@ -192,7 +178,7 @@ class Query < ActiveRecord::Base
|
||||
case operator_for(field)
|
||||
when "=", ">=", "<=", "><"
|
||||
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-"
|
||||
when ">t-", "<t-", "t-"
|
||||
add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) }
|
||||
end
|
||||
end
|
||||
@@ -235,57 +221,44 @@ class Query < ActiveRecord::Base
|
||||
|
||||
def available_filters
|
||||
return @available_filters if @available_filters
|
||||
@available_filters = {
|
||||
"status_id" => {
|
||||
:type => :list_status, :order => 0,
|
||||
:values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] }
|
||||
},
|
||||
"tracker_id" => {
|
||||
:type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] }
|
||||
},
|
||||
"priority_id" => {
|
||||
:type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] }
|
||||
},
|
||||
"subject" => { :type => :text, :order => 8 },
|
||||
"created_on" => { :type => :date_past, :order => 9 },
|
||||
"updated_on" => { :type => :date_past, :order => 10 },
|
||||
"start_date" => { :type => :date, :order => 11 },
|
||||
"due_date" => { :type => :date, :order => 12 },
|
||||
"estimated_hours" => { :type => :float, :order => 13 },
|
||||
"done_ratio" => { :type => :integer, :order => 14 }
|
||||
}
|
||||
IssueRelation::TYPES.each do |relation_type, options|
|
||||
@available_filters[relation_type] = {
|
||||
:type => :relation, :order => @available_filters.size + 100,
|
||||
:label => options[:name]
|
||||
}
|
||||
end
|
||||
|
||||
@available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
|
||||
"tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
|
||||
"priority_id" => { :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } },
|
||||
"subject" => { :type => :text, :order => 8 },
|
||||
"created_on" => { :type => :date_past, :order => 9 },
|
||||
"updated_on" => { :type => :date_past, :order => 10 },
|
||||
"start_date" => { :type => :date, :order => 11 },
|
||||
"due_date" => { :type => :date, :order => 12 },
|
||||
"estimated_hours" => { :type => :float, :order => 13 },
|
||||
"done_ratio" => { :type => :integer, :order => 14 }}
|
||||
|
||||
principals = []
|
||||
if project
|
||||
principals += project.principals.sort
|
||||
unless project.leaf?
|
||||
subprojects = project.descendants.visible.all
|
||||
if subprojects.any?
|
||||
@available_filters["subproject_id"] = {
|
||||
:type => :list_subprojects, :order => 13,
|
||||
:values => subprojects.collect{|s| [s.name, s.id.to_s] }
|
||||
}
|
||||
@available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => subprojects.collect{|s| [s.name, s.id.to_s] } }
|
||||
principals += Principal.member_of(subprojects)
|
||||
end
|
||||
end
|
||||
else
|
||||
all_projects = Project.visible.all
|
||||
if all_projects.any?
|
||||
# members of visible projects
|
||||
principals += Principal.member_of(all_projects)
|
||||
|
||||
# project filter
|
||||
project_values = []
|
||||
if User.current.logged? && User.current.memberships.any?
|
||||
project_values << ["<< #{l(:label_my_projects).downcase} >>", "mine"]
|
||||
end
|
||||
project_values += all_projects_values
|
||||
@available_filters["project_id"] = {
|
||||
:type => :list, :order => 1, :values => project_values
|
||||
} unless project_values.empty?
|
||||
Project.project_tree(all_projects) do |p, level|
|
||||
prefix = (level > 0 ? ('--' * level + ' ') : '')
|
||||
project_values << ["#{prefix}#{p.name}", p.id.to_s]
|
||||
end
|
||||
@available_filters["project_id"] = { :type => :list, :order => 1, :values => project_values} unless project_values.empty?
|
||||
end
|
||||
end
|
||||
principals.uniq!
|
||||
@@ -294,88 +267,63 @@ class Query < ActiveRecord::Base
|
||||
|
||||
assigned_to_values = []
|
||||
assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
|
||||
assigned_to_values += (Setting.issue_group_assignment? ?
|
||||
principals : users).collect{|s| [s.name, s.id.to_s] }
|
||||
@available_filters["assigned_to_id"] = {
|
||||
:type => :list_optional, :order => 4, :values => assigned_to_values
|
||||
} unless assigned_to_values.empty?
|
||||
assigned_to_values += (Setting.issue_group_assignment? ? principals : users).collect{|s| [s.name, s.id.to_s] }
|
||||
@available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => assigned_to_values } unless assigned_to_values.empty?
|
||||
|
||||
author_values = []
|
||||
author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
|
||||
author_values += users.collect{|s| [s.name, s.id.to_s] }
|
||||
@available_filters["author_id"] = {
|
||||
:type => :list, :order => 5, :values => author_values
|
||||
} unless author_values.empty?
|
||||
@available_filters["author_id"] = { :type => :list, :order => 5, :values => author_values } unless author_values.empty?
|
||||
|
||||
group_values = Group.all.collect {|g| [g.name, g.id.to_s] }
|
||||
@available_filters["member_of_group"] = {
|
||||
:type => :list_optional, :order => 6, :values => group_values
|
||||
} unless group_values.empty?
|
||||
@available_filters["member_of_group"] = { :type => :list_optional, :order => 6, :values => group_values } unless group_values.empty?
|
||||
|
||||
role_values = Role.givable.collect {|r| [r.name, r.id.to_s] }
|
||||
@available_filters["assigned_to_role"] = {
|
||||
:type => :list_optional, :order => 7, :values => role_values
|
||||
} unless role_values.empty?
|
||||
@available_filters["assigned_to_role"] = { :type => :list_optional, :order => 7, :values => role_values } unless role_values.empty?
|
||||
|
||||
if User.current.logged?
|
||||
@available_filters["watcher_id"] = {
|
||||
:type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]]
|
||||
}
|
||||
@available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] }
|
||||
end
|
||||
|
||||
if project
|
||||
# project specific filters
|
||||
categories = project.issue_categories.all
|
||||
unless categories.empty?
|
||||
@available_filters["category_id"] = {
|
||||
:type => :list_optional, :order => 6,
|
||||
:values => categories.collect{|s| [s.name, s.id.to_s] }
|
||||
}
|
||||
@available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => categories.collect{|s| [s.name, s.id.to_s] } }
|
||||
end
|
||||
versions = project.shared_versions.all
|
||||
unless versions.empty?
|
||||
@available_filters["fixed_version_id"] = {
|
||||
:type => :list_optional, :order => 7,
|
||||
:values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] }
|
||||
}
|
||||
@available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
|
||||
end
|
||||
add_custom_fields_filters(project.all_issue_custom_fields)
|
||||
else
|
||||
# global filters for cross project issue list
|
||||
system_shared_versions = Version.visible.find_all_by_sharing('system')
|
||||
unless system_shared_versions.empty?
|
||||
@available_filters["fixed_version_id"] = {
|
||||
:type => :list_optional, :order => 7,
|
||||
:values => system_shared_versions.sort.collect{|s|
|
||||
["#{s.project.name} - #{s.name}", s.id.to_s]
|
||||
}
|
||||
}
|
||||
@available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => system_shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
|
||||
end
|
||||
add_custom_fields_filters(
|
||||
IssueCustomField.find(:all,
|
||||
:conditions => {
|
||||
:is_filter => true,
|
||||
:is_for_all => true
|
||||
}))
|
||||
add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
|
||||
end
|
||||
|
||||
add_associations_custom_fields_filters :project, :author, :assigned_to, :fixed_version
|
||||
|
||||
if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
|
||||
User.current.allowed_to?(:set_own_issues_private, nil, :global => true)
|
||||
@available_filters["is_private"] = {
|
||||
:type => :list, :order => 16,
|
||||
:values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]]
|
||||
}
|
||||
@available_filters["is_private"] = { :type => :list, :order => 15, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]] }
|
||||
end
|
||||
|
||||
Tracker.disabled_core_fields(trackers).each {|field|
|
||||
@available_filters.delete field
|
||||
}
|
||||
|
||||
@available_filters.each do |field, options|
|
||||
options[:name] ||= l(options[:label] || "field_#{field}".gsub(/_id$/, ''))
|
||||
options[:name] ||= l("field_#{field}".gsub(/_id$/, ''))
|
||||
end
|
||||
|
||||
@available_filters
|
||||
end
|
||||
|
||||
# Returns a representation of the available filters for JSON serialization
|
||||
# Returns a representation of the available filters for JSON serialization
|
||||
def available_filters_as_json
|
||||
json = {}
|
||||
available_filters.each do |field, options|
|
||||
@@ -384,21 +332,6 @@ class Query < ActiveRecord::Base
|
||||
json
|
||||
end
|
||||
|
||||
def all_projects
|
||||
@all_projects ||= Project.visible.all
|
||||
end
|
||||
|
||||
def all_projects_values
|
||||
return @all_projects_values if @all_projects_values
|
||||
|
||||
values = []
|
||||
Project.project_tree(all_projects) do |p, level|
|
||||
prefix = (level > 0 ? ('--' * level + ' ') : '')
|
||||
values << ["#{prefix}#{p.name}", p.id.to_s]
|
||||
end
|
||||
@all_projects_values = values
|
||||
end
|
||||
|
||||
def add_filter(field, operator, values)
|
||||
# values must be an array
|
||||
return unless values.nil? || values.is_a?(Array)
|
||||
@@ -518,22 +451,6 @@ class Query < ActiveRecord::Base
|
||||
end.compact
|
||||
end
|
||||
|
||||
def inline_columns
|
||||
columns.select(&:inline?)
|
||||
end
|
||||
|
||||
def block_columns
|
||||
columns.reject(&:inline?)
|
||||
end
|
||||
|
||||
def available_inline_columns
|
||||
available_columns.select(&:inline?)
|
||||
end
|
||||
|
||||
def available_block_columns
|
||||
available_columns.reject(&:inline?)
|
||||
end
|
||||
|
||||
def default_columns_names
|
||||
@default_columns_names ||= begin
|
||||
default_columns = Setting.issue_list_default_columns.map(&:to_sym)
|
||||
@@ -567,7 +484,7 @@ class Query < ActiveRecord::Base
|
||||
if arg.is_a?(Hash)
|
||||
arg = arg.keys.sort.collect {|k| arg[k]}
|
||||
end
|
||||
c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, (o == 'desc' || o == false) ? 'desc' : 'asc']}
|
||||
c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, o == 'desc' ? o : 'asc']}
|
||||
write_attribute(:sort_criteria, c)
|
||||
end
|
||||
|
||||
@@ -583,17 +500,12 @@ class Query < ActiveRecord::Base
|
||||
sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
|
||||
end
|
||||
|
||||
def sort_criteria_order_for(key)
|
||||
sort_criteria.detect {|k, order| key.to_s == k}.try(:last)
|
||||
end
|
||||
|
||||
# Returns the SQL sort order that should be prepended for grouping
|
||||
def group_by_sort_order
|
||||
if grouped? && (column = group_by_column)
|
||||
order = sort_criteria_order_for(column.name) || column.default_order
|
||||
column.sortable.is_a?(Array) ?
|
||||
column.sortable.collect {|s| "#{s} #{order}"}.join(',') :
|
||||
"#{column.sortable} #{order}"
|
||||
column.sortable.collect {|s| "#{s} #{column.default_order}"}.join(',') :
|
||||
"#{column.sortable} #{column.default_order}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -723,9 +635,6 @@ class Query < ActiveRecord::Base
|
||||
if has_column?(:spent_hours)
|
||||
Issue.load_visible_spent_hours(issues)
|
||||
end
|
||||
if has_column?(:relations)
|
||||
Issue.load_visible_relations(issues)
|
||||
end
|
||||
issues
|
||||
rescue ::ActiveRecord::StatementInvalid => e
|
||||
raise StatementInvalid.new(e.message)
|
||||
@@ -802,10 +711,10 @@ class Query < ActiveRecord::Base
|
||||
"(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}" +
|
||||
" WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id))"
|
||||
when "=", "!"
|
||||
role_cond = value.any? ?
|
||||
role_cond = value.any? ?
|
||||
"#{MemberRole.table_name}.role_id IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" :
|
||||
"1=0"
|
||||
|
||||
|
||||
sw = operator == "!" ? 'NOT' : ''
|
||||
nl = operator == "!" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : ''
|
||||
"(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}, #{MemberRole.table_name}" +
|
||||
@@ -820,42 +729,6 @@ class Query < ActiveRecord::Base
|
||||
"#{Issue.table_name}.is_private #{op} (#{va})"
|
||||
end
|
||||
|
||||
def sql_for_relations(field, operator, value, options={})
|
||||
relation_options = IssueRelation::TYPES[field]
|
||||
return relation_options unless relation_options
|
||||
|
||||
relation_type = field
|
||||
join_column, target_join_column = "issue_from_id", "issue_to_id"
|
||||
if relation_options[:reverse] || options[:reverse]
|
||||
relation_type = relation_options[:reverse] || relation_type
|
||||
join_column, target_join_column = target_join_column, join_column
|
||||
end
|
||||
|
||||
sql = case operator
|
||||
when "*", "!*"
|
||||
op = (operator == "*" ? 'IN' : 'NOT IN')
|
||||
"#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}')"
|
||||
when "=", "!"
|
||||
op = (operator == "=" ? 'IN' : 'NOT IN')
|
||||
"#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = #{value.first.to_i})"
|
||||
when "=p", "=!p", "!p"
|
||||
op = (operator == "!p" ? 'NOT IN' : 'IN')
|
||||
comp = (operator == "=!p" ? '<>' : '=')
|
||||
"#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name}, #{Issue.table_name} relissues WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = relissues.id AND relissues.project_id #{comp} #{value.first.to_i})"
|
||||
end
|
||||
|
||||
if relation_options[:sym] == field && !options[:reverse]
|
||||
sqls = [sql, sql_for_relations(field, operator, value, :reverse => true)]
|
||||
sqls.join(["!", "!*", "!p"].include?(operator) ? " AND " : " OR ")
|
||||
else
|
||||
sql
|
||||
end
|
||||
end
|
||||
|
||||
IssueRelation::TYPES.keys.each do |relation_type|
|
||||
alias_method "sql_for_#{relation_type}_field".to_sym, :sql_for_relations
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sql_for_custom_field(field, operator, value, custom_field_id)
|
||||
@@ -961,35 +834,21 @@ class Query < ActiveRecord::Base
|
||||
sql = "#{Issue.table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_false})" if field == "status_id"
|
||||
when "c"
|
||||
sql = "#{Issue.table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_true})" if field == "status_id"
|
||||
when "><t-"
|
||||
# between today - n days and today
|
||||
sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0)
|
||||
when ">t-"
|
||||
# >= today - n days
|
||||
sql = relative_date_clause(db_table, db_field, - value.first.to_i, nil)
|
||||
sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0)
|
||||
when "<t-"
|
||||
# <= today - n days
|
||||
sql = relative_date_clause(db_table, db_field, nil, - value.first.to_i)
|
||||
when "t-"
|
||||
# = n days in past
|
||||
sql = relative_date_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
|
||||
when "><t+"
|
||||
# between today and today + n days
|
||||
sql = relative_date_clause(db_table, db_field, 0, value.first.to_i)
|
||||
when ">t+"
|
||||
# >= today + n days
|
||||
sql = relative_date_clause(db_table, db_field, value.first.to_i, nil)
|
||||
when "<t+"
|
||||
# <= today + n days
|
||||
sql = relative_date_clause(db_table, db_field, nil, value.first.to_i)
|
||||
sql = relative_date_clause(db_table, db_field, 0, value.first.to_i)
|
||||
when "t+"
|
||||
# = today + n days
|
||||
sql = relative_date_clause(db_table, db_field, value.first.to_i, value.first.to_i)
|
||||
when "t"
|
||||
# = today
|
||||
sql = relative_date_clause(db_table, db_field, 0, 0)
|
||||
when "w"
|
||||
# = this week
|
||||
first_day_of_week = l(:general_first_day_of_week).to_i
|
||||
day_of_week = Date.today.cwday
|
||||
days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
|
||||
@@ -1039,11 +898,7 @@ class Query < ActiveRecord::Base
|
||||
filter_id = "#{assoc}.#{filter_id}"
|
||||
filter_name = l("label_attribute_of_#{assoc}", :name => filter_name)
|
||||
end
|
||||
@available_filters[filter_id] = options.merge({
|
||||
:name => filter_name,
|
||||
:format => field.field_format,
|
||||
:field => field
|
||||
})
|
||||
@available_filters[filter_id] = options.merge({ :name => filter_name, :format => field.field_format })
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ class ScmFetchError < Exception; end
|
||||
class Repository < ActiveRecord::Base
|
||||
include Redmine::Ciphering
|
||||
include Redmine::SafeAttributes
|
||||
|
||||
|
||||
# Maximum length for repository identifiers
|
||||
IDENTIFIER_MAX_LENGTH = 255
|
||||
|
||||
@@ -418,7 +418,7 @@ class Repository < ActiveRecord::Base
|
||||
|
||||
# Deletes repository data
|
||||
def clear_changesets
|
||||
cs = Changeset.table_name
|
||||
cs = Changeset.table_name
|
||||
ch = Change.table_name
|
||||
ci = "#{table_name_prefix}changesets_issues#{table_name_suffix}"
|
||||
cp = "#{table_name_prefix}changeset_parents#{table_name_suffix}"
|
||||
|
||||
@@ -37,28 +37,7 @@ class Repository::Bazaar < Repository
|
||||
'Bazaar'
|
||||
end
|
||||
|
||||
def entry(path=nil, identifier=nil)
|
||||
scm.bzr_path_encodig = log_encoding
|
||||
scm.entry(path, identifier)
|
||||
end
|
||||
|
||||
def cat(path, identifier=nil)
|
||||
scm.bzr_path_encodig = log_encoding
|
||||
scm.cat(path, identifier)
|
||||
end
|
||||
|
||||
def annotate(path, identifier=nil)
|
||||
scm.bzr_path_encodig = log_encoding
|
||||
scm.annotate(path, identifier)
|
||||
end
|
||||
|
||||
def diff(path, rev, rev_to)
|
||||
scm.bzr_path_encodig = log_encoding
|
||||
scm.diff(path, rev, rev_to)
|
||||
end
|
||||
|
||||
def entries(path=nil, identifier=nil)
|
||||
scm.bzr_path_encodig = log_encoding
|
||||
entries = scm.entries(path, identifier)
|
||||
if entries
|
||||
entries.each do |e|
|
||||
@@ -89,7 +68,6 @@ class Repository::Bazaar < Repository
|
||||
end
|
||||
|
||||
def fetch_changesets
|
||||
scm.bzr_path_encodig = log_encoding
|
||||
scm_info = scm.info
|
||||
if scm_info
|
||||
# latest revision found in database
|
||||
@@ -102,7 +80,7 @@ class Repository::Bazaar < Repository
|
||||
while (identifier_from <= scm_revision)
|
||||
# loads changesets by batches of 200
|
||||
identifier_to = [identifier_from + 199, scm_revision].min
|
||||
revisions = scm.revisions('', identifier_to, identifier_from)
|
||||
revisions = scm.revisions('', identifier_to, identifier_from, :with_paths => true)
|
||||
transaction do
|
||||
revisions.reverse_each do |revision|
|
||||
changeset = Changeset.create(:repository => self,
|
||||
|
||||
@@ -28,41 +28,11 @@ class User < Principal
|
||||
|
||||
# Different ways of displaying/sorting users
|
||||
USER_FORMATS = {
|
||||
:firstname_lastname => {
|
||||
:string => '#{firstname} #{lastname}',
|
||||
:order => %w(firstname lastname id),
|
||||
:setting_order => 1
|
||||
},
|
||||
:firstname_lastinitial => {
|
||||
:string => '#{firstname} #{lastname.to_s.chars.first}.',
|
||||
:order => %w(firstname lastname id),
|
||||
:setting_order => 2
|
||||
},
|
||||
:firstname => {
|
||||
:string => '#{firstname}',
|
||||
:order => %w(firstname id),
|
||||
:setting_order => 3
|
||||
},
|
||||
:lastname_firstname => {
|
||||
:string => '#{lastname} #{firstname}',
|
||||
:order => %w(lastname firstname id),
|
||||
:setting_order => 4
|
||||
},
|
||||
:lastname_coma_firstname => {
|
||||
:string => '#{lastname}, #{firstname}',
|
||||
:order => %w(lastname firstname id),
|
||||
:setting_order => 5
|
||||
},
|
||||
:lastname => {
|
||||
:string => '#{lastname}',
|
||||
:order => %w(lastname id),
|
||||
:setting_order => 6
|
||||
},
|
||||
:username => {
|
||||
:string => '#{login}',
|
||||
:order => %w(login id),
|
||||
:setting_order => 7
|
||||
},
|
||||
:firstname_lastname => {:string => '#{firstname} #{lastname}', :order => %w(firstname lastname id)},
|
||||
:firstname => {:string => '#{firstname}', :order => %w(firstname id)},
|
||||
:lastname_firstname => {:string => '#{lastname} #{firstname}', :order => %w(lastname firstname id)},
|
||||
:lastname_coma_firstname => {:string => '#{lastname}, #{firstname}', :order => %w(lastname firstname id)},
|
||||
:username => {:string => '#{login}', :order => %w(login id)},
|
||||
}
|
||||
|
||||
MAIL_NOTIFICATION_OPTIONS = [
|
||||
@@ -97,7 +67,7 @@ class User < Principal
|
||||
|
||||
validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
|
||||
validates_uniqueness_of :login, :if => Proc.new { |user| user.login_changed? && user.login.present? }, :case_sensitive => false
|
||||
validates_uniqueness_of :mail, :if => Proc.new { |user| user.mail_changed? && user.mail.present? }, :case_sensitive => false
|
||||
validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
|
||||
# Login must contain lettres, numbers, underscores only
|
||||
validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
|
||||
validates_length_of :login, :maximum => LOGIN_LENGTH_LIMIT
|
||||
@@ -114,11 +84,11 @@ class User < Principal
|
||||
|
||||
scope :in_group, lambda {|group|
|
||||
group_id = group.is_a?(Group) ? group.id : group.to_i
|
||||
where("#{User.table_name}.id IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id)
|
||||
{ :conditions => ["#{User.table_name}.id IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id] }
|
||||
}
|
||||
scope :not_in_group, lambda {|group|
|
||||
group_id = group.is_a?(Group) ? group.id : group.to_i
|
||||
where("#{User.table_name}.id NOT IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id)
|
||||
{ :conditions => ["#{User.table_name}.id NOT IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id] }
|
||||
}
|
||||
|
||||
def set_mail_notification
|
||||
@@ -360,10 +330,10 @@ class User < Principal
|
||||
# version. Exact matches will be given priority.
|
||||
def self.find_by_login(login)
|
||||
# First look for an exact match
|
||||
user = where(:login => login).all.detect {|u| u.login == login}
|
||||
user = all(:conditions => {:login => login}).detect {|u| u.login == login}
|
||||
unless user
|
||||
# Fail over to case-insensitive if none was found
|
||||
user = where("LOWER(login) = ?", login.to_s.downcase).first
|
||||
user = first(:conditions => ["LOWER(login) = ?", login.to_s.downcase])
|
||||
end
|
||||
user
|
||||
end
|
||||
@@ -380,7 +350,7 @@ class User < Principal
|
||||
|
||||
# Makes find_by_mail case-insensitive
|
||||
def self.find_by_mail(mail)
|
||||
where("LOWER(mail) = ?", mail.to_s.downcase).first
|
||||
find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
|
||||
end
|
||||
|
||||
# Returns true if the default admin account can no longer be used
|
||||
@@ -392,17 +362,6 @@ class User < Principal
|
||||
name
|
||||
end
|
||||
|
||||
CSS_CLASS_BY_STATUS = {
|
||||
STATUS_ANONYMOUS => 'anon',
|
||||
STATUS_ACTIVE => 'active',
|
||||
STATUS_REGISTERED => 'registered',
|
||||
STATUS_LOCKED => 'locked'
|
||||
}
|
||||
|
||||
def css_classes
|
||||
"user #{CSS_CLASS_BY_STATUS[status]}"
|
||||
end
|
||||
|
||||
# Returns the current day according to user's time zone
|
||||
def today
|
||||
if time_zone.nil?
|
||||
@@ -503,17 +462,17 @@ class User < Principal
|
||||
|
||||
roles = roles_for_project(context)
|
||||
return false unless roles
|
||||
roles.any? {|role|
|
||||
roles.detect {|role|
|
||||
(context.is_public? || role.member?) &&
|
||||
role.allowed_to?(action) &&
|
||||
(block_given? ? yield(role, self) : true)
|
||||
}
|
||||
elsif context && context.is_a?(Array)
|
||||
if context.empty?
|
||||
false
|
||||
else
|
||||
# Authorize if user is authorized on every element of the array
|
||||
context.map {|project| allowed_to?(action, project, options, &block)}.reduce(:&)
|
||||
# Authorize if user is authorized on every element of the array
|
||||
context.map do |project|
|
||||
allowed_to?(action, project, options, &block)
|
||||
end.inject do |memo,allowed|
|
||||
memo && allowed
|
||||
end
|
||||
elsif options[:global]
|
||||
# Admin users are always authorized
|
||||
@@ -522,7 +481,7 @@ class User < Principal
|
||||
# authorize if user has at least one role that has this permission
|
||||
roles = memberships.collect {|m| m.roles}.flatten.uniq
|
||||
roles << (self.logged? ? Role.non_member : Role.anonymous)
|
||||
roles.any? {|role|
|
||||
roles.detect {|role|
|
||||
role.allowed_to?(action) &&
|
||||
(block_given? ? yield(role, self) : true)
|
||||
}
|
||||
@@ -540,7 +499,7 @@ class User < Principal
|
||||
# Returns true if the user is allowed to delete his own account
|
||||
def own_account_deletable?
|
||||
Setting.unsubscribe? &&
|
||||
(!admin? || User.active.where("admin = ? AND id <> ?", true, id).exists?)
|
||||
(!admin? || User.active.first(:conditions => ["admin = ? AND id <> ?", true, id]).present?)
|
||||
end
|
||||
|
||||
safe_attributes 'login',
|
||||
@@ -611,7 +570,7 @@ class User < Principal
|
||||
# Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
|
||||
# one anonymous user per database.
|
||||
def self.anonymous
|
||||
anonymous_user = AnonymousUser.first
|
||||
anonymous_user = AnonymousUser.find(:first)
|
||||
if anonymous_user.nil?
|
||||
anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
|
||||
raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
|
||||
@@ -624,11 +583,11 @@ class User < Principal
|
||||
# This method is used in the SaltPasswords migration and is to be kept as is
|
||||
def self.salt_unsalted_passwords!
|
||||
transaction do
|
||||
User.where("salt IS NULL OR salt = ''").find_each do |user|
|
||||
User.find_each(:conditions => "salt IS NULL OR salt = ''") do |user|
|
||||
next if user.hashed_password.blank?
|
||||
salt = User.generate_salt
|
||||
hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
|
||||
User.where(:id => user.id).update_all(:salt => salt, :hashed_password => hashed_password)
|
||||
User.update_all("salt = '#{salt}', hashed_password = '#{hashed_password}'", ["id = ?", user.id] )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -35,11 +35,10 @@ class Version < ActiveRecord::Base
|
||||
validates_inclusion_of :sharing, :in => VERSION_SHARINGS
|
||||
validate :validate_version
|
||||
|
||||
scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)}
|
||||
scope :open, where(:status => 'open')
|
||||
scope :visible, lambda {|*args|
|
||||
includes(:project).where(Project.allowed_to_condition(args.first || User.current, :view_issues))
|
||||
}
|
||||
scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}}
|
||||
scope :open, :conditions => {:status => 'open'}
|
||||
scope :visible, lambda {|*args| { :include => :project,
|
||||
:conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } }
|
||||
|
||||
safe_attributes 'name',
|
||||
'description',
|
||||
@@ -80,7 +79,7 @@ class Version < ActiveRecord::Base
|
||||
|
||||
# Returns the total reported time for this version
|
||||
def spent_hours
|
||||
@spent_hours ||= TimeEntry.joins(:issue).where("#{Issue.table_name}.fixed_version_id = ?", id).sum(:hours).to_f
|
||||
@spent_hours ||= TimeEntry.sum(:hours, :joins => :issue, :conditions => ["#{Issue.table_name}.fixed_version_id = ?", id]).to_f
|
||||
end
|
||||
|
||||
def closed?
|
||||
@@ -93,7 +92,7 @@ class Version < ActiveRecord::Base
|
||||
|
||||
# Returns true if the version is completed: due date reached and no open issues
|
||||
def completed?
|
||||
effective_date && (effective_date < Date.today) && (open_issues_count == 0)
|
||||
effective_date && (effective_date <= Date.today) && (open_issues_count == 0)
|
||||
end
|
||||
|
||||
def behind_schedule?
|
||||
@@ -269,7 +268,9 @@ class Version < ActiveRecord::Base
|
||||
if issues_count > 0
|
||||
ratio = open ? 'done_ratio' : 100
|
||||
|
||||
done = fixed_issues.open(open).sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}").to_f
|
||||
done = fixed_issues.sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}",
|
||||
:joins => :status,
|
||||
:conditions => ["#{IssueStatus.table_name}.is_closed = ?", !open]).to_f
|
||||
progress = done / (estimated_average * issues_count)
|
||||
end
|
||||
progress
|
||||
|
||||
@@ -73,8 +73,6 @@ class WikiContent < ActiveRecord::Base
|
||||
"LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id " +
|
||||
"LEFT JOIN #{Project.table_name} ON #{Project.table_name}.id = #{Wiki.table_name}.project_id"}
|
||||
|
||||
after_destroy :page_update_after_destroy
|
||||
|
||||
def text=(plain)
|
||||
case Setting.wiki_compression
|
||||
when 'gzip'
|
||||
@@ -117,31 +115,10 @@ class WikiContent < ActiveRecord::Base
|
||||
|
||||
# Returns the previous version or nil
|
||||
def previous
|
||||
@previous ||= WikiContent::Version.
|
||||
reorder('version DESC').
|
||||
includes(:author).
|
||||
where("wiki_content_id = ? AND version < ?", wiki_content_id, version).first
|
||||
end
|
||||
|
||||
# Returns the next version or nil
|
||||
def next
|
||||
@next ||= WikiContent::Version.
|
||||
reorder('version ASC').
|
||||
includes(:author).
|
||||
where("wiki_content_id = ? AND version > ?", wiki_content_id, version).first
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Updates page's content if the latest version is removed
|
||||
# or destroys the page if it was the only version
|
||||
def page_update_after_destroy
|
||||
latest = page.content.versions.reorder("#{self.class.table_name}.version DESC").first
|
||||
if latest && page.content.version != latest.version
|
||||
raise ActiveRecord::Rollback unless page.content.revert_to!(latest)
|
||||
elsif latest.nil?
|
||||
raise ActiveRecord::Rollback unless page.destroy
|
||||
end
|
||||
@previous ||= WikiContent::Version.find(:first,
|
||||
:order => 'version DESC',
|
||||
:include => :author,
|
||||
:conditions => ["wiki_content_id = ? AND version < ?", wiki_content_id, version])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -50,14 +50,14 @@ class WikiPage < ActiveRecord::Base
|
||||
|
||||
# eager load information about last updates, without loading text
|
||||
scope :with_updated_on, {
|
||||
:select => "#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_on, #{WikiContent.table_name}.version",
|
||||
:select => "#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_on",
|
||||
:joins => "LEFT JOIN #{WikiContent.table_name} ON #{WikiContent.table_name}.page_id = #{WikiPage.table_name}.id"
|
||||
}
|
||||
|
||||
# Wiki pages that are protected by default
|
||||
DEFAULT_PROTECTED_PAGES = %w(sidebar)
|
||||
|
||||
safe_attributes 'parent_id', 'parent_title',
|
||||
safe_attributes 'parent_id',
|
||||
:if => lambda {|page, user| page.new_record? || user.allowed_to?(:rename_wiki_pages, page.project)}
|
||||
|
||||
def initialize(attributes=nil, *args)
|
||||
@@ -111,13 +111,11 @@ class WikiPage < ActiveRecord::Base
|
||||
|
||||
def diff(version_to=nil, version_from=nil)
|
||||
version_to = version_to ? version_to.to_i : self.content.version
|
||||
content_to = content.versions.find_by_version(version_to)
|
||||
content_from = version_from ? content.versions.find_by_version(version_from.to_i) : content_to.try(:previous)
|
||||
return nil unless content_to && content_from
|
||||
version_from = version_from ? version_from.to_i : version_to - 1
|
||||
version_to, version_from = version_from, version_to unless version_from < version_to
|
||||
|
||||
if content_from.version > content_to.version
|
||||
content_to, content_from = content_from, content_to
|
||||
end
|
||||
content_to = content.versions.find_by_version(version_to)
|
||||
content_from = content.versions.find_by_version(version_from)
|
||||
|
||||
(content_to && content_from) ? WikiDiff.new(content_to, content_from) : nil
|
||||
end
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<table>
|
||||
<tr>
|
||||
<td align="right"><label for="username"><%=l(:field_login)%>:</label></td>
|
||||
<td align="left"><%= text_field_tag 'username', params[:username], :tabindex => '1' %></td>
|
||||
<td align="left"><%= text_field_tag 'username', nil, :tabindex => '1' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right"><label for="password"><%=l(:field_password)%>:</label></td>
|
||||
@@ -36,12 +36,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<%= javascript_tag "$('#username').focus();" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<%= call_hook :view_account_login_bottom %>
|
||||
|
||||
<% if params[:username].present? %>
|
||||
<%= javascript_tag "$('#password').focus();" %>
|
||||
<% else %>
|
||||
<%= javascript_tag "$('#username').focus();" %>
|
||||
<% end %>
|
||||
|
||||
@@ -6,14 +6,16 @@
|
||||
<p><%= link_to_attachment @attachment, :text => l(:button_download), :download => true -%>
|
||||
<span class="size">(<%= number_to_human_size @attachment.filesize %>)</span></p>
|
||||
</div>
|
||||
<p>
|
||||
<%= form_tag({}, :method => 'get') do %>
|
||||
<p>
|
||||
<%= l(:label_view_diff) %>:
|
||||
<label><%= radio_button_tag 'type', 'inline', @diff_type != 'sbs', :onchange => "this.form.submit()" %> <%= l(:label_diff_inline) %></label>
|
||||
<label><%= radio_button_tag 'type', 'sbs', @diff_type == 'sbs', :onchange => "this.form.submit()" %> <%= l(:label_diff_side_by_side) %></label>
|
||||
</p>
|
||||
<label><%= l(:label_view_diff) %></label>
|
||||
<%= select_tag 'type',
|
||||
options_for_select(
|
||||
[[l(:label_diff_inline), "inline"], [l(:label_diff_side_by_side), "sbs"]], @diff_type),
|
||||
:onchange => "if (this.value != '') {this.form.submit()}" %>
|
||||
<% end %>
|
||||
<%= render :partial => 'common/diff', :locals => {:diff => @diff, :diff_type => @diff_type, :diff_style => nil} %>
|
||||
</p>
|
||||
<%= render :partial => 'common/diff', :locals => {:diff => @diff, :diff_type => @diff_type} %>
|
||||
|
||||
<% html_title @attachment.filename %>
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<% diff = Redmine::UnifiedDiff.new(
|
||||
diff, :type => diff_type,
|
||||
:max_lines => Setting.diff_max_lines_displayed.to_i,
|
||||
:style => diff_style) -%>
|
||||
:max_lines => Setting.diff_max_lines_displayed.to_i) -%>
|
||||
|
||||
<% diff.each do |table_file| -%>
|
||||
<div class="autoscroll">
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
<li><%= bulk_update_custom_field_context_menu_link(field, text, value || text) %></li>
|
||||
<% end %>
|
||||
<% unless field.is_required? %>
|
||||
<li><%= bulk_update_custom_field_context_menu_link(field, l(:label_none), '__none__') %></li>
|
||||
<li><%= bulk_update_custom_field_context_menu_link(field, l(:label_none), '') %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
api.array @klass.name.underscore.pluralize do
|
||||
@enumerations.each do |enumeration|
|
||||
api.__send__ @klass.name.underscore do
|
||||
api.id enumeration.id
|
||||
api.name enumeration.name
|
||||
api.is_default enumeration.is_default
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -201,10 +201,9 @@
|
||||
style += "width: #{width}px;"
|
||||
style += "height: #{height}px;"
|
||||
style += "font-size:0.7em;"
|
||||
clss = "gantt_hdr"
|
||||
clss << " nwday" if @gantt.non_working_week_days.include?(wday)
|
||||
style += 'background:#f1f1f1;' if wday > 5
|
||||
%>
|
||||
<%= content_tag(:div, :style => style, :class => clss) do %>
|
||||
<%= content_tag(:div, :style => style, :class => "gantt_hdr") do %>
|
||||
<%= day_letter(wday) %>
|
||||
<% end %>
|
||||
<%
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="contextual">
|
||||
<%= link_to_if_authorized(l(:button_update), {:controller => 'issues', :action => 'edit', :id => @issue }, :onclick => 'showAndScrollTo("update", "issue_notes"); return false;', :class => 'icon icon-edit', :accesskey => accesskey(:edit)) %>
|
||||
<%= link_to_if_authorized(l(:button_update), {:controller => 'issues', :action => 'edit', :id => @issue }, :onclick => 'showAndScrollTo("update", "notes"); return false;', :class => 'icon icon-edit', :accesskey => accesskey(:edit)) %>
|
||||
<%= link_to l(:button_log_time), new_issue_time_entry_path(@issue), :class => 'icon icon-time-add' if User.current.allowed_to?(:log_time, @project) %>
|
||||
<%= watcher_tag(@issue, User.current) %>
|
||||
<%= link_to_if_authorized l(:button_copy), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue}, :class => 'icon icon-copy' %>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<div class="splitcontent">
|
||||
<div class="splitcontentleft">
|
||||
<% if @issue.safe_attribute?('status_id') && @allowed_statuses.present? %>
|
||||
<% if @issue.safe_attribute? 'status_id' %>
|
||||
<p><%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), {:required => true},
|
||||
:onchange => "updateIssueFrom('#{escape_javascript project_issue_form_path(@project, :id => @issue, :format => 'js')}')" %></p>
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<div class="splitcontentright">
|
||||
<% if @issue.safe_attribute? 'parent_issue_id' %>
|
||||
<p id="parent_issue"><%= f.text_field :parent_issue_id, :size => 10, :required => @issue.required_attribute?('parent_issue_id') %></p>
|
||||
<%= javascript_tag "observeAutocompleteField('issue_parent_issue_id', '#{escape_javascript auto_complete_issues_path}')" %>
|
||||
<%= javascript_tag "observeAutocompleteField('issue_parent_issue_id', '#{escape_javascript auto_complete_issues_path(:project_id => @issue.project)}')" %>
|
||||
<% end %>
|
||||
|
||||
<% if @issue.safe_attribute? 'start_date' %>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
</div>
|
||||
<p>
|
||||
<label><%= radio_button_tag 'conflict_resolution', 'overwrite' %> <%= l(:text_issue_conflict_resolution_overwrite) %></label><br />
|
||||
<% if @issue.notes.present? %>
|
||||
<% if @notes.present? %>
|
||||
<label><%= radio_button_tag 'conflict_resolution', 'add_notes' %> <%= l(:text_issue_conflict_resolution_add_notes) %></label><br />
|
||||
<% end %>
|
||||
<label><%= radio_button_tag 'conflict_resolution', 'cancel' %> <%= l(:text_issue_conflict_resolution_cancel, :link => link_to_issue(@issue, :subject => false)).html_safe %></label>
|
||||
|
||||
@@ -27,18 +27,11 @@
|
||||
<% end %>
|
||||
|
||||
<fieldset><legend><%= l(:field_notes) %></legend>
|
||||
<%= f.text_area :notes, :cols => 60, :rows => 10, :class => 'wiki-edit', :no_label => true %>
|
||||
<%= wikitoolbar_for 'issue_notes' %>
|
||||
|
||||
<% if @issue.safe_attribute? 'private_notes' %>
|
||||
<label for="issue_private_notes"><%= f.check_box :private_notes, :no_label => true %> <%= l(:field_private_notes) %></label>
|
||||
<% end %>
|
||||
|
||||
<%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %>
|
||||
<%= wikitoolbar_for 'notes' %>
|
||||
<%= call_hook(:view_issues_edit_notes_bottom, { :issue => @issue, :notes => @notes, :form => f }) %>
|
||||
</fieldset>
|
||||
|
||||
<fieldset><legend><%= l(:label_attachment_plural) %></legend>
|
||||
<p><%= render :partial => 'attachments/form', :locals => {:container => @issue} %></p>
|
||||
<p><%=l(:label_attachment_plural)%><br /><%= render :partial => 'attachments/form', :locals => {:container => @issue} %></p>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<% end %>
|
||||
|
||||
<% if @issue.safe_attribute? 'subject' %>
|
||||
<p><%= f.text_field :subject, :size => 80, :maxlength => 255, :required => true %></p>
|
||||
<p><%= f.text_field :subject, :size => 80, :required => true %></p>
|
||||
<% end %>
|
||||
|
||||
<% if @issue.safe_attribute? 'description' %>
|
||||
|
||||
@@ -2,30 +2,25 @@
|
||||
<%= hidden_field_tag 'back_url', url_for(params), :id => nil %>
|
||||
<div class="autoscroll">
|
||||
<table class="list issues">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="checkbox hide-when-print">
|
||||
<%= link_to image_tag('toggle_check.png'), {},
|
||||
:onclick => 'toggleIssuesSelection(this); return false;',
|
||||
:title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %>
|
||||
</th>
|
||||
<%= sort_header_tag('id', :caption => '#', :default_order => 'desc') %>
|
||||
<% query.inline_columns.each do |column| %>
|
||||
<%= column_header(column) %>
|
||||
<% end %>
|
||||
</tr>
|
||||
</thead>
|
||||
<thead><tr>
|
||||
<th class="checkbox hide-when-print"><%= link_to image_tag('toggle_check.png'), {}, :onclick => 'toggleIssuesSelection(this); return false;',
|
||||
:title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %>
|
||||
</th>
|
||||
<%= sort_header_tag('id', :caption => '#', :default_order => 'desc') %>
|
||||
<% query.columns.each do |column| %>
|
||||
<%= column_header(column) %>
|
||||
<% end %>
|
||||
</tr></thead>
|
||||
<% previous_group = false %>
|
||||
<tbody>
|
||||
<% issue_list(issues) do |issue, level| -%>
|
||||
<% if @query.grouped? && (group = @query.group_by_column.value(issue)) != previous_group %>
|
||||
<% reset_cycle %>
|
||||
<tr class="group open">
|
||||
<td colspan="<%= query.inline_columns.size + 2 %>">
|
||||
<td colspan="<%= query.columns.size + 2 %>">
|
||||
<span class="expander" onclick="toggleRowGroup(this);"> </span>
|
||||
<%= group.blank? ? l(:label_none) : column_content(@query.group_by_column, issue) %> <span class="count"><%= @issue_count_by_group[group] %></span>
|
||||
<%= link_to_function("#{l(:button_collapse_all)}/#{l(:button_expand_all)}",
|
||||
"toggleAllRowGroups(this)", :class => 'toggle-all') %>
|
||||
<%= group.blank? ? l(:label_none) : column_content(@query.group_by_column, issue) %> <span class="count">(<%= @issue_count_by_group[group] %>)</span>
|
||||
<%= link_to_function("#{l(:button_collapse_all)}/#{l(:button_expand_all)}", "toggleAllRowGroups(this)", :class => 'toggle-all') %>
|
||||
</td>
|
||||
</tr>
|
||||
<% previous_group = group %>
|
||||
@@ -33,15 +28,8 @@
|
||||
<tr id="issue-<%= issue.id %>" class="hascontextmenu <%= cycle('odd', 'even') %> <%= issue.css_classes %> <%= level > 0 ? "idnt idnt-#{level}" : nil %>">
|
||||
<td class="checkbox hide-when-print"><%= check_box_tag("ids[]", issue.id, false, :id => nil) %></td>
|
||||
<td class="id"><%= link_to issue.id, issue_path(issue) %></td>
|
||||
<%= raw query.inline_columns.map {|column| "<td class=\"#{column.css_classes}\">#{column_content(column, issue)}</td>"}.join %>
|
||||
<%= raw query.columns.map {|column| "<td class=\"#{column.css_classes}\">#{column_content(column, issue)}</td>"}.join %>
|
||||
</tr>
|
||||
<% @query.block_columns.each do |column|
|
||||
if (text = column_content(column, issue)) && text.present? -%>
|
||||
<tr class="<%= current_cycle %>">
|
||||
<td colspan="<%= @query.inline_columns.size + 2 %>" class="<%= column.css_classes %>"><%= text %></td>
|
||||
</tr>
|
||||
<% end -%>
|
||||
<% end -%>
|
||||
<% end -%>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -1 +1 @@
|
||||
$('#content').html('<%= escape_javascript(render :template => 'issues/bulk_edit', :formats => [:html]) %>');
|
||||
$('#content').html('<%= escape_javascript(render :template => 'issues/bulk_edit.html') %>');
|
||||
|
||||
@@ -34,10 +34,6 @@
|
||||
@query.group_by)
|
||||
) %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= l(:button_show) %></td>
|
||||
<td><%= available_block_columns_tags(@query) %></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</fieldset>
|
||||
@@ -77,7 +73,7 @@
|
||||
<label><%= radio_button_tag 'columns', 'all' %> <%= l(:description_all_columns) %></label>
|
||||
</p>
|
||||
<p>
|
||||
<label><%= check_box_tag 'description', '1', @query.has_column?(:description) %> <%= l(:field_description) %></label>
|
||||
<label><%= check_box_tag 'description', '1' %> <%= l(:field_description) %></label>
|
||||
</p>
|
||||
<p class="buttons">
|
||||
<%= submit_tag l(:button_export), :name => nil, :onclick => "hideModal(this);" %>
|
||||
|
||||
@@ -48,7 +48,7 @@ api.issue do
|
||||
end if include_in_api_response?('changesets') && User.current.allowed_to?(:view_changesets, @project)
|
||||
|
||||
api.array :journals do
|
||||
@journals.each do |journal|
|
||||
@issue.journals.each do |journal|
|
||||
api.journal :id => journal.id do
|
||||
api.user(:id => journal.user_id, :name => journal.user.name) unless journal.user.nil?
|
||||
api.notes journal.notes
|
||||
|
||||
@@ -71,7 +71,6 @@ end %>
|
||||
<% if @issue.description? || @issue.attachments.any? -%>
|
||||
<hr />
|
||||
<% if @issue.description? %>
|
||||
<div class="description">
|
||||
<div class="contextual">
|
||||
<%= link_to l(:button_quote),
|
||||
{:controller => 'journals', :action => 'new', :id => @issue},
|
||||
@@ -84,7 +83,6 @@ end %>
|
||||
<div class="wiki">
|
||||
<%= textilizable @issue, :description, :attachments => @issue.attachments %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= link_to_attachments @issue, :thumbnails => true %>
|
||||
<% end -%>
|
||||
@@ -95,7 +93,7 @@ end %>
|
||||
<hr />
|
||||
<div id="issue_tree">
|
||||
<div class="contextual">
|
||||
<%= link_to_new_subtask(@issue) if User.current.allowed_to?(:manage_subtasks, @project) %>
|
||||
<%= link_to(l(:button_add), {:controller => 'issues', :action => 'new', :project_id => @project, :issue => {:parent_issue_id => @issue}}) if User.current.allowed_to?(:manage_subtasks, @project) %>
|
||||
</div>
|
||||
<p><strong><%=l(:label_subtask_plural)%></strong></p>
|
||||
<%= render_descendants_tree(@issue) unless @issue.leaf? %>
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
$('#issue_notes').val("<%= raw escape_javascript(@content) %>");
|
||||
<%
|
||||
# when quoting a private journal, check the private checkbox
|
||||
if @journal && @journal.private_notes?
|
||||
%>
|
||||
$('#issue_private_notes').attr('checked', true);
|
||||
<% end %>
|
||||
|
||||
$('#notes').val("<%= raw escape_javascript(@content) %>");
|
||||
showAndScrollTo("update", "notes");
|
||||
$('#notes').scrollTop = $('#notes').scrollHeight - $('#notes').clientHeight;
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
<body class="<%=h body_css_classes %>">
|
||||
<div id="wrapper">
|
||||
<div id="wrapper2">
|
||||
<div id="wrapper3">
|
||||
<div id="top-menu">
|
||||
<div id="account">
|
||||
<%= render_menu :account_menu -%>
|
||||
@@ -63,7 +62,6 @@
|
||||
<div style="clear:both;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="ajax-indicator" style="display:none;"><span><%= l(:label_loading) %></span></div>
|
||||
<div id="ajax-modal" style="display:none;"></div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<%= labelled_form_for @news, :html => { :id => 'news-form', :multipart => true, :method => :put } do |f| %>
|
||||
<%= render :partial => 'form', :locals => { :f => f } %>
|
||||
<%= submit_tag l(:button_save) %>
|
||||
<%= preview_link preview_news_path(:project_id => @project, :id => @news), 'news-form' %>
|
||||
<%= preview_link preview_news_path(:project_id => @project), 'news-form' %>
|
||||
<% end %>
|
||||
<div id="preview" class="wiki"></div>
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
:html => { :id => 'news-form', :multipart => true, :method => :put } do |f| %>
|
||||
<%= render :partial => 'form', :locals => { :f => f } %>
|
||||
<%= submit_tag l(:button_save) %>
|
||||
<%= preview_link preview_news_path(:project_id => @project, :id => @news), 'news-form' %> |
|
||||
<%= preview_link preview_news_path(:project_id => @project), 'news-form' %> |
|
||||
<%= link_to l(:button_cancel), "#", :onclick => '$("#edit-news").hide(); return false;' %>
|
||||
<% end %>
|
||||
<div id="preview" class="wiki"></div>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<%= label_tag "available_columns", l(:description_available_columns) %>
|
||||
<br />
|
||||
<%= select_tag 'available_columns',
|
||||
options_for_select((query.available_inline_columns - query.columns).collect {|column| [column.caption, column.name]}),
|
||||
options_for_select((query.available_columns - query.columns).collect {|column| [column.caption, column.name]}),
|
||||
:multiple => true, :size => 10, :style => "width:150px",
|
||||
:ondblclick => "moveOptions(this.form.available_columns, this.form.selected_columns);" %>
|
||||
</td>
|
||||
@@ -18,7 +18,7 @@
|
||||
<%= label_tag "selected_columns", l(:description_selected_columns) %>
|
||||
<br />
|
||||
<%= select_tag((defined?(tag_name) ? tag_name : 'c[]'),
|
||||
options_for_select(query.inline_columns.collect {|column| [column.caption, column.name]}),
|
||||
options_for_select(query.columns.collect {|column| [column.caption, column.name]}),
|
||||
:id => 'selected_columns', :multiple => true, :size => 10, :style => "width:150px",
|
||||
:ondblclick => "moveOptions(this.form.selected_columns, this.form.available_columns);") %>
|
||||
</td>
|
||||
|
||||
@@ -3,7 +3,6 @@ var operatorLabels = <%= raw_json Query.operators_labels %>;
|
||||
var operatorByType = <%= raw_json Query.operators_by_filter_type %>;
|
||||
var availableFilters = <%= raw_json query.available_filters_as_json %>;
|
||||
var labelDayPlural = <%= raw_json l(:label_day_plural) %>;
|
||||
var allProjects = <%= raw query.all_projects_values.to_json %>;
|
||||
$(document).ready(function(){
|
||||
initFilters();
|
||||
<% query.filters.each do |field, options| %>
|
||||
|
||||
@@ -21,9 +21,6 @@
|
||||
|
||||
<p><label for="query_group_by"><%= l(:field_group_by) %></label>
|
||||
<%= select 'query', 'group_by', @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, :include_blank => true %></p>
|
||||
|
||||
<p><label><%= l(:button_show) %></label>
|
||||
<%= available_block_columns_tags(@query) %></p>
|
||||
</div>
|
||||
|
||||
<fieldset id="filters"><legend><%= l(:label_filter_plural) %></legend>
|
||||
|
||||
@@ -6,14 +6,16 @@
|
||||
:path => to_path_param(@path), :rev=> @rev}, :method => 'get') do %>
|
||||
<%= hidden_field_tag('rev_to', params[:rev_to]) if params[:rev_to] %>
|
||||
<p>
|
||||
<%= l(:label_view_diff) %>:
|
||||
<label><%= radio_button_tag 'type', 'inline', @diff_type != 'sbs', :onchange => "this.form.submit()" %> <%= l(:label_diff_inline) %></label>
|
||||
<label><%= radio_button_tag 'type', 'sbs', @diff_type == 'sbs', :onchange => "this.form.submit()" %> <%= l(:label_diff_side_by_side) %></label>
|
||||
<label><%= l(:label_view_diff) %></label>
|
||||
<%= select_tag 'type',
|
||||
options_for_select(
|
||||
[[l(:label_diff_inline), "inline"], [l(:label_diff_side_by_side), "sbs"]], @diff_type),
|
||||
:onchange => "if (this.value != '') {this.form.submit()}" %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<% cache(@cache_key) do -%>
|
||||
<%= render :partial => 'common/diff', :locals => {:diff => @diff, :diff_type => @diff_type, :diff_style => @repository.class.scm_name} %>
|
||||
<%= render :partial => 'common/diff', :locals => {:diff => @diff, :diff_type => @diff_type} %>
|
||||
<% end -%>
|
||||
|
||||
<% other_formats_links do |f| %>
|
||||
|
||||
@@ -1 +1 @@
|
||||
$('#content').html('<%= escape_javascript(render :template => 'repositories/new', :formats => [:html]) %>');
|
||||
$('#content').html('<%= escape_javascript(render :template => 'repositories/new.html') %>');
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
:repository_id => @repository.identifier_param},
|
||||
:method => :get
|
||||
) do %>
|
||||
<%= l(:label_revision) %>: <%= text_field_tag 'rev', nil, :size => 8 %>
|
||||
<%= l(:label_revision) %>: <%= text_field_tag 'rev', @rev, :size => 8 %>
|
||||
<%= submit_tag 'OK' %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
api.role do
|
||||
api.id @role.id
|
||||
api.name @role.name
|
||||
api.array :permissions do
|
||||
@role.permissions.each do |perm|
|
||||
api.permission(perm.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -3,8 +3,6 @@
|
||||
<div class="box tabular settings">
|
||||
<p><%= setting_check_box :cross_project_issue_relations %></p>
|
||||
|
||||
<p><%= setting_select :cross_project_subtasks, cross_project_subtasks_options %></p>
|
||||
|
||||
<p><%= setting_check_box :issue_group_assignment %></p>
|
||||
|
||||
<p><%= setting_check_box :default_issue_start_date_to_creation_date %></p>
|
||||
@@ -13,8 +11,6 @@
|
||||
|
||||
<p><%= setting_select :issue_done_ratio, Issue::DONE_RATIO_OPTIONS.collect {|i| [l("setting_issue_done_ratio_#{i}"), i]} %></p>
|
||||
|
||||
<p><%= setting_multiselect :non_working_week_days, (1..7).map {|d| [day_name(d), d.to_s]}, :inline => true %></p>
|
||||
|
||||
<p><%= setting_text_field :issues_export_limit, :size => 6 %></p>
|
||||
|
||||
<p><%= setting_text_field :gantt_items_limit, :size => 6 %></p>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<p><%= f.text_field :issue_id, :size => 6 %> <em><%= h("#{@time_entry.issue.tracker.name} ##{@time_entry.issue.id}: #{@time_entry.issue.subject}") if @time_entry.issue %></em></p>
|
||||
<p><%= f.text_field :spent_on, :size => 10, :required => true %><%= calendar_for('time_entry_spent_on') %></p>
|
||||
<p><%= f.text_field :hours, :size => 6, :required => true %></p>
|
||||
<p><%= f.text_field :comments, :size => 100, :maxlength => 255 %></p>
|
||||
<p><%= f.text_field :comments, :size => 100 %></p>
|
||||
<p><%= f.select :activity_id, activity_collection_for_select_options(@time_entry), :required => true %></p>
|
||||
<% @time_entry.custom_field_values.each do |value| %>
|
||||
<p><%= custom_field_tag_with_label :time_entry, value %></p>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<% for user in @users -%>
|
||||
<tr class="<%= user.css_classes %> <%= cycle("odd", "even") %>">
|
||||
<tr class="user <%= cycle("odd", "even") %> <%= %w(anon active registered locked)[user.status] %>">
|
||||
<td class="username"><%= avatar(user, :size => "14") %><%= link_to h(user.login), edit_user_path(user) %></td>
|
||||
<td class="firstname"><%= h(user.firstname) %></td>
|
||||
<td class="lastname"><%= h(user.lastname) %></td>
|
||||
|
||||
@@ -14,11 +14,12 @@
|
||||
<% counts.each do |count| %>
|
||||
<tr>
|
||||
<td width="130px" align="right" >
|
||||
<% if count[:group] -%>
|
||||
<%= link_to(h(count[:group]), project_issues_path(version.project, :set_filter => 1, :status_id => '*', :fixed_version_id => version, "#{criteria}_id" => count[:group])) %>
|
||||
<% else -%>
|
||||
<%= link_to(l(:label_none), project_issues_path(version.project, :set_filter => 1, :status_id => '*', :fixed_version_id => version, "#{criteria}_id" => "!*")) %>
|
||||
<% end %>
|
||||
<%= link_to h(count[:group]), {:controller => 'issues',
|
||||
:action => 'index',
|
||||
:project_id => version.project,
|
||||
:set_filter => 1,
|
||||
:status_id => '*',
|
||||
:fixed_version_id => version}.merge("#{criteria}_id".to_sym => count[:group]) %>
|
||||
</td>
|
||||
<td width="240px">
|
||||
<%= progress_bar((count[:closed].to_f / count[:total])*100,
|
||||
|
||||
@@ -28,15 +28,12 @@
|
||||
<td class="updated_on"><%= format_time(ver.updated_on) %></td>
|
||||
<td class="author"><%= link_to_user ver.author %></td>
|
||||
<td class="comments"><%=h ver.comments %></td>
|
||||
<td class="buttons">
|
||||
<%= link_to l(:button_annotate), :action => 'annotate', :id => @page.title, :version => ver.version %>
|
||||
<%= delete_link wiki_page_path(@page, :version => ver.version) if User.current.allowed_to?(:delete_wiki_pages, @page.project) && @version_count > 1 %>
|
||||
</td>
|
||||
<td class="buttons"><%= link_to l(:button_annotate), :action => 'annotate', :id => @page.title, :version => ver.version %></td>
|
||||
</tr>
|
||||
<% line_num += 1 %>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<%= submit_tag l(:label_view_diff), :class => 'small' if show_diff %>
|
||||
<span class="pagination"><%= pagination_links_full @version_pages, @version_count %></span>
|
||||
<span class="pagination"><%= pagination_links_full @version_pages, @version_count, :page_param => :p %></span>
|
||||
<% end %>
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
api.array :wiki_pages do
|
||||
@pages.each do |page|
|
||||
api.wiki_page do
|
||||
api.title page.title
|
||||
if page.parent
|
||||
api.parent :title => page.parent.title
|
||||
end
|
||||
api.version page.version
|
||||
api.created_on page.created_on
|
||||
api.updated_on page.updated_on
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,18 +0,0 @@
|
||||
api.wiki_page do
|
||||
api.title @page.title
|
||||
if @page.parent
|
||||
api.parent :title => @page.parent.title
|
||||
end
|
||||
api.text @content.text
|
||||
api.version @content.version
|
||||
api.author(:id => @content.author_id, :name => @content.author.name)
|
||||
api.comments @page.content.comments
|
||||
api.created_on @page.created_on
|
||||
api.updated_on @content.updated_on
|
||||
|
||||
api.array :attachments do
|
||||
@page.attachments.each do |attachment|
|
||||
render_api_attachment(attachment, api)
|
||||
end
|
||||
end if include_in_api_response?('attachments')
|
||||
end
|
||||
@@ -1,15 +1,12 @@
|
||||
<div class="contextual">
|
||||
<% if @editable %>
|
||||
<% if @content.current_version? %>
|
||||
<%= link_to_if_authorized(l(:button_edit), {:action => 'edit', :id => @page.title}, :class => 'icon icon-edit', :accesskey => accesskey(:edit)) %>
|
||||
<%= watcher_tag(@page, User.current) %>
|
||||
<%= link_to_if_authorized(l(:button_lock), {:action => 'protect', :id => @page.title, :protected => 1}, :method => :post, :class => 'icon icon-lock') if !@page.protected? %>
|
||||
<%= link_to_if_authorized(l(:button_unlock), {:action => 'protect', :id => @page.title, :protected => 0}, :method => :post, :class => 'icon icon-unlock') if @page.protected? %>
|
||||
<%= link_to_if_authorized(l(:button_rename), {:action => 'rename', :id => @page.title}, :class => 'icon icon-move') %>
|
||||
<%= link_to_if_authorized(l(:button_delete), {:action => 'destroy', :id => @page.title}, :method => :delete, :data => {:confirm => l(:text_are_you_sure)}, :class => 'icon icon-del') %>
|
||||
<% else %>
|
||||
<%= link_to_if_authorized(l(:button_rollback), {:action => 'edit', :id => @page.title, :version => @content.version }, :class => 'icon icon-cancel') %>
|
||||
<% end %>
|
||||
<%= link_to_if_authorized(l(:button_edit), {:action => 'edit', :id => @page.title}, :class => 'icon icon-edit', :accesskey => accesskey(:edit)) if @content.current_version? %>
|
||||
<%= watcher_tag(@page, User.current) %>
|
||||
<%= link_to_if_authorized(l(:button_lock), {:action => 'protect', :id => @page.title, :protected => 1}, :method => :post, :class => 'icon icon-lock') if !@page.protected? %>
|
||||
<%= link_to_if_authorized(l(:button_unlock), {:action => 'protect', :id => @page.title, :protected => 0}, :method => :post, :class => 'icon icon-unlock') if @page.protected? %>
|
||||
<%= link_to_if_authorized(l(:button_rename), {:action => 'rename', :id => @page.title}, :class => 'icon icon-move') if @content.current_version? %>
|
||||
<%= link_to_if_authorized(l(:button_delete), {:action => 'destroy', :id => @page.title}, :method => :delete, :data => {:confirm => l(:text_are_you_sure)}, :class => 'icon icon-del') %>
|
||||
<%= link_to_if_authorized(l(:button_rollback), {:action => 'edit', :id => @page.title, :version => @content.version }, :class => 'icon icon-cancel') unless @content.current_version? %>
|
||||
<% end %>
|
||||
<%= link_to_if_authorized(l(:label_history), {:action => 'history', :id => @page.title}, :class => 'icon icon-history') %>
|
||||
</div>
|
||||
@@ -20,15 +17,15 @@
|
||||
<p>
|
||||
<%= link_to(("\xc2\xab " + l(:label_previous)),
|
||||
:action => 'show', :id => @page.title, :project_id => @page.project,
|
||||
:version => @content.previous.version) + " - " if @content.previous %>
|
||||
:version => (@content.version - 1)) + " - " if @content.version > 1 %>
|
||||
<%= "#{l(:label_version)} #{@content.version}/#{@page.content.version}" %>
|
||||
<%= '('.html_safe + link_to(l(:label_diff), :controller => 'wiki', :action => 'diff',
|
||||
:id => @page.title, :project_id => @page.project,
|
||||
:version => @content.version) + ')'.html_safe if @content.previous %> -
|
||||
:version => @content.version) + ')'.html_safe if @content.version > 1 %> -
|
||||
<%= link_to((l(:label_next) + " \xc2\xbb"), :action => 'show',
|
||||
:id => @page.title, :project_id => @page.project,
|
||||
:version => @content.next.version) + " - " if @content.next %>
|
||||
<%= link_to(l(:label_current_version), :action => 'show', :id => @page.title, :project_id => @page.project, :version => nil) %>
|
||||
:version => (@content.version + 1)) + " - " if @content.version < @page.content.version %>
|
||||
<%= link_to(l(:label_current_version), :action => 'show', :id => @page.title, :project_id => @page.project) %>
|
||||
<br />
|
||||
<em><%= @content.author ? link_to_user(@content.author) : l(:label_user_anonymous)
|
||||
%>, <%= format_time(@content.updated_on) %> </em><br />
|
||||
|
||||
@@ -50,9 +50,6 @@ module RedmineApp
|
||||
|
||||
config.action_mailer.perform_deliveries = false
|
||||
|
||||
# Do not include all helpers
|
||||
config.action_controller.include_all_helpers = false
|
||||
|
||||
config.session_store :cookie_store, :key => '_redmine_session'
|
||||
|
||||
if File.exists?(File.join(File.dirname(__FILE__), 'additional_environment.rb'))
|
||||
|
||||
@@ -166,28 +166,6 @@ default:
|
||||
# the ImageMagick's `convert` binary. Used to generate attachment thumbnails.
|
||||
#imagemagick_convert_command:
|
||||
|
||||
# Configuration of RMagcik font.
|
||||
#
|
||||
# Redmine uses RMagcik in order to export gantt png.
|
||||
# You don't need this setting if you don't install RMagcik.
|
||||
#
|
||||
# In CJK (Chinese, Japanese and Korean),
|
||||
# in order to show CJK characters correctly,
|
||||
# you need to set this configuration.
|
||||
#
|
||||
# Because there is no standard font across platforms in CJK,
|
||||
# you need to set a font installed in your server.
|
||||
#
|
||||
# This setting is not necessary in non CJK.
|
||||
#
|
||||
# Examples for Japanese:
|
||||
# Windows:
|
||||
# rmagick_font_path: C:\windows\fonts\msgothic.ttc
|
||||
# Linux:
|
||||
# rmagick_font_path: /usr/share/fonts/ipa-mincho/ipam.ttf
|
||||
#
|
||||
rmagick_font_path:
|
||||
|
||||
# specific configuration options for production environment
|
||||
# that overrides the default ones
|
||||
production:
|
||||
|
||||
@@ -7,7 +7,7 @@ production:
|
||||
database: redmine
|
||||
host: localhost
|
||||
username: root
|
||||
password: ""
|
||||
password:
|
||||
encoding: utf8
|
||||
|
||||
development:
|
||||
@@ -15,7 +15,7 @@ development:
|
||||
database: redmine_development
|
||||
host: localhost
|
||||
username: root
|
||||
password: ""
|
||||
password:
|
||||
encoding: utf8
|
||||
|
||||
# Warning: The database defined as "test" will be erased and
|
||||
@@ -26,7 +26,7 @@ test:
|
||||
database: redmine_test
|
||||
host: localhost
|
||||
username: root
|
||||
password: ""
|
||||
password:
|
||||
encoding: utf8
|
||||
|
||||
test_pgsql:
|
||||
|
||||
@@ -125,24 +125,6 @@ ActionMailer::Base.add_delivery_method :async_smtp, DeliveryMethods::AsyncSMTP
|
||||
ActionMailer::Base.add_delivery_method :async_sendmail, DeliveryMethods::AsyncSendmail
|
||||
ActionMailer::Base.add_delivery_method :tmp_file, DeliveryMethods::TmpFile
|
||||
|
||||
# Changes how sent emails are logged
|
||||
# Rails doesn't log cc and bcc which is misleading when using bcc only (#12090)
|
||||
module ActionMailer
|
||||
class LogSubscriber < ActiveSupport::LogSubscriber
|
||||
def deliver(event)
|
||||
recipients = [:to, :cc, :bcc].inject("") do |s, header|
|
||||
r = Array.wrap(event.payload[header])
|
||||
if r.any?
|
||||
s << "\n #{header}: #{r.join(', ')}"
|
||||
end
|
||||
s
|
||||
end
|
||||
info("\nSent email \"#{event.payload[:subject]}\" (%1.fms)#{recipients}" % event.duration)
|
||||
debug(event.payload[:mail])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module ActionController
|
||||
module MimeResponds
|
||||
class Collector
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
I18n.default_locale = 'en'
|
||||
I18n.backend = Redmine::I18n::Backend.new
|
||||
# Adds fallback to default locale for untranslated strings
|
||||
I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
|
||||
|
||||
require 'redmine'
|
||||
|
||||
|
||||
@@ -899,6 +899,7 @@ ar:
|
||||
text_subprojects_destroy_warning: "subproject(s): سيتم حذف أيضا."
|
||||
text_workflow_edit: حدد دوراً وتعقب لتحرير سير العمل
|
||||
text_are_you_sure: هل أنت متأكد؟
|
||||
text_are_you_sure_with_children: "حذف الموضوع وجميع المسائل المتعلقة بالطفل؟"
|
||||
text_journal_changed: "%{label} تغير %{old} الى %{new}"
|
||||
text_journal_changed_no_detail: "%{label} تم التحديث"
|
||||
text_journal_set_to: "%{label} تغير الى %{value}"
|
||||
@@ -1062,22 +1063,3 @@ ar:
|
||||
label_attribute_of_assigned_to: Assignee's %{name}
|
||||
label_attribute_of_fixed_version: Target version's %{name}
|
||||
label_copy_subtasks: Copy subtasks
|
||||
label_copied_to: copied to
|
||||
label_copied_from: copied from
|
||||
label_any_issues_in_project: any issues in project
|
||||
label_any_issues_not_in_project: any issues not in project
|
||||
field_private_notes: Private notes
|
||||
permission_view_private_notes: View private notes
|
||||
permission_set_notes_private: Set notes as private
|
||||
label_no_issues_in_project: no issues in project
|
||||
label_any: جميع
|
||||
label_last_n_weeks: last %{count} weeks
|
||||
setting_cross_project_subtasks: Allow cross-project subtasks
|
||||
label_cross_project_descendants: يشارك
|
||||
label_cross_project_tree: مع شجرة المشروع
|
||||
label_cross_project_hierarchy: مع التسلسل الهرمي للمشروع
|
||||
label_cross_project_system: مع جميع المشاريع
|
||||
button_hide: Hide
|
||||
setting_non_working_week_days: Non-working days
|
||||
label_in_the_next_days: in the next
|
||||
label_in_the_past_days: in the past
|
||||
|
||||
@@ -332,7 +332,6 @@ bg:
|
||||
field_core_fields: Стандартни полета
|
||||
field_timeout: Таймаут (в секунди)
|
||||
field_board_parent: Родителски форум
|
||||
field_private_notes: Лични бележки
|
||||
|
||||
setting_app_title: Заглавие
|
||||
setting_app_subtitle: Описание
|
||||
@@ -358,7 +357,6 @@ bg:
|
||||
setting_date_format: Формат на датата
|
||||
setting_time_format: Формат на часа
|
||||
setting_cross_project_issue_relations: Релации на задачи между проекти
|
||||
setting_cross_project_subtasks: Подзадачи от други проекти
|
||||
setting_issue_list_default_columns: Показвани колони по подразбиране
|
||||
setting_repositories_encodings: Кодова таблица на прикачените файлове и хранилищата
|
||||
setting_emails_header: Emails header
|
||||
@@ -400,7 +398,6 @@ bg:
|
||||
setting_session_timeout: Таймаут за неактивност преди прекратяване на сесиите
|
||||
setting_thumbnails_enabled: Показване на миниатюри на прикачените изображения
|
||||
setting_thumbnails_size: Размер на миниатюрите (в пиксели)
|
||||
setting_non_working_week_days: Не работни дни
|
||||
|
||||
permission_add_project: Създаване на проект
|
||||
permission_add_subprojects: Създаване на подпроекти
|
||||
@@ -420,8 +417,6 @@ bg:
|
||||
permission_add_issue_notes: Добавяне на бележки
|
||||
permission_edit_issue_notes: Редактиране на бележки
|
||||
permission_edit_own_issue_notes: Редактиране на собствени бележки
|
||||
permission_view_private_notes: Разглеждане на лични бележки
|
||||
permission_set_notes_private: Установяване на бележките лични
|
||||
permission_move_issues: Преместване на задачи
|
||||
permission_delete_issues: Изтриване на задачи
|
||||
permission_manage_public_queries: Управление на публичните заявки
|
||||
@@ -622,7 +617,6 @@ bg:
|
||||
label_current_status: Текущо състояние
|
||||
label_new_statuses_allowed: Позволени състояния
|
||||
label_all: всички
|
||||
label_any: която и да е
|
||||
label_none: никакви
|
||||
label_nobody: никой
|
||||
label_next: Следващ
|
||||
@@ -657,8 +651,6 @@ bg:
|
||||
label_not_equals: не е
|
||||
label_in_less_than: след по-малко от
|
||||
label_in_more_than: след повече от
|
||||
label_in_the_next_days: в следващите
|
||||
label_in_the_past_days: в предишните
|
||||
label_greater_or_equal: ">="
|
||||
label_less_or_equal: <=
|
||||
label_between: между
|
||||
@@ -668,7 +660,6 @@ bg:
|
||||
label_yesterday: вчера
|
||||
label_this_week: тази седмица
|
||||
label_last_week: последната седмица
|
||||
label_last_n_weeks: последните %{count} седмици
|
||||
label_last_n_days: "последните %{count} дни"
|
||||
label_this_month: текущия месец
|
||||
label_last_month: последния месец
|
||||
@@ -679,9 +670,6 @@ bg:
|
||||
label_ago: преди
|
||||
label_contains: съдържа
|
||||
label_not_contains: не съдържа
|
||||
label_any_issues_in_project: задачи от проект
|
||||
label_any_issues_not_in_project: задачи, които не са в проект
|
||||
label_no_issues_in_project: никакви задачи в проект
|
||||
label_day_plural: дни
|
||||
label_repository: Хранилище
|
||||
label_repository_new: Ново хранилище
|
||||
@@ -757,8 +745,6 @@ bg:
|
||||
label_blocked_by: блокирана от
|
||||
label_precedes: предшества
|
||||
label_follows: изпълнява се след
|
||||
label_copied_to: копирана в
|
||||
label_copied_from: копирана от
|
||||
label_end_to_start: край към начало
|
||||
label_end_to_end: край към край
|
||||
label_start_to_start: начало към начало
|
||||
@@ -833,7 +819,7 @@ bg:
|
||||
label_generate_key: Генериране на ключ
|
||||
label_issue_watchers: Наблюдатели
|
||||
label_example: Пример
|
||||
label_display: Показване
|
||||
label_display: Display
|
||||
label_sort: Сортиране
|
||||
label_ascending: Нарастващ
|
||||
label_descending: Намаляващ
|
||||
@@ -886,13 +872,9 @@ bg:
|
||||
label_attribute_of_author: Author's %{name}
|
||||
label_attribute_of_assigned_to: Assignee's %{name}
|
||||
label_attribute_of_fixed_version: Target version's %{name}
|
||||
label_cross_project_descendants: С подпроекти
|
||||
label_cross_project_tree: С дърво на проектите
|
||||
label_cross_project_hierarchy: С проектна йерархия
|
||||
label_cross_project_system: С всички проекти
|
||||
|
||||
button_login: Вход
|
||||
button_submit: Изпращане
|
||||
button_submit: Прикачване
|
||||
button_save: Запис
|
||||
button_check_all: Избор на всички
|
||||
button_uncheck_all: Изчистване на всички
|
||||
@@ -937,7 +919,6 @@ bg:
|
||||
button_quote: Цитат
|
||||
button_duplicate: Дублиране
|
||||
button_show: Показване
|
||||
button_hide: Скриване
|
||||
button_edit_section: Редактиране на тази секция
|
||||
button_export: Експорт
|
||||
button_delete_my_account: Премахване на моя профил
|
||||
@@ -965,6 +946,7 @@ bg:
|
||||
text_subprojects_destroy_warning: "Неговите подпроекти: %{value} също ще бъдат изтрити."
|
||||
text_workflow_edit: Изберете роля и тракер за да редактирате работния процес
|
||||
text_are_you_sure: Сигурни ли сте?
|
||||
text_are_you_sure_with_children: Изтриване на задачата и нейните подзадачи?
|
||||
text_journal_changed: "%{label} променен от %{old} на %{new}"
|
||||
text_journal_changed_no_detail: "%{label} променен"
|
||||
text_journal_set_to: "%{label} установен на %{value}"
|
||||
|
||||
@@ -933,6 +933,7 @@ bs:
|
||||
project_module_gantt: Gantt
|
||||
project_module_calendar: Calendar
|
||||
button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}"
|
||||
text_are_you_sure_with_children: Delete issue and all child issues?
|
||||
field_text: Text field
|
||||
label_user_mail_option_only_owner: Only for things I am the owner of
|
||||
setting_default_notification_option: Default notification option
|
||||
@@ -1075,22 +1076,3 @@ bs:
|
||||
label_attribute_of_assigned_to: Assignee's %{name}
|
||||
label_attribute_of_fixed_version: Target version's %{name}
|
||||
label_copy_subtasks: Copy subtasks
|
||||
label_copied_to: copied to
|
||||
label_copied_from: copied from
|
||||
label_any_issues_in_project: any issues in project
|
||||
label_any_issues_not_in_project: any issues not in project
|
||||
field_private_notes: Private notes
|
||||
permission_view_private_notes: View private notes
|
||||
permission_set_notes_private: Set notes as private
|
||||
label_no_issues_in_project: no issues in project
|
||||
label_any: sve
|
||||
label_last_n_weeks: last %{count} weeks
|
||||
setting_cross_project_subtasks: Allow cross-project subtasks
|
||||
label_cross_project_descendants: With subprojects
|
||||
label_cross_project_tree: With project tree
|
||||
label_cross_project_hierarchy: With project hierarchy
|
||||
label_cross_project_system: With all projects
|
||||
button_hide: Hide
|
||||
setting_non_working_week_days: Non-working days
|
||||
label_in_the_next_days: in the next
|
||||
label_in_the_past_days: in the past
|
||||
|
||||
@@ -922,6 +922,7 @@ ca:
|
||||
enumeration_system_activity: Activitat del sistema
|
||||
|
||||
button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}"
|
||||
text_are_you_sure_with_children: Delete issue and all child issues?
|
||||
field_text: Text field
|
||||
label_user_mail_option_only_owner: Only for things I am the owner of
|
||||
setting_default_notification_option: Default notification option
|
||||
@@ -1064,22 +1065,3 @@ ca:
|
||||
label_attribute_of_assigned_to: Assignee's %{name}
|
||||
label_attribute_of_fixed_version: Target version's %{name}
|
||||
label_copy_subtasks: Copy subtasks
|
||||
label_copied_to: copied to
|
||||
label_copied_from: copied from
|
||||
label_any_issues_in_project: any issues in project
|
||||
label_any_issues_not_in_project: any issues not in project
|
||||
field_private_notes: Private notes
|
||||
permission_view_private_notes: View private notes
|
||||
permission_set_notes_private: Set notes as private
|
||||
label_no_issues_in_project: no issues in project
|
||||
label_any: tots
|
||||
label_last_n_weeks: last %{count} weeks
|
||||
setting_cross_project_subtasks: Allow cross-project subtasks
|
||||
label_cross_project_descendants: "Amb tots els subprojectes"
|
||||
label_cross_project_tree: "Amb l'arbre del projecte"
|
||||
label_cross_project_hierarchy: "Amb la jerarquia del projecte"
|
||||
label_cross_project_system: "Amb tots els projectes"
|
||||
button_hide: Hide
|
||||
setting_non_working_week_days: Non-working days
|
||||
label_in_the_next_days: in the next
|
||||
label_in_the_past_days: in the past
|
||||
|
||||
@@ -140,7 +140,7 @@ cs:
|
||||
general_text_Yes: 'Ano'
|
||||
general_text_no: 'ne'
|
||||
general_text_yes: 'ano'
|
||||
general_lang_name: 'Czech (Čeština)'
|
||||
general_lang_name: 'Čeština'
|
||||
general_csv_separator: ','
|
||||
general_csv_decimal_separator: '.'
|
||||
general_csv_encoding: UTF-8
|
||||
@@ -863,6 +863,7 @@ cs:
|
||||
text_subprojects_destroy_warning: "Jeho podprojek(y): %{value} budou také smazány."
|
||||
text_workflow_edit: Vyberte roli a frontu k editaci průběhu práce
|
||||
text_are_you_sure: Jste si jisti?
|
||||
text_are_you_sure_with_children: Smazat úkol včetně všech podúkolů?
|
||||
text_journal_changed: "%{label} změněn z %{old} na %{new}"
|
||||
text_journal_set_to: "%{label} nastaven na %{value}"
|
||||
text_journal_deleted: "%{label} smazán (%{old})"
|
||||
@@ -1065,22 +1066,3 @@ cs:
|
||||
label_attribute_of_assigned_to: Assignee's %{name}
|
||||
label_attribute_of_fixed_version: Target version's %{name}
|
||||
label_copy_subtasks: Copy subtasks
|
||||
label_copied_to: copied to
|
||||
label_copied_from: copied from
|
||||
label_any_issues_in_project: any issues in project
|
||||
label_any_issues_not_in_project: any issues not in project
|
||||
field_private_notes: Private notes
|
||||
permission_view_private_notes: View private notes
|
||||
permission_set_notes_private: Set notes as private
|
||||
label_no_issues_in_project: no issues in project
|
||||
label_any: vše
|
||||
label_last_n_weeks: last %{count} weeks
|
||||
setting_cross_project_subtasks: Allow cross-project subtasks
|
||||
label_cross_project_descendants: S podprojekty
|
||||
label_cross_project_tree: Se stromem projektu
|
||||
label_cross_project_hierarchy: S hierarchií projektu
|
||||
label_cross_project_system: Se všemi projekty
|
||||
button_hide: Hide
|
||||
setting_non_working_week_days: Non-working days
|
||||
label_in_the_next_days: in the next
|
||||
label_in_the_past_days: in the past
|
||||
|
||||
@@ -936,6 +936,7 @@ da:
|
||||
project_module_gantt: Gantt
|
||||
project_module_calendar: Kalender
|
||||
button_edit_associated_wikipage: "Redigér tilknyttet Wiki side: %{page_title}"
|
||||
text_are_you_sure_with_children: Slet sag og alle undersager?
|
||||
field_text: Tekstfelt
|
||||
label_user_mail_option_only_owner: Kun for ting jeg er ejer af
|
||||
setting_default_notification_option: Standardpåmindelsesmulighed
|
||||
@@ -1079,22 +1080,3 @@ da:
|
||||
label_attribute_of_assigned_to: Assignee's %{name}
|
||||
label_attribute_of_fixed_version: Target version's %{name}
|
||||
label_copy_subtasks: Copy subtasks
|
||||
label_copied_to: copied to
|
||||
label_copied_from: copied from
|
||||
label_any_issues_in_project: any issues in project
|
||||
label_any_issues_not_in_project: any issues not in project
|
||||
field_private_notes: Private notes
|
||||
permission_view_private_notes: View private notes
|
||||
permission_set_notes_private: Set notes as private
|
||||
label_no_issues_in_project: no issues in project
|
||||
label_any: alle
|
||||
label_last_n_weeks: last %{count} weeks
|
||||
setting_cross_project_subtasks: Allow cross-project subtasks
|
||||
label_cross_project_descendants: Med underprojekter
|
||||
label_cross_project_tree: Med projekttræ
|
||||
label_cross_project_hierarchy: Med projekthierarki
|
||||
label_cross_project_system: Med alle projekter
|
||||
button_hide: Hide
|
||||
setting_non_working_week_days: Non-working days
|
||||
label_in_the_next_days: in the next
|
||||
label_in_the_past_days: in the past
|
||||
|
||||
@@ -322,7 +322,7 @@ de:
|
||||
setting_app_title: Applikations-Titel
|
||||
setting_app_subtitle: Applikations-Untertitel
|
||||
setting_welcome_text: Willkommenstext
|
||||
setting_default_language: Standardsprache
|
||||
setting_default_language: Default-Sprache
|
||||
setting_login_required: Authentifizierung erforderlich
|
||||
setting_self_registration: Anmeldung ermöglicht
|
||||
setting_attachment_max_size: Max. Dateigröße
|
||||
@@ -343,7 +343,7 @@ de:
|
||||
setting_date_format: Datumsformat
|
||||
setting_time_format: Zeitformat
|
||||
setting_cross_project_issue_relations: Ticket-Beziehungen zwischen Projekten erlauben
|
||||
setting_issue_list_default_columns: Standard-Spalten in der Ticket-Auflistung
|
||||
setting_issue_list_default_columns: Default-Spalten in der Ticket-Auflistung
|
||||
setting_emails_footer: E-Mail-Fußzeile
|
||||
setting_protocol: Protokoll
|
||||
setting_per_page_options: Objekte pro Seite
|
||||
@@ -698,7 +698,7 @@ de:
|
||||
label_blocks: Blockiert
|
||||
label_blocked_by: Blockiert durch
|
||||
label_precedes: Vorgänger von
|
||||
label_follows: Folgt
|
||||
label_follows: folgt
|
||||
label_end_to_start: Ende - Anfang
|
||||
label_end_to_end: Ende - Ende
|
||||
label_start_to_start: Anfang - Anfang
|
||||
@@ -860,6 +860,7 @@ de:
|
||||
text_subprojects_destroy_warning: "Dessen Unterprojekte (%{value}) werden ebenfalls gelöscht."
|
||||
text_workflow_edit: Workflow zum Bearbeiten auswählen
|
||||
text_are_you_sure: Sind Sie sicher?
|
||||
text_are_you_sure_with_children: "Lösche Aufgabe und alle Unteraufgaben?"
|
||||
text_journal_changed: "%{label} wurde von %{old} zu %{new} geändert"
|
||||
text_journal_set_to: "%{label} wurde auf %{value} gesetzt"
|
||||
text_journal_deleted: "%{label} %{old} wurde gelöscht"
|
||||
@@ -994,7 +995,7 @@ de:
|
||||
|
||||
notice_issue_successful_create: Ticket %{id} erstellt.
|
||||
label_between: zwischen
|
||||
setting_issue_group_assignment: Ticketzuweisung an Gruppen erlauben
|
||||
setting_issue_group_assignment: Erlaubt die Ticket-Zuweisung an Gruppen
|
||||
label_diff: diff
|
||||
text_git_repository_note: Repository steht für sich alleine (bare) und liegt lokal (z.B. /gitrepo, c:\gitrepo)
|
||||
|
||||
@@ -1078,22 +1079,3 @@ de:
|
||||
label_attribute_of_assigned_to: "%{name} des Bearbeiters"
|
||||
label_attribute_of_fixed_version: "%{name} der Zielversion"
|
||||
label_copy_subtasks: Unteraufgaben kopieren
|
||||
label_copied_to: Kopiert nach
|
||||
label_copied_from: Kopiert von
|
||||
label_any_issues_in_project: irgendein Ticket im Projekt
|
||||
label_any_issues_not_in_project: irgendein Ticket nicht im Projekt
|
||||
field_private_notes: Privater Kommentar
|
||||
permission_view_private_notes: Private Kommentare sehen
|
||||
permission_set_notes_private: Kommentar als privat markieren
|
||||
label_no_issues_in_project: keine Tickets im Projekt
|
||||
label_any: alle
|
||||
label_last_n_weeks: letzte %{count} Wochen
|
||||
setting_cross_project_subtasks: Projektübergreifende Unteraufgaben erlauben
|
||||
label_cross_project_descendants: Mit Unterprojekten
|
||||
label_cross_project_tree: Mit Projektbaum
|
||||
label_cross_project_hierarchy: Mit Projekthierarchie
|
||||
label_cross_project_system: Mit allen Projekten
|
||||
button_hide: Verstecken
|
||||
setting_non_working_week_days: Arbeitsfreie Tage
|
||||
label_in_the_next_days: in den nächsten
|
||||
label_in_the_past_days: in den letzten
|
||||
|
||||
@@ -137,7 +137,7 @@ el:
|
||||
general_text_Yes: 'Ναι'
|
||||
general_text_no: 'όχι'
|
||||
general_text_yes: 'ναι'
|
||||
general_lang_name: 'Greek (Ελληνικά)'
|
||||
general_lang_name: 'Ελληνικά'
|
||||
general_csv_separator: ','
|
||||
general_csv_decimal_separator: '.'
|
||||
general_csv_encoding: UTF-8
|
||||
@@ -920,6 +920,7 @@ el:
|
||||
project_module_gantt: Gantt
|
||||
project_module_calendar: Calendar
|
||||
button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}"
|
||||
text_are_you_sure_with_children: Delete issue and all child issues?
|
||||
field_text: Text field
|
||||
label_user_mail_option_only_owner: Only for things I am the owner of
|
||||
setting_default_notification_option: Default notification option
|
||||
@@ -1062,22 +1063,3 @@ el:
|
||||
label_attribute_of_assigned_to: Assignee's %{name}
|
||||
label_attribute_of_fixed_version: Target version's %{name}
|
||||
label_copy_subtasks: Copy subtasks
|
||||
label_copied_to: copied to
|
||||
label_copied_from: copied from
|
||||
label_any_issues_in_project: any issues in project
|
||||
label_any_issues_not_in_project: any issues not in project
|
||||
field_private_notes: Private notes
|
||||
permission_view_private_notes: View private notes
|
||||
permission_set_notes_private: Set notes as private
|
||||
label_no_issues_in_project: no issues in project
|
||||
label_any: όλα
|
||||
label_last_n_weeks: last %{count} weeks
|
||||
setting_cross_project_subtasks: Allow cross-project subtasks
|
||||
label_cross_project_descendants: With subprojects
|
||||
label_cross_project_tree: With project tree
|
||||
label_cross_project_hierarchy: With project hierarchy
|
||||
label_cross_project_system: With all projects
|
||||
button_hide: Hide
|
||||
setting_non_working_week_days: Non-working days
|
||||
label_in_the_next_days: in the next
|
||||
label_in_the_past_days: in the past
|
||||
|
||||
@@ -873,6 +873,7 @@ en-GB:
|
||||
text_subprojects_destroy_warning: "Its subproject(s): %{value} will be also deleted."
|
||||
text_workflow_edit: Select a role and a tracker to edit the workflow
|
||||
text_are_you_sure: Are you sure?
|
||||
text_are_you_sure_with_children: "Delete issue and all child issues?"
|
||||
text_journal_changed: "%{label} changed from %{old} to %{new}"
|
||||
text_journal_changed_no_detail: "%{label} updated"
|
||||
text_journal_set_to: "%{label} set to %{value}"
|
||||
@@ -1064,22 +1065,3 @@ en-GB:
|
||||
label_attribute_of_assigned_to: Assignee's %{name}
|
||||
label_attribute_of_fixed_version: Target version's %{name}
|
||||
label_copy_subtasks: Copy subtasks
|
||||
label_copied_to: copied to
|
||||
label_copied_from: copied from
|
||||
label_any_issues_in_project: any issues in project
|
||||
label_any_issues_not_in_project: any issues not in project
|
||||
field_private_notes: Private notes
|
||||
permission_view_private_notes: View private notes
|
||||
permission_set_notes_private: Set notes as private
|
||||
label_no_issues_in_project: no issues in project
|
||||
label_any: all
|
||||
label_last_n_weeks: last %{count} weeks
|
||||
setting_cross_project_subtasks: Allow cross-project subtasks
|
||||
label_cross_project_descendants: With subprojects
|
||||
label_cross_project_tree: With project tree
|
||||
label_cross_project_hierarchy: With project hierarchy
|
||||
label_cross_project_system: With all projects
|
||||
button_hide: Hide
|
||||
setting_non_working_week_days: Non-working days
|
||||
label_in_the_next_days: in the next
|
||||
label_in_the_past_days: in the past
|
||||
|
||||
@@ -331,7 +331,6 @@ en:
|
||||
field_core_fields: Standard fields
|
||||
field_timeout: "Timeout (in seconds)"
|
||||
field_board_parent: Parent forum
|
||||
field_private_notes: Private notes
|
||||
|
||||
setting_app_title: Application title
|
||||
setting_app_subtitle: Application subtitle
|
||||
@@ -357,7 +356,6 @@ en:
|
||||
setting_date_format: Date format
|
||||
setting_time_format: Time format
|
||||
setting_cross_project_issue_relations: Allow cross-project issue relations
|
||||
setting_cross_project_subtasks: Allow cross-project subtasks
|
||||
setting_issue_list_default_columns: Default columns displayed on the issue list
|
||||
setting_repositories_encodings: Attachments and repositories encodings
|
||||
setting_emails_header: Emails header
|
||||
@@ -399,7 +397,6 @@ en:
|
||||
setting_session_timeout: Session inactivity timeout
|
||||
setting_thumbnails_enabled: Display attachment thumbnails
|
||||
setting_thumbnails_size: Thumbnails size (in pixels)
|
||||
setting_non_working_week_days: Non-working days
|
||||
|
||||
permission_add_project: Create project
|
||||
permission_add_subprojects: Create subprojects
|
||||
@@ -419,8 +416,6 @@ en:
|
||||
permission_add_issue_notes: Add notes
|
||||
permission_edit_issue_notes: Edit notes
|
||||
permission_edit_own_issue_notes: Edit own notes
|
||||
permission_view_private_notes: View private notes
|
||||
permission_set_notes_private: Set notes as private
|
||||
permission_move_issues: Move issues
|
||||
permission_delete_issues: Delete issues
|
||||
permission_manage_public_queries: Manage public queries
|
||||
@@ -621,7 +616,6 @@ en:
|
||||
label_current_status: Current status
|
||||
label_new_statuses_allowed: New statuses allowed
|
||||
label_all: all
|
||||
label_any: any
|
||||
label_none: none
|
||||
label_nobody: nobody
|
||||
label_next: Next
|
||||
@@ -656,8 +650,6 @@ en:
|
||||
label_not_equals: is not
|
||||
label_in_less_than: in less than
|
||||
label_in_more_than: in more than
|
||||
label_in_the_next_days: in the next
|
||||
label_in_the_past_days: in the past
|
||||
label_greater_or_equal: '>='
|
||||
label_less_or_equal: '<='
|
||||
label_between: between
|
||||
@@ -667,7 +659,6 @@ en:
|
||||
label_yesterday: yesterday
|
||||
label_this_week: this week
|
||||
label_last_week: last week
|
||||
label_last_n_weeks: "last %{count} weeks"
|
||||
label_last_n_days: "last %{count} days"
|
||||
label_this_month: this month
|
||||
label_last_month: last month
|
||||
@@ -678,9 +669,6 @@ en:
|
||||
label_ago: days ago
|
||||
label_contains: contains
|
||||
label_not_contains: doesn't contain
|
||||
label_any_issues_in_project: any issues in project
|
||||
label_any_issues_not_in_project: any issues not in project
|
||||
label_no_issues_in_project: no issues in project
|
||||
label_day_plural: days
|
||||
label_repository: Repository
|
||||
label_repository_new: New repository
|
||||
@@ -749,15 +737,13 @@ en:
|
||||
label_loading: Loading...
|
||||
label_relation_new: New relation
|
||||
label_relation_delete: Delete relation
|
||||
label_relates_to: Related to
|
||||
label_duplicates: Duplicates
|
||||
label_duplicated_by: Duplicated by
|
||||
label_blocks: Blocks
|
||||
label_blocked_by: Blocked by
|
||||
label_precedes: Precedes
|
||||
label_follows: Follows
|
||||
label_copied_to: Copied to
|
||||
label_copied_from: Copied from
|
||||
label_relates_to: related to
|
||||
label_duplicates: duplicates
|
||||
label_duplicated_by: duplicated by
|
||||
label_blocks: blocks
|
||||
label_blocked_by: blocked by
|
||||
label_precedes: precedes
|
||||
label_follows: follows
|
||||
label_end_to_start: end to start
|
||||
label_end_to_end: end to end
|
||||
label_start_to_start: start to start
|
||||
@@ -885,10 +871,6 @@ en:
|
||||
label_attribute_of_author: "Author's %{name}"
|
||||
label_attribute_of_assigned_to: "Assignee's %{name}"
|
||||
label_attribute_of_fixed_version: "Target version's %{name}"
|
||||
label_cross_project_descendants: With subprojects
|
||||
label_cross_project_tree: With project tree
|
||||
label_cross_project_hierarchy: With project hierarchy
|
||||
label_cross_project_system: With all projects
|
||||
|
||||
button_login: Login
|
||||
button_submit: Submit
|
||||
@@ -936,7 +918,6 @@ en:
|
||||
button_quote: Quote
|
||||
button_duplicate: Duplicate
|
||||
button_show: Show
|
||||
button_hide: Hide
|
||||
button_edit_section: Edit this section
|
||||
button_export: Export
|
||||
button_delete_my_account: Delete my account
|
||||
@@ -964,6 +945,7 @@ en:
|
||||
text_subprojects_destroy_warning: "Its subproject(s): %{value} will be also deleted."
|
||||
text_workflow_edit: Select a role and a tracker to edit the workflow
|
||||
text_are_you_sure: Are you sure?
|
||||
text_are_you_sure_with_children: "Delete issue and all child issues?"
|
||||
text_journal_changed: "%{label} changed from %{old} to %{new}"
|
||||
text_journal_changed_no_detail: "%{label} updated"
|
||||
text_journal_set_to: "%{label} set to %{value}"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user