You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

query.rb 52KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684
  1. # frozen_string_literal: true
  2. # Redmine - project management software
  3. # Copyright (C) 2006-2023 Jean-Philippe Lang
  4. #
  5. # This program is free software; you can redistribute it and/or
  6. # modify it under the terms of the GNU General Public License
  7. # as published by the Free Software Foundation; either version 2
  8. # of the License, or (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, write to the Free Software
  17. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. class QueryColumn
  19. attr_accessor :name, :totalable, :default_order
  20. attr_writer :sortable, :groupable
  21. include Redmine::I18n
  22. def initialize(name, options={})
  23. self.name = name
  24. self.sortable = options[:sortable]
  25. self.groupable = options[:groupable] || false
  26. self.totalable = options[:totalable] || false
  27. self.default_order = options[:default_order]
  28. @inline = options.key?(:inline) ? options[:inline] : true
  29. @caption_key = options[:caption] || :"field_#{name}"
  30. @frozen = options[:frozen]
  31. end
  32. def caption
  33. case @caption_key
  34. when Symbol
  35. l(@caption_key)
  36. when Proc
  37. @caption_key.call
  38. else
  39. @caption_key
  40. end
  41. end
  42. def groupable?
  43. @groupable
  44. end
  45. # Returns true if the column is sortable, otherwise false
  46. def sortable?
  47. @sortable.present?
  48. end
  49. def sortable
  50. @sortable.is_a?(Proc) ? @sortable.call : @sortable
  51. end
  52. def inline?
  53. @inline
  54. end
  55. def frozen?
  56. @frozen
  57. end
  58. def value(object)
  59. object.send name
  60. end
  61. def value_object(object)
  62. object.send name
  63. end
  64. # Returns the group that object belongs to when grouping query results
  65. def group_value(object)
  66. value(object)
  67. end
  68. def css_classes
  69. name
  70. end
  71. def group_by_statement
  72. name.to_s
  73. end
  74. end
  75. class TimestampQueryColumn < QueryColumn
  76. def groupable?
  77. group_by_statement.present?
  78. end
  79. def group_by_statement
  80. Redmine::Database.timestamp_to_date(sortable, User.current.time_zone)
  81. end
  82. def group_value(object)
  83. if time = value(object)
  84. User.current.time_to_date(time)
  85. end
  86. end
  87. end
  88. class QueryAssociationColumn < QueryColumn
  89. def initialize(association, attribute, options={})
  90. @association = association
  91. @attribute = attribute
  92. name_with_assoc = :"#{association}.#{attribute}"
  93. super(name_with_assoc, options)
  94. end
  95. def value_object(object)
  96. assoc = object.send(@association)
  97. if assoc && assoc.visible?
  98. assoc.send @attribute
  99. end
  100. end
  101. def css_classes
  102. @css_classes ||= "#{@association}-#{@attribute}"
  103. end
  104. end
  105. class QueryCustomFieldColumn < QueryColumn
  106. def initialize(custom_field, options={})
  107. name = :"cf_#{custom_field.id}"
  108. super(
  109. name,
  110. :sortable => custom_field.order_statement || false,
  111. :totalable => options.key?(:totalable) ? !!options[:totalable] : custom_field.totalable?,
  112. :inline => custom_field.full_width_layout? ? false : true
  113. )
  114. @cf = custom_field
  115. end
  116. def groupable?
  117. group_by_statement.present?
  118. end
  119. def group_by_statement
  120. @cf.group_statement
  121. end
  122. def caption
  123. @cf.name
  124. end
  125. def custom_field
  126. @cf
  127. end
  128. def value_object(object)
  129. project = object.project if object.respond_to?(:project)
  130. if custom_field.visible_by?(project, User.current)
  131. cv = object.custom_values.select {|v| v.custom_field_id == @cf.id}
  132. cv.size > 1 ? cv.sort_by {|e| e.value.to_s} : cv.first
  133. else
  134. nil
  135. end
  136. end
  137. def value(object)
  138. raw = value_object(object)
  139. if raw.is_a?(Array)
  140. raw.map {|r| @cf.cast_value(r.value)}
  141. elsif raw
  142. @cf.cast_value(raw.value)
  143. else
  144. nil
  145. end
  146. end
  147. def css_classes
  148. @css_classes ||= "#{name} #{@cf.field_format}"
  149. end
  150. end
  151. class QueryAssociationCustomFieldColumn < QueryCustomFieldColumn
  152. def initialize(association, custom_field, options={})
  153. super(custom_field, options)
  154. self.name = :"#{association}.cf_#{custom_field.id}"
  155. # TODO: support sorting by association custom field
  156. self.sortable = false
  157. self.groupable = false
  158. @association = association
  159. end
  160. def value_object(object)
  161. assoc = object.send(@association)
  162. if assoc && assoc.visible?
  163. super(assoc)
  164. end
  165. end
  166. def css_classes
  167. @css_classes ||= "#{@association}_cf_#{@cf.id} #{@cf.field_format}"
  168. end
  169. # TODO: support grouping by association custom field
  170. def groupable?
  171. false
  172. end
  173. end
  174. class QueryFilter
  175. include Redmine::I18n
  176. def initialize(field, options)
  177. @field = field.to_s
  178. @options = options
  179. @options[:name] ||= l(options[:label] || "field_#{field}".delete_suffix('_id'))
  180. # Consider filters with a Proc for values as remote by default
  181. @remote = options.key?(:remote) ? options[:remote] : options[:values].is_a?(Proc)
  182. end
  183. def [](arg)
  184. if arg == :values
  185. values
  186. else
  187. @options[arg]
  188. end
  189. end
  190. def values
  191. @values ||= begin
  192. values = @options[:values]
  193. if values.is_a?(Proc)
  194. values = values.call
  195. end
  196. values
  197. end
  198. end
  199. def remote
  200. @remote
  201. end
  202. end
  203. class Query < ApplicationRecord
  204. class StatementInvalid < ::ActiveRecord::StatementInvalid
  205. end
  206. class QueryError < StandardError
  207. end
  208. include Redmine::SubclassFactory
  209. VISIBILITY_PRIVATE = 0
  210. VISIBILITY_ROLES = 1
  211. VISIBILITY_PUBLIC = 2
  212. belongs_to :project
  213. belongs_to :user
  214. has_and_belongs_to_many :roles, :join_table => "#{table_name_prefix}queries_roles#{table_name_suffix}", :foreign_key => "query_id"
  215. serialize :filters
  216. serialize :column_names
  217. serialize :sort_criteria, type: Array
  218. serialize :options, type: Hash
  219. validates_presence_of :name
  220. validates_length_of :name, :maximum => 255
  221. validates :visibility, :inclusion => {:in => [VISIBILITY_PUBLIC, VISIBILITY_ROLES, VISIBILITY_PRIVATE]}
  222. validate :validate_query_filters
  223. validate do |query|
  224. errors.add(:base, l(:label_role_plural) + ' ' + l('activerecord.errors.messages.blank')) if query.visibility == VISIBILITY_ROLES && roles.blank?
  225. end
  226. after_save do |query|
  227. if query.saved_change_to_visibility? && query.visibility != VISIBILITY_ROLES
  228. query.roles.clear
  229. end
  230. end
  231. class_attribute :operators
  232. self.operators = {
  233. "=" => :label_equals,
  234. "!" => :label_not_equals,
  235. "o" => :label_open_issues,
  236. "c" => :label_closed_issues,
  237. "!*" => :label_none,
  238. "*" => :label_any,
  239. ">=" => :label_greater_or_equal,
  240. "<=" => :label_less_or_equal,
  241. "><" => :label_between,
  242. "<t+" => :label_in_less_than,
  243. ">t+" => :label_in_more_than,
  244. "><t+"=> :label_in_the_next_days,
  245. "t+" => :label_in,
  246. "nd" => :label_tomorrow,
  247. "t" => :label_today,
  248. "ld" => :label_yesterday,
  249. "nw" => :label_next_week,
  250. "w" => :label_this_week,
  251. "lw" => :label_last_week,
  252. "l2w" => [:label_last_n_weeks, {:count => 2}],
  253. "nm" => :label_next_month,
  254. "m" => :label_this_month,
  255. "lm" => :label_last_month,
  256. "y" => :label_this_year,
  257. ">t-" => :label_less_than_ago,
  258. "<t-" => :label_more_than_ago,
  259. "><t-"=> :label_in_the_past_days,
  260. "t-" => :label_ago,
  261. "~" => :label_contains,
  262. "!~" => :label_not_contains,
  263. "*~" => :label_contains_any_of,
  264. "^" => :label_starts_with,
  265. "$" => :label_ends_with,
  266. "=p" => :label_any_issues_in_project,
  267. "=!p" => :label_any_issues_not_in_project,
  268. "!p" => :label_no_issues_in_project,
  269. "*o" => :label_any_open_issues,
  270. "!o" => :label_no_open_issues,
  271. "ev" => :label_has_been, # "ev" stands for "ever"
  272. "!ev" => :label_has_never_been,
  273. "cf" => :label_changed_from
  274. }
  275. class_attribute :operators_by_filter_type
  276. self.operators_by_filter_type = {
  277. :list => [ "=", "!" ],
  278. :list_with_history => [ "=", "!", "ev", "!ev", "cf" ],
  279. :list_status => [ "o", "=", "!", "ev", "!ev", "cf", "c", "*" ],
  280. :list_optional => [ "=", "!", "!*", "*" ],
  281. :list_optional_with_history => [ "=", "!", "ev", "!ev", "cf", "!*", "*" ],
  282. :list_subprojects => [ "*", "!*", "=", "!" ],
  283. :date => [ "=", ">=", "<=", "><", "<t+", ">t+", "><t+", "t+", "nd", "t", "ld", "nw", "w", "lw", "l2w", "nm", "m", "lm", "y", ">t-", "<t-", "><t-", "t-", "!*", "*" ],
  284. :date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "><t-", "t-", "t", "ld", "w", "lw", "l2w", "m", "lm", "y", "!*", "*" ],
  285. :string => [ "~", "*~", "=", "!~", "!", "^", "$", "!*", "*" ],
  286. :text => [ "~", "*~", "!~", "^", "$", "!*", "*" ],
  287. :search => [ "~", "*~", "!~" ],
  288. :integer => [ "=", ">=", "<=", "><", "!*", "*" ],
  289. :float => [ "=", ">=", "<=", "><", "!*", "*" ],
  290. :relation => ["=", "!", "=p", "=!p", "!p", "*o", "!o", "!*", "*"],
  291. :tree => ["=", "~", "!*", "*"]
  292. }
  293. class_attribute :available_columns
  294. self.available_columns = []
  295. class_attribute :queried_class
  296. # Permission required to view the queries, set on subclasses.
  297. class_attribute :view_permission
  298. # Scope of queries that are global or on the given project
  299. scope :global_or_on_project, (lambda do |project|
  300. where(:project_id => (project.nil? ? nil : [nil, project.id]))
  301. end)
  302. scope :sorted, lambda {order(:name, :id)}
  303. scope :only_public, ->{ where(visibility: VISIBILITY_PUBLIC) }
  304. # to be implemented in subclasses that have a way to determine a default
  305. # query for the given options
  306. def self.default(**_)
  307. nil
  308. end
  309. # Scope of visible queries, can be used from subclasses only.
  310. # Unlike other visible scopes, a class methods is used as it
  311. # let handle inheritance more nicely than scope DSL.
  312. def self.visible(*args)
  313. if self == ::Query
  314. # Visibility depends on permissions for each subclass,
  315. # raise an error if the scope is called from Query (eg. Query.visible)
  316. raise "Cannot call .visible scope from the base Query class, but from subclasses only."
  317. end
  318. user = args.shift || User.current
  319. base = Project.allowed_to_condition(user, view_permission, *args)
  320. scope = joins("LEFT OUTER JOIN #{Project.table_name} ON #{table_name}.project_id = #{Project.table_name}.id").
  321. where("#{table_name}.project_id IS NULL OR (#{base})")
  322. if user.admin?
  323. scope.where("#{table_name}.visibility <> ? OR #{table_name}.user_id = ?", VISIBILITY_PRIVATE, user.id)
  324. elsif user.memberships.any?
  325. scope.where(
  326. "#{table_name}.visibility = ?" +
  327. " OR (#{table_name}.visibility = ? AND #{table_name}.id IN (" +
  328. "SELECT DISTINCT q.id FROM #{table_name} q" +
  329. " INNER JOIN #{table_name_prefix}queries_roles#{table_name_suffix} qr on qr.query_id = q.id" +
  330. " INNER JOIN #{MemberRole.table_name} mr ON mr.role_id = qr.role_id" +
  331. " INNER JOIN #{Member.table_name} m ON m.id = mr.member_id AND m.user_id = ?" +
  332. " INNER JOIN #{Project.table_name} p ON p.id = m.project_id AND p.status <> ?" +
  333. " WHERE q.project_id IS NULL OR q.project_id = m.project_id))" +
  334. " OR #{table_name}.user_id = ?",
  335. VISIBILITY_PUBLIC, VISIBILITY_ROLES, user.id, Project::STATUS_ARCHIVED, user.id)
  336. elsif user.logged?
  337. scope.where("#{table_name}.visibility = ? OR #{table_name}.user_id = ?", VISIBILITY_PUBLIC, user.id)
  338. else
  339. scope.where("#{table_name}.visibility = ?", VISIBILITY_PUBLIC)
  340. end
  341. end
  342. # Returns true if the query is visible to +user+ or the current user.
  343. def visible?(user=User.current)
  344. return true if user.admin?
  345. return false unless project.nil? || user.allowed_to?(self.class.view_permission, project)
  346. case visibility
  347. when VISIBILITY_PUBLIC
  348. true
  349. when VISIBILITY_ROLES
  350. if project
  351. (user.roles_for_project(project) & roles).any?
  352. else
  353. user.memberships.joins(:member_roles).where(:member_roles => {:role_id => roles.map(&:id)}).any?
  354. end
  355. else
  356. user == self.user
  357. end
  358. end
  359. def is_private?
  360. visibility == VISIBILITY_PRIVATE
  361. end
  362. def is_public?
  363. !is_private?
  364. end
  365. # Returns true if the query is available for all projects
  366. def is_global?
  367. new_record? ? project_id.nil? : project_id_in_database.nil?
  368. end
  369. def queried_table_name
  370. @queried_table_name ||= self.class.queried_class.table_name
  371. end
  372. # Builds the query from the given params
  373. def build_from_params(params, defaults={})
  374. if params[:fields] || params[:f]
  375. self.filters = {}
  376. add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v])
  377. else
  378. available_filters.each_key do |field|
  379. add_short_filter(field, params[field]) if params[field]
  380. end
  381. end
  382. query_params = params[:query] || defaults || {}
  383. self.group_by = params[:group_by] || query_params[:group_by] || self.group_by
  384. self.column_names = params[:c] || query_params[:column_names] || self.column_names
  385. self.totalable_names = params[:t] || query_params[:totalable_names] || self.totalable_names
  386. self.sort_criteria = params[:sort] || query_params[:sort_criteria] || self.sort_criteria
  387. self.display_type = params[:display_type] || query_params[:display_type] || self.display_type
  388. self
  389. end
  390. # Builds a new query from the given params and attributes
  391. def self.build_from_params(params, attributes={})
  392. new(attributes).build_from_params(params)
  393. end
  394. def as_params
  395. if new_record?
  396. params = {}
  397. filters.each do |field, options|
  398. params[:f] ||= []
  399. params[:f] << field
  400. params[:op] ||= {}
  401. params[:op][field] = options[:operator]
  402. params[:v] ||= {}
  403. params[:v][field] = options[:values]
  404. end
  405. params[:c] = column_names
  406. params[:group_by] = group_by.to_s if group_by.present?
  407. params[:t] = totalable_names.map(&:to_s) if totalable_names.any?
  408. params[:sort] = sort_criteria.to_param
  409. params[:set_filter] = 1
  410. params
  411. else
  412. {:query_id => id}
  413. end
  414. end
  415. def validate_query_filters
  416. filters.each_key do |field|
  417. if values_for(field)
  418. case type_for(field)
  419. when :integer
  420. if values_for(field).detect {|v| v.present? && !/\A[+-]?\d+(,[+-]?\d+)*\z/.match?(v)}
  421. add_filter_error(field, :invalid)
  422. end
  423. when :float
  424. if values_for(field).detect {|v| v.present? && !/\A[+-]?\d+(\.\d*)?\z/.match?(v)}
  425. add_filter_error(field, :invalid)
  426. end
  427. when :date, :date_past
  428. case operator_for(field)
  429. when "=", ">=", "<=", "><"
  430. if values_for(field).detect do |v|
  431. v.present? &&
  432. (!/\A\d{4}-\d{2}-\d{2}(T\d{2}((:)?\d{2}){0,2}(Z|\d{2}:?\d{2})?)?\z/.match?(v) ||
  433. parse_date(v).nil?)
  434. end
  435. add_filter_error(field, :invalid)
  436. end
  437. when ">t-", "<t-", "t-", ">t+", "<t+", "t+", "><t+", "><t-"
  438. if values_for(field).detect {|v| v.present? && !/^\d+$/.match?(v)}
  439. add_filter_error(field, :invalid)
  440. end
  441. end
  442. end
  443. end
  444. add_filter_error(field, :blank) unless
  445. # filter requires one or more values
  446. (values_for(field) and values_for(field).first.present?) or
  447. # filter doesn't require any value
  448. ["o", "c", "!*", "*", "nd", "t", "ld", "nw", "w", "lw", "l2w", "nm", "m", "lm", "y", "*o", "!o"].include? operator_for(field)
  449. end if filters
  450. end
  451. def add_filter_error(field, message)
  452. m = label_for(field) + " " + l(message, :scope => 'activerecord.errors.messages')
  453. errors.add(:base, m)
  454. end
  455. def editable_by?(user)
  456. return false unless user
  457. # Admin can edit them all and regular users can edit their private queries
  458. return true if user.admin? || (is_private? && self.user_id == user.id)
  459. # Members can not edit public queries that are for all project (only admin is allowed to)
  460. is_public? && !is_global? && user.allowed_to?(:manage_public_queries, project)
  461. end
  462. def trackers
  463. @trackers ||= (project.nil? ? Tracker.all : project.rolled_up_trackers).visible.sorted
  464. end
  465. # Returns a hash of localized labels for all filter operators
  466. def self.operators_labels
  467. operators.inject({}) {|h, operator| h[operator.first] = l(*operator.last); h}
  468. end
  469. # Returns a representation of the available filters for JSON serialization
  470. def available_filters_as_json
  471. json = {}
  472. available_filters.each do |field, filter|
  473. options = {:type => filter[:type], :name => filter[:name]}
  474. options[:remote] = true if filter.remote
  475. if has_filter?(field) || !filter.remote
  476. options[:values] = filter.values
  477. if options[:values] && values_for(field)
  478. missing = Array(values_for(field)).select(&:present?) - options[:values].pluck(1)
  479. if missing.any? && respond_to?(method = "find_#{field}_filter_values")
  480. options[:values] += send(method, missing)
  481. end
  482. end
  483. end
  484. json[field] = options.stringify_keys
  485. end
  486. json
  487. end
  488. def all_projects
  489. @all_projects ||= Project.visible.to_a
  490. end
  491. def all_projects_values
  492. return @all_projects_values if @all_projects_values
  493. values = []
  494. Project.project_tree(all_projects) do |p, level|
  495. prefix = (level > 0 ? ('--' * level + ' ') : '')
  496. values << ["#{prefix}#{p.name}", p.id.to_s]
  497. end
  498. @all_projects_values = values
  499. end
  500. def project_values
  501. project_values = []
  502. if User.current.logged?
  503. project_values << ["<< #{l(:label_my_projects).downcase} >>", "mine"] if User.current.memberships.any?
  504. project_values << ["<< #{l(:label_my_bookmarks).downcase} >>", "bookmarks"] if User.current.bookmarked_project_ids.any?
  505. end
  506. project_values += all_projects_values
  507. project_values
  508. end
  509. def subproject_values
  510. project.descendants.visible.pluck(:name, :id).map {|name, id| [name, id.to_s]}
  511. end
  512. def principals
  513. @principal ||= begin
  514. principals = []
  515. if project
  516. principals += Principal.member_of(project).visible
  517. unless project.leaf?
  518. principals += Principal.member_of(project.descendants.visible).visible
  519. end
  520. else
  521. principals += Principal.member_of(all_projects).visible
  522. end
  523. principals.uniq!
  524. principals.sort!
  525. principals.reject! {|p| p.is_a?(GroupBuiltin)}
  526. principals
  527. end
  528. end
  529. def users
  530. principals.select {|p| p.is_a?(User)}
  531. end
  532. def author_values
  533. author_values = []
  534. author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
  535. author_values +=
  536. users.sort_by(&:status).
  537. collect{|s| [s.name, s.id.to_s, l("status_#{User::LABEL_BY_STATUS[s.status]}")]}
  538. author_values << [l(:label_user_anonymous), User.anonymous.id.to_s]
  539. author_values
  540. end
  541. def assigned_to_values
  542. assigned_to_values = []
  543. assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
  544. assigned_to_values +=
  545. (Setting.issue_group_assignment? ? principals : users).sort_by(&:status).
  546. collect{|s| [s.name, s.id.to_s, l("status_#{User::LABEL_BY_STATUS[s.status]}")]}
  547. assigned_to_values
  548. end
  549. def fixed_version_values
  550. versions = []
  551. if project
  552. versions = project.shared_versions.to_a
  553. else
  554. versions = Version.visible.to_a
  555. end
  556. Version.sort_by_status(versions).
  557. collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s, l("version_status_#{s.status}")]}
  558. end
  559. # Returns a scope of issue statuses that are available as columns for filters
  560. def issue_statuses_values
  561. if project
  562. statuses = project.rolled_up_statuses
  563. else
  564. statuses = IssueStatus.all.sorted
  565. end
  566. statuses.pluck(:name, :id).map {|name, id| [name, id.to_s]}
  567. end
  568. def watcher_values
  569. watcher_values = [["<< #{l(:label_me)} >>", "me"]]
  570. if User.current.allowed_to?(:view_issue_watchers, self.project, global: true)
  571. watcher_values +=
  572. principals.sort_by(&:status).
  573. collect{|s| [s.name, s.id.to_s, l("status_#{User::LABEL_BY_STATUS[s.status]}")]}
  574. end
  575. watcher_values
  576. end
  577. # Returns a scope of issue custom fields that are available as columns or filters
  578. def issue_custom_fields
  579. if project
  580. project.rolled_up_custom_fields
  581. else
  582. IssueCustomField.sorted
  583. end
  584. end
  585. # Returns a scope of project custom fields that are available as columns or filters
  586. def project_custom_fields
  587. ProjectCustomField.sorted
  588. end
  589. # Returns a scope of time entry custom fields that are available as columns or filters
  590. def time_entry_custom_fields
  591. TimeEntryCustomField.sorted
  592. end
  593. # Returns a scope of project statuses that are available as columns or filters
  594. def project_statuses_values
  595. [
  596. [l(:project_status_active), "#{Project::STATUS_ACTIVE}"],
  597. [l(:project_status_closed), "#{Project::STATUS_CLOSED}"]
  598. ]
  599. end
  600. # Adds available filters
  601. def initialize_available_filters
  602. # implemented by sub-classes
  603. end
  604. protected :initialize_available_filters
  605. # Adds an available filter
  606. def add_available_filter(field, options)
  607. @available_filters ||= ActiveSupport::OrderedHash.new
  608. @available_filters[field] = QueryFilter.new(field, options)
  609. @available_filters
  610. end
  611. # Removes an available filter
  612. def delete_available_filter(field)
  613. if @available_filters
  614. @available_filters.delete(field)
  615. end
  616. end
  617. # Return a hash of available filters
  618. def available_filters
  619. unless @available_filters
  620. initialize_available_filters
  621. @available_filters ||= {}
  622. end
  623. @available_filters
  624. end
  625. def add_filter(field, operator, values=nil)
  626. # values must be an array
  627. return unless values.nil? || values.is_a?(Array)
  628. # check if field is defined as an available filter
  629. if available_filters.has_key? field
  630. filters[field] = {:operator => operator, :values => (values || [''])}
  631. end
  632. end
  633. def add_short_filter(field, expression)
  634. return unless expression && available_filters.has_key?(field)
  635. field_type = available_filters[field][:type]
  636. operators_by_filter_type[field_type].sort.reverse.detect do |operator|
  637. next unless expression =~ /^#{Regexp.escape(operator)}(.*)$/
  638. values = $1
  639. add_filter field, operator, values.present? ? values.split('|') : ['']
  640. end || add_filter(field, '=', expression.to_s.split('|'))
  641. end
  642. # Add multiple filters using +add_filter+
  643. def add_filters(fields, operators, values)
  644. if fields.present? && operators.present?
  645. fields.each do |field|
  646. add_filter(field, operators[field], values && values[field])
  647. end
  648. end
  649. end
  650. def has_filter?(field)
  651. filters and filters[field]
  652. end
  653. def type_for(field)
  654. available_filters[field][:type] if available_filters.has_key?(field)
  655. end
  656. def operator_for(field)
  657. has_filter?(field) ? filters[field][:operator] : nil
  658. end
  659. def values_for(field)
  660. has_filter?(field) ? filters[field][:values] : nil
  661. end
  662. def value_for(field, index=0)
  663. (values_for(field) || [])[index]
  664. end
  665. def label_for(field)
  666. label = available_filters[field][:name] if available_filters.has_key?(field)
  667. label ||= queried_class.human_attribute_name(field, :default => field)
  668. end
  669. def self.add_available_column(column)
  670. self.available_columns << (column) if column.is_a?(QueryColumn)
  671. end
  672. # Returns an array of columns that can be used to group the results
  673. def groupable_columns
  674. available_columns.select(&:groupable?)
  675. end
  676. # Returns a Hash of columns and the key for sorting
  677. def sortable_columns
  678. available_columns.inject({}) do |h, column|
  679. h[column.name.to_s] = column.sortable
  680. h
  681. end
  682. end
  683. def columns
  684. return [] if available_columns.empty?
  685. # preserve the column_names order
  686. cols = (has_default_columns? ? default_columns_names : column_names).filter_map do |name|
  687. available_columns.find {|col| col.name == name}
  688. end
  689. available_columns.select(&:frozen?) | cols
  690. end
  691. def inline_columns
  692. columns.select(&:inline?)
  693. end
  694. def block_columns
  695. columns.reject(&:inline?)
  696. end
  697. def available_inline_columns
  698. available_columns.select(&:inline?)
  699. end
  700. def available_block_columns
  701. available_columns.reject(&:inline?)
  702. end
  703. def available_totalable_columns
  704. available_columns.select(&:totalable)
  705. end
  706. def default_columns_names
  707. []
  708. end
  709. def default_totalable_names
  710. []
  711. end
  712. def default_display_type
  713. self.available_display_types.first
  714. end
  715. def column_names=(names)
  716. if names
  717. names = names.select {|n| n.is_a?(Symbol) || n.present?}
  718. names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym}
  719. if names.delete(:all_inline)
  720. names = available_inline_columns.map(&:name) | names
  721. end
  722. # Set column_names to nil if default columns
  723. if names == default_columns_names
  724. names = nil
  725. end
  726. end
  727. write_attribute(:column_names, names)
  728. end
  729. def has_column?(column)
  730. name = column.is_a?(QueryColumn) ? column.name : column
  731. columns.detect {|c| c.name == name}
  732. end
  733. def has_custom_field_column?
  734. columns.any?(QueryCustomFieldColumn)
  735. end
  736. def has_default_columns?
  737. column_names.nil? || column_names.empty?
  738. end
  739. def totalable_columns
  740. names = totalable_names
  741. available_totalable_columns.select {|column| names.include?(column.name)}
  742. end
  743. def totalable_names=(names)
  744. if names
  745. names = names.select(&:present?).map {|n| n.is_a?(Symbol) ? n : n.to_sym}
  746. end
  747. options[:totalable_names] = names
  748. end
  749. def totalable_names
  750. options[:totalable_names] || default_totalable_names || []
  751. end
  752. def default_sort_criteria
  753. []
  754. end
  755. def sort_criteria=(arg)
  756. c = Redmine::SortCriteria.new(arg)
  757. write_attribute(:sort_criteria, c.to_a)
  758. c
  759. end
  760. def sort_criteria
  761. c = read_attribute(:sort_criteria)
  762. if c.blank?
  763. c = default_sort_criteria
  764. end
  765. Redmine::SortCriteria.new(c)
  766. end
  767. def sort_criteria_key(index)
  768. sort_criteria[index].try(:first)
  769. end
  770. def sort_criteria_order(index)
  771. sort_criteria[index].try(:last)
  772. end
  773. def sort_clause
  774. if clause = sort_criteria.sort_clause(sortable_columns)
  775. clause.map {|c| Arel.sql c}
  776. end
  777. end
  778. # Returns the SQL sort order that should be prepended for grouping
  779. def group_by_sort_order
  780. if column = group_by_column
  781. order = (sort_criteria.order_for(column.name) || column.default_order || 'asc').try(:upcase)
  782. column_sortable = column.sortable
  783. if column.is_a?(TimestampQueryColumn)
  784. column_sortable = Redmine::Database.timestamp_to_date(column.sortable, User.current.time_zone)
  785. end
  786. Array(column_sortable).map {|s| Arel.sql("#{s} #{order}")}
  787. end
  788. end
  789. # Returns true if the query is a grouped query
  790. def grouped?
  791. !group_by_column.nil?
  792. end
  793. def group_by_column
  794. groupable_columns.detect {|c| c.groupable? && c.name.to_s == group_by}
  795. end
  796. def group_by_statement
  797. group_by_column.try(:group_by_statement)
  798. end
  799. def project_statement
  800. project_clauses = []
  801. subprojects_ids = []
  802. subprojects_ids = project.descendants.where.not(status: Project::STATUS_ARCHIVED).ids if project
  803. if subprojects_ids.any?
  804. if has_filter?("subproject_id")
  805. case operator_for("subproject_id")
  806. when '='
  807. # include the selected subprojects
  808. ids = [project.id] + values_for("subproject_id").map(&:to_i)
  809. project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
  810. when '!'
  811. # exclude the selected subprojects
  812. ids = [project.id] + subprojects_ids - values_for("subproject_id").map(&:to_i)
  813. project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
  814. when '!*'
  815. # main project only
  816. project_clauses << "#{Project.table_name}.id = %d" % project.id
  817. else
  818. # all subprojects
  819. project_clauses << "#{Project.table_name}.lft >= #{project.lft} AND #{Project.table_name}.rgt <= #{project.rgt}"
  820. end
  821. elsif Setting.display_subprojects_issues?
  822. project_clauses << "#{Project.table_name}.lft >= #{project.lft} AND #{Project.table_name}.rgt <= #{project.rgt}"
  823. else
  824. project_clauses << "#{Project.table_name}.id = %d" % project.id
  825. end
  826. elsif project
  827. project_clauses << "#{Project.table_name}.id = %d" % project.id
  828. end
  829. project_clauses.any? ? project_clauses.join(' AND ') : nil
  830. end
  831. def statement
  832. # filters clauses
  833. filters_clauses = []
  834. filters.each_key do |field|
  835. next if field == "subproject_id"
  836. v = values_for(field).clone
  837. next unless v and !v.empty?
  838. operator = operator_for(field)
  839. # "me" value substitution
  840. if %w(assigned_to_id author_id user_id watcher_id updated_by last_updated_by).include?(field)
  841. if v.delete("me")
  842. if User.current.logged?
  843. v.push(User.current.id.to_s)
  844. v += User.current.group_ids.map(&:to_s) if %w(assigned_to_id watcher_id).include?(field)
  845. else
  846. v.push("0")
  847. end
  848. end
  849. end
  850. if field == 'project_id' || (self.type == 'ProjectQuery' && %w[id parent_id].include?(field))
  851. if v.delete('mine')
  852. v += User.current.memberships.pluck(:project_id).map(&:to_s)
  853. end
  854. if v.delete('bookmarks')
  855. v += User.current.bookmarked_project_ids
  856. end
  857. end
  858. if field =~ /^cf_(\d+)\.cf_(\d+)$/
  859. filters_clauses << sql_for_chained_custom_field(field, operator, v, $1, $2)
  860. elsif field =~ /cf_(\d+)$/
  861. # custom field
  862. filters_clauses << sql_for_custom_field(field, operator, v, $1)
  863. elsif field =~ /^cf_(\d+)\.(.+)$/
  864. filters_clauses << sql_for_custom_field_attribute(field, operator, v, $1, $2)
  865. elsif respond_to?(method = "sql_for_#{field.tr('.', '_')}_field")
  866. # specific statement
  867. filters_clauses << send(method, field, operator, v)
  868. else
  869. # regular field
  870. filters_clauses << '(' + sql_for_field(field, operator, v, queried_table_name, field) + ')'
  871. end
  872. end if filters and valid?
  873. if (c = group_by_column) && c.is_a?(QueryCustomFieldColumn)
  874. # Excludes results for which the grouped custom field is not visible
  875. filters_clauses << c.custom_field.visibility_by_project_condition
  876. end
  877. filters_clauses << project_statement
  878. filters_clauses.reject!(&:blank?)
  879. filters_clauses.any? ? filters_clauses.join(' AND ') : nil
  880. end
  881. # Returns the result count by group or nil if query is not grouped
  882. def result_count_by_group
  883. grouped_query do |scope|
  884. scope.count
  885. end
  886. end
  887. # Returns the sum of values for the given column
  888. def total_for(column)
  889. total_with_scope(column, base_scope)
  890. end
  891. # Returns a hash of the sum of the given column for each group,
  892. # or nil if the query is not grouped
  893. def total_by_group_for(column)
  894. grouped_query do |scope|
  895. total_with_scope(column, scope)
  896. end
  897. end
  898. def totals
  899. totals = totalable_columns.map {|column| [column, total_for(column)]}
  900. yield totals if block_given?
  901. totals
  902. end
  903. def totals_by_group
  904. totals = totalable_columns.map {|column| [column, total_by_group_for(column)]}
  905. yield totals if block_given?
  906. totals
  907. end
  908. def css_classes
  909. s = sort_criteria.first
  910. if s.present?
  911. key, asc = s
  912. "sort-by-#{key.to_s.dasherize} sort-#{asc}"
  913. end
  914. end
  915. def display_type
  916. options[:display_type] || self.default_display_type
  917. end
  918. def display_type=(type)
  919. unless type && self.available_display_types.include?(type)
  920. type = self.available_display_types.first
  921. end
  922. options[:display_type] = type
  923. end
  924. def available_display_types
  925. ['list']
  926. end
  927. private
  928. def grouped_query(&block)
  929. r = nil
  930. if grouped?
  931. r = yield base_group_scope
  932. c = group_by_column
  933. if c.is_a?(QueryCustomFieldColumn)
  934. r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h}
  935. end
  936. end
  937. r
  938. rescue ::ActiveRecord::StatementInvalid => e
  939. raise StatementInvalid.new(e.message)
  940. end
  941. def total_with_scope(column, scope)
  942. unless column.is_a?(QueryColumn)
  943. column = column.to_sym
  944. column = available_totalable_columns.detect {|c| c.name == column}
  945. end
  946. if column.is_a?(QueryCustomFieldColumn)
  947. custom_field = column.custom_field
  948. send :total_for_custom_field, custom_field, scope
  949. else
  950. send :"total_for_#{column.name}", scope
  951. end
  952. rescue ::ActiveRecord::StatementInvalid => e
  953. raise StatementInvalid.new(e.message)
  954. end
  955. def base_scope
  956. raise "unimplemented"
  957. end
  958. def base_group_scope
  959. base_scope.
  960. joins(joins_for_order_statement(group_by_statement)).
  961. group(group_by_statement)
  962. end
  963. def total_for_custom_field(custom_field, scope, &block)
  964. total = custom_field.format.total_for_scope(custom_field, scope)
  965. total = map_total(total) {|t| custom_field.format.cast_total_value(custom_field, t)}
  966. total
  967. end
  968. def map_total(total, &block)
  969. if total.is_a?(Hash)
  970. total.each_key {|k| total[k] = yield total[k]}
  971. else
  972. total = yield total
  973. end
  974. total
  975. end
  976. def sql_for_custom_field(field, operator, value, custom_field_id)
  977. db_table = CustomValue.table_name
  978. db_field = 'value'
  979. filter = @available_filters[field]
  980. return nil unless filter
  981. if filter[:field].format.target_class && filter[:field].format.target_class <= User
  982. if value.delete('me')
  983. value.push User.current.id.to_s
  984. end
  985. end
  986. not_in = nil
  987. if operator == '!'
  988. # Makes ! operator work for custom fields with multiple values
  989. operator = '='
  990. not_in = 'NOT'
  991. end
  992. customized_key = "id"
  993. customized_class = queried_class
  994. if field =~ /^(.+)\.cf_/
  995. assoc = $1
  996. customized_key = "#{assoc}_id"
  997. customized_class = queried_class.reflect_on_association(assoc.to_sym).klass.base_class rescue nil
  998. raise QueryError, "Unknown #{queried_class.name} association #{assoc}" unless customized_class
  999. end
  1000. where = sql_for_field(field, operator, value, db_table, db_field, true)
  1001. if /[<>]/.match?(operator)
  1002. where = "(#{where}) AND #{db_table}.#{db_field} <> ''"
  1003. end
  1004. "#{not_in} EXISTS (" \
  1005. "SELECT ct.id FROM #{customized_class.table_name} ct" \
  1006. " LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='#{customized_class}'" \
  1007. " AND #{db_table}.customized_id=ct.id" \
  1008. " AND #{db_table}.custom_field_id=#{custom_field_id}" \
  1009. " WHERE #{queried_table_name}.#{customized_key} = ct.id AND " \
  1010. " (#{where}) AND (#{filter[:field].visibility_by_project_condition}))"
  1011. end
  1012. def sql_for_chained_custom_field(field, operator, value, custom_field_id, chained_custom_field_id)
  1013. not_in = nil
  1014. if operator == '!'
  1015. # Makes ! operator work for custom fields with multiple values
  1016. operator = '='
  1017. not_in = 'NOT'
  1018. end
  1019. filter = available_filters[field]
  1020. target_class = filter[:through].format.target_class.base_class
  1021. "#{queried_table_name}.id #{not_in} IN (" +
  1022. "SELECT customized_id FROM #{CustomValue.table_name}" +
  1023. " WHERE customized_type='#{queried_class}' AND custom_field_id=#{custom_field_id}" +
  1024. " AND CAST(CASE value WHEN '' THEN '0' ELSE value END AS decimal(30,0)) IN (" +
  1025. " SELECT customized_id FROM #{CustomValue.table_name}" +
  1026. " WHERE customized_type='#{target_class}' AND custom_field_id=#{chained_custom_field_id}" +
  1027. " AND #{sql_for_field(field, operator, value, CustomValue.table_name, 'value', true)}))"
  1028. end
  1029. def sql_for_custom_field_attribute(field, operator, value, custom_field_id, attribute)
  1030. attribute = 'effective_date' if attribute == 'due_date'
  1031. not_in = nil
  1032. if operator == '!'
  1033. # Makes ! operator work for custom fields with multiple values
  1034. operator = '='
  1035. not_in = 'NOT'
  1036. end
  1037. filter = available_filters[field]
  1038. target_table_name = filter[:field].format.target_class.table_name
  1039. "#{queried_table_name}.id #{not_in} IN (" +
  1040. "SELECT customized_id FROM #{CustomValue.table_name}" +
  1041. " WHERE customized_type='#{queried_class}' AND custom_field_id=#{custom_field_id}" +
  1042. " AND CAST(CASE value WHEN '' THEN '0' ELSE value END AS decimal(30,0)) IN (" +
  1043. " SELECT id FROM #{target_table_name} WHERE #{sql_for_field(field, operator, value, filter[:field].format.target_class.table_name, attribute)}))"
  1044. end
  1045. # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
  1046. def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
  1047. sql = ''
  1048. case operator
  1049. when "="
  1050. if value.any?
  1051. case type_for(field)
  1052. when :date, :date_past
  1053. sql = date_clause(db_table, db_field, parse_date(value.first),
  1054. parse_date(value.first), is_custom_filter)
  1055. when :integer
  1056. int_values = value.first.to_s.scan(/[+-]?\d+/).map(&:to_i).join(",")
  1057. if int_values.present?
  1058. if is_custom_filter
  1059. sql =
  1060. "(#{db_table}.#{db_field} <> '' AND " \
  1061. "CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' " \
  1062. "ELSE #{db_table}.#{db_field} END AS decimal(30,3)) IN (#{int_values}))"
  1063. else
  1064. sql = "#{db_table}.#{db_field} IN (#{int_values})"
  1065. end
  1066. else
  1067. sql = "1=0"
  1068. end
  1069. when :float
  1070. if is_custom_filter
  1071. sql =
  1072. "(#{db_table}.#{db_field} <> '' AND " \
  1073. "CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' " \
  1074. "ELSE #{db_table}.#{db_field} END AS decimal(30,3)) " \
  1075. "BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5})"
  1076. else
  1077. sql = "#{db_table}.#{db_field} BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5}"
  1078. end
  1079. else
  1080. sql = queried_class.send(:sanitize_sql_for_conditions, ["#{db_table}.#{db_field} IN (?)", value])
  1081. end
  1082. else
  1083. # IN an empty set
  1084. sql = "1=0"
  1085. end
  1086. when "!"
  1087. if value.any?
  1088. sql =
  1089. queried_class.send(
  1090. :sanitize_sql_for_conditions,
  1091. ["(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (?))", value]
  1092. )
  1093. else
  1094. # NOT IN an empty set
  1095. sql = "1=1"
  1096. end
  1097. when "!*"
  1098. sql = "#{db_table}.#{db_field} IS NULL"
  1099. sql += " OR #{db_table}.#{db_field} = ''" if is_custom_filter || [:text, :string].include?(type_for(field))
  1100. when "*"
  1101. sql = "#{db_table}.#{db_field} IS NOT NULL"
  1102. sql += " AND #{db_table}.#{db_field} <> ''" if is_custom_filter || [:text, :string].include?(type_for(field))
  1103. when ">="
  1104. if [:date, :date_past].include?(type_for(field))
  1105. sql = date_clause(db_table, db_field, parse_date(value.first), nil, is_custom_filter)
  1106. else
  1107. if is_custom_filter
  1108. sql =
  1109. "(#{db_table}.#{db_field} <> '' AND " \
  1110. "CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' " \
  1111. "ELSE #{db_table}.#{db_field} END AS decimal(30,3)) >= #{value.first.to_f})"
  1112. else
  1113. sql = "#{db_table}.#{db_field} >= #{value.first.to_f}"
  1114. end
  1115. end
  1116. when "<="
  1117. if [:date, :date_past].include?(type_for(field))
  1118. sql = date_clause(db_table, db_field, nil, parse_date(value.first), is_custom_filter)
  1119. else
  1120. if is_custom_filter
  1121. sql =
  1122. "(#{db_table}.#{db_field} <> '' AND " \
  1123. "CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' " \
  1124. "ELSE #{db_table}.#{db_field} END AS decimal(30,3)) <= #{value.first.to_f})"
  1125. else
  1126. sql = "#{db_table}.#{db_field} <= #{value.first.to_f}"
  1127. end
  1128. end
  1129. when "><"
  1130. if [:date, :date_past].include?(type_for(field))
  1131. sql = date_clause(db_table, db_field, parse_date(value[0]), parse_date(value[1]), is_custom_filter)
  1132. else
  1133. if is_custom_filter
  1134. sql =
  1135. "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} " \
  1136. "WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) " \
  1137. "BETWEEN #{value[0].to_f} AND #{value[1].to_f})"
  1138. else
  1139. sql = "#{db_table}.#{db_field} BETWEEN #{value[0].to_f} AND #{value[1].to_f}"
  1140. end
  1141. end
  1142. when "o"
  1143. if field == "status_id"
  1144. sql =
  1145. "#{queried_table_name}.status_id IN " \
  1146. "(SELECT id FROM #{IssueStatus.table_name} " \
  1147. "WHERE is_closed=#{self.class.connection.quoted_false})"
  1148. end
  1149. when "c"
  1150. if field == "status_id"
  1151. sql =
  1152. "#{queried_table_name}.status_id IN " \
  1153. "(SELECT id FROM #{IssueStatus.table_name} " \
  1154. "WHERE is_closed=#{self.class.connection.quoted_true})"
  1155. end
  1156. when "><t-"
  1157. # between today - n days and today
  1158. sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0, is_custom_filter)
  1159. when ">t-"
  1160. # >= today - n days
  1161. sql = relative_date_clause(db_table, db_field, - value.first.to_i, nil, is_custom_filter)
  1162. when "<t-"
  1163. # <= today - n days
  1164. sql = relative_date_clause(db_table, db_field, nil, - value.first.to_i, is_custom_filter)
  1165. when "t-"
  1166. # = n days in past
  1167. sql = relative_date_clause(db_table, db_field, - value.first.to_i, - value.first.to_i, is_custom_filter)
  1168. when "><t+"
  1169. # between today and today + n days
  1170. sql = relative_date_clause(db_table, db_field, 0, value.first.to_i, is_custom_filter)
  1171. when ">t+"
  1172. # >= today + n days
  1173. sql = relative_date_clause(db_table, db_field, value.first.to_i, nil, is_custom_filter)
  1174. when "<t+"
  1175. # <= today + n days
  1176. sql = relative_date_clause(db_table, db_field, nil, value.first.to_i, is_custom_filter)
  1177. when "t+"
  1178. # = today + n days
  1179. sql = relative_date_clause(db_table, db_field, value.first.to_i, value.first.to_i, is_custom_filter)
  1180. when "t"
  1181. # = today
  1182. sql = relative_date_clause(db_table, db_field, 0, 0, is_custom_filter)
  1183. when "ld"
  1184. # = yesterday
  1185. sql = relative_date_clause(db_table, db_field, -1, -1, is_custom_filter)
  1186. when "nd"
  1187. # = tomorrow
  1188. sql = relative_date_clause(db_table, db_field, 1, 1, is_custom_filter)
  1189. when "w"
  1190. # = this week
  1191. first_day_of_week = l(:general_first_day_of_week).to_i
  1192. day_of_week = User.current.today.cwday
  1193. days_ago =
  1194. if day_of_week >= first_day_of_week
  1195. day_of_week - first_day_of_week
  1196. else
  1197. day_of_week + 7 - first_day_of_week
  1198. end
  1199. sql = relative_date_clause(db_table, db_field, - days_ago, - days_ago + 6, is_custom_filter)
  1200. when "lw"
  1201. # = last week
  1202. first_day_of_week = l(:general_first_day_of_week).to_i
  1203. day_of_week = User.current.today.cwday
  1204. days_ago =
  1205. if day_of_week >= first_day_of_week
  1206. day_of_week - first_day_of_week
  1207. else
  1208. day_of_week + 7 - first_day_of_week
  1209. end
  1210. sql = relative_date_clause(db_table, db_field, - days_ago - 7, - days_ago - 1, is_custom_filter)
  1211. when "l2w"
  1212. # = last 2 weeks
  1213. first_day_of_week = l(:general_first_day_of_week).to_i
  1214. day_of_week = User.current.today.cwday
  1215. days_ago =
  1216. if day_of_week >= first_day_of_week
  1217. day_of_week - first_day_of_week
  1218. else
  1219. day_of_week + 7 - first_day_of_week
  1220. end
  1221. sql = relative_date_clause(db_table, db_field, - days_ago - 14, - days_ago - 1, is_custom_filter)
  1222. when "nw"
  1223. # = next week
  1224. first_day_of_week = l(:general_first_day_of_week).to_i
  1225. day_of_week = User.current.today.cwday
  1226. from =
  1227. -(
  1228. if day_of_week >= first_day_of_week
  1229. day_of_week - first_day_of_week
  1230. else
  1231. day_of_week + 7 - first_day_of_week
  1232. end
  1233. ) + 7
  1234. sql = relative_date_clause(db_table, db_field, from, from + 6, is_custom_filter)
  1235. when "m"
  1236. # = this month
  1237. date = User.current.today
  1238. sql = date_clause(db_table, db_field,
  1239. date.beginning_of_month, date.end_of_month,
  1240. is_custom_filter)
  1241. when "lm"
  1242. # = last month
  1243. date = User.current.today.prev_month
  1244. sql = date_clause(db_table, db_field,
  1245. date.beginning_of_month, date.end_of_month,
  1246. is_custom_filter)
  1247. when "nm"
  1248. # = next month
  1249. date = User.current.today.next_month
  1250. sql = date_clause(db_table, db_field,
  1251. date.beginning_of_month, date.end_of_month,
  1252. is_custom_filter)
  1253. when "y"
  1254. # = this year
  1255. date = User.current.today
  1256. sql = date_clause(db_table, db_field,
  1257. date.beginning_of_year, date.end_of_year,
  1258. is_custom_filter)
  1259. when "~"
  1260. sql = sql_contains("#{db_table}.#{db_field}", value.first)
  1261. when "!~"
  1262. sql = sql_contains("#{db_table}.#{db_field}", value.first, :match => false)
  1263. when "*~"
  1264. sql = sql_contains("#{db_table}.#{db_field}", value.first, :all_words => false)
  1265. when "^"
  1266. sql = sql_contains("#{db_table}.#{db_field}", value.first, :starts_with => true)
  1267. when "$"
  1268. sql = sql_contains("#{db_table}.#{db_field}", value.first, :ends_with => true)
  1269. when "ev", "!ev", "cf"
  1270. # has been, has never been, changed from
  1271. if queried_class == Issue && value.present?
  1272. neg = (operator.start_with?('!') ? 'NOT' : '')
  1273. subquery =
  1274. "SELECT 1 FROM #{Journal.table_name}" +
  1275. " INNER JOIN #{JournalDetail.table_name} ON #{Journal.table_name}.id = #{JournalDetail.table_name}.journal_id" +
  1276. " WHERE (#{Journal.visible_notes_condition(User.current, :skip_pre_condition => true)}" +
  1277. " AND #{Journal.table_name}.journalized_type = 'Issue'" +
  1278. " AND #{Journal.table_name}.journalized_id = #{db_table}.id" +
  1279. " AND #{JournalDetail.table_name}.property = 'attr'" +
  1280. " AND #{JournalDetail.table_name}.prop_key = '#{db_field}'" +
  1281. " AND " +
  1282. queried_class.send(:sanitize_sql_for_conditions, ["#{JournalDetail.table_name}.old_value IN (?)", value.map(&:to_s)]) +
  1283. ")"
  1284. sql_ev =
  1285. if %w[ev !ev].include?(operator)
  1286. " OR " + queried_class.send(:sanitize_sql_for_conditions, ["#{db_table}.#{db_field} IN (?)", value.map(&:to_s)])
  1287. else
  1288. ''
  1289. end
  1290. sql = "#{neg} (EXISTS (#{subquery})#{sql_ev})"
  1291. else
  1292. sql = '1=0'
  1293. end
  1294. else
  1295. raise QueryError, "Unknown query operator #{operator}"
  1296. end
  1297. return sql
  1298. end
  1299. # Returns a SQL LIKE statement with wildcards
  1300. #
  1301. # valid options:
  1302. # * :match - use NOT LIKE if false
  1303. # * :starts_with - use LIKE 'value%' if true
  1304. # * :ends_with - use LIKE '%value' if true
  1305. # * :all_words - use OR instead of AND if false
  1306. # (ignored if :starts_with or :ends_with is true)
  1307. def sql_contains(db_field, value, options={})
  1308. options = {} unless options.is_a?(Hash)
  1309. options.symbolize_keys!
  1310. queried_class.sanitize_sql_for_conditions(
  1311. ::Query.tokenized_like_conditions(db_field, value, **options)
  1312. )
  1313. end
  1314. # rubocop:disable Lint/IneffectiveAccessModifier
  1315. def self.tokenized_like_conditions(db_field, value, **options)
  1316. tokens = Redmine::Search::Tokenizer.new(value).tokens
  1317. tokens = [value] unless tokens.present?
  1318. if options[:starts_with]
  1319. prefix, suffix = nil, '%'
  1320. logical_opr = ' OR '
  1321. elsif options[:ends_with]
  1322. prefix, suffix = '%', nil
  1323. logical_opr = ' OR '
  1324. else
  1325. prefix = suffix = '%'
  1326. logical_opr = options[:all_words] == false ? ' OR ' : ' AND '
  1327. end
  1328. sql, values = tokens.map do |token|
  1329. [Redmine::Database.like(db_field, '?', options), "#{prefix}#{sanitize_sql_like token}#{suffix}"]
  1330. end.transpose
  1331. [sql.join(logical_opr), *values]
  1332. end
  1333. # rubocop:enable Lint/IneffectiveAccessModifier
  1334. # Adds a filter for the given custom field
  1335. def add_custom_field_filter(field, assoc=nil)
  1336. options = field.query_filter_options(self)
  1337. filter_id = "cf_#{field.id}"
  1338. filter_name = field.name
  1339. if assoc.present?
  1340. filter_id = "#{assoc}.#{filter_id}"
  1341. filter_name = l("label_attribute_of_#{assoc}", :name => filter_name)
  1342. end
  1343. add_available_filter(
  1344. filter_id,
  1345. options.merge(
  1346. {
  1347. :name => filter_name,
  1348. :field => field
  1349. }
  1350. )
  1351. )
  1352. end
  1353. # Adds filters for custom fields associated to the custom field target class
  1354. # Eg. having a version custom field "Milestone" for issues and a date custom field "Release date"
  1355. # for versions, it will add an issue filter on Milestone'e Release date.
  1356. def add_chained_custom_field_filters(field)
  1357. klass = field.format.target_class
  1358. if klass
  1359. CustomField.where(:is_filter => true, :type => "#{klass.name}CustomField").each do |chained|
  1360. options = chained.query_filter_options(self)
  1361. filter_id = "cf_#{field.id}.cf_#{chained.id}"
  1362. add_available_filter(
  1363. filter_id,
  1364. options.merge(
  1365. {
  1366. :name => l(:label_attribute_of_object,
  1367. :name => chained.name,
  1368. :object_name => field.name),
  1369. :field => chained,
  1370. :through => field
  1371. }
  1372. )
  1373. )
  1374. end
  1375. end
  1376. end
  1377. # Adds filters for the given custom fields scope
  1378. def add_custom_fields_filters(scope, assoc=nil)
  1379. scope.visible.where(:is_filter => true).sorted.each do |field|
  1380. add_custom_field_filter(field, assoc)
  1381. if assoc.nil?
  1382. add_chained_custom_field_filters(field)
  1383. if field.format.target_class && field.format.target_class == Version
  1384. add_available_filter(
  1385. "cf_#{field.id}.due_date",
  1386. :type => :date,
  1387. :field => field,
  1388. :name => l(:label_attribute_of_object, :name => l(:field_effective_date),
  1389. :object_name => field.name)
  1390. )
  1391. add_available_filter(
  1392. "cf_#{field.id}.status",
  1393. :type => :list,
  1394. :field => field,
  1395. :name => l(:label_attribute_of_object, :name => l(:field_status),
  1396. :object_name => field.name),
  1397. :values => Version::VERSION_STATUSES.map{|s| [l("version_status_#{s}"), s]}
  1398. )
  1399. end
  1400. end
  1401. end
  1402. end
  1403. # Adds filters for the given associations custom fields
  1404. def add_associations_custom_fields_filters(*associations)
  1405. fields_by_class = CustomField.visible.where(:is_filter => true).group_by(&:class)
  1406. associations.each do |assoc|
  1407. association_klass = queried_class.reflect_on_association(assoc).klass
  1408. fields_by_class.each do |field_class, fields|
  1409. if field_class.customized_class <= association_klass
  1410. fields.sort.each do |field|
  1411. add_custom_field_filter(field, assoc)
  1412. end
  1413. end
  1414. end
  1415. end
  1416. end
  1417. def quoted_time(time, is_custom_filter)
  1418. if is_custom_filter
  1419. # Custom field values are stored as strings in the DB
  1420. # using this format that does not depend on DB date representation
  1421. time.strftime("%Y-%m-%d %H:%M:%S")
  1422. else
  1423. self.class.connection.quoted_date(time)
  1424. end
  1425. end
  1426. def date_for_user_time_zone(y, m, d)
  1427. if tz = User.current.time_zone
  1428. tz.local y, m, d
  1429. else
  1430. Time.local y, m, d
  1431. end
  1432. end
  1433. # Returns a SQL clause for a date or datetime field.
  1434. def date_clause(table, field, from, to, is_custom_filter)
  1435. s = []
  1436. if from
  1437. if from.is_a?(Date)
  1438. from = date_for_user_time_zone(from.year, from.month, from.day).yesterday.end_of_day
  1439. else
  1440. from = from - 1 # second
  1441. end
  1442. if ActiveRecord.default_timezone == :utc
  1443. from = from.utc
  1444. end
  1445. s << ("#{table}.#{field} > '%s'" % [quoted_time(from, is_custom_filter)])
  1446. end
  1447. if to
  1448. if to.is_a?(Date)
  1449. to = date_for_user_time_zone(to.year, to.month, to.day).end_of_day
  1450. end
  1451. if ActiveRecord.default_timezone == :utc
  1452. to = to.utc
  1453. end
  1454. s << ("#{table}.#{field} <= '%s'" % [quoted_time(to, is_custom_filter)])
  1455. end
  1456. s.join(' AND ')
  1457. end
  1458. # Returns a SQL clause for a date or datetime field using relative dates.
  1459. def relative_date_clause(table, field, days_from, days_to, is_custom_filter)
  1460. date_clause(
  1461. table, field, (days_from ? User.current.today + days_from : nil),
  1462. (days_to ? User.current.today + days_to : nil), is_custom_filter
  1463. )
  1464. end
  1465. # Returns a Date or Time from the given filter value
  1466. def parse_date(arg)
  1467. if /\A\d{4}-\d{2}-\d{2}T/.match?(arg.to_s)
  1468. Time.parse(arg) rescue nil
  1469. else
  1470. Date.parse(arg) rescue nil
  1471. end
  1472. end
  1473. # Additional joins required for the given sort options
  1474. def joins_for_order_statement(order_options)
  1475. joins = []
  1476. if order_options
  1477. order_options.scan(/cf_\d+/).uniq.each do |name|
  1478. column = available_columns.detect {|c| c.name.to_s == name}
  1479. join = column && column.custom_field.join_for_order_statement
  1480. if join
  1481. joins << join
  1482. end
  1483. end
  1484. end
  1485. joins.any? ? joins.join(' ') : nil
  1486. end
  1487. end