git-svn-id: http://svn.redmine.org/redmine/trunk@16228 e93f8b46-1217-0410-a6f0-8f06a7374b81tags/3.4.0
add_available_filter "issue_id", :type => :integer, :label => :label_issue | add_available_filter "issue_id", :type => :integer, :label => :label_issue | ||||
add_available_filter("updated_by", | |||||
:type => :list, :values => lambda { author_values } | |||||
) | |||||
add_available_filter("last_updated_by", | |||||
:type => :list, :values => lambda { author_values } | |||||
) | |||||
Tracker.disabled_core_fields(trackers).each {|field| | Tracker.disabled_core_fields(trackers).each {|field| | ||||
delete_available_filter field | delete_available_filter field | ||||
} | } | ||||
raise StatementInvalid.new(e.message) | raise StatementInvalid.new(e.message) | ||||
end | end | ||||
def sql_for_updated_by_field(field, operator, value) | |||||
neg = (operator == '!' ? 'NOT' : '') | |||||
subquery = "SELECT 1 FROM #{Journal.table_name}" + | |||||
" WHERE #{Journal.table_name}.journalized_type='Issue' AND #{Journal.table_name}.journalized_id=#{Issue.table_name}.id" + | |||||
" AND (#{sql_for_field field, '=', value, Journal.table_name, 'user_id'})" + | |||||
" AND (#{Journal.visible_notes_condition(User.current, :skip_pre_condition => true)})" | |||||
"#{neg} EXISTS (#{subquery})" | |||||
end | |||||
def sql_for_last_updated_by_field(field, operator, value) | |||||
neg = (operator == '!' ? 'NOT' : '') | |||||
subquery = "SELECT 1 FROM #{Journal.table_name} sj" + | |||||
" WHERE sj.journalized_type='Issue' AND sj.journalized_id=#{Issue.table_name}.id AND (#{sql_for_field field, '=', value, 'sj', 'user_id'})" + | |||||
" AND sj.id = (SELECT MAX(#{Journal.table_name}.id) FROM #{Journal.table_name}" + | |||||
" WHERE #{Journal.table_name}.journalized_type='Issue' AND #{Journal.table_name}.journalized_id=#{Issue.table_name}.id" + | |||||
" AND (#{Journal.visible_notes_condition(User.current, :skip_pre_condition => true)}))" | |||||
"#{neg} EXISTS (#{subquery})" | |||||
end | |||||
def sql_for_watcher_id_field(field, operator, value) | def sql_for_watcher_id_field(field, operator, value) | ||||
db_table = Watcher.table_name | db_table = Watcher.table_name | ||||
"#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " + | "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " + |
scope :visible, lambda {|*args| | scope :visible, lambda {|*args| | ||||
user = args.shift || User.current | user = args.shift || User.current | ||||
private_notes_condition = Project.allowed_to_condition(user, :view_private_notes, *args) | |||||
options = args.shift || {} | |||||
joins(:issue => :project). | joins(:issue => :project). | ||||
where(Issue.visible_condition(user, *args)). | |||||
where("(#{Journal.table_name}.private_notes = ? OR #{Journal.table_name}.user_id = ? OR (#{private_notes_condition}))", false, user.id) | |||||
where(Issue.visible_condition(user, options)). | |||||
where(Journal.visible_notes_condition(user, :skip_pre_condition => true)) | |||||
} | } | ||||
safe_attributes 'notes', | safe_attributes 'notes', | ||||
safe_attributes 'private_notes', | safe_attributes 'private_notes', | ||||
:if => lambda {|journal, user| user.allowed_to?(:set_notes_private, journal.project)} | :if => lambda {|journal, user| user.allowed_to?(:set_notes_private, journal.project)} | ||||
# Returns a SQL condition to filter out journals with notes that are not visible to user | |||||
def self.visible_notes_condition(user=User.current, options={}) | |||||
private_notes_permission = Project.allowed_to_condition(user, :view_private_notes, options) | |||||
sanitize_sql_for_conditions(["(#{table_name}.private_notes = ? OR #{table_name}.user_id = ? OR (#{private_notes_permission}))", false, user.id]) | |||||
end | |||||
def initialize(*args) | def initialize(*args) | ||||
super | super | ||||
if journalized | if journalized |
# Returns a SQL conditions string used to find all projects for which +user+ has the given +permission+ | # Returns a SQL conditions string used to find all projects for which +user+ has the given +permission+ | ||||
# | # | ||||
# Valid options: | # Valid options: | ||||
# * :project => limit the condition to project | |||||
# * :with_subprojects => limit the condition to project and its subprojects | |||||
# * :member => limit the condition to the user projects | |||||
# * :skip_pre_condition => true don't check that the module is enabled (eg. when the condition is already set elsewhere in the query) | |||||
# * :project => project limit the condition to project | |||||
# * :with_subprojects => true limit the condition to project and its subprojects | |||||
# * :member => true limit the condition to the user projects | |||||
def self.allowed_to_condition(user, permission, options={}) | def self.allowed_to_condition(user, permission, options={}) | ||||
perm = Redmine::AccessControl.permission(permission) | perm = Redmine::AccessControl.permission(permission) | ||||
base_statement = (perm && perm.read? ? "#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED}" : "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}") | base_statement = (perm && perm.read? ? "#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED}" : "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}") | ||||
if perm && perm.project_module | |||||
if !options[:skip_pre_condition] && perm && perm.project_module | |||||
# If the permission belongs to a project module, make sure the module is enabled | # If the permission belongs to a project module, make sure the module is enabled | ||||
base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')" | base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')" | ||||
end | end |
operator = operator_for(field) | operator = operator_for(field) | ||||
# "me" value substitution | # "me" value substitution | ||||
if %w(assigned_to_id author_id user_id watcher_id).include?(field) | |||||
if %w(assigned_to_id author_id user_id watcher_id updated_by last_updated_by).include?(field) | |||||
if v.delete("me") | if v.delete("me") | ||||
if User.current.logged? | if User.current.logged? | ||||
v.push(User.current.id.to_s) | v.push(User.current.id.to_s) |
field_default_version: Default version | field_default_version: Default version | ||||
field_remote_ip: IP address | field_remote_ip: IP address | ||||
field_textarea_font: Font used for text areas | field_textarea_font: Font used for text areas | ||||
field_updated_by: Updated by | |||||
field_last_updated_by: Last updated by | |||||
setting_app_title: Application title | setting_app_title: Application title | ||||
setting_app_subtitle: Application subtitle | setting_app_subtitle: Application subtitle |
field_total_estimated_hours: Temps estimé total | field_total_estimated_hours: Temps estimé total | ||||
field_default_version: Version par défaut | field_default_version: Version par défaut | ||||
field_textarea_font: Police utilisée pour les champs texte | field_textarea_font: Police utilisée pour les champs texte | ||||
field_updated_by: Mise à jour par | |||||
field_last_updated_by: Dernière mise à jour par | |||||
setting_app_title: Titre de l'application | setting_app_title: Titre de l'application | ||||
setting_app_subtitle: Sous-titre de l'application | setting_app_subtitle: Sous-titre de l'application |
end | end | ||||
end | end | ||||
def test_filter_updated_by | |||||
user = User.generate! | |||||
Journal.create!(:user_id => user.id, :journalized => Issue.find(2), :notes => 'Notes') | |||||
Journal.create!(:user_id => user.id, :journalized => Issue.find(3), :notes => 'Notes') | |||||
Journal.create!(:user_id => 2, :journalized => Issue.find(3), :notes => 'Notes') | |||||
query = IssueQuery.new(:name => '_') | |||||
filter_name = "updated_by" | |||||
assert_include filter_name, query.available_filters.keys | |||||
query.filters = {filter_name => {:operator => '=', :values => [user.id]}} | |||||
assert_equal [2, 3], find_issues_with_query(query).map(&:id).sort | |||||
query.filters = {filter_name => {:operator => '!', :values => [user.id]}} | |||||
assert_equal (Issue.ids.sort - [2, 3]), find_issues_with_query(query).map(&:id).sort | |||||
end | |||||
def test_filter_updated_by_should_ignore_private_notes_that_are_not_visible | |||||
user = User.generate! | |||||
Journal.create!(:user_id => user.id, :journalized => Issue.find(2), :notes => 'Notes', :private_notes => true) | |||||
Journal.create!(:user_id => user.id, :journalized => Issue.find(3), :notes => 'Notes') | |||||
query = IssueQuery.new(:name => '_') | |||||
filter_name = "updated_by" | |||||
assert_include filter_name, query.available_filters.keys | |||||
with_current_user User.anonymous do | |||||
query.filters = {filter_name => {:operator => '=', :values => [user.id]}} | |||||
assert_equal [3], find_issues_with_query(query).map(&:id).sort | |||||
end | |||||
end | |||||
def test_filter_updated_by_me | |||||
user = User.generate! | |||||
Journal.create!(:user_id => user.id, :journalized => Issue.find(2), :notes => 'Notes') | |||||
with_current_user user do | |||||
query = IssueQuery.new(:name => '_') | |||||
filter_name = "updated_by" | |||||
assert_include filter_name, query.available_filters.keys | |||||
query.filters = {filter_name => {:operator => '=', :values => ['me']}} | |||||
assert_equal [2], find_issues_with_query(query).map(&:id).sort | |||||
end | |||||
end | |||||
def test_filter_last_updated_by | |||||
user = User.generate! | |||||
Journal.create!(:user_id => user.id, :journalized => Issue.find(2), :notes => 'Notes') | |||||
Journal.create!(:user_id => user.id, :journalized => Issue.find(3), :notes => 'Notes') | |||||
Journal.create!(:user_id => 2, :journalized => Issue.find(3), :notes => 'Notes') | |||||
query = IssueQuery.new(:name => '_') | |||||
filter_name = "last_updated_by" | |||||
assert_include filter_name, query.available_filters.keys | |||||
query.filters = {filter_name => {:operator => '=', :values => [user.id]}} | |||||
assert_equal [2], find_issues_with_query(query).map(&:id).sort | |||||
end | |||||
def test_filter_last_updated_by_should_ignore_private_notes_that_are_not_visible | |||||
user1 = User.generate! | |||||
user2 = User.generate! | |||||
Journal.create!(:user_id => user1.id, :journalized => Issue.find(2), :notes => 'Notes') | |||||
Journal.create!(:user_id => user2.id, :journalized => Issue.find(2), :notes => 'Notes', :private_notes => true) | |||||
query = IssueQuery.new(:name => '_') | |||||
filter_name = "last_updated_by" | |||||
assert_include filter_name, query.available_filters.keys | |||||
with_current_user User.anonymous do | |||||
query.filters = {filter_name => {:operator => '=', :values => [user1.id]}} | |||||
assert_equal [2], find_issues_with_query(query).map(&:id).sort | |||||
query.filters = {filter_name => {:operator => '=', :values => [user2.id]}} | |||||
assert_equal [], find_issues_with_query(query).map(&:id).sort | |||||
end | |||||
with_current_user User.find(2) do | |||||
query.filters = {filter_name => {:operator => '=', :values => [user1.id]}} | |||||
assert_equal [], find_issues_with_query(query).map(&:id).sort | |||||
query.filters = {filter_name => {:operator => '=', :values => [user2.id]}} | |||||
assert_equal [2], find_issues_with_query(query).map(&:id).sort | |||||
end | |||||
end | |||||
def test_user_custom_field_filtered_on_me | def test_user_custom_field_filtered_on_me | ||||
User.current = User.find(2) | User.current = User.find(2) | ||||
cf = IssueCustomField.create!(:field_format => 'user', :is_for_all => true, :is_filter => true, :name => 'User custom field', :tracker_ids => [1]) | cf = IssueCustomField.create!(:field_format => 'user', :is_for_all => true, :is_filter => true, :name => 'User custom field', :tracker_ids => [1]) |