@@ -165,6 +165,14 class IssueQuery < Query | |||
|
165 | 165 | |
|
166 | 166 | add_available_filter "issue_id", :type => :integer, :label => :label_issue |
|
167 | 167 | |
|
168 | add_available_filter("updated_by", | |
|
169 | :type => :list, :values => lambda { author_values } | |
|
170 | ) | |
|
171 | ||
|
172 | add_available_filter("last_updated_by", | |
|
173 | :type => :list, :values => lambda { author_values } | |
|
174 | ) | |
|
175 | ||
|
168 | 176 | Tracker.disabled_core_fields(trackers).each {|field| |
|
169 | 177 | delete_available_filter field |
|
170 | 178 | } |
@@ -341,6 +349,27 class IssueQuery < Query | |||
|
341 | 349 | raise StatementInvalid.new(e.message) |
|
342 | 350 | end |
|
343 | 351 | |
|
352 | def sql_for_updated_by_field(field, operator, value) | |
|
353 | neg = (operator == '!' ? 'NOT' : '') | |
|
354 | subquery = "SELECT 1 FROM #{Journal.table_name}" + | |
|
355 | " WHERE #{Journal.table_name}.journalized_type='Issue' AND #{Journal.table_name}.journalized_id=#{Issue.table_name}.id" + | |
|
356 | " AND (#{sql_for_field field, '=', value, Journal.table_name, 'user_id'})" + | |
|
357 | " AND (#{Journal.visible_notes_condition(User.current, :skip_pre_condition => true)})" | |
|
358 | ||
|
359 | "#{neg} EXISTS (#{subquery})" | |
|
360 | end | |
|
361 | ||
|
362 | def sql_for_last_updated_by_field(field, operator, value) | |
|
363 | neg = (operator == '!' ? 'NOT' : '') | |
|
364 | subquery = "SELECT 1 FROM #{Journal.table_name} sj" + | |
|
365 | " WHERE sj.journalized_type='Issue' AND sj.journalized_id=#{Issue.table_name}.id AND (#{sql_for_field field, '=', value, 'sj', 'user_id'})" + | |
|
366 | " AND sj.id = (SELECT MAX(#{Journal.table_name}.id) FROM #{Journal.table_name}" + | |
|
367 | " WHERE #{Journal.table_name}.journalized_type='Issue' AND #{Journal.table_name}.journalized_id=#{Issue.table_name}.id" + | |
|
368 | " AND (#{Journal.visible_notes_condition(User.current, :skip_pre_condition => true)}))" | |
|
369 | ||
|
370 | "#{neg} EXISTS (#{subquery})" | |
|
371 | end | |
|
372 | ||
|
344 | 373 | def sql_for_watcher_id_field(field, operator, value) |
|
345 | 374 | db_table = Watcher.table_name |
|
346 | 375 | "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " + |
@@ -47,10 +47,11 class Journal < ActiveRecord::Base | |||
|
47 | 47 | |
|
48 | 48 | scope :visible, lambda {|*args| |
|
49 | 49 | user = args.shift || User.current |
|
50 | private_notes_condition = Project.allowed_to_condition(user, :view_private_notes, *args) | |
|
50 | options = args.shift || {} | |
|
51 | ||
|
51 | 52 | joins(:issue => :project). |
|
52 |
where(Issue.visible_condition(user, |
|
|
53 | where("(#{Journal.table_name}.private_notes = ? OR #{Journal.table_name}.user_id = ? OR (#{private_notes_condition}))", false, user.id) | |
|
53 | where(Issue.visible_condition(user, options)). | |
|
54 | where(Journal.visible_notes_condition(user, :skip_pre_condition => true)) | |
|
54 | 55 | } |
|
55 | 56 | |
|
56 | 57 | safe_attributes 'notes', |
@@ -58,6 +59,12 class Journal < ActiveRecord::Base | |||
|
58 | 59 | safe_attributes 'private_notes', |
|
59 | 60 | :if => lambda {|journal, user| user.allowed_to?(:set_notes_private, journal.project)} |
|
60 | 61 | |
|
62 | # Returns a SQL condition to filter out journals with notes that are not visible to user | |
|
63 | def self.visible_notes_condition(user=User.current, options={}) | |
|
64 | private_notes_permission = Project.allowed_to_condition(user, :view_private_notes, options) | |
|
65 | sanitize_sql_for_conditions(["(#{table_name}.private_notes = ? OR #{table_name}.user_id = ? OR (#{private_notes_permission}))", false, user.id]) | |
|
66 | end | |
|
67 | ||
|
61 | 68 | def initialize(*args) |
|
62 | 69 | super |
|
63 | 70 | if journalized |
@@ -173,13 +173,14 class Project < ActiveRecord::Base | |||
|
173 | 173 | # Returns a SQL conditions string used to find all projects for which +user+ has the given +permission+ |
|
174 | 174 | # |
|
175 | 175 | # Valid options: |
|
176 | # * :project => limit the condition to project | |
|
177 |
# * : |
|
|
178 |
# * : |
|
|
176 | # * :skip_pre_condition => true don't check that the module is enabled (eg. when the condition is already set elsewhere in the query) | |
|
177 | # * :project => project limit the condition to project | |
|
178 | # * :with_subprojects => true limit the condition to project and its subprojects | |
|
179 | # * :member => true limit the condition to the user projects | |
|
179 | 180 | def self.allowed_to_condition(user, permission, options={}) |
|
180 | 181 | perm = Redmine::AccessControl.permission(permission) |
|
181 | 182 | base_statement = (perm && perm.read? ? "#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED}" : "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}") |
|
182 | if perm && perm.project_module | |
|
183 | if !options[:skip_pre_condition] && perm && perm.project_module | |
|
183 | 184 | # If the permission belongs to a project module, make sure the module is enabled |
|
184 | 185 | base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')" |
|
185 | 186 | end |
@@ -802,7 +802,7 class Query < ActiveRecord::Base | |||
|
802 | 802 | operator = operator_for(field) |
|
803 | 803 | |
|
804 | 804 | # "me" value substitution |
|
805 | if %w(assigned_to_id author_id user_id watcher_id).include?(field) | |
|
805 | if %w(assigned_to_id author_id user_id watcher_id updated_by last_updated_by).include?(field) | |
|
806 | 806 | if v.delete("me") |
|
807 | 807 | if User.current.logged? |
|
808 | 808 | v.push(User.current.id.to_s) |
@@ -370,6 +370,8 en: | |||
|
370 | 370 | field_default_version: Default version |
|
371 | 371 | field_remote_ip: IP address |
|
372 | 372 | field_textarea_font: Font used for text areas |
|
373 | field_updated_by: Updated by | |
|
374 | field_last_updated_by: Last updated by | |
|
373 | 375 | |
|
374 | 376 | setting_app_title: Application title |
|
375 | 377 | setting_app_subtitle: Application subtitle |
@@ -382,6 +382,8 fr: | |||
|
382 | 382 | field_total_estimated_hours: Temps estimΓ© total |
|
383 | 383 | field_default_version: Version par dΓ©faut |
|
384 | 384 | field_textarea_font: Police utilisΓ©e pour les champs texte |
|
385 | field_updated_by: Mise Γ jour par | |
|
386 | field_last_updated_by: Dernière mise à jour par | |
|
385 | 387 | |
|
386 | 388 | setting_app_title: Titre de l'application |
|
387 | 389 | setting_app_subtitle: Sous-titre de l'application |
@@ -719,6 +719,93 class QueryTest < ActiveSupport::TestCase | |||
|
719 | 719 | end |
|
720 | 720 | end |
|
721 | 721 | |
|
722 | def test_filter_updated_by | |
|
723 | user = User.generate! | |
|
724 | Journal.create!(:user_id => user.id, :journalized => Issue.find(2), :notes => 'Notes') | |
|
725 | Journal.create!(:user_id => user.id, :journalized => Issue.find(3), :notes => 'Notes') | |
|
726 | Journal.create!(:user_id => 2, :journalized => Issue.find(3), :notes => 'Notes') | |
|
727 | ||
|
728 | query = IssueQuery.new(:name => '_') | |
|
729 | filter_name = "updated_by" | |
|
730 | assert_include filter_name, query.available_filters.keys | |
|
731 | ||
|
732 | query.filters = {filter_name => {:operator => '=', :values => [user.id]}} | |
|
733 | assert_equal [2, 3], find_issues_with_query(query).map(&:id).sort | |
|
734 | ||
|
735 | query.filters = {filter_name => {:operator => '!', :values => [user.id]}} | |
|
736 | assert_equal (Issue.ids.sort - [2, 3]), find_issues_with_query(query).map(&:id).sort | |
|
737 | end | |
|
738 | ||
|
739 | def test_filter_updated_by_should_ignore_private_notes_that_are_not_visible | |
|
740 | user = User.generate! | |
|
741 | Journal.create!(:user_id => user.id, :journalized => Issue.find(2), :notes => 'Notes', :private_notes => true) | |
|
742 | Journal.create!(:user_id => user.id, :journalized => Issue.find(3), :notes => 'Notes') | |
|
743 | ||
|
744 | query = IssueQuery.new(:name => '_') | |
|
745 | filter_name = "updated_by" | |
|
746 | assert_include filter_name, query.available_filters.keys | |
|
747 | ||
|
748 | with_current_user User.anonymous do | |
|
749 | query.filters = {filter_name => {:operator => '=', :values => [user.id]}} | |
|
750 | assert_equal [3], find_issues_with_query(query).map(&:id).sort | |
|
751 | end | |
|
752 | end | |
|
753 | ||
|
754 | def test_filter_updated_by_me | |
|
755 | user = User.generate! | |
|
756 | Journal.create!(:user_id => user.id, :journalized => Issue.find(2), :notes => 'Notes') | |
|
757 | ||
|
758 | with_current_user user do | |
|
759 | query = IssueQuery.new(:name => '_') | |
|
760 | filter_name = "updated_by" | |
|
761 | assert_include filter_name, query.available_filters.keys | |
|
762 | ||
|
763 | query.filters = {filter_name => {:operator => '=', :values => ['me']}} | |
|
764 | assert_equal [2], find_issues_with_query(query).map(&:id).sort | |
|
765 | end | |
|
766 | end | |
|
767 | ||
|
768 | def test_filter_last_updated_by | |
|
769 | user = User.generate! | |
|
770 | Journal.create!(:user_id => user.id, :journalized => Issue.find(2), :notes => 'Notes') | |
|
771 | Journal.create!(:user_id => user.id, :journalized => Issue.find(3), :notes => 'Notes') | |
|
772 | Journal.create!(:user_id => 2, :journalized => Issue.find(3), :notes => 'Notes') | |
|
773 | ||
|
774 | query = IssueQuery.new(:name => '_') | |
|
775 | filter_name = "last_updated_by" | |
|
776 | assert_include filter_name, query.available_filters.keys | |
|
777 | ||
|
778 | query.filters = {filter_name => {:operator => '=', :values => [user.id]}} | |
|
779 | assert_equal [2], find_issues_with_query(query).map(&:id).sort | |
|
780 | end | |
|
781 | ||
|
782 | def test_filter_last_updated_by_should_ignore_private_notes_that_are_not_visible | |
|
783 | user1 = User.generate! | |
|
784 | user2 = User.generate! | |
|
785 | Journal.create!(:user_id => user1.id, :journalized => Issue.find(2), :notes => 'Notes') | |
|
786 | Journal.create!(:user_id => user2.id, :journalized => Issue.find(2), :notes => 'Notes', :private_notes => true) | |
|
787 | ||
|
788 | query = IssueQuery.new(:name => '_') | |
|
789 | filter_name = "last_updated_by" | |
|
790 | assert_include filter_name, query.available_filters.keys | |
|
791 | ||
|
792 | with_current_user User.anonymous do | |
|
793 | query.filters = {filter_name => {:operator => '=', :values => [user1.id]}} | |
|
794 | assert_equal [2], find_issues_with_query(query).map(&:id).sort | |
|
795 | ||
|
796 | query.filters = {filter_name => {:operator => '=', :values => [user2.id]}} | |
|
797 | assert_equal [], find_issues_with_query(query).map(&:id).sort | |
|
798 | end | |
|
799 | ||
|
800 | with_current_user User.find(2) do | |
|
801 | query.filters = {filter_name => {:operator => '=', :values => [user1.id]}} | |
|
802 | assert_equal [], find_issues_with_query(query).map(&:id).sort | |
|
803 | ||
|
804 | query.filters = {filter_name => {:operator => '=', :values => [user2.id]}} | |
|
805 | assert_equal [2], find_issues_with_query(query).map(&:id).sort | |
|
806 | end | |
|
807 | end | |
|
808 | ||
|
722 | 809 | def test_user_custom_field_filtered_on_me |
|
723 | 810 | User.current = User.find(2) |
|
724 | 811 | cf = IssueCustomField.create!(:field_format => 'user', :is_for_all => true, :is_filter => true, :name => 'User custom field', :tracker_ids => [1]) |
General Comments 0
You need to be logged in to leave comments.
Login now