PageRenderTime 43ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 1ms

/core/filter_api.php

https://github.com/Kirill/mantisbt
PHP | 4032 lines | 3307 code | 282 blank | 443 comment | 413 complexity | f5aa88adf8f0e19613695b08e5d9c349 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. # MantisBT - A PHP based bugtracking system
  3. # MantisBT is free software: you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation, either version 2 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # MantisBT is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with MantisBT. If not, see <http://www.gnu.org/licenses/>.
  15. /**
  16. * Filter API
  17. *
  18. * @package CoreAPI
  19. * @subpackage FilterAPI
  20. * @copyright Copyright 2000 - 2002 Kenzaburo Ito - kenito@300baud.org
  21. * @copyright Copyright 2002 MantisBT Team - mantisbt-dev@lists.sourceforge.net
  22. * @link http://www.mantisbt.org
  23. *
  24. * @uses access_api.php
  25. * @uses authentication_api.php
  26. * @uses bug_api.php
  27. * @uses collapse_api.php
  28. * @uses columns_api.php
  29. * @uses config_api.php
  30. * @uses constant_inc.php
  31. * @uses current_user_api.php
  32. * @uses custom_field_api.php
  33. * @uses database_api.php
  34. * @uses date_api.php
  35. * @uses error_api.php
  36. * @uses event_api.php
  37. * @uses filter_constants_inc.php
  38. * @uses gpc_api.php
  39. * @uses helper_api.php
  40. * @uses lang_api.php
  41. * @uses logging_api.php
  42. * @uses print_api.php
  43. * @uses profile_api.php
  44. * @uses project_api.php
  45. * @uses relationship_api.php
  46. * @uses session_api.php
  47. * @uses string_api.php
  48. * @uses tag_api.php
  49. * @uses user_api.php
  50. * @uses utility_api.php
  51. * @uses version_api.php
  52. * @uses filter_form_api.php
  53. */
  54. require_api( 'access_api.php' );
  55. require_api( 'authentication_api.php' );
  56. require_api( 'bug_api.php' );
  57. require_api( 'collapse_api.php' );
  58. require_api( 'columns_api.php' );
  59. require_api( 'config_api.php' );
  60. require_api( 'constant_inc.php' );
  61. require_api( 'current_user_api.php' );
  62. require_api( 'custom_field_api.php' );
  63. require_api( 'database_api.php' );
  64. require_api( 'date_api.php' );
  65. require_api( 'error_api.php' );
  66. require_api( 'event_api.php' );
  67. require_api( 'filter_constants_inc.php' );
  68. require_api( 'gpc_api.php' );
  69. require_api( 'helper_api.php' );
  70. require_api( 'lang_api.php' );
  71. require_api( 'logging_api.php' );
  72. require_api( 'print_api.php' );
  73. require_api( 'profile_api.php' );
  74. require_api( 'project_api.php' );
  75. require_api( 'relationship_api.php' );
  76. require_api( 'session_api.php' );
  77. require_api( 'string_api.php' );
  78. require_api( 'tag_api.php' );
  79. require_api( 'user_api.php' );
  80. require_api( 'utility_api.php' );
  81. require_api( 'version_api.php' );
  82. require_api( 'filter_form_api.php' );
  83. # @global array $g_filter Filter array for the filter in use through view_all_bug_page
  84. # This gets initialized on filter load
  85. # @TODO cproensa We should move towards not relying on this variable, as we reuse filter logic
  86. # to allow operating on other filter different that the one in use for view_all_bug_page.
  87. # For example: manage and edit stored filters.
  88. $g_filter = null;
  89. # ==========================================================================
  90. # CACHING
  91. # ==========================================================================
  92. # We cache filter requests to reduce the number of SQL queries
  93. # @global array $g_cache_filter_db_rows
  94. # indexed by filter_id, contains the filter rows as read from db table
  95. $g_cache_filter_db_rows = array();
  96. /**
  97. * Initialize the filter API with the current filter.
  98. * @param array $p_filter The filter to set as the current filter.
  99. */
  100. function filter_init( $p_filter ) {
  101. global $g_filter;
  102. $g_filter = $p_filter;
  103. }
  104. /**
  105. * Allow plugins to define a set of class-based filters, and register/load
  106. * them here to be used by the rest of filter_api.
  107. * @return array Mapping of field name to filter object
  108. */
  109. function filter_get_plugin_filters() {
  110. static $s_field_array = null;
  111. if( is_null( $s_field_array ) ) {
  112. $s_field_array = array();
  113. $t_all_plugin_filters = event_signal( 'EVENT_FILTER_FIELDS' );
  114. foreach( $t_all_plugin_filters as $t_plugin => $t_plugin_filters ) {
  115. foreach( $t_plugin_filters as $t_callback => $t_plugin_filter_array ) {
  116. if( is_array( $t_plugin_filter_array ) ) {
  117. foreach( $t_plugin_filter_array as $t_filter_item ) {
  118. if( is_object( $t_filter_item ) && $t_filter_item instanceof MantisFilter ) {
  119. $t_filter_object = $t_filter_item;
  120. } elseif( class_exists( $t_filter_item ) && is_subclass_of( $t_filter_item, 'MantisFilter' ) ) {
  121. $t_filter_object = new $t_filter_item();
  122. } else {
  123. continue;
  124. }
  125. $t_filter_name = mb_strtolower( $t_plugin . '_' . $t_filter_object->field );
  126. $s_field_array[$t_filter_name] = $t_filter_object;
  127. }
  128. }
  129. }
  130. }
  131. }
  132. return $s_field_array;
  133. }
  134. /**
  135. * Get a permanent link for the current active filter. The results of using these fields by other users
  136. * can be inconsistent with the original results due to fields like "Myself", "Current Project",
  137. * and due to access level.
  138. * @param array $p_custom_filter Array containing a custom filter definition.
  139. * @return string the search.php?xxxx or an empty string if no criteria applied.
  140. */
  141. function filter_get_url( array $p_custom_filter ) {
  142. $t_query = array();
  143. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_PROJECT_ID] ) ) {
  144. $t_project_id = $p_custom_filter[FILTER_PROPERTY_PROJECT_ID];
  145. if( count( $t_project_id ) == 1 && $t_project_id[0] == META_FILTER_CURRENT ) {
  146. $t_project_id = array(
  147. helper_get_current_project(),
  148. );
  149. }
  150. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_PROJECT_ID, $t_project_id );
  151. }
  152. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_SEARCH] ) ) {
  153. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_SEARCH, $p_custom_filter[FILTER_PROPERTY_SEARCH] );
  154. }
  155. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_CATEGORY_ID] ) ) {
  156. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_CATEGORY_ID, $p_custom_filter[FILTER_PROPERTY_CATEGORY_ID] );
  157. }
  158. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_REPORTER_ID] ) ) {
  159. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_REPORTER_ID, $p_custom_filter[FILTER_PROPERTY_REPORTER_ID] );
  160. }
  161. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_STATUS] ) ) {
  162. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_STATUS, $p_custom_filter[FILTER_PROPERTY_STATUS] );
  163. }
  164. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_MONITOR_USER_ID] ) ) {
  165. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_MONITOR_USER_ID, $p_custom_filter[FILTER_PROPERTY_MONITOR_USER_ID] );
  166. }
  167. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_HANDLER_ID] ) ) {
  168. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_HANDLER_ID, $p_custom_filter[FILTER_PROPERTY_HANDLER_ID] );
  169. }
  170. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_NOTE_USER_ID] ) ) {
  171. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_NOTE_USER_ID, $p_custom_filter[FILTER_PROPERTY_NOTE_USER_ID] );
  172. }
  173. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_SEVERITY] ) ) {
  174. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_SEVERITY, $p_custom_filter[FILTER_PROPERTY_SEVERITY] );
  175. }
  176. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_RESOLUTION] ) ) {
  177. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_RESOLUTION, $p_custom_filter[FILTER_PROPERTY_RESOLUTION] );
  178. }
  179. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_PRIORITY] ) ) {
  180. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_PRIORITY, $p_custom_filter[FILTER_PROPERTY_PRIORITY] );
  181. }
  182. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_VIEW_STATE] ) ) {
  183. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_VIEW_STATE, $p_custom_filter[FILTER_PROPERTY_VIEW_STATE] );
  184. }
  185. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_STICKY] ) ) {
  186. $t_query[] = filter_encode_field_and_value(
  187. FILTER_PROPERTY_STICKY,
  188. $p_custom_filter[FILTER_PROPERTY_STICKY] ? 'on' : 'off' );
  189. }
  190. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_VERSION] ) ) {
  191. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_VERSION, $p_custom_filter[FILTER_PROPERTY_VERSION] );
  192. }
  193. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_BUILD] ) ) {
  194. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_BUILD, $p_custom_filter[FILTER_PROPERTY_BUILD] );
  195. }
  196. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_FIXED_IN_VERSION] ) ) {
  197. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_FIXED_IN_VERSION, $p_custom_filter[FILTER_PROPERTY_FIXED_IN_VERSION] );
  198. }
  199. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_TARGET_VERSION] ) ) {
  200. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_TARGET_VERSION, $p_custom_filter[FILTER_PROPERTY_TARGET_VERSION] );
  201. }
  202. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_SORT_FIELD_NAME] ) ) {
  203. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_SORT_FIELD_NAME, $p_custom_filter[FILTER_PROPERTY_SORT_FIELD_NAME] );
  204. }
  205. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_SORT_DIRECTION] ) ) {
  206. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_SORT_DIRECTION, $p_custom_filter[FILTER_PROPERTY_SORT_DIRECTION] );
  207. }
  208. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_ISSUES_PER_PAGE] ) ) {
  209. if( $p_custom_filter[FILTER_PROPERTY_ISSUES_PER_PAGE] != config_get( 'default_limit_view' ) ) {
  210. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_ISSUES_PER_PAGE, $p_custom_filter[FILTER_PROPERTY_ISSUES_PER_PAGE] );
  211. }
  212. }
  213. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_HIGHLIGHT_CHANGED] ) ) {
  214. if( $p_custom_filter[FILTER_PROPERTY_HIGHLIGHT_CHANGED] != config_get( 'default_show_changed' ) ) {
  215. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_HIGHLIGHT_CHANGED, $p_custom_filter[FILTER_PROPERTY_HIGHLIGHT_CHANGED] );
  216. }
  217. }
  218. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_HIDE_STATUS] ) ) {
  219. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_HIDE_STATUS, $p_custom_filter[FILTER_PROPERTY_HIDE_STATUS] );
  220. }
  221. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_FILTER_BY_DATE_SUBMITTED] ) ) {
  222. $t_query[] = filter_encode_field_and_value(
  223. FILTER_PROPERTY_FILTER_BY_DATE_SUBMITTED,
  224. $p_custom_filter[FILTER_PROPERTY_FILTER_BY_DATE_SUBMITTED] ? 'on' : 'off' );
  225. # The start and end dates are only applicable if filter by date is set.
  226. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_START_DAY] ) ) {
  227. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_DATE_SUBMITTED_START_DAY, $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_START_DAY] );
  228. }
  229. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_END_DAY] ) ) {
  230. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_DATE_SUBMITTED_END_DAY, $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_END_DAY] );
  231. }
  232. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_START_MONTH] ) ) {
  233. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_DATE_SUBMITTED_START_MONTH, $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_START_MONTH] );
  234. }
  235. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_END_MONTH] ) ) {
  236. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_DATE_SUBMITTED_END_MONTH, $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_END_MONTH] );
  237. }
  238. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_START_YEAR] ) ) {
  239. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_DATE_SUBMITTED_START_YEAR, $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_START_YEAR] );
  240. }
  241. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_END_YEAR] ) ) {
  242. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_DATE_SUBMITTED_END_YEAR, $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_END_YEAR] );
  243. }
  244. }
  245. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_FILTER_BY_LAST_UPDATED_DATE] ) ) {
  246. $t_query[] = filter_encode_field_and_value(
  247. FILTER_PROPERTY_FILTER_BY_LAST_UPDATED_DATE,
  248. $p_custom_filter[FILTER_PROPERTY_FILTER_BY_LAST_UPDATED_DATE] ? 'on' : 'off' );
  249. # The start and end dates are only applicable if filter by date is set.
  250. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_START_DAY] ) ) {
  251. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_LAST_UPDATED_START_DAY, $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_START_DAY] );
  252. }
  253. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_END_DAY] ) ) {
  254. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_LAST_UPDATED_END_DAY, $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_END_DAY] );
  255. }
  256. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_START_MONTH] ) ) {
  257. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_LAST_UPDATED_START_MONTH, $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_START_MONTH] );
  258. }
  259. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_END_MONTH] ) ) {
  260. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_LAST_UPDATED_END_MONTH, $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_END_MONTH] );
  261. }
  262. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_START_YEAR] ) ) {
  263. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_LAST_UPDATED_START_YEAR, $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_START_YEAR] );
  264. }
  265. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_END_YEAR] ) ) {
  266. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_LAST_UPDATED_END_YEAR, $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_END_YEAR] );
  267. }
  268. }
  269. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_RELATIONSHIP_TYPE] ) ) {
  270. if( $p_custom_filter[FILTER_PROPERTY_RELATIONSHIP_TYPE] != -1 ) {
  271. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_RELATIONSHIP_TYPE, $p_custom_filter[FILTER_PROPERTY_RELATIONSHIP_TYPE] );
  272. }
  273. }
  274. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_RELATIONSHIP_BUG] ) ) {
  275. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_RELATIONSHIP_BUG, $p_custom_filter[FILTER_PROPERTY_RELATIONSHIP_BUG] );
  276. }
  277. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_PLATFORM] ) ) {
  278. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_PLATFORM, $p_custom_filter[FILTER_PROPERTY_PLATFORM] );
  279. }
  280. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_OS] ) ) {
  281. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_OS, $p_custom_filter[FILTER_PROPERTY_OS] );
  282. }
  283. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_OS_BUILD] ) ) {
  284. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_OS_BUILD, $p_custom_filter[FILTER_PROPERTY_OS_BUILD] );
  285. }
  286. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_TAG_STRING] ) ) {
  287. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_TAG_STRING, $p_custom_filter[FILTER_PROPERTY_TAG_STRING] );
  288. }
  289. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_TAG_SELECT] ) ) {
  290. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_TAG_SELECT, $p_custom_filter[FILTER_PROPERTY_TAG_SELECT] );
  291. }
  292. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_MATCH_TYPE, $p_custom_filter[FILTER_PROPERTY_MATCH_TYPE] );
  293. if( isset( $p_custom_filter['custom_fields'] ) ) {
  294. foreach( $p_custom_filter['custom_fields'] as $t_custom_field_id => $t_custom_field_values ) {
  295. if( !filter_field_is_any( $t_custom_field_values ) ) {
  296. $t_query[] = filter_encode_field_and_value( 'custom_field_' . $t_custom_field_id, $t_custom_field_values );
  297. }
  298. }
  299. }
  300. # Allow plugins to add filter fields
  301. $t_plugin_filter_array = filter_get_plugin_filters();
  302. foreach( $t_plugin_filter_array as $t_field_name => $t_filter_object ) {
  303. if( !filter_field_is_any( $p_custom_filter[$t_field_name] ) ) {
  304. $t_query[] = filter_encode_field_and_value( $t_field_name, $p_custom_filter[$t_field_name], $t_filter_object->type );
  305. }
  306. }
  307. if( count( $t_query ) > 0 ) {
  308. $t_query_str = implode( $t_query, '&' );
  309. $t_url = config_get_global( 'path' ) . 'search.php?' . $t_query_str;
  310. } else {
  311. $t_url = '';
  312. }
  313. return $t_url;
  314. }
  315. /**
  316. * Encodes a field and it's value for the filter URL. This handles the URL encoding and arrays.
  317. * @param string $p_field_name The field name.
  318. * @param string $p_field_value The field value (can be an array).
  319. * @param integer $p_field_type Field Type e.g. FILTER_TYPE_MULTI_STRING.
  320. * @return string url encoded string
  321. */
  322. function filter_encode_field_and_value( $p_field_name, $p_field_value, $p_field_type = null ) {
  323. $t_query_array = array();
  324. if( is_array( $p_field_value ) ) {
  325. $t_count = count( $p_field_value );
  326. if( $t_count > 1 || $p_field_type == FILTER_TYPE_MULTI_STRING || $p_field_type == FILTER_TYPE_MULTI_INT ) {
  327. foreach( $p_field_value as $t_value ) {
  328. $t_query_array[] = urlencode( $p_field_name . '[]' ) . '=' . urlencode( $t_value );
  329. }
  330. } else if( $t_count == 1 ) {
  331. $t_query_array[] = urlencode( $p_field_name ) . '=' . urlencode( $p_field_value[0] );
  332. }
  333. } else {
  334. $t_query_array[] = urlencode( $p_field_name ) . '=' . urlencode( $p_field_value );
  335. }
  336. return implode( $t_query_array, '&' );
  337. }
  338. /**
  339. * Checks the supplied value to see if it is an ANY value.
  340. * @param string $p_field_value The value to check.
  341. * @return boolean true for "ANY" values and false for others. "ANY" means filter criteria not active.
  342. */
  343. function filter_field_is_any( $p_field_value ) {
  344. if( is_array( $p_field_value ) ) {
  345. if( count( $p_field_value ) == 0 ) {
  346. return true;
  347. }
  348. foreach( $p_field_value as $t_value ) {
  349. if( ( META_FILTER_ANY == $t_value ) && ( is_numeric( $t_value ) ) ) {
  350. return true;
  351. }
  352. }
  353. } else {
  354. if( is_string( $p_field_value ) && is_blank( $p_field_value ) ) {
  355. return true;
  356. }
  357. if( is_bool( $p_field_value ) && !$p_field_value ) {
  358. return true;
  359. }
  360. if( ( META_FILTER_ANY == $p_field_value ) && ( is_numeric( $p_field_value ) ) ) {
  361. return true;
  362. }
  363. }
  364. return false;
  365. }
  366. /**
  367. * Checks the supplied value to see if it is a NONE value.
  368. * @param string $p_field_value The value to check.
  369. * @return boolean true for "NONE" values and false for others.
  370. * @todo is a check for these necessary? if( ( $t_filter_value === 'none' ) || ( $t_filter_value === '[none]' ) )
  371. */
  372. function filter_field_is_none( $p_field_value ) {
  373. if( is_array( $p_field_value ) ) {
  374. foreach( $p_field_value as $t_value ) {
  375. if( ( META_FILTER_NONE == $t_value ) && ( is_numeric( $t_value ) ) ) {
  376. return true;
  377. }
  378. }
  379. } else {
  380. if( is_string( $p_field_value ) && is_blank( $p_field_value ) ) {
  381. return false;
  382. }
  383. if( ( META_FILTER_NONE == $p_field_value ) && ( is_numeric( $p_field_value ) ) ) {
  384. return true;
  385. }
  386. }
  387. return false;
  388. }
  389. /**
  390. * Checks the supplied value to see if it is a MYSELF value.
  391. * @param string $p_field_value The value to check.
  392. * @return boolean true for "MYSELF" values and false for others.
  393. */
  394. function filter_field_is_myself( $p_field_value ) {
  395. return( META_FILTER_MYSELF == $p_field_value ? true : false );
  396. }
  397. /**
  398. * Filter per page
  399. * @param array $p_filter Filter.
  400. * @param integer $p_count Count.
  401. * @param integer $p_per_page Per page.
  402. * @return integer
  403. */
  404. function filter_per_page( array $p_filter, $p_count, $p_per_page ) {
  405. $p_per_page = (( null == $p_per_page ) ? (int)$p_filter[FILTER_PROPERTY_ISSUES_PER_PAGE] : $p_per_page );
  406. $p_per_page = (( 0 == $p_per_page || -1 == $p_per_page ) ? $p_count : $p_per_page );
  407. return (int)abs( $p_per_page );
  408. }
  409. /**
  410. * Use $p_count and $p_per_page to determine how many pages to split this list up into.
  411. * For the sake of consistency have at least one page, even if it is empty.
  412. * @param integer $p_count Count.
  413. * @param integer $p_per_page Per page.
  414. * @return integer page count
  415. */
  416. function filter_page_count( $p_count, $p_per_page ) {
  417. $t_page_count = ceil( $p_count / $p_per_page );
  418. if( $t_page_count < 1 ) {
  419. $t_page_count = 1;
  420. }
  421. return $t_page_count;
  422. }
  423. /**
  424. * Checks to make sure $p_page_number isn't past the last page.
  425. * and that $p_page_number isn't before the first page
  426. * @param integer $p_page_number Page number.
  427. * @param integer $p_page_count Page count.
  428. * @return integer
  429. */
  430. function filter_valid_page_number( $p_page_number, $p_page_count ) {
  431. if( $p_page_number > $p_page_count ) {
  432. $p_page_number = $p_page_count;
  433. }
  434. if( $p_page_number < 1 ) {
  435. $p_page_number = 1;
  436. }
  437. return $p_page_number;
  438. }
  439. /**
  440. * Figure out the offset into the db query, offset is which record to start querying from
  441. * @param integer $p_page_number Page number.
  442. * @param integer $p_per_page Per page.
  443. * @return integer
  444. */
  445. function filter_offset( $p_page_number, $p_per_page ) {
  446. return(( (int)$p_page_number -1 ) * (int)$p_per_page );
  447. }
  448. /**
  449. * Make sure the filter array contains all the fields. If any field is missing,
  450. * create it with a default value.
  451. * @param array $p_filter_arr Input filter array
  452. * @return array Processed filter array
  453. */
  454. function filter_ensure_fields( array $p_filter_arr ) {
  455. # Fill missing filter properties with defaults
  456. if( isset( $p_filter_arr['_view_type'] ) ) {
  457. $t_filter_default = filter_get_default_array( $p_filter_arr['_view_type'] );
  458. } else {
  459. $t_filter_default = filter_get_default_array();
  460. }
  461. foreach( $t_filter_default as $t_key => $t_default_value ) {
  462. if( !isset( $p_filter_arr[$t_key] ) ) {
  463. $p_filter_arr[$t_key] = $t_default_value;
  464. }
  465. }
  466. # Veryfy custom fields
  467. foreach( $t_filter_default['custom_fields'] as $t_cfid => $t_cf_data ) {
  468. if( !isset( $p_filter_arr['custom_fields'][$t_cfid] ) ) {
  469. $p_filter_arr['custom_fields'][$t_cfid] = $t_cf_data;
  470. }
  471. }
  472. return $p_filter_arr;
  473. }
  474. /**
  475. * A wrapper to compare filter version syntax
  476. * Note: Currently, filter versions have this syntax: "vN", * where N is an integer number.
  477. * @param string $p_version1 First version number
  478. * @param string $p_version2 Second version number
  479. * @param string $p_operator Comparison test, if provided. As expected by version_compare()
  480. * @return mixed As returned by version_compare()
  481. */
  482. function filter_version_compare( $p_version1, $p_version2, $p_operator = null ) {
  483. return version_compare( $p_version1, $p_version2, $p_operator );
  484. }
  485. /**
  486. * Upgrade a filter array to the current filter structure, by converting properties
  487. * that have changed from previous filter versions
  488. * @param array $p_filter Filter array to upgrade
  489. * @return array Updgraded filter array
  490. */
  491. function filter_version_upgrade( array $p_filter ) {
  492. # This is a stub for future version upgrades
  493. # After conversions are made, update filter value to current version
  494. $p_filter['_version'] = FILTER_VERSION;
  495. return $p_filter;
  496. }
  497. /**
  498. * Make sure that our filters are entirely correct and complete (it is possible that they are not).
  499. * We need to do this to cover cases where we don't have complete control over the filters given.
  500. * @param array $p_filter_arr A filter array
  501. * @return array Validated filter array
  502. */
  503. function filter_ensure_valid_filter( array $p_filter_arr ) {
  504. if( !isset( $p_filter_arr['_version'] ) ) {
  505. $p_filter_arr['_version'] = FILTER_VERSION;
  506. }
  507. if( filter_version_compare( $p_filter_arr['_version'], FILTER_VERSION, '<' ) ) {
  508. $p_filter_arr = filter_version_upgrade( $p_filter_arr );
  509. }
  510. $p_filter_arr = filter_ensure_fields( $p_filter_arr );
  511. $t_config_view_filters = config_get( 'view_filters' );
  512. $t_view_type = $p_filter_arr['_view_type'];
  513. if( ADVANCED_ONLY == $t_config_view_filters ) {
  514. $t_view_type = FILTER_VIEW_TYPE_ADVANCED;
  515. }
  516. if( SIMPLE_ONLY == $t_config_view_filters ) {
  517. $t_view_type = FILTER_VIEW_TYPE_SIMPLE;
  518. }
  519. if( !in_array( $t_view_type, array( FILTER_VIEW_TYPE_SIMPLE, FILTER_VIEW_TYPE_ADVANCED ) ) ) {
  520. $t_view_type = filter_get_default_view_type();
  521. }
  522. $p_filter_arr['_view_type'] = $t_view_type;
  523. $t_sort_fields = explode( ',', $p_filter_arr[FILTER_PROPERTY_SORT_FIELD_NAME] );
  524. $t_dir_fields = explode( ',', $p_filter_arr[FILTER_PROPERTY_SORT_DIRECTION] );
  525. # both arrays should be equal length, just in case
  526. $t_sort_fields_count = min( count( $t_sort_fields ), count( $t_dir_fields ) );
  527. # clean up sort fields, remove invalid columns
  528. $t_new_sort_array = array();
  529. $t_new_dir_array = array();
  530. $t_all_columns = columns_get_all_active_columns();
  531. for( $ix = 0; $ix < $t_sort_fields_count; $ix++ ) {
  532. if( isset( $t_sort_fields[$ix] ) ) {
  533. $t_column = $t_sort_fields[$ix];
  534. # check that the column name exist
  535. if( !in_array( $t_column, $t_all_columns ) ) {
  536. continue;
  537. }
  538. # check that it has not been already used
  539. if( in_array( $t_column, $t_new_sort_array ) ) {
  540. continue;
  541. }
  542. # check that it is sortable
  543. if( !column_is_sortable( $t_column ) ) {
  544. continue;
  545. }
  546. $t_new_sort_array[] = $t_column;
  547. # if there is no dir field, set a dummy value
  548. if( isset( $t_dir_fields[$ix] ) ) {
  549. $t_dir = $t_dir_fields[$ix];
  550. } else {
  551. $t_dir = '';
  552. }
  553. # normalize sort_dir value
  554. $t_dir = ( $t_dir == 'ASC' ) ? 'ASC' : 'DESC';
  555. $t_new_dir_array[] = $t_dir;
  556. }
  557. }
  558. if( count( $t_new_sort_array ) > 0 ) {
  559. $p_filter_arr[FILTER_PROPERTY_SORT_FIELD_NAME] = implode( ',', $t_new_sort_array );
  560. $p_filter_arr[FILTER_PROPERTY_SORT_DIRECTION] = implode( ',', $t_new_dir_array );
  561. } else {
  562. $p_filter_arr[FILTER_PROPERTY_SORT_FIELD_NAME] = filter_get_default_property( FILTER_PROPERTY_SORT_FIELD_NAME, $t_view_type );
  563. $p_filter_arr[FILTER_PROPERTY_SORT_DIRECTION] = filter_get_default_property( FILTER_PROPERTY_SORT_DIRECTION, $t_view_type );
  564. }
  565. # Validate types for values.
  566. # helper function to validate types
  567. $t_function_validate_type = function( $p_value, $p_type ) {
  568. $t_value = stripslashes( $p_value );
  569. if( ( $t_value === 'any' ) || ( $t_value === '[any]' ) ) {
  570. $t_value = META_FILTER_ANY;
  571. }
  572. if( ( $t_value === 'none' ) || ( $t_value === '[none]' ) ) {
  573. $t_value = META_FILTER_NONE;
  574. }
  575. # Ensure the filter property has the right type - see #20087
  576. switch( $p_type ) {
  577. case 'string' :
  578. case 'int' :
  579. settype( $t_value, $p_type );
  580. break;
  581. }
  582. return $t_value;
  583. };
  584. # Validate properties that must not be arrays
  585. $t_single_value_list = array(
  586. FILTER_PROPERTY_VIEW_STATE => 'int',
  587. FILTER_PROPERTY_RELATIONSHIP_TYPE => 'int',
  588. FILTER_PROPERTY_RELATIONSHIP_BUG => 'int',
  589. );
  590. foreach( $t_single_value_list as $t_field_name => $t_field_type ) {
  591. $t_value = $p_filter_arr[$t_field_name];
  592. if( is_array( $t_value ) ) {
  593. if( count( $t_value ) > 0 ) {
  594. $p_filter_arr[$t_field_name] = reset( $t_value );
  595. } else {
  596. $p_filter_arr[$t_field_name] = filter_get_default_property( $t_field_name, $t_view_type );
  597. }
  598. }
  599. $p_filter_arr[$t_field_name] = $t_function_validate_type( $p_filter_arr[$t_field_name], $t_field_type );
  600. }
  601. # Validate properties that must be arrays, and the type of its elements
  602. $t_array_values_list = array(
  603. FILTER_PROPERTY_CATEGORY_ID => 'string',
  604. FILTER_PROPERTY_SEVERITY => 'int',
  605. FILTER_PROPERTY_STATUS => 'int',
  606. FILTER_PROPERTY_REPORTER_ID => 'int',
  607. FILTER_PROPERTY_HANDLER_ID => 'int',
  608. FILTER_PROPERTY_NOTE_USER_ID => 'int',
  609. FILTER_PROPERTY_RESOLUTION => 'int',
  610. FILTER_PROPERTY_PRIORITY => 'int',
  611. FILTER_PROPERTY_BUILD => 'string',
  612. FILTER_PROPERTY_VERSION => 'string',
  613. FILTER_PROPERTY_HIDE_STATUS => 'int',
  614. FILTER_PROPERTY_FIXED_IN_VERSION => 'string',
  615. FILTER_PROPERTY_TARGET_VERSION => 'string',
  616. FILTER_PROPERTY_MONITOR_USER_ID => 'int',
  617. FILTER_PROPERTY_PROFILE_ID => 'int',
  618. FILTER_PROPERTY_PLATFORM => 'string',
  619. FILTER_PROPERTY_OS => 'string',
  620. FILTER_PROPERTY_OS_BUILD => 'string',
  621. FILTER_PROPERTY_PROJECT_ID => 'int'
  622. );
  623. foreach( $t_array_values_list as $t_multi_field_name => $t_multi_field_type ) {
  624. if( !is_array( $p_filter_arr[$t_multi_field_name] ) ) {
  625. $p_filter_arr[$t_multi_field_name] = array(
  626. $p_filter_arr[$t_multi_field_name],
  627. );
  628. }
  629. $t_checked_array = array();
  630. foreach( $p_filter_arr[$t_multi_field_name] as $t_filter_value ) {
  631. $t_checked_array[] = $t_function_validate_type( $t_filter_value, $t_multi_field_type );
  632. }
  633. $p_filter_arr[$t_multi_field_name] = $t_checked_array;
  634. }
  635. $t_custom_fields = custom_field_get_ids();
  636. if( is_array( $t_custom_fields ) && ( count( $t_custom_fields ) > 0 ) ) {
  637. foreach( $t_custom_fields as $t_cfid ) {
  638. if( isset( $p_filter_arr['custom_fields'][$t_cfid]) ) {
  639. if( !is_array( $p_filter_arr['custom_fields'][$t_cfid] ) ) {
  640. $p_filter_arr['custom_fields'][$t_cfid] = array(
  641. $p_filter_arr['custom_fields'][$t_cfid],
  642. );
  643. }
  644. $t_checked_array = array();
  645. foreach( $p_filter_arr['custom_fields'][$t_cfid] as $t_filter_value ) {
  646. $t_filter_value = stripslashes( $t_filter_value );
  647. if( ( $t_filter_value === 'any' ) || ( $t_filter_value === '[any]' ) ) {
  648. $t_filter_value = META_FILTER_ANY;
  649. }
  650. $t_checked_array[] = $t_filter_value;
  651. }
  652. $p_filter_arr['custom_fields'][$t_cfid] = $t_checked_array;
  653. }
  654. }
  655. }
  656. # If view_type is advanced, and hide_status is present, modify status array
  657. # to remove hidden status. This may happen after switching from simple to advanced.
  658. # Then, remove hide_status property, as it does not apply to advanced filter
  659. if( $p_filter_arr['_view_type'] == FILTER_VIEW_TYPE_ADVANCED
  660. && !filter_field_is_none( $p_filter_arr[FILTER_PROPERTY_HIDE_STATUS] ) ) {
  661. if( filter_field_is_any( $p_filter_arr[FILTER_PROPERTY_STATUS] ) ) {
  662. $t_selected_status_array = MantisEnum::getValues( config_get( 'status_enum_string' ) );
  663. } else {
  664. $t_selected_status_array = $p_filter_arr[FILTER_PROPERTY_STATUS];
  665. }
  666. $t_hide_status = $p_filter_arr[FILTER_PROPERTY_HIDE_STATUS][0];
  667. $t_new_status_array = array();
  668. foreach( $t_selected_status_array as $t_status ) {
  669. if( $t_status < $t_hide_status ) {
  670. $t_new_status_array[] = $t_status;
  671. }
  672. }
  673. # If there is no status left, reset the status property to "any"
  674. if( empty( $t_new_status_array ) ) {
  675. $t_new_status_array[] = META_FILTER_ANY;
  676. }
  677. $p_filter_arr[FILTER_PROPERTY_STATUS] = $t_new_status_array;
  678. $p_filter_arr[FILTER_PROPERTY_HIDE_STATUS] = META_FILTER_NONE;
  679. }
  680. #If view_type is simple, resolve conflicts between show_status and hide_status
  681. if( $p_filter_arr['_view_type'] == FILTER_VIEW_TYPE_SIMPLE
  682. && !filter_field_is_none( $p_filter_arr[FILTER_PROPERTY_HIDE_STATUS] ) ) {
  683. # get array of hidden status ids
  684. $t_all_status = MantisEnum::getValues( config_get( 'status_enum_string' ) );
  685. $t_hidden_status = $p_filter_arr[FILTER_PROPERTY_HIDE_STATUS][0];
  686. $t_hidden_status_array = array();
  687. foreach( $t_all_status as $t_status ) {
  688. if( $t_status >= $t_hidden_status ) {
  689. $t_hidden_status_array[] = $t_status;
  690. }
  691. }
  692. # remove hidden status from show_status property array
  693. # note that this will keep the "any" meta value, if present
  694. $t_show_status_array = array_diff( $p_filter_arr[FILTER_PROPERTY_STATUS], $t_hidden_status_array );
  695. # If there is no status left, reset the status property previous values, and remove hide_status
  696. if( empty( $t_show_status_array ) ) {
  697. $t_show_status_array = $p_filter_arr[FILTER_PROPERTY_STATUS];
  698. $p_filter_arr[FILTER_PROPERTY_HIDE_STATUS] = META_FILTER_NONE;
  699. }
  700. $p_filter_arr[FILTER_PROPERTY_STATUS] = $t_show_status_array;
  701. }
  702. # validate relationship fields
  703. if( !(
  704. $p_filter_arr[FILTER_PROPERTY_RELATIONSHIP_BUG] > 0
  705. || $p_filter_arr[FILTER_PROPERTY_RELATIONSHIP_BUG] == META_FILTER_ANY
  706. || $p_filter_arr[FILTER_PROPERTY_RELATIONSHIP_BUG] == META_FILTER_NONE
  707. ) ) {
  708. $p_filter_arr[FILTER_PROPERTY_RELATIONSHIP_BUG] = filter_get_default_property( FILTER_PROPERTY_RELATIONSHIP_BUG, $t_view_type );
  709. }
  710. # all of our filter values are now guaranteed to be there, and correct.
  711. return $p_filter_arr;
  712. }
  713. /**
  714. * Get a filter array with default values
  715. * Optional view type parameter is used to initialize some fields properly,
  716. * as some may differ in the default content.
  717. * @param string $p_view_type FILTER_VIEW_TYPE_SIMPLE or FILTER_VIEW_TYPE_ADVANCED
  718. * @return array Filter array with default values
  719. */
  720. function filter_get_default_array( $p_view_type = null ) {
  721. static $t_cache_default_array = array();
  722. $t_default_view_type = filter_get_default_view_type();
  723. if( !in_array( $p_view_type, array( FILTER_VIEW_TYPE_SIMPLE, FILTER_VIEW_TYPE_ADVANCED ) ) ) {
  724. $p_view_type = $t_default_view_type;
  725. }
  726. # this function is called multiple times from filter api so return a cached value if possible
  727. if( isset( $t_cache_default_array[$p_view_type] ) ) {
  728. return $t_cache_default_array[$p_view_type];
  729. }
  730. $t_default_show_changed = config_get( 'default_show_changed' );
  731. $t_meta_filter_any_array = array( META_FILTER_ANY );
  732. $t_config_view_filters = config_get( 'view_filters' );
  733. if( ADVANCED_ONLY == $t_config_view_filters ) {
  734. $t_view_type = FILTER_VIEW_TYPE_ADVANCED;
  735. } elseif( SIMPLE_ONLY == $t_config_view_filters ) {
  736. $t_view_type = FILTER_VIEW_TYPE_SIMPLE;
  737. } else {
  738. $t_view_type = $p_view_type;
  739. }
  740. if( $t_view_type == FILTER_VIEW_TYPE_SIMPLE ) {
  741. $t_hide_status_default = config_get( 'hide_status_default' );
  742. } else {
  743. $t_hide_status_default = META_FILTER_NONE;
  744. }
  745. $t_filter = array(
  746. '_version' => FILTER_VERSION,
  747. '_view_type' => $t_view_type,
  748. FILTER_PROPERTY_CATEGORY_ID => $t_meta_filter_any_array,
  749. FILTER_PROPERTY_SEVERITY => $t_meta_filter_any_array,
  750. FILTER_PROPERTY_STATUS => $t_meta_filter_any_array,
  751. FILTER_PROPERTY_HIGHLIGHT_CHANGED => $t_default_show_changed,
  752. FILTER_PROPERTY_REPORTER_ID => $t_meta_filter_any_array,
  753. FILTER_PROPERTY_HANDLER_ID => $t_meta_filter_any_array,
  754. FILTER_PROPERTY_PROJECT_ID => array( META_FILTER_CURRENT ),
  755. FILTER_PROPERTY_RESOLUTION => $t_meta_filter_any_array,
  756. FILTER_PROPERTY_BUILD => $t_meta_filter_any_array,
  757. FILTER_PROPERTY_VERSION => $t_meta_filter_any_array,
  758. FILTER_PROPERTY_HIDE_STATUS => array( $t_hide_status_default ),
  759. FILTER_PROPERTY_MONITOR_USER_ID => $t_meta_filter_any_array,
  760. FILTER_PROPERTY_SORT_FIELD_NAME => 'last_updated',
  761. FILTER_PROPERTY_SORT_DIRECTION => 'DESC',
  762. FILTER_PROPERTY_ISSUES_PER_PAGE => config_get( 'default_limit_view' ),
  763. FILTER_PROPERTY_MATCH_TYPE => FILTER_MATCH_ALL,
  764. FILTER_PROPERTY_PLATFORM => $t_meta_filter_any_array,
  765. FILTER_PROPERTY_OS => $t_meta_filter_any_array,
  766. FILTER_PROPERTY_OS_BUILD => $t_meta_filter_any_array,
  767. FILTER_PROPERTY_FIXED_IN_VERSION => $t_meta_filter_any_array,
  768. FILTER_PROPERTY_TARGET_VERSION => $t_meta_filter_any_array,
  769. FILTER_PROPERTY_PROFILE_ID => $t_meta_filter_any_array,
  770. FILTER_PROPERTY_PRIORITY => $t_meta_filter_any_array,
  771. FILTER_PROPERTY_NOTE_USER_ID => $t_meta_filter_any_array,
  772. FILTER_PROPERTY_STICKY => gpc_string_to_bool( config_get( 'show_sticky_issues' ) ),
  773. FILTER_PROPERTY_FILTER_BY_DATE_SUBMITTED => false,
  774. FILTER_PROPERTY_DATE_SUBMITTED_START_MONTH => date( 'm' ),
  775. FILTER_PROPERTY_DATE_SUBMITTED_END_MONTH => date( 'm' ),
  776. FILTER_PROPERTY_DATE_SUBMITTED_START_DAY => 1,
  777. FILTER_PROPERTY_DATE_SUBMITTED_END_DAY => date( 'd' ),
  778. FILTER_PROPERTY_DATE_SUBMITTED_START_YEAR => date( 'Y' ),
  779. FILTER_PROPERTY_DATE_SUBMITTED_END_YEAR => date( 'Y' ),
  780. FILTER_PROPERTY_FILTER_BY_LAST_UPDATED_DATE => false,
  781. FILTER_PROPERTY_LAST_UPDATED_START_MONTH => date( 'm' ),
  782. FILTER_PROPERTY_LAST_UPDATED_END_MONTH => date( 'm' ),
  783. FILTER_PROPERTY_LAST_UPDATED_START_DAY => 1,
  784. FILTER_PROPERTY_LAST_UPDATED_END_DAY => date( 'd' ),
  785. FILTER_PROPERTY_LAST_UPDATED_START_YEAR => date( 'Y' ),
  786. FILTER_PROPERTY_LAST_UPDATED_END_YEAR => date( 'Y' ),
  787. FILTER_PROPERTY_SEARCH => '',
  788. FILTER_PROPERTY_VIEW_STATE => META_FILTER_ANY,
  789. FILTER_PROPERTY_TAG_STRING => '',
  790. FILTER_PROPERTY_TAG_SELECT => 0,
  791. FILTER_PROPERTY_RELATIONSHIP_TYPE => BUG_REL_ANY,
  792. FILTER_PROPERTY_RELATIONSHIP_BUG => META_FILTER_ANY,
  793. );
  794. # initialize plugin filters
  795. $t_plugin_filters = filter_get_plugin_filters();
  796. foreach( $t_plugin_filters as $t_field_name => $t_filter_object ) {
  797. switch( $t_filter_object->type ) {
  798. case FILTER_TYPE_STRING:
  799. $t_filter[$t_field_name] = $t_filter_object->default;
  800. break;
  801. case FILTER_TYPE_INT:
  802. $t_filter[$t_field_name] = (int)$t_filter_object->default;
  803. break;
  804. case FILTER_TYPE_BOOLEAN:
  805. $t_filter[$t_field_name] = (bool)$t_filter_object->default;
  806. break;
  807. case FILTER_TYPE_MULTI_STRING:
  808. $t_filter[$t_field_name] = array( (string)META_FILTER_ANY );
  809. break;
  810. case FILTER_TYPE_MULTI_INT:
  811. $t_filter[$t_field_name] = array( META_FILTER_ANY );
  812. break;
  813. default:
  814. $t_filter[$t_field_name] = (string)META_FILTER_ANY;
  815. }
  816. if( !$t_filter_object->validate( $t_filter[$t_field_name] ) ) {
  817. $t_filter[$t_field_name] = $t_filter_object->default;
  818. }
  819. }
  820. $t_custom_fields = custom_field_get_ids();
  821. # @@@ (thraxisp) This should really be the linked ids, but we don't know the project
  822. $f_custom_fields_data = array();
  823. if( is_array( $t_custom_fields ) && ( count( $t_custom_fields ) > 0 ) ) {
  824. foreach( $t_custom_fields as $t_cfid ) {
  825. $f_custom_fields_data[$t_cfid] = array( (string)META_FILTER_ANY );
  826. }
  827. }
  828. $t_filter['custom_fields'] = $f_custom_fields_data;
  829. $t_cache_default_array[$p_view_type] = $t_filter;
  830. return $t_filter;
  831. }
  832. /**
  833. * Returns the default view type for filters
  834. * @return string Default view type
  835. */
  836. function filter_get_default_view_type() {
  837. if( ADVANCED_DEFAULT == config_get( 'view_filters' ) ) {
  838. return FILTER_VIEW_TYPE_ADVANCED;
  839. } else {
  840. return FILTER_VIEW_TYPE_SIMPLE;
  841. }
  842. }
  843. /**
  844. * Returns the default value for a filter property.
  845. * Relies on filter_get_default_array() to get a defaulted filter.
  846. * @param string $p_filter_property The requested filter property name
  847. * @param string $p_view_type Optional, view type for the defaulted filter (simple/advanced)
  848. * @return mixed The property default value, or null if it doesn't exist
  849. */
  850. function filter_get_default_property( $p_filter_property, $p_view_type = null ) {
  851. $t_default_array = filter_get_default_array( $p_view_type );
  852. if( isset( $t_default_array[$p_filter_property] ) ) {
  853. return $t_default_array[$p_filter_property];
  854. } else {
  855. return null;
  856. }
  857. }
  858. /**
  859. * Get the standard filter that is to be used when no filter was previously saved.
  860. * When creating specific filters, this can be used as a basis for the filter, where
  861. * specific entries can be overridden.
  862. * @return mixed
  863. */
  864. function filter_get_default() {
  865. # Create empty array, validation will fill it with defaults
  866. $t_filter = array();
  867. return filter_ensure_valid_filter( $t_filter );
  868. }
  869. /**
  870. * Deserialize filter string
  871. * Expected strings have this format: "<version>#<json string>" where:
  872. * - <version> is the versio number of the filter structure used. See constant FILTER_VERSION
  873. * - # is a separator
  874. * - <json string> is the json encoded filter array.
  875. * @param string $p_serialized_filter Serialized filter string.
  876. * @return mixed $t_filter array
  877. * @see filter_ensure_valid_filter
  878. */
  879. function filter_deserialize( $p_serialized_filter ) {
  880. if( is_blank( $p_serialized_filter ) ) {
  881. return false;
  882. }
  883. #@TODO cproensa, we could accept a pure json array, without version prefix
  884. # in this case, the filter version field inside the array is to be used
  885. # and if not present, set the current filter version
  886. # check filter version mark
  887. $t_setting_arr = explode( '#', $p_serialized_filter, 2 );
  888. $t_version_string = $t_setting_arr[0];
  889. if( in_array( $t_version_string, array( 'v1', 'v2', 'v3', 'v4' ) ) ) {
  890. # these versions can't be salvaged, they are too old to update
  891. return false;
  892. } elseif( in_array( $t_version_string, array( 'v5', 'v6', 'v7', 'v8' ) ) ) {
  893. # filters from v5 onwards should cope with changing filter indices dynamically
  894. $t_filter_array = unserialize( $t_setting_arr[1] );
  895. } else {
  896. # filters from v9 onwards are stored as json
  897. $t_filter_array = json_decode( $t_setting_arr[1], /* assoc array */ true );
  898. }
  899. # If the unserialez data is not an array, the some error happened, eg, invalid format
  900. if( !is_array( $t_filter_array ) ) {
  901. return false;
  902. }
  903. # Set the filter version that was loaded in the array
  904. $t_filter_array['_version'] = $t_setting_arr[0];
  905. # If upgrade in filter content is needed, it will be done in filter_ensure_valid_filter()
  906. return filter_ensure_valid_filter( $t_filter_array );
  907. }
  908. /**
  909. * Creates a serialized filter with the correct format
  910. * @param array $p_filter_array Filter array to be serialized
  911. * @return string Serialized filter string
  912. */
  913. function filter_serialize( $p_filter_array ) {
  914. $t_cookie_version = FILTER_VERSION;
  915. $p_filter_array = filter_clean_runtime_properties( $p_filter_array );
  916. $t_settings_serialized = json_encode( $p_filter_array );
  917. $t_settings_string = $t_cookie_version . '#' . $t_settings_serialized;
  918. return $t_settings_string;
  919. }
  920. /**
  921. * Get the filter db row $p_filter_id
  922. * using the cached row if it's available
  923. * @global array $g_cache_filter_db_rows
  924. * @param integer $p_filter_id A filter identifier to look up in the database.
  925. * @return array|boolean The row of filter data as stored in db table, or false if does not exist
  926. */
  927. function filter_get_row( $p_filter_id ) {
  928. global $g_cache_filter_db_rows;
  929. if( !isset( $g_cache_filter_db_rows[$p_filter_id] ) ) {
  930. filter_cache_rows( array($p_filter_id) );
  931. }
  932. $t_row = $g_cache_filter_db_rows[$p_filter_id];
  933. return $t_row;
  934. }
  935. /**
  936. * Get the value of the filter field specified by filter id and field name
  937. * @param integer $p_filter_id A filter identifier to look up in the database.
  938. * @param string $p_field_name Name of the filter field to retrieve.
  939. * @return string
  940. */
  941. function filter_get_field( $p_filter_id, $p_field_name ) {
  942. $t_row = filter_get_row( $p_filter_id );
  943. if( isset( $t_row[$p_field_name] ) ) {
  944. return $t_row[$p_field_name];
  945. } else {
  946. error_parameters( $p_field_name );
  947. trigger_error( ERROR_DB_FIELD_NOT_FOUND, WARNING );
  948. return '';
  949. }
  950. }
  951. /**
  952. * Add sort parameters to the query clauses
  953. * @param array &$p_filter Filter to sort.
  954. * @param boolean $p_show_sticky Whether to show sticky items.
  955. * @param array $p_query_clauses Array of query clauses.
  956. * @return array $p_query_clauses
  957. *
  958. * @deprecated Use BugFilterQuery class
  959. */
  960. function filter_get_query_sort_data( array &$p_filter, $p_show_sticky, array $p_query_clauses ) {
  961. error_parameters( __FUNCTION__ . '()', 'BugFilterQuery class' );
  962. trigger_error( ERROR_DEPRECATED_SUPERSEDED, DEPRECATED );
  963. $p_query_clauses['order'] = array();
  964. # Get only the visible, and sortable, column properties
  965. # @TODO cproensa: this defaults to COLUMNS_TARGET_VIEW_PAGE
  966. # are we sure that filters are only used with the column set for view page?
  967. $p_sort_properties = filter_get_visible_sort_properties_array( $p_filter );
  968. $t_sort_fields = $p_sort_properties[FILTER_PROPERTY_SORT_FIELD_NAME];
  969. $t_dir_fields = $p_sort_properties[FILTER_PROPERTY_SORT_DIRECTION];
  970. if( gpc_string_to_bool( $p_filter[FILTER_PROPERTY_STICKY] ) && ( null !== $p_show_sticky ) ) {
  971. $p_query_clauses['order'][] = '{bug}.sticky DESC';
  972. }
  973. $t_included_project_ids = $p_query_clauses['metadata']['included_projects'];
  974. $t_user_id = $p_query_clauses['metadata']['user_id'];
  975. $t_count = count( $t_sort_fields );
  976. for( $i = 0; $i < $t_count; $i++ ) {
  977. $c_sort = $t_sort_fields[$i];
  978. $c_dir = 'DESC' == $t_dir_fields[$i] ? 'DESC' : 'ASC';
  979. # if sorting by a custom field
  980. if( column_is_custom_field( $c_sort ) ) {
  981. $t_custom_field = column_get_custom_field_name( $c_sort );
  982. $t_custom_field_id = custom_field_get_id_from_name( $t_custom_field );
  983. $t_def = custom_field_get_definition( $t_custom_field_id );
  984. $t_value_field = ( $t_def['type'] == CUSTOM_FIELD_TYPE_TEXTAREA ? 'text' : 'value' );
  985. $t_table_name = '';
  986. # if the custom field was filtered, there is already a calculated join, so reuse that table alias
  987. # otherwise, a new join must be calculated
  988. if( isset( $p_query_clauses['metadata']['cf_alias'][$t_custom_field_id] ) ) {
  989. $t_table_name = $p_query_clauses['metadata']['cf_alias'][$t_custom_field_id];
  990. } else {
  991. # @TODO This code for CF visibility is the same as filter_get_bug_rows_query_clauses()
  992. # It should be encapsulated and reused
  993. $t_searchable_projects = array_intersect( $t_included_project_ids, custom_field_get_project_ids( $t_custom_field_id ) );
  994. $t_projects_can_view_field = access_project_array_filter( (int)$t_def['access_level_r'], $t_searchable_projects, $t_user_id );
  995. if( empty( $t_projects_can_view_field ) ) {
  996. continue;
  997. }
  998. $t_table_name = 'cf_sort_' . $t_custom_field_id;
  999. $t_cf_join_clause = 'LEFT OUTER JOIN {custom_field_string} ' . $t_table_name . ' ON {bug}.id = ' . $t_table_name . '.bug_id AND ' . $t_table_name . '.field_id = ' . $t_custom_field_id;
  1000. # This diff will contain those included projects that can't view this custom field
  1001. $t_diff = array_diff( $t_included_project_ids, $t_projects_can_view_field );
  1002. # If not empty, it means there are some projects that can't view the field values,
  1003. # so a project filter must be used to not include values from those projects
  1004. if( !empty( $t_diff ) ) {
  1005. $t_cf_join_clause .= ' AND {bug}.project_id IN (' . implode( ',', $t_projects_can_view_field ) . ')';
  1006. }
  1007. $p_query_clauses['metadata']['cf_alias'][$t_custom_field_id] = $t_table_name;
  1008. $p_query_clauses['join'][] = $t_cf_join_clause;
  1009. }
  1010. # if no join can be used (eg, no view access), skip this field from the order clause
  1011. if( empty( $t_table_name ) ) {
  1012. continue;
  1013. }
  1014. $t_field_alias = 'cf_sortfield_' . $t_custom_field_id;
  1015. $t_sort_col = $t_table_name . '.' . $t_value_field;
  1016. # which types need special type cast
  1017. switch( $t_def['type'] ) {
  1018. case CUSTOM_FIELD_TYPE_FLOAT:
  1019. # mysql can't cast to float, use alternative syntax
  1020. $t_sort_expr = db_is_mysql() ? $t_sort_col . '+0.0' : 'CAST(NULLIF(' . $t_sort_col . ',\'\') AS FLOAT)';
  1021. break;
  1022. case CUSTOM_FIELD_TYPE_DATE:
  1023. case CUSTOM_FIELD_TYPE_NUMERIC:
  1024. $t_sort_expr = 'CAST(NULLIF(' . $t_sort_col . ',\'\') AS DECIMAL)';
  1025. break;
  1026. default: # no cast needed
  1027. $t_sort_expr = $t_sort_col;
  1028. }
  1029. # which types need special treatment for null sorting
  1030. switch( $t_def['type'] ) {
  1031. case CUSTOM_FIELD_TYPE_DATE:
  1032. case CUSTOM_FIELD_TYPE_NUMERIC:
  1033. case CUSTOM_FIELD_TYPE_FLOAT:
  1034. $t_null_last = true;
  1035. break;
  1036. default:
  1037. $t_null_last = false;
  1038. }
  1039. if( $t_null_last ) {
  1040. $t_null_expr = 'CASE WHEN NULLIF(' . $t_sort_col . ', \'\') IS NULL THEN 1 ELSE 0 END';
  1041. $t_clause_for_select = $t_null_expr . ' AS ' . $t_field_alias . '_null';
  1042. $t_clause_for_select .= ', ' . $t_sort_expr . ' AS ' . $t_field_alias;
  1043. $t_clause_for_order = $t_field_alias . '_null ASC, ' . $t_field_alias . ' ' . $c_dir;
  1044. } else {
  1045. $t_clause_for_select = $t_sort_expr . ' AS ' . $t_field_alias;
  1046. $t_clause_for_order = $t_field_alias . ' ' . $c_dir;
  1047. }
  1048. # Note: pgsql needs the sort expression to appear as member of the "select distinct"
  1049. $p_query_clauses['select'][] = $t_clause_for_select;
  1050. $p_query_clauses['order'][] = $t_clause_for_order;
  1051. # if sorting by plugin columns
  1052. } else if( column_is_plugin_column( $c_sort ) ) {
  1053. $t_plugin_columns = columns_get_plugin_columns();
  1054. $t_column_object = $t_plugin_columns[$c_sort];
  1055. $t_clauses = $t_column_object->sortquery( $c_dir );
  1056. if( is_array( $t_clauses ) ) {
  1057. if( isset( $t_clauses['join'] ) ) {
  1058. $p_query_clauses['join'][] = $t_clauses['join'];
  1059. }
  1060. if( isset( $t_clauses['order'] ) ) {
  1061. $p_query_clauses['order'][] = $t_clauses['order'];
  1062. }
  1063. }
  1064. # standard column
  1065. } else {
  1066. $t_sort_col = '{bug}.' . $c_sort;
  1067. # When sorting by due_date, always display undefined dates last.
  1068. # Undefined date is defaulted as "1" in database, so add a special
  1069. # sort clause to group and sort by this.
  1070. if( 'due_date' == $c_sort && 'ASC' == $c_dir ) {
  1071. $t_null_expr = 'CASE ' . $t_sort_col . ' WHEN 1 THEN 1 ELSE 0 END';
  1072. $p_query_clauses['select'][] = $t_null_expr . ' AS due_date_sort_null';
  1073. $p_query_clauses['order'][] = 'due_date_sort_null ASC';
  1074. }
  1075. # main sort clause for due date
  1076. $p_query_clauses['order'][] = $t_sort_col . ' ' .$c_dir;
  1077. }
  1078. }
  1079. # add basic sorting if necessary
  1080. if( !in_array( 'last_updated', $t_sort_fields ) ) {
  1081. $p_query_clauses['order'][] = '{bug}.last_updated DESC';
  1082. }
  1083. if( !in_array( 'date_submitted', $t_sort_fields ) ) {
  1084. $p_query_clauses['order'][] = '{bug}.date_submitted DESC';
  1085. }
  1086. return $p_query_clauses;
  1087. }
  1088. /**
  1089. * Remove any duplicate values in certain elements of query_clauses
  1090. * Do not loop over query clauses as some keys may contain valid duplicate values.
  1091. * We basically want unique values for just the base query elements select, from, and join
  1092. * 'where' and 'where_values' key should not have duplicates as that is handled earlier and applying
  1093. * array_unique here could cause problems with the query.
  1094. * @param array $p_query_clauses Array of query clauses.
  1095. * @return array
  1096. *
  1097. * @deprecated Use BugFilterQuery class
  1098. */
  1099. function filter_unique_query_clauses( array $p_query_clauses ) {
  1100. error_parameters( __FUNCTION__ . '()', 'BugFilterQuery class' );
  1101. trigger_error( ERROR_DEPRECATED_SUPERSEDED, DEPRECATED );
  1102. $p_query_clauses['select'] = array_unique( $p_query_clauses['select'] );
  1103. $p_query_clauses['from'] = array_unique( $p_query_clauses['from'] );
  1104. $p_query_clauses['join'] = array_unique( $p_query_clauses['join'] );
  1105. return $p_query_clauses;
  1106. }
  1107. /**
  1108. * Build a query with the query clauses array, query for bug count and return the result
  1109. *
  1110. * Note: The parameter $p_pop_param can be used as 'false' to keep db_params in the stack,
  1111. * if the same query clauses object is reused for several queries. In that case a db_param_pop()
  1112. * should be used manually when required.
  1113. * This is the case when "filter_get_bug_count" is used followed by "filter_get_bug_rows_result"
  1114. * @param array $p_query_clauses Array of query clauses.
  1115. * @param boolean $p_pop_param Whether to pop DB params from the stack
  1116. * @return integer
  1117. *
  1118. * @deprecated Use BugFilterQuery class
  1119. */
  1120. function filter_get_bug_count( array $p_query_clauses, $p_pop_param = true ) {
  1121. error_parameters( __FUNCTION__ . '()', 'BugFilterQuery class' );
  1122. trigger_error( ERROR_DEPRECATED_SUPERSEDED, DEPRECATED );
  1123. error_parameters( __FUNCTION__ . '()', 'BugFilterQuery class' );
  1124. trigger_error( ERROR_DEPRECATED_SUPERSEDED, DEPRECATED );
  1125. # If query clauses is an empty array, the query can't be created
  1126. if( empty( $p_query_clauses ) ) {
  1127. if( $p_pop_param ) {
  1128. # reset the db_param stack, this woould have been done by db_query if executed
  1129. db_param_pop();
  1130. }
  1131. return 0;
  1132. }
  1133. $p_query_clauses = filter_unique_query_clauses( $p_query_clauses );
  1134. $t_select_string = 'SELECT Count( DISTINCT {bug}.id ) as idcnt ';
  1135. $t_from_string = ' FROM ' . implode( ', ', $p_query_clauses['from'] );
  1136. $t_join_string = (( count( $p_query_clauses['join'] ) > 0 ) ? implode( ' ', $p_query_clauses['join'] ) : '' );
  1137. $t_where_string = count( $p_query_clauses['project_where'] ) > 0 ? 'WHERE '. implode( ' AND ', $p_query_clauses['project_where'] ) : '';
  1138. if( count( $p_query_clauses['where'] ) > 0 ) {
  1139. $t_where_string .= ' AND ( ';
  1140. $t_where_string .= implode( $p_query_clauses['operator'], $p_query_clauses['where'] );
  1141. $t_where_string .= ' ) ';
  1142. }
  1143. $t_result = db_query(
  1144. $t_select_string . ' ' . $t_from_string . ' ' . $t_join_string . ' ' . $t_where_string,
  1145. $p_query_clauses['where_values'],
  1146. /* limit */ -1, /* offset */ -1,
  1147. $p_pop_param );
  1148. return db_result( $t_result );
  1149. }
  1150. /**
  1151. * Get set of bug rows from given filter
  1152. * @todo Had to make all these parameters required because we can't use call-time pass by reference anymore.
  1153. * I really preferred not having to pass all the params in if you didn't want to, but I wanted to get
  1154. * rid of the errors for now. If we can think of a better way later (maybe return an object) that would be great.
  1155. *
  1156. * @param integer &$p_page_number Page number of the page you want to see (set to the actual page on return).
  1157. * @param integer &$p_per_page The number of bugs to see per page (set to actual on return)
  1158. * -1 indicates you want to see all bugs
  1159. * null indicates you want to use the value specified in the filter.
  1160. * @param integer &$p_page_count You don't need to give a value here, the number of pages will be stored here on return.
  1161. * @param integer &$p_bug_count You don't need to give a value here, the number of bugs will be stored here on return.
  1162. * @param mixed $p_custom_filter Custom Filter to use.
  1163. * @param integer $p_project_id Project id to use in filtering.
  1164. * @param integer $p_user_id User id to use as current user when filtering.
  1165. * @param boolean $p_show_sticky True/false - get sticky issues only.
  1166. * @return boolean|array
  1167. */
  1168. function filter_get_bug_rows( &$p_page_number, &$p_per_page, &$p_page_count, &$p_bug_count, $p_custom_filter = null, $p_project_id = null, $p_user_id = null, $p_show_sticky = null ) {
  1169. # assigning to $p_* for this function writes the values back in case the caller wants to know
  1170. if( $p_custom_filter === null ) {
  1171. $t_filter = filter_get_bug_rows_filter( $p_project_id, $p_user_id );
  1172. } else {
  1173. $t_filter = filter_ensure_valid_filter( $p_custom_filter );
  1174. }
  1175. # build a filter query, here for counting results
  1176. $t_filter_query = new BugFilterQuery(
  1177. $t_filter,
  1178. array(
  1179. 'query_type' => BugFilterQuery::QUERY_TYPE_LIST,
  1180. 'project_id' => $p_project_id,
  1181. 'user_id' => $p_user_id,
  1182. 'use_sticky' => $p_show_sticky
  1183. )
  1184. );
  1185. $p_bug_count = $t_filter_query->get_bug_count();
  1186. if( 0 == $p_bug_count ) {
  1187. return array();
  1188. }
  1189. # Calculate pagination
  1190. $p_per_page = filter_per_page( $t_filter, $p_bug_count, $p_per_page );
  1191. $p_page_count = filter_page_count( $p_bug_count, $p_per_page );
  1192. $p_page_number = filter_valid_page_number( $p_page_number, $p_page_count );
  1193. $t_offset = filter_offset( $p_page_number, $p_per_page );
  1194. $t_filter_query->set_limit( $p_per_page );
  1195. $t_filter_query->set_offset( $t_offset );
  1196. # Execute query
  1197. $t_rows = $t_filter_query->fetch_all();
  1198. $t_bug_id_array = array_column( $t_rows, 'id' );
  1199. # Return the processed rows: cache data, convert to bug objects
  1200. return filter_cache_result( $t_rows, $t_bug_id_array );
  1201. }
  1202. /**
  1203. * Get the filter defined by user and project.
  1204. * @param integer $p_project_id Project id to use in filtering.
  1205. * @param integer $p_user_id User id to use as current user when filtering.
  1206. * @return array
  1207. */
  1208. function filter_get_bug_rows_filter( $p_project_id = null, $p_user_id = null ) {
  1209. $t_current_user_id = auth_get_current_user_id();
  1210. if( $p_user_id === null || $p_user_id === 0 ) {
  1211. $t_user_id = $t_current_user_id;
  1212. } else {
  1213. $t_user_id = $p_user_id;
  1214. }
  1215. if( null === $p_project_id ) {
  1216. # @@@ If project_id is not specified, then use the project id(s) in the filter if set, otherwise, use current project.
  1217. $t_project_id = helper_get_current_project();
  1218. } else {
  1219. $t_project_id = $p_project_id;
  1220. }
  1221. if( $t_user_id == $t_current_user_id ) {
  1222. $t_filter = current_user_get_bug_filter();
  1223. } else {
  1224. $t_filter = user_get_bug_filter( $t_user_id, $t_project_id );
  1225. }
  1226. # if filter isn't return above, create a new filter from an empty array.
  1227. if( false === $t_filter ) {
  1228. $t_filter = array();
  1229. }
  1230. return $t_filter;
  1231. }
  1232. /**
  1233. * Creates a sql query with the supplied filter query clauses, and returns the unprocessed result set opbject
  1234. *
  1235. * Note: The parameter $p_pop_param can be used as 'false' to keep db_params in the stack,
  1236. * if the same query clauses object is reused for several queries. In that case a db_param_pop()
  1237. * should be used manually when required.
  1238. * This is the case when "filter_get_bug_count" is used followed by "filter_get_bug_rows_result"
  1239. * @param array $p_query_clauses Array of query clauses
  1240. * @param integer $p_count The number of rows to return
  1241. * -1 or null indicates default query (no limits)
  1242. * @param integer $p_offset Offset query results for paging (number of rows)
  1243. * -1 or null indicates default query (no offset)
  1244. * @param boolean $p_pop_param Whether to pop DB params from the stack
  1245. * @return IteratorAggregate|boolean adodb result set or false if the query failed.
  1246. *
  1247. * @deprecated Use BugFilterQuery class
  1248. */
  1249. function filter_get_bug_rows_result( array $p_query_clauses, $p_count = null, $p_offset = null, $p_pop_param = true ) {
  1250. error_parameters( __FUNCTION__ . '()', 'BugFilterQuery class' );
  1251. trigger_error( ERROR_DEPRECATED_SUPERSEDED, DEPRECATED );
  1252. # if the query can't be formed, there are no results
  1253. if( empty( $p_query_clauses ) ) {
  1254. if( $p_pop_param ) {
  1255. # reset the db_param stack, this woould have been done by db_query if executed
  1256. db_param_pop();
  1257. }
  1258. return db_empty_result();
  1259. }
  1260. if( null === $p_count ) {
  1261. $t_count = -1;
  1262. } else {
  1263. $t_count = $p_count;
  1264. }
  1265. if( null === $p_offset ) {
  1266. $t_offset = -1;
  1267. } else {
  1268. $t_offset = $p_offset;
  1269. }
  1270. $t_query_clauses = $p_query_clauses;
  1271. $t_select_string = 'SELECT DISTINCT ' . implode( ', ', $t_query_clauses['select'] );
  1272. $t_from_string = ' FROM ' . implode( ', ', $t_query_clauses['from'] );
  1273. $t_order_string = ' ORDER BY ' . implode( ', ', $t_query_clauses['order'] );
  1274. $t_join_string = count( $t_query_clauses['join'] ) > 0 ? implode( ' ', $t_query_clauses['join'] ) : ' ';
  1275. $t_where_string = ' WHERE '. implode( ' AND ', $t_query_clauses['project_where'] );
  1276. if( count( $t_query_clauses['where'] ) > 0 ) {
  1277. $t_where_string .= ' AND ( ';
  1278. $t_where_string .= implode( $t_query_clauses['operator'], $t_query_clauses['where'] );
  1279. $t_where_string .= ' ) ';
  1280. }
  1281. $t_result = db_query(
  1282. $t_select_string . $t_from_string . $t_join_string . $t_where_string . $t_order_string,
  1283. $t_query_clauses['where_values'],
  1284. $t_count,
  1285. $t_offset,
  1286. $p_pop_param
  1287. );
  1288. return $t_result;
  1289. }
  1290. /**
  1291. * Creates an array of formatted query clauses, based on the supplied
  1292. * filter and parameters.
  1293. * Note: this function executes db_param_push():
  1294. * - If the returned query is not executed, db_param_pop() should be executed
  1295. * to clean up the parameter stack
  1296. * - If the final query adds db_param() outside of this function,
  1297. * they must be added after this function is called.
  1298. * @param array $p_filter Filter array object
  1299. * @param integer $p_project_id Project id to use in filtering.
  1300. * @param integer $p_user_id User id to use as current user when filtering.
  1301. * @param boolean $p_show_sticky True/false - get sticky issues only.
  1302. * @return array
  1303. *
  1304. * @deprecated Use BugFilterQuery class
  1305. */
  1306. function filter_get_bug_rows_query_clauses( array $p_filter, $p_project_id = null, $p_user_id = null, $p_show_sticky = null ) {
  1307. error_parameters( __FUNCTION__ . '()', 'BugFilterQuery class' );
  1308. trigger_error( ERROR_DEPRECATED_SUPERSEDED, DEPRECATED );
  1309. log_event( LOG_FILTERING, 'START NEW FILTER QUERY' );
  1310. $t_limit_reporters = config_get( 'limit_reporters' );
  1311. $t_report_bug_threshold = config_get( 'report_bug_threshold' );
  1312. $t_current_user_id = auth_get_current_user_id();
  1313. if( $p_user_id === null || $p_user_id === 0 ) {
  1314. $t_user_id = $t_current_user_id;
  1315. } else {
  1316. $t_user_id = $p_user_id;
  1317. }
  1318. $c_user_id = (int)$t_user_id;
  1319. if( null === $p_project_id ) {
  1320. # @@@ If project_id is not specified, then use the project id(s) in the filter if set, otherwise, use current project.
  1321. $t_project_id = helper_get_current_project();
  1322. } else {
  1323. $t_project_id = $p_project_id;
  1324. }
  1325. $t_filter = filter_ensure_valid_filter( $p_filter );
  1326. db_param_push();
  1327. # project query clauses must be AND-ed always, irrespective of how the filter
  1328. # clauses are requested by the user ( all matching -> AND, any matching -> OR )
  1329. $t_where_clauses = array();
  1330. $t_project_where_clauses = array( '{project}.enabled = ' . db_param() );
  1331. $t_where_params = array(
  1332. 1,
  1333. );
  1334. $t_select_clauses = array(
  1335. '{bug}.*',
  1336. );
  1337. $t_from_clauses = array(
  1338. '{bug}',
  1339. );
  1340. $t_join_clauses = array(
  1341. ' JOIN {project} ON {project}.id = {bug}.project_id',
  1342. );
  1343. # Metadata array will store information needed at later points, for example,
  1344. # when calculating the sort clauses.
  1345. $t_metadata = array();
  1346. $t_metadata['user_id'] = $c_user_id;
  1347. $t_projects_query_required = true;
  1348. $t_included_project_ids = filter_get_included_projects( $t_filter, $t_project_id, $t_user_id, true /* return all projects */ );
  1349. if( ALL_PROJECTS == $t_included_project_ids ) {
  1350. # The list of expanded projects is needed later even if project_query is not required
  1351. $t_included_project_ids = filter_get_included_projects( $t_filter, $t_project_id, $t_user_id, false /* return all projects */ );
  1352. # this special case can skip the projects query clause:
  1353. if( user_is_administrator( $t_user_id ) ) {
  1354. log_event( LOG_FILTERING, 'all projects + administrator, hence no project filter.' );
  1355. $t_projects_query_required = false;
  1356. }
  1357. }
  1358. $t_metadata['included_projects'] = $t_included_project_ids;
  1359. if( $t_projects_query_required ) {
  1360. # if no projects are accessible, then return an empty array.
  1361. if( count( $t_included_project_ids ) == 0 ) {
  1362. log_event( LOG_FILTERING, 'no accessible projects' );
  1363. return array();
  1364. }
  1365. # this array is to be populated with project ids for which we only want to show public issues. This is due to the limited
  1366. # access of the current user.
  1367. $t_public_only_project_ids = array();
  1368. # this array is populated with project ids that the current user has full access to.
  1369. $t_private_and_public_project_ids = array();
  1370. $t_limited_projects = array();
  1371. # make sure the project rows are cached, as they will be used to check access levels.
  1372. project_cache_array_rows( $t_included_project_ids );
  1373. foreach( $t_included_project_ids as $t_pid ) {
  1374. # limit reporters to visible projects
  1375. if( ( ON === $t_limit_reporters ) && ( !access_has_project_level( access_threshold_min_level( config_get( 'report_bug_threshold', null, $t_user_id, $t_pid ) ) + 1, $t_pid, $t_user_id ) ) ) {
  1376. array_push( $t_limited_projects, '({bug}.project_id=' . $t_pid . ' AND ({bug}.reporter_id=' . $t_user_id . ') )' );
  1377. } else {
  1378. $t_access_required_to_view_private_bugs = config_get( 'private_bug_threshold', null, null, $t_pid );
  1379. if( access_has_project_level( $t_access_required_to_view_private_bugs, $t_pid, $t_user_id ) ) {
  1380. $t_private_and_public_project_ids[] = $t_pid;
  1381. } else {
  1382. $t_public_only_project_ids[] = $t_pid;
  1383. }
  1384. }
  1385. }
  1386. log_event( LOG_FILTERING, 'project_ids (with public/private access) = @P' . implode( ', @P', $t_private_and_public_project_ids ) );
  1387. log_event( LOG_FILTERING, 'project_ids (with public access) = @P' . implode( ', @P', $t_public_only_project_ids ) );
  1388. $t_count_private_and_public_project_ids = count( $t_private_and_public_project_ids );
  1389. if( $t_count_private_and_public_project_ids == 1 ) {
  1390. $t_private_and_public_query = '( {bug}.project_id = ' . $t_private_and_public_project_ids[0] . ' )';
  1391. } else if( $t_count_private_and_public_project_ids > 1 ) {
  1392. $t_private_and_public_query = '( {bug}.project_id in (' . implode( ', ', $t_private_and_public_project_ids ) . ') )';
  1393. } else {
  1394. $t_private_and_public_query = null;
  1395. }
  1396. $t_count_public_only_project_ids = count( $t_public_only_project_ids );
  1397. $t_public_view_state_check = '( ( {bug}.view_state = ' . VS_PUBLIC . ' ) OR ( {bug}.reporter_id = ' . $t_user_id . ') )';
  1398. if( $t_count_public_only_project_ids == 1 ) {
  1399. $t_public_only_query = '( ( {bug}.project_id = ' . $t_public_only_project_ids[0] . ' ) AND ' . $t_public_view_state_check . ')';
  1400. } else if( $t_count_public_only_project_ids > 1 ) {
  1401. $t_public_only_query = '( ( {bug}.project_id in (' . implode( ', ', $t_public_only_project_ids ) . ') ) AND ' . $t_public_view_state_check . ')';
  1402. } else {
  1403. $t_public_only_query = null;
  1404. }
  1405. # both queries can't be null, so we either have one of them or both.
  1406. if( $t_private_and_public_query === null ) {
  1407. $t_project_query = $t_public_only_query;
  1408. } else if( $t_public_only_query === null ) {
  1409. $t_project_query = $t_private_and_public_query;
  1410. } else {
  1411. $t_project_query = '( ' . $t_public_only_query . ' OR ' . $t_private_and_public_query . ' )';
  1412. }
  1413. if( !empty( $t_limited_projects ) ) {
  1414. foreach( $t_limited_projects as $t_string ) {
  1415. if( $t_project_query == "" ) {
  1416. $t_project_query = " ( $t_string ) ";
  1417. } else {
  1418. $t_project_query = " ( $t_project_query OR ( $t_string ) )";
  1419. }
  1420. }
  1421. }
  1422. log_event( LOG_FILTERING, 'project query = ' . $t_project_query );
  1423. array_push( $t_project_where_clauses, $t_project_query );
  1424. }
  1425. # creation date filter
  1426. if( ( 'on' == $t_filter[FILTER_PROPERTY_FILTER_BY_DATE_SUBMITTED] )
  1427. && is_numeric( $t_filter[FILTER_PROPERTY_DATE_SUBMITTED_START_MONTH] )
  1428. && is_numeric( $t_filter[FILTER_PROPERTY_DATE_SUBMITTED_START_DAY] )
  1429. && is_numeric( $t_filter[FILTER_PROPERTY_DATE_SUBMITTED_START_YEAR] )
  1430. && is_numeric( $t_filter[FILTER_PROPERTY_DATE_SUBMITTED_END_MONTH] )
  1431. && is_numeric( $t_filter[FILTER_PROPERTY_DATE_SUBMITTED_END_DAY] )
  1432. && is_numeric( $t_filter[FILTER_PROPERTY_DATE_SUBMITTED_END_YEAR] )
  1433. ) {
  1434. $t_start_string = $t_filter[FILTER_PROPERTY_DATE_SUBMITTED_START_YEAR] . '-' . $t_filter[FILTER_PROPERTY_DATE_SUBMITTED_START_MONTH] . '-' . $t_filter[FILTER_PROPERTY_DATE_SUBMITTED_START_DAY] . ' 00:00:00';
  1435. $t_end_string = $t_filter[FILTER_PROPERTY_DATE_SUBMITTED_END_YEAR] . '-' . $t_filter[FILTER_PROPERTY_DATE_SUBMITTED_END_MONTH] . '-' . $t_filter[FILTER_PROPERTY_DATE_SUBMITTED_END_DAY] . ' 23:59:59';
  1436. $t_where_params[] = strtotime( $t_start_string );
  1437. $t_where_params[] = strtotime( $t_end_string );
  1438. array_push( $t_project_where_clauses, '({bug}.date_submitted BETWEEN ' . db_param() . ' AND ' . db_param() . ' )' );
  1439. }
  1440. # last update date filter
  1441. if( ( 'on' == $t_filter[FILTER_PROPERTY_FILTER_BY_LAST_UPDATED_DATE] )
  1442. && is_numeric( $t_filter[FILTER_PROPERTY_LAST_UPDATED_START_MONTH] )
  1443. && is_numeric( $t_filter[FILTER_PROPERTY_LAST_UPDATED_START_DAY] )
  1444. && is_numeric( $t_filter[FILTER_PROPERTY_LAST_UPDATED_START_YEAR] )
  1445. && is_numeric( $t_filter[FILTER_PROPERTY_LAST_UPDATED_END_MONTH] )
  1446. && is_numeric( $t_filter[FILTER_PROPERTY_LAST_UPDATED_END_DAY] )
  1447. && is_numeric( $t_filter[FILTER_PROPERTY_LAST_UPDATED_END_YEAR] )
  1448. ) {
  1449. $t_start_string = $t_filter[FILTER_PROPERTY_LAST_UPDATED_START_YEAR] . '-' . $t_filter[FILTER_PROPERTY_LAST_UPDATED_START_MONTH] . '-' . $t_filter[FILTER_PROPERTY_LAST_UPDATED_START_DAY] . ' 00:00:00';
  1450. $t_end_string = $t_filter[FILTER_PROPERTY_LAST_UPDATED_END_YEAR] . '-' . $t_filter[FILTER_PROPERTY_LAST_UPDATED_END_MONTH] . '-' . $t_filter[FILTER_PROPERTY_LAST_UPDATED_END_DAY] . ' 23:59:59';
  1451. $t_where_params[] = strtotime( $t_start_string );
  1452. $t_where_params[] = strtotime( $t_end_string );
  1453. array_push( $t_project_where_clauses, '({bug}.last_updated BETWEEN ' . db_param() . ' AND ' . db_param() . ' )' );
  1454. }
  1455. # view state
  1456. $t_view_state = (int)$t_filter[FILTER_PROPERTY_VIEW_STATE];
  1457. if( !filter_field_is_any( $t_filter[FILTER_PROPERTY_VIEW_STATE] ) ) {
  1458. $t_view_state_query = '({bug}.view_state=' . db_param() . ')';
  1459. log_event( LOG_FILTERING, 'view_state query = ' . $t_view_state_query );
  1460. $t_where_params[] = $t_view_state;
  1461. array_push( $t_where_clauses, $t_view_state_query );
  1462. } else {
  1463. log_event( LOG_FILTERING, 'no view_state query' );
  1464. }
  1465. # reporter
  1466. if( !filter_field_is_any( $t_filter[FILTER_PROPERTY_REPORTER_ID] ) ) {
  1467. $t_clauses = array();
  1468. foreach( $t_filter[FILTER_PROPERTY_REPORTER_ID] as $t_filter_member ) {
  1469. if( filter_field_is_none( $t_filter_member ) ) {
  1470. array_push( $t_clauses, '0' );
  1471. } else {
  1472. $c_reporter_id = (int)$t_filter_member;
  1473. if( filter_field_is_myself( $c_reporter_id ) ) {
  1474. array_push( $t_clauses, $c_user_id );
  1475. } else {
  1476. array_push( $t_clauses, $c_reporter_id );
  1477. }
  1478. }
  1479. }
  1480. if( 1 < count( $t_clauses ) ) {
  1481. $t_reporter_query = '( {bug}.reporter_id in (' . implode( ', ', $t_clauses ) . ') )';
  1482. } else {
  1483. $t_reporter_query = '( {bug}.reporter_id=' . $t_clauses[0] . ' )';
  1484. }
  1485. log_event( LOG_FILTERING, 'reporter query = ' . $t_reporter_query );
  1486. array_push( $t_where_clauses, $t_reporter_query );
  1487. } else {
  1488. log_event( LOG_FILTERING, 'no reporter query' );
  1489. }
  1490. # handler
  1491. if( !filter_field_is_any( $t_filter[FILTER_PROPERTY_HANDLER_ID] ) ) {
  1492. $t_clauses = array();
  1493. foreach( $t_filter[FILTER_PROPERTY_HANDLER_ID] as $t_filter_member ) {
  1494. if( filter_field_is_none( $t_filter_member ) ) {
  1495. array_push( $t_clauses, 0 );
  1496. } else {
  1497. $c_handler_id = (int)$t_filter_member;
  1498. if( filter_field_is_myself( $c_handler_id ) ) {
  1499. array_push( $t_clauses, $c_user_id );
  1500. } else {
  1501. array_push( $t_clauses, $c_handler_id );
  1502. }
  1503. }
  1504. }
  1505. if( 1 < count( $t_clauses ) ) {
  1506. $t_handler_query = '( {bug}.handler_id in (' . implode( ', ', $t_clauses ) . ') )';
  1507. } else {
  1508. $t_handler_query = '( {bug}.handler_id=' . $t_clauses[0] . ' )';
  1509. }
  1510. log_event( LOG_FILTERING, 'handler query = ' . $t_handler_query );
  1511. array_push( $t_where_clauses, $t_handler_query );
  1512. } else {
  1513. log_event( LOG_FILTERING, 'no handler query' );
  1514. }
  1515. # category
  1516. if( !filter_field_is_any( $t_filter[FILTER_PROPERTY_CATEGORY_ID] ) ) {
  1517. $t_clauses = array();
  1518. foreach( $t_filter[FILTER_PROPERTY_CATEGORY_ID] as $t_filter_member ) {
  1519. if( !filter_field_is_none( $t_filter_member ) ) {
  1520. array_push( $t_clauses, $t_filter_member );
  1521. }
  1522. }
  1523. if( 1 < count( $t_clauses ) ) {
  1524. $t_where_tmp = array();
  1525. foreach( $t_clauses as $t_clause ) {
  1526. $t_where_tmp[] = db_param();
  1527. $t_where_params[] = $t_clause;
  1528. }
  1529. array_push( $t_where_clauses, '( {bug}.category_id in ( SELECT id FROM {category} WHERE name in (' . implode( ', ', $t_where_tmp ) . ') ) )' );
  1530. } else {
  1531. $t_where_params[] = $t_clauses[0];
  1532. array_push( $t_where_clauses, '( {bug}.category_id in ( SELECT id FROM {category} WHERE name=' . db_param() . ') )' );
  1533. }
  1534. }
  1535. # severity
  1536. if( !filter_field_is_any( $t_filter[FILTER_PROPERTY_SEVERITY] ) ) {
  1537. $t_clauses = array();
  1538. foreach( $t_filter[FILTER_PROPERTY_SEVERITY] as $t_filter_member ) {
  1539. $c_show_severity = (int)$t_filter_member;
  1540. array_push( $t_clauses, $c_show_severity );
  1541. }
  1542. if( 1 < count( $t_clauses ) ) {
  1543. $t_where_tmp = array();
  1544. foreach( $t_clauses as $t_clause ) {
  1545. $t_where_tmp[] = db_param();
  1546. $t_where_params[] = $t_clause;
  1547. }
  1548. array_push( $t_where_clauses, '( {bug}.severity in (' . implode( ', ', $t_where_tmp ) . ') )' );
  1549. } else {
  1550. $t_where_params[] = $t_clauses[0];
  1551. array_push( $t_where_clauses, '( {bug}.severity=' . db_param() . ' )' );
  1552. }
  1553. }
  1554. # show / hide status
  1555. # take a list of all available statuses then remove the ones that we want hidden, then make sure
  1556. # the ones we want shown are still available
  1557. $t_desired_statuses = $t_filter[FILTER_PROPERTY_STATUS];
  1558. # simple filtering: restrict by the hide status value if present
  1559. if( FILTER_VIEW_TYPE_SIMPLE == $t_filter['_view_type'] ) {
  1560. if( isset( $t_filter[FILTER_PROPERTY_HIDE_STATUS][0] ) && !filter_field_is_none( $t_filter[FILTER_PROPERTY_HIDE_STATUS][0] ) ) {
  1561. $t_selected_status_array = $t_filter[FILTER_PROPERTY_STATUS];
  1562. # if we have metavalue for "any", expand to all status, to filter them
  1563. if( filter_field_is_any( $t_selected_status_array ) ) {
  1564. $t_selected_status_array = MantisEnum::getValues( config_get( 'status_enum_string' ) );
  1565. }
  1566. $t_hide_status = $t_filter[FILTER_PROPERTY_HIDE_STATUS][0];
  1567. # Filter out status that must be hidden
  1568. $t_desired_statuses = array();
  1569. foreach( $t_selected_status_array as $t_this_status ) {
  1570. if( $t_hide_status > $t_this_status ) {
  1571. $t_desired_statuses[] = $t_this_status;
  1572. }
  1573. }
  1574. }
  1575. }
  1576. # advanced filtering: ignore hide_status, do nothing.
  1577. # if show_status is "any", empty the array, to not include any condition on status.
  1578. if( filter_field_is_any( $t_desired_statuses ) ) {
  1579. $t_desired_statuses = array();
  1580. }
  1581. if( count( $t_desired_statuses ) > 0 ) {
  1582. $t_clauses = array();
  1583. foreach( $t_desired_statuses as $t_filter_member ) {
  1584. $t_clauses[] = (int)$t_filter_member;
  1585. }
  1586. if( 1 < count( $t_clauses ) ) {
  1587. $t_where_tmp = array();
  1588. foreach( $t_clauses as $t_clause ) {
  1589. $t_where_tmp[] = db_param();
  1590. $t_where_params[] = $t_clause;
  1591. }
  1592. array_push( $t_where_clauses, '( {bug}.status in (' . implode( ', ', $t_where_tmp ) . ') )' );
  1593. } else {
  1594. $t_where_params[] = $t_clauses[0];
  1595. array_push( $t_where_clauses, '( {bug}.status=' . db_param() . ' )' );
  1596. }
  1597. }
  1598. # resolution
  1599. if( !filter_field_is_any( $t_filter[FILTER_PROPERTY_RESOLUTION] ) ) {
  1600. $t_clauses = array();
  1601. foreach( $t_filter[FILTER_PROPERTY_RESOLUTION] as $t_filter_member ) {
  1602. $c_show_resolution = (int)$t_filter_member;
  1603. array_push( $t_clauses, $c_show_resolution );
  1604. }
  1605. if( 1 < count( $t_clauses ) ) {
  1606. $t_where_tmp = array();
  1607. foreach( $t_clauses as $t_clause ) {
  1608. $t_where_tmp[] = db_param();
  1609. $t_where_params[] = $t_clause;
  1610. }
  1611. array_push( $t_where_clauses, '( {bug}.resolution in (' . implode( ', ', $t_where_tmp ) . ') )' );
  1612. } else {
  1613. $t_where_params[] = $t_clauses[0];
  1614. array_push( $t_where_clauses, '( {bug}.resolution=' . db_param() . ' )' );
  1615. }
  1616. }
  1617. # priority
  1618. if( !filter_field_is_any( $t_filter[FILTER_PROPERTY_PRIORITY] ) ) {
  1619. $t_clauses = array();
  1620. foreach( $t_filter[FILTER_PROPERTY_PRIORITY] as $t_filter_member ) {
  1621. $c_show_priority = (int)$t_filter_member;
  1622. array_push( $t_clauses, $c_show_priority );
  1623. }
  1624. if( 1 < count( $t_clauses ) ) {
  1625. $t_where_tmp = array();
  1626. foreach( $t_clauses as $t_clause ) {
  1627. $t_where_tmp[] = db_param();
  1628. $t_where_params[] = $t_clause;
  1629. }
  1630. array_push( $t_where_clauses, '( {bug}.priority in (' . implode( ', ', $t_where_tmp ) . ') )' );
  1631. } else {
  1632. $t_where_params[] = $t_clauses[0];
  1633. array_push( $t_where_clauses, '( {bug}.priority=' . db_param() . ' )' );
  1634. }
  1635. }
  1636. # product build
  1637. if( !filter_field_is_any( $t_filter[FILTER_PROPERTY_BUILD] ) ) {
  1638. $t_clauses = array();
  1639. foreach( $t_filter[FILTER_PROPERTY_BUILD] as $t_filter_member ) {
  1640. $t_filter_member = stripslashes( $t_filter_member );
  1641. if( filter_field_is_none( $t_filter_member ) ) {
  1642. array_push( $t_clauses, '' );
  1643. } else {
  1644. $c_show_build = $t_filter_member;
  1645. array_push( $t_clauses, $c_show_build );
  1646. }
  1647. }
  1648. if( 1 < count( $t_clauses ) ) {
  1649. $t_where_tmp = array();
  1650. foreach( $t_clauses as $t_clause ) {
  1651. $t_where_tmp[] = db_param();
  1652. $t_where_params[] = $t_clause;
  1653. }
  1654. array_push( $t_where_clauses, '( {bug}.build in (' . implode( ', ', $t_where_tmp ) . ') )' );
  1655. } else {
  1656. $t_where_params[] = $t_clauses[0];
  1657. array_push( $t_where_clauses, '( {bug}.build=' . db_param() . ' )' );
  1658. }
  1659. }
  1660. # product version
  1661. if( !filter_field_is_any( $t_filter[FILTER_PROPERTY_VERSION] ) ) {
  1662. $t_clauses = array();
  1663. foreach( $t_filter[FILTER_PROPERTY_VERSION] as $t_filter_member ) {
  1664. $t_filter_member = stripslashes( $t_filter_member );
  1665. if( filter_field_is_none( $t_filter_member ) ) {
  1666. array_push( $t_clauses, '' );
  1667. } else {
  1668. $c_show_version = $t_filter_member;
  1669. array_push( $t_clauses, $c_show_version );
  1670. }
  1671. }
  1672. if( 1 < count( $t_clauses ) ) {
  1673. $t_where_tmp = array();
  1674. foreach( $t_clauses as $t_clause ) {
  1675. $t_where_tmp[] = db_param();
  1676. $t_where_params[] = $t_clause;
  1677. }
  1678. array_push( $t_where_clauses, '( {bug}.version in (' . implode( ', ', $t_where_tmp ) . ') )' );
  1679. } else {
  1680. $t_where_params[] = $t_clauses[0];
  1681. array_push( $t_where_clauses, '( {bug}.version=' . db_param() . ' )' );
  1682. }
  1683. }
  1684. # profile
  1685. if( !filter_field_is_any( $t_filter[FILTER_PROPERTY_PROFILE_ID] ) ) {
  1686. $t_clauses = array();
  1687. foreach( $t_filter[FILTER_PROPERTY_PROFILE_ID] as $t_filter_member ) {
  1688. $t_filter_member = stripslashes( $t_filter_member );
  1689. if( filter_field_is_none( $t_filter_member ) ) {
  1690. array_push( $t_clauses, '0' );
  1691. } else {
  1692. $c_show_profile = (int)$t_filter_member;
  1693. array_push( $t_clauses, $c_show_profile );
  1694. }
  1695. }
  1696. if( 1 < count( $t_clauses ) ) {
  1697. $t_where_tmp = array();
  1698. foreach( $t_clauses as $t_clause ) {
  1699. $t_where_tmp[] = db_param();
  1700. $t_where_params[] = $t_clause;
  1701. }
  1702. array_push( $t_where_clauses, '( {bug}.profile_id in (' . implode( ', ', $t_where_tmp ) . ') )' );
  1703. } else {
  1704. $t_where_params[] = $t_clauses[0];
  1705. array_push( $t_where_clauses, '( {bug}.profile_id=' . db_param() . ' )' );
  1706. }
  1707. }
  1708. # platform
  1709. if( !filter_field_is_any( $t_filter[FILTER_PROPERTY_PLATFORM] ) ) {
  1710. $t_clauses = array();
  1711. foreach( $t_filter[FILTER_PROPERTY_PLATFORM] as $t_filter_member ) {
  1712. $t_filter_member = stripslashes( $t_filter_member );
  1713. if( filter_field_is_none( $t_filter_member ) ) {
  1714. array_push( $t_clauses, '' );
  1715. } else {
  1716. $c_platform = $t_filter_member;
  1717. array_push( $t_clauses, $c_platform );
  1718. }
  1719. }
  1720. if( 1 < count( $t_clauses ) ) {
  1721. $t_where_tmp = array();
  1722. foreach( $t_clauses as $t_clause ) {
  1723. $t_where_tmp[] = db_param();
  1724. $t_where_params[] = $t_clause;
  1725. }
  1726. array_push( $t_where_clauses, '( {bug}.platform in (' . implode( ', ', $t_where_tmp ) . ') )' );
  1727. } else {
  1728. $t_where_params[] = $t_clauses[0];
  1729. array_push( $t_where_clauses, '( {bug}.platform = ' . db_param() . ' )' );
  1730. }
  1731. }
  1732. # Operating System (os)
  1733. if( !filter_field_is_any( $t_filter[FILTER_PROPERTY_OS] ) ) {
  1734. $t_clauses = array();
  1735. foreach( $t_filter[FILTER_PROPERTY_OS] as $t_filter_member ) {
  1736. $t_filter_member = stripslashes( $t_filter_member );
  1737. if( filter_field_is_none( $t_filter_member ) ) {
  1738. array_push( $t_clauses, '' );
  1739. } else {
  1740. $c_os = $t_filter_member;
  1741. array_push( $t_clauses, $c_os );
  1742. }
  1743. }
  1744. if( 1 < count( $t_clauses ) ) {
  1745. $t_where_tmp = array();
  1746. foreach( $t_clauses as $t_clause ) {
  1747. $t_where_tmp[] = db_param();
  1748. $t_where_params[] = $t_clause;
  1749. }
  1750. array_push( $t_where_clauses, '( {bug}.os in (' . implode( ', ', $t_where_tmp ) . ') )' );
  1751. } else {
  1752. $t_where_params[] = $t_clauses[0];
  1753. array_push( $t_where_clauses, '( {bug}.os = ' . db_param() . ' )' );
  1754. }
  1755. }
  1756. # Operating System Build (os_build)
  1757. if( !filter_field_is_any( $t_filter[FILTER_PROPERTY_OS_BUILD] ) ) {
  1758. $t_clauses = array();
  1759. foreach( $t_filter[FILTER_PROPERTY_OS_BUILD] as $t_filter_member ) {
  1760. $t_filter_member = stripslashes( $t_filter_member );
  1761. if( filter_field_is_none( $t_filter_member ) ) {
  1762. array_push( $t_clauses, '' );
  1763. } else {
  1764. $c_os_build = $t_filter_member;
  1765. array_push( $t_clauses, $c_os_build );
  1766. }
  1767. }
  1768. if( 1 < count( $t_clauses ) ) {
  1769. $t_where_tmp = array();
  1770. foreach( $t_clauses as $t_clause ) {
  1771. $t_where_tmp[] = db_param();
  1772. $t_where_params[] = $t_clause;
  1773. }
  1774. array_push( $t_where_clauses, '( {bug}.os_build in (' . implode( ', ', $t_where_tmp ) . ') )' );
  1775. } else {
  1776. $t_where_params[] = $t_clauses[0];
  1777. array_push( $t_where_clauses, '( {bug}.os_build = ' . db_param() . ' )' );
  1778. }
  1779. }
  1780. # fixed in version
  1781. if( !filter_field_is_any( $t_filter[FILTER_PROPERTY_FIXED_IN_VERSION] ) ) {
  1782. $t_clauses = array();
  1783. foreach( $t_filter[FILTER_PROPERTY_FIXED_IN_VERSION] as $t_filter_member ) {
  1784. $t_filter_member = stripslashes( $t_filter_member );
  1785. if( filter_field_is_none( $t_filter_member ) ) {
  1786. array_push( $t_clauses, '' );
  1787. } else {
  1788. $c_fixed_in_version = $t_filter_member;
  1789. array_push( $t_clauses, $c_fixed_in_version );
  1790. }
  1791. }
  1792. if( 1 < count( $t_clauses ) ) {
  1793. $t_where_tmp = array();
  1794. foreach( $t_clauses as $t_clause ) {
  1795. $t_where_tmp[] = db_param();
  1796. $t_where_params[] = $t_clause;
  1797. }
  1798. array_push( $t_where_clauses, '( {bug}.fixed_in_version in (' . implode( ', ', $t_where_tmp ) . ') )' );
  1799. } else {
  1800. $t_where_params[] = $t_clauses[0];
  1801. array_push( $t_where_clauses, '( {bug}.fixed_in_version=' . db_param() . ' )' );
  1802. }
  1803. }
  1804. # target version
  1805. if( !filter_field_is_any( $t_filter[FILTER_PROPERTY_TARGET_VERSION] ) ) {
  1806. $t_clauses = array();
  1807. foreach( $t_filter[FILTER_PROPERTY_TARGET_VERSION] as $t_filter_member ) {
  1808. $t_filter_member = stripslashes( $t_filter_member );
  1809. if( filter_field_is_none( $t_filter_member ) ) {
  1810. array_push( $t_clauses, '' );
  1811. } else {
  1812. $c_target_version = $t_filter_member;
  1813. array_push( $t_clauses, $c_target_version );
  1814. }
  1815. }
  1816. # echo var_dump( $t_clauses ); exit;
  1817. if( 1 < count( $t_clauses ) ) {
  1818. $t_where_tmp = array();
  1819. foreach( $t_clauses as $t_clause ) {
  1820. $t_where_tmp[] = db_param();
  1821. $t_where_params[] = $t_clause;
  1822. }
  1823. array_push( $t_where_clauses, '( {bug}.target_version in (' . implode( ', ', $t_where_tmp ) . ') )' );
  1824. } else {
  1825. $t_where_params[] = $t_clauses[0];
  1826. array_push( $t_where_clauses, '( {bug}.target_version=' . db_param() . ' )' );
  1827. }
  1828. }
  1829. # users monitoring a bug
  1830. if( !filter_field_is_any( $t_filter[FILTER_PROPERTY_MONITOR_USER_ID] ) ) {
  1831. $t_clauses = array();
  1832. $t_table_name = 'user_monitor';
  1833. array_push( $t_join_clauses, 'LEFT JOIN {bug_monitor} ' . $t_table_name . ' ON ' . $t_table_name . '.bug_id = {bug}.id' );
  1834. foreach( $t_filter[FILTER_PROPERTY_MONITOR_USER_ID] as $t_filter_member ) {
  1835. $c_user_monitor = (int)$t_filter_member;
  1836. if( filter_field_is_myself( $c_user_monitor ) ) {
  1837. array_push( $t_clauses, $c_user_id );
  1838. } else {
  1839. array_push( $t_clauses, $c_user_monitor );
  1840. }
  1841. }
  1842. if( 1 < count( $t_clauses ) ) {
  1843. $t_where_tmp = array();
  1844. foreach( $t_clauses as $t_clause ) {
  1845. $t_where_tmp[] = db_param();
  1846. $t_where_params[] = $t_clause;
  1847. }
  1848. array_push( $t_where_clauses, '( ' . $t_table_name . '.user_id in (' . implode( ', ', $t_where_tmp ) . ') )' );
  1849. } else {
  1850. $t_where_params[] = $t_clauses[0];
  1851. array_push( $t_where_clauses, '( ' . $t_table_name . '.user_id=' . db_param() . ' )' );
  1852. }
  1853. }
  1854. # bug relationship
  1855. $t_any_found = false;
  1856. $c_rel_type = $t_filter[FILTER_PROPERTY_RELATIONSHIP_TYPE];
  1857. $c_rel_bug = $t_filter[FILTER_PROPERTY_RELATIONSHIP_BUG];
  1858. if( -1 == $c_rel_type || 0 == $c_rel_bug ) {
  1859. $t_any_found = true;
  1860. }
  1861. if( !$t_any_found ) {
  1862. # use the complementary type
  1863. $t_comp_type = relationship_get_complementary_type( $c_rel_type );
  1864. $t_clauses = array();
  1865. $t_table_dst = 'rel_dst';
  1866. $t_table_src = 'rel_src';
  1867. array_push( $t_join_clauses, 'LEFT JOIN {bug_relationship} ' . $t_table_dst . ' ON ' . $t_table_dst . '.destination_bug_id = {bug}.id' );
  1868. array_push( $t_join_clauses, 'LEFT JOIN {bug_relationship} ' . $t_table_src . ' ON ' . $t_table_src . '.source_bug_id = {bug}.id' );
  1869. # get reverse relationships
  1870. $t_where_params[] = $t_comp_type;
  1871. $t_where_params[] = $c_rel_bug;
  1872. $t_where_params[] = $c_rel_type;
  1873. $t_where_params[] = $c_rel_bug;
  1874. array_push( $t_clauses, '(' . $t_table_dst . '.relationship_type=' . db_param() . ' AND ' . $t_table_dst . '.source_bug_id=' . db_param() . ')' );
  1875. array_push( $t_clauses, '(' . $t_table_src . '.relationship_type=' . db_param() . ' AND ' . $t_table_src . '.destination_bug_id=' . db_param() . ')' );
  1876. array_push( $t_where_clauses, '(' . implode( ' OR ', $t_clauses ) . ')' );
  1877. }
  1878. # tags
  1879. $c_tag_string = trim( $t_filter[FILTER_PROPERTY_TAG_STRING] );
  1880. $c_tag_select = (int)$t_filter[FILTER_PROPERTY_TAG_SELECT];
  1881. if( !is_blank( $c_tag_string ) || $c_tag_select > 0 ) {
  1882. $t_tags = tag_parse_filters( $c_tag_string );
  1883. if( count( $t_tags ) || $c_tag_select > 0 ) {
  1884. $t_projects_can_view_tags = access_project_array_filter( 'tag_view_threshold', $t_included_project_ids, $t_user_id );
  1885. if( !empty( $t_projects_can_view_tags ) ) {
  1886. $t_diff = array_diff( $t_included_project_ids, $t_projects_can_view_tags );
  1887. # If tags can't be viewed in all included project, a filter must be used
  1888. if( empty( $t_diff ) ) {
  1889. $t_tag_projects_clause = '';
  1890. } else {
  1891. $t_tag_projects_clause = ' AND {bug}.project_id IN (' . implode( ',', $t_projects_can_view_tags ) . ')';
  1892. }
  1893. $t_tags_all = array();
  1894. $t_tags_any = array();
  1895. $t_tags_none = array();
  1896. foreach( $t_tags as $t_tag_row ) {
  1897. switch( $t_tag_row['filter'] ) {
  1898. case 1:
  1899. $t_tags_all[] = $t_tag_row;
  1900. break;
  1901. case 0:
  1902. $t_tags_any[] = $t_tag_row;
  1903. break;
  1904. case -1:
  1905. $t_tags_none[] = $t_tag_row;
  1906. break;
  1907. }
  1908. }
  1909. # Add the tag id to the array, from filter field "tag_select"
  1910. if( 0 < $c_tag_select && tag_exists( $c_tag_select ) ) {
  1911. $t_tags_any[] = tag_get( $c_tag_select );
  1912. }
  1913. $t_tag_counter = 0;
  1914. if( count( $t_tags_all ) ) {
  1915. foreach( $t_tags_all as $t_tag_row ) {
  1916. $t_tag_alias = 'bug_tag_alias_' . ++$t_tag_counter;
  1917. array_push( $t_join_clauses,
  1918. 'JOIN {bug_tag} ' . $t_tag_alias . ' ON ' . $t_tag_alias . '.bug_id = {bug}.id'
  1919. . ' AND ' . $t_tag_alias . '.tag_id=' . (int)$t_tag_row['id']
  1920. . $t_tag_projects_clause
  1921. );
  1922. }
  1923. }
  1924. if( count( $t_tags_any ) ) {
  1925. $t_tag_alias = 'bug_tag_alias_' . ++$t_tag_counter;
  1926. $t_tag_ids = array();
  1927. foreach( $t_tags_any as $t_tag_row ) {
  1928. $t_tag_ids[] = (int)$t_tag_row['id'];
  1929. }
  1930. array_push( $t_join_clauses,
  1931. 'LEFT OUTER JOIN {bug_tag} ' . $t_tag_alias . ' ON ' . $t_tag_alias . '.bug_id = {bug}.id'
  1932. . ' AND ' . $t_tag_alias . '.tag_id IN (' . implode( ',', $t_tag_ids ) . ')'
  1933. . $t_tag_projects_clause
  1934. );
  1935. # If the isn't a non-outer join, check that at least one of the tags has been matched by the outer join
  1936. if( !count( $t_tags_all ) ) {
  1937. array_push( $t_where_clauses, $t_tag_alias . '.tag_id IS NOT NULL' );
  1938. }
  1939. }
  1940. if( count( $t_tags_none ) ) {
  1941. $t_tag_alias = 'bug_tag_alias_' . ++$t_tag_counter;
  1942. $t_tag_ids = array();
  1943. foreach( $t_tags_none as $t_tag_row ) {
  1944. $t_tag_ids[] = (int)$t_tag_row['id'];
  1945. }
  1946. array_push( $t_join_clauses,
  1947. 'LEFT OUTER JOIN {bug_tag} ' . $t_tag_alias . ' ON ' . $t_tag_alias . '.bug_id = {bug}.id'
  1948. . ' AND ' . $t_tag_alias . '.tag_id IN (' . implode( ',', $t_tag_ids ) . ')'
  1949. . $t_tag_projects_clause
  1950. );
  1951. array_push( $t_where_clauses, $t_tag_alias . '.tag_id IS NULL' );
  1952. }
  1953. }
  1954. }
  1955. }
  1956. # note user id
  1957. if( !filter_field_is_any( $t_filter[FILTER_PROPERTY_NOTE_USER_ID] ) ) {
  1958. $t_bugnote_table_alias = 'mbnt';
  1959. $t_clauses = array();
  1960. array_push( $t_join_clauses, 'LEFT JOIN {bugnote} ' . $t_bugnote_table_alias . ' ON {bug}.id = ' . $t_bugnote_table_alias . '.bug_id' );
  1961. foreach( $t_filter[FILTER_PROPERTY_NOTE_USER_ID] as $t_filter_member ) {
  1962. $c_note_user_id = (int)$t_filter_member;
  1963. if( filter_field_is_myself( $c_note_user_id ) ) {
  1964. array_push( $t_clauses, $c_user_id );
  1965. } else {
  1966. array_push( $t_clauses, $c_note_user_id );
  1967. }
  1968. }
  1969. if( 1 < count( $t_clauses ) ) {
  1970. $t_where_tmp = array();
  1971. foreach( $t_clauses as $t_clause ) {
  1972. $t_where_tmp[] = db_param();
  1973. $t_where_params[] = $t_clause;
  1974. }
  1975. array_push( $t_where_clauses, '( ' . $t_bugnote_table_alias . '.reporter_id in (' . implode( ', ', $t_where_tmp ) . ') )' );
  1976. } else {
  1977. $t_where_params[] = $t_clauses[0];
  1978. array_push( $t_where_clauses, '( ' . $t_bugnote_table_alias . '.reporter_id=' . db_param() . ' )' );
  1979. }
  1980. }
  1981. # plugin filters
  1982. $t_plugin_filters = filter_get_plugin_filters();
  1983. foreach( $t_plugin_filters as $t_field_name => $t_filter_object ) {
  1984. if( !filter_field_is_any( $t_filter[$t_field_name] ) || $t_filter_object->type == FILTER_TYPE_BOOLEAN ) {
  1985. $t_filter_query = $t_filter_object->query( $t_filter[$t_field_name] );
  1986. if( is_array( $t_filter_query ) ) {
  1987. if( isset( $t_filter_query['join'] ) ) {
  1988. array_push( $t_join_clauses, $t_filter_query['join'] );
  1989. }
  1990. if( isset( $t_filter_query['where'] ) ) {
  1991. array_push( $t_where_clauses, $t_filter_query['where'] );
  1992. }
  1993. if( isset( $t_filter_query['params'] ) && is_array( $t_filter_query['params'] ) ) {
  1994. $t_where_params = array_merge( $t_where_params, $t_filter_query['params'] );
  1995. }
  1996. }
  1997. }
  1998. }
  1999. # custom field filters
  2000. if( ON == config_get( 'filter_by_custom_fields' ) ) {
  2001. $t_custom_fields = custom_field_get_linked_ids( $t_included_project_ids );
  2002. foreach( $t_custom_fields as $t_cfid ) {
  2003. $t_field_info = custom_field_cache_row( $t_cfid, true );
  2004. if( !$t_field_info['filter_by'] ) {
  2005. continue;
  2006. # skip this custom field it shouldn't be filterable
  2007. }
  2008. $t_field = $t_filter['custom_fields'][$t_cfid];
  2009. $t_custom_where_clause = '';
  2010. # Ignore all custom filters that are not set, or that are set to '' or "any"
  2011. if( !filter_field_is_any( $t_field ) ) {
  2012. $t_def = custom_field_get_definition( $t_cfid );
  2013. # skip date custom fields with value of "any"
  2014. if( $t_def['type'] == CUSTOM_FIELD_TYPE_DATE && $t_field[0] == CUSTOM_FIELD_DATE_ANY ) {
  2015. break;
  2016. }
  2017. $t_table_name = 'cf_alias_' . $t_cfid;
  2018. # We need to filter each joined table or the result query will explode in dimensions
  2019. # Each custom field will result in a exponential growth like Number_of_Issues^Number_of_Custom_Fields
  2020. # and only after this process ends (if it is able to) the result query will be filtered
  2021. # by the WHERE clause and by the DISTINCT clause
  2022. $t_cf_join_clause = 'LEFT OUTER JOIN {custom_field_string} ' . $t_table_name . ' ON {bug}.id = ' . $t_table_name . '.bug_id AND ' . $t_table_name . '.field_id = ' . $t_cfid;
  2023. $t_searchable_projects = array_intersect( $t_included_project_ids, custom_field_get_project_ids( $t_cfid ) );
  2024. $t_projects_can_view_field = access_project_array_filter( (int)$t_def['access_level_r'], $t_searchable_projects, $t_user_id );
  2025. if( empty( $t_projects_can_view_field ) ) {
  2026. continue;
  2027. }
  2028. # This diff will contain those included projects that can't view this custom field
  2029. $t_diff = array_diff( $t_included_project_ids, $t_projects_can_view_field );
  2030. # If not empty, it means there are some projects that can't view the field values,
  2031. # so a project filter must be used to not include values from those projects
  2032. if( !empty( $t_diff ) ) {
  2033. $t_cf_join_clause .= ' AND {bug}.project_id IN (' . implode( ',', $t_projects_can_view_field ) . ')';
  2034. }
  2035. $t_metadata['cf_alias'][$t_cfid] = $t_table_name;
  2036. if( $t_def['type'] == CUSTOM_FIELD_TYPE_DATE ) {
  2037. # Define the value field with type cast to integer
  2038. $t_value_field = 'CAST(COALESCE(NULLIF(' . $t_table_name . '.value, \'\'), \'0\') AS DECIMAL)';
  2039. switch( $t_field[0] ) {
  2040. # Closing parenthesis intentionally omitted, will be added later on
  2041. case CUSTOM_FIELD_DATE_ANY:
  2042. break;
  2043. case CUSTOM_FIELD_DATE_NONE:
  2044. array_push( $t_join_clauses, $t_cf_join_clause );
  2045. $t_custom_where_clause = '( ' . $t_table_name . '.bug_id is null OR ' . $t_value_field . ' = 0 ';
  2046. break;
  2047. case CUSTOM_FIELD_DATE_BEFORE:
  2048. array_push( $t_join_clauses, $t_cf_join_clause );
  2049. $t_custom_where_clause = '( ' . $t_value_field . ' != 0 AND ' . $t_value_field . ' < ' . $t_field[2];
  2050. break;
  2051. case CUSTOM_FIELD_DATE_AFTER:
  2052. array_push( $t_join_clauses, $t_cf_join_clause );
  2053. $t_custom_where_clause = '( ' . $t_value_field . ' > ' . ( $t_field[1] + 1 );
  2054. break;
  2055. default:
  2056. array_push( $t_join_clauses, $t_cf_join_clause );
  2057. $t_custom_where_clause = '( ' . $t_value_field . ' BETWEEN ' . $t_field[1] . ' AND ' . $t_field[2];
  2058. break;
  2059. }
  2060. } else {
  2061. array_push( $t_join_clauses, $t_cf_join_clause );
  2062. $t_filter_array = array();
  2063. foreach( $t_field as $t_filter_member ) {
  2064. $t_filter_member = stripslashes( $t_filter_member );
  2065. if( filter_field_is_none( $t_filter_member ) ) {
  2066. # coerce filter value if selecting META_FILTER_NONE so it will match empty fields
  2067. $t_filter_member = '';
  2068. # but also add those _not_ present in the custom field string table
  2069. array_push( $t_filter_array, $t_table_name . '.value IS NULL' );
  2070. }
  2071. switch( $t_def['type'] ) {
  2072. case CUSTOM_FIELD_TYPE_CHECKBOX:
  2073. case CUSTOM_FIELD_TYPE_MULTILIST:
  2074. $t_where_params[] = '%|' . $t_filter_member . '|%';
  2075. array_push( $t_filter_array, db_helper_like( $t_table_name . '.value' ) );
  2076. break;
  2077. case CUSTOM_FIELD_TYPE_TEXTAREA:
  2078. $t_where_params[] = '%' . $t_filter_member . '%';
  2079. array_push( $t_filter_array, db_helper_like( $t_table_name . '.text' ) );
  2080. break;
  2081. default:
  2082. $t_where_params[] = $t_filter_member;
  2083. array_push( $t_filter_array, $t_table_name . '.value = ' . db_param() );
  2084. }
  2085. }
  2086. $t_custom_where_clause .= '(' . implode( ' OR ', $t_filter_array );
  2087. }
  2088. if( !is_blank( $t_custom_where_clause ) ) {
  2089. array_push( $t_where_clauses, $t_custom_where_clause . ')' );
  2090. }
  2091. }
  2092. }
  2093. }
  2094. # Text search
  2095. if( !is_blank( $t_filter[FILTER_PROPERTY_SEARCH] ) ) {
  2096. # break up search terms by spacing or quoting
  2097. preg_match_all( "/-?([^'\"\s]+|\"[^\"]+\"|'[^']+')/", $t_filter[FILTER_PROPERTY_SEARCH], $t_matches, PREG_SET_ORDER );
  2098. # organize terms without quoting, paying attention to negation
  2099. $t_search_terms = array();
  2100. foreach( $t_matches as $t_match ) {
  2101. $t_search_terms[trim( $t_match[1], "\'\"" )] = ( $t_match[0][0] == '-' );
  2102. }
  2103. # build a big where-clause and param list for all search terms, including negations
  2104. $t_first = true;
  2105. $t_textsearch_where_clause = '( ';
  2106. foreach( $t_search_terms as $t_search_term => $t_negate ) {
  2107. if( !$t_first ) {
  2108. $t_textsearch_where_clause .= ' AND ';
  2109. }
  2110. if( $t_negate ) {
  2111. $t_textsearch_where_clause .= 'NOT ';
  2112. }
  2113. $c_search = '%' . $t_search_term . '%';
  2114. $t_textsearch_where_clause .= '( ' . db_helper_like( '{bug}.summary' ) .
  2115. ' OR ' . db_helper_like( '{bug_text}.description' ) .
  2116. ' OR ' . db_helper_like( '{bug_text}.steps_to_reproduce' ) .
  2117. ' OR ' . db_helper_like( '{bug_text}.additional_information' ) .
  2118. ' OR ' . db_helper_like( '{bugnote_text}.note' );
  2119. $t_where_params[] = $c_search;
  2120. $t_where_params[] = $c_search;
  2121. $t_where_params[] = $c_search;
  2122. $t_where_params[] = $c_search;
  2123. $t_where_params[] = $c_search;
  2124. if( is_numeric( $t_search_term ) ) {
  2125. # Note: no need to test negative values, '-' sign has been removed
  2126. if( $t_search_term <= DB_MAX_INT ) {
  2127. $c_search_int = (int)$t_search_term;
  2128. $t_textsearch_where_clause .= ' OR {bug}.id = ' . db_param();
  2129. $t_textsearch_where_clause .= ' OR {bugnote}.id = ' . db_param();
  2130. $t_where_params[] = $c_search_int;
  2131. $t_where_params[] = $c_search_int;
  2132. }
  2133. }
  2134. $t_textsearch_where_clause .= ' )';
  2135. $t_first = false;
  2136. }
  2137. $t_textsearch_where_clause .= ' )';
  2138. # add text query elements to arrays
  2139. if( !$t_first ) {
  2140. $t_join_clauses[] = 'JOIN {bug_text} ON {bug}.bug_text_id = {bug_text}.id';
  2141. $t_join_clauses[] = 'LEFT JOIN {bugnote} ON {bug}.id = {bugnote}.bug_id';
  2142. # Outer join required otherwise we don't retrieve issues without notes
  2143. $t_join_clauses[] = 'LEFT JOIN {bugnote_text} ON {bugnote}.bugnote_text_id = {bugnote_text}.id';
  2144. $t_where_clauses[] = $t_textsearch_where_clause;
  2145. }
  2146. }
  2147. # End text search
  2148. # Determine join operator
  2149. if( $t_filter[FILTER_PROPERTY_MATCH_TYPE] == FILTER_MATCH_ANY ) {
  2150. $t_join_operator = ' OR ';
  2151. } else {
  2152. $t_join_operator = ' AND ';
  2153. }
  2154. log_event( LOG_FILTERING, 'Join operator : ' . $t_join_operator );
  2155. $t_query_clauses['select'] = $t_select_clauses;
  2156. $t_query_clauses['from'] = $t_from_clauses;
  2157. $t_query_clauses['join'] = $t_join_clauses;
  2158. $t_query_clauses['where'] = $t_where_clauses;
  2159. $t_query_clauses['where_values'] = $t_where_params;
  2160. $t_query_clauses['project_where'] = $t_project_where_clauses;
  2161. $t_query_clauses['operator'] = $t_join_operator;
  2162. $t_query_clauses['metadata'] = $t_metadata;
  2163. $t_query_clauses = filter_get_query_sort_data( $t_filter, $p_show_sticky, $t_query_clauses );
  2164. $t_query_clauses = filter_unique_query_clauses( $t_query_clauses );
  2165. return $t_query_clauses;
  2166. }
  2167. /**
  2168. * Cache the filter results with bugnote stats for later use
  2169. * @param array $p_rows Results of the filter query.
  2170. * @param array $p_id_array_lastmod Array of bug ids.
  2171. * @return array
  2172. */
  2173. function filter_cache_result( array $p_rows, array $p_id_array_lastmod ) {
  2174. $t_stats = bug_get_bugnote_stats_array( $p_id_array_lastmod );
  2175. $t_rows = array();
  2176. foreach( $p_rows as $t_row ) {
  2177. if( array_key_exists( $t_row['id'], $t_stats ) ) {
  2178. $t_rows[] = bug_row_to_object( bug_cache_database_result( $t_row, $t_stats[$t_row['id']] ) );
  2179. } else {
  2180. $t_rows[] = bug_row_to_object( bug_cache_database_result( $t_row ) );
  2181. }
  2182. }
  2183. return $t_rows;
  2184. }
  2185. /**
  2186. * Prints the filter selection area for both the bug list view screen and
  2187. * the bug list print screen. This function was an attempt to make it easier to
  2188. * add new filters and rearrange them on screen for both pages.
  2189. * @return void
  2190. */
  2191. function filter_draw_selection_area() {
  2192. $t_form_name_suffix = '_open';
  2193. $t_filter = current_user_get_bug_filter();
  2194. $t_filter = filter_ensure_valid_filter( $t_filter === false ? array() : $t_filter );
  2195. $t_view_type = $t_filter['_view_type'];
  2196. ?>
  2197. <div class="col-md-12 col-xs-12">
  2198. <div class="filter-box">
  2199. <?php
  2200. $t_stored_queries_arr = filter_db_get_available_queries();
  2201. $t_is_temporary = filter_is_temporary( $t_filter );
  2202. $t_tmp_filter_param = $t_is_temporary ? '&filter=' . filter_get_temporary_key( $t_filter ) : '';
  2203. $t_can_persist = filter_user_can_use_persistent( auth_get_current_user_id() );
  2204. $t_collapse_block = is_collapsed( 'filter' );
  2205. $t_block_css = $t_collapse_block ? 'collapsed' : '';
  2206. $t_block_icon = $t_collapse_block ? 'fa-chevron-down' : 'fa-chevron-up';
  2207. # further use of this icon must be inlined to avoid spaces in rendered html
  2208. $t_temporary_icon_html = ( $t_is_temporary && $t_can_persist ) ?
  2209. '<i class="fa fa-clock-o fa-xs-top" title="' . lang_get( 'temporary_filter' ) . '"></i>'
  2210. : '';
  2211. $t_url_reset_filter = 'view_all_set.php?type=' . FILTER_ACTION_RESET;
  2212. $t_url_persist_filter = 'view_all_set.php?temporary=n' . $t_tmp_filter_param . '&set_project_id=' . helper_get_current_project();
  2213. ?>
  2214. <div id="filter" class="widget-box widget-color-blue2 <?php echo $t_block_css ?>">
  2215. <div class="widget-header widget-header-small">
  2216. <h4 class="widget-title lighter">
  2217. <i class="ace-icon fa fa-filter"><?php echo $t_temporary_icon_html ?>
  2218. </i>
  2219. <?php echo lang_get( 'filters' ) ?>
  2220. </h4>
  2221. <div class="widget-toolbar">
  2222. <?php
  2223. $t_view_filters = config_get('view_filters');
  2224. if( ( ( SIMPLE_ONLY != $t_view_filters ) && ( ADVANCED_ONLY != $t_view_filters ) ) ||
  2225. access_has_project_level( config_get( 'create_permalink_threshold' ) ) ||
  2226. count( $t_stored_queries_arr ) > 0 ) { ?>
  2227. <div class="widget-menu">
  2228. <a href="#" data-action="settings" data-toggle="dropdown">
  2229. <i class="ace-icon fa fa-bars bigger-125"></i>
  2230. </a>
  2231. <ul class="dropdown-menu dropdown-menu-right dropdown-yellow dropdown-caret dropdown-closer">
  2232. <?php
  2233. $t_url = config_get( 'use_dynamic_filters' )
  2234. ? 'view_all_set.php?type=' . FILTER_ACTION_PARSE_ADD . $t_tmp_filter_param . '&view_type='
  2235. : 'view_filters_page.php?view_type=';
  2236. filter_print_view_type_toggle( $t_url, $t_filter['_view_type'] );
  2237. if( access_has_project_level( config_get( 'create_permalink_threshold' ) ) ) {
  2238. # Add CSRF protection, see #22702
  2239. $t_permalink_url = urlencode( filter_get_url( $t_filter ) )
  2240. . form_security_param( 'permalink' );
  2241. echo '<li>';
  2242. echo '<a href="permalink_page.php?url=' . $t_permalink_url . '">';
  2243. echo '<i class="ace-icon fa fa-link"></i>&#160;&#160;' . lang_get( 'create_filter_link' );
  2244. echo '</a>';
  2245. echo '</li>';
  2246. }
  2247. if( count( $t_stored_queries_arr ) > 0 ) {
  2248. echo '<li>';
  2249. echo '<a href="manage_filter_page.php">';
  2250. echo '<i class="ace-icon fa fa-wrench"></i>&#160;&#160;' . lang_get( 'open_queries' );
  2251. echo '</a>';
  2252. echo '</li>';
  2253. }
  2254. if( $t_is_temporary && $t_can_persist ) {
  2255. echo '<li>';
  2256. echo '<a href="' . $t_url_persist_filter . '">';
  2257. echo '<i class="ace-icon fa fa-thumb-tack"></i>&#160;&#160;' . lang_get( 'set_as_persistent_filter' );
  2258. echo '</a>';
  2259. echo '</li>';
  2260. }
  2261. ?>
  2262. </ul>
  2263. </div>
  2264. <?php } ?>
  2265. <a id="filter-toggle" data-action="collapse" href="#">
  2266. <i class="1 ace-icon fa bigger-125 <?php echo $t_block_icon ?>"></i>
  2267. </a>
  2268. </div>
  2269. <div id="filter-bar-queries" class="widget-toolbar no-border" style="display: <?php echo $t_collapse_block ? 'block' : 'none' ?>">
  2270. <div class="widget-menu margin-left-8">
  2271. <?php
  2272. if( $t_is_temporary && $t_can_persist ) {
  2273. ?>
  2274. <a class="btn btn-primary btn-white btn-round btn-xs"
  2275. title="<?php echo lang_get( 'set_as_persistent_filter' ) ?>"
  2276. href="<?php echo $t_url_persist_filter ?>">
  2277. <i class="ace-icon fa fa-thumb-tack"></i>
  2278. </a>
  2279. <?php
  2280. }
  2281. ?>
  2282. <a class="btn btn-primary btn-white btn-round btn-xs"
  2283. title="<?php echo lang_get( 'reset_query' ) ?>"
  2284. href="<?php echo $t_url_reset_filter ?>">
  2285. <i class="ace-icon fa fa-times"></i>
  2286. </a>
  2287. </div>
  2288. <?php if( count( $t_stored_queries_arr ) > 0 ) { ?>
  2289. <div class="widget-menu hidden-xs">
  2290. <form method="post" action="view_all_set.php">
  2291. <input type="hidden" name="type" value="<?php echo FILTER_ACTION_LOAD ?>" />
  2292. <select id="filter-bar-query-id" class="input-xs">
  2293. <option value="-1"></option>
  2294. <?php
  2295. $t_source_query_id = isset( $t_filter['_source_query_id'] ) ? (int)$t_filter['_source_query_id'] : -1;
  2296. foreach( $t_stored_queries_arr as $t_query_id => $t_query_name ) {
  2297. echo '<option value="' . $t_query_id . '" ';
  2298. check_selected( $t_query_id, $t_source_query_id );
  2299. echo '>' . string_display_line( $t_query_name ) . '</option>';
  2300. }
  2301. ?>
  2302. </select>
  2303. </form>
  2304. </div>
  2305. <?php } ?>
  2306. <div class="widget-menu margin-right-8">
  2307. <form method="post" action="view_all_set.php">
  2308. <input type="hidden" name="type" value="<?php echo FILTER_ACTION_PARSE_ADD ?>" />
  2309. <input id="filter-bar-search-txt" type="text" size="16" class="input-xs"
  2310. placeholder="<?php echo lang_get( 'search' ) ?>"
  2311. name="<?php echo FILTER_PROPERTY_SEARCH ?>"
  2312. value="<?php echo string_attribute( $t_filter[FILTER_PROPERTY_SEARCH] ); ?>" />
  2313. <button id="filter-bar-search-btn" type="submit" name="filter_submit" class="btn btn-primary btn-white btn-round btn-xs"
  2314. title="<?php echo lang_get( 'filter_button' ) ?>">
  2315. <i class="ace-icon fa fa-search"></i>
  2316. </button>
  2317. </form>
  2318. </div>
  2319. </div>
  2320. </div>
  2321. <div class="widget-body">
  2322. <div class="widget-toolbox padding-4 clearfix">
  2323. <div class="btn-toolbar">
  2324. <div class="form-inline">
  2325. <div class="btn-group pull-left">
  2326. <?php
  2327. # Top left toolbar for buttons
  2328. $t_url_reset_filter = 'view_all_set.php?type=' . FILTER_ACTION_RESET;
  2329. if( $t_is_temporary && $t_can_persist ) {
  2330. ?>
  2331. <a class="btn btn-sm btn-primary btn-white btn-round" href="<?php echo $t_url_persist_filter ?>">
  2332. <i class="ace-icon fa fa-thumb-tack"></i>
  2333. <?php echo lang_get( 'persist' ) ?>
  2334. </a>
  2335. <?php
  2336. }
  2337. ?>
  2338. <a class="btn btn-sm btn-primary btn-white btn-round" href="<?php echo $t_url_reset_filter ?>">
  2339. <i class="ace-icon fa fa-times"></i>
  2340. <?php echo lang_get( 'reset' ) ?>
  2341. </a>
  2342. <?php
  2343. if( access_has_project_level( config_get( 'stored_query_create_threshold' ) ) ) {
  2344. $t_url_save_filter = 'query_store_page.php';
  2345. if( filter_is_temporary( $t_filter ) ) {
  2346. $t_url_save_filter .= '?filter=' . filter_get_temporary_key( $t_filter );
  2347. }
  2348. ?>
  2349. <a class="btn btn-sm btn-primary btn-white btn-round" href="<?php echo $t_url_save_filter ?>">
  2350. <i class="ace-icon fa fa-floppy-o"></i>
  2351. <?php echo lang_get( 'save' ) ?>
  2352. </a>
  2353. <?php
  2354. }
  2355. ?>
  2356. </div>
  2357. <?php
  2358. if( count( $t_stored_queries_arr ) > 0 ) { ?>
  2359. <form id="filter-queries-form" class="form-inline pull-left padding-left-8" method="get" name="list_queries<?php echo $t_form_name_suffix;?>" action="view_all_set.php">
  2360. <?php # CSRF protection not required here - form does not result in modifications?>
  2361. <input type="hidden" name="type" value="<?php echo FILTER_ACTION_LOAD ?>" />
  2362. <label><?php echo lang_get( 'load' ) ?>
  2363. <select class="input-s" name="source_query_id">
  2364. <option value="-1"></option>
  2365. <?php
  2366. $t_source_query_id = isset( $t_filter['_source_query_id'] ) ? (int)$t_filter['_source_query_id'] : -1;
  2367. foreach( $t_stored_queries_arr as $t_query_id => $t_query_name ) {
  2368. echo '<option value="' . $t_query_id . '" ';
  2369. check_selected( $t_query_id, $t_source_query_id );
  2370. echo '>' . string_display_line( $t_query_name ) . '</option>';
  2371. }
  2372. ?>
  2373. </select>
  2374. </label>
  2375. </form>
  2376. <?php
  2377. }
  2378. ?>
  2379. </div>
  2380. </div>
  2381. </div>
  2382. <form method="post" name="filters<?php echo $t_form_name_suffix?>" id="filters_form<?php echo $t_form_name_suffix?>" action="view_all_set.php">
  2383. <?php # CSRF protection not required here - form does not result in modifications ?>
  2384. <input type="hidden" name="type" value="<?php echo FILTER_ACTION_PARSE_NEW ?>" />
  2385. <?php
  2386. if( filter_is_temporary( $t_filter ) ) {
  2387. echo '<input type="hidden" name="filter" value="' . filter_get_temporary_key( $t_filter ) . '" />';
  2388. }
  2389. ?>
  2390. <input type="hidden" name="view_type" value="<?php echo $t_view_type?>" />
  2391. <div class="widget-main no-padding">
  2392. <div class="table-responsive">
  2393. <?php
  2394. filter_form_draw_inputs( $t_filter, true, false, 'view_filters_page.php', false /* don't show search */ );
  2395. ?>
  2396. </div>
  2397. </div>
  2398. <div class="widget-toolbox padding-8 clearfix">
  2399. <div class="btn-toolbar pull-left">
  2400. <div class="form-inline">
  2401. <?php echo '<input type="text" id="filter-search-txt" class="input-sm" size="16" name="', FILTER_PROPERTY_SEARCH, '"'
  2402. , ' placeholder="' . lang_get( 'search' ) . '" value="', string_attribute( $t_filter[FILTER_PROPERTY_SEARCH] ), '" />';
  2403. ?>
  2404. <input type="submit" class="btn btn-primary btn-sm btn-white btn-round no-float" name="filter_submit" value="<?php echo lang_get( 'filter_button' )?>" />
  2405. </div>
  2406. </div>
  2407. </div>
  2408. </form>
  2409. </div>
  2410. </div>
  2411. </div>
  2412. </div>
  2413. <?php
  2414. }
  2415. function filter_cache_rows( array $p_filter_ids ) {
  2416. global $g_cache_filter_db_rows;
  2417. if( empty( $p_filter_ids ) ) {
  2418. return;
  2419. }
  2420. $t_ids_not_found = array();
  2421. $t_params = array();
  2422. $t_sql_params = array();
  2423. db_param_push();
  2424. foreach( $p_filter_ids as $t_id ) {
  2425. $t_sql_params[] = db_param();
  2426. $t_params[] = (int)$t_id;
  2427. $t_ids_not_found[$t_id] = $t_id;
  2428. }
  2429. $t_query = 'SELECT * FROM {filters} WHERE id IN ('
  2430. . implode( ',', $t_sql_params ) . ')';
  2431. $t_result = db_query( $t_query, $t_params );
  2432. while( $t_row = db_fetch_array( $t_result ) ) {
  2433. $g_cache_filter_db_rows[$t_row['id']] = $t_row;
  2434. unset( $t_ids_not_found[$t_row['id']] );
  2435. }
  2436. foreach( $t_ids_not_found as $t_id ) {
  2437. $g_cache_filter_db_rows[$t_id] = false;
  2438. }
  2439. }
  2440. /**
  2441. * Clear the filter cache (or just the given id if specified)
  2442. * @param integer $p_filter_id Filter id.
  2443. * @return boolean
  2444. */
  2445. function filter_clear_cache( $p_filter_id = null ) {
  2446. global $g_cache_filter_db_rows;
  2447. if( null === $p_filter_id ) {
  2448. $g_cache_filter_db_rows = array();
  2449. } else {
  2450. unset( $g_cache_filter_db_rows[(int)$p_filter_id] );
  2451. }
  2452. return true;
  2453. }
  2454. /**
  2455. * Update a filter identified by its id
  2456. * Some parameters are optional and only will be updated if provided
  2457. * Note that values are not validated
  2458. * @param int $p_filter_id Filter id
  2459. * @param string $p_filter_string Filter string in custom serialized format
  2460. * @param int $p_project_id
  2461. * @param bool $p_is_public
  2462. * @param string $p_name
  2463. */
  2464. function filter_db_update_filter( $p_filter_id, $p_filter_string, $p_project_id = null, $p_is_public = null, $p_name = null ) {
  2465. db_param_push();
  2466. $t_params = array();
  2467. $t_query = 'UPDATE {filters} SET filter_string=' . db_param();
  2468. $t_params[] = $p_filter_string;
  2469. if( null !== $p_project_id ) {
  2470. $t_query .= ', project_id=' . db_param();
  2471. $t_params[] = (int)$p_project_id;
  2472. }
  2473. if( null !== $p_is_public ) {
  2474. $t_query .= ', is_public=' . db_param();
  2475. $t_params[] = (bool)$p_is_public;
  2476. }
  2477. if( null !== $p_name ) {
  2478. $t_query .= ', name=' . db_param();
  2479. $t_params[] = $p_name;
  2480. }
  2481. $t_query .= ' WHERE id=' . db_param();
  2482. $t_params[] = (int)$p_filter_id;
  2483. db_query( $t_query, $t_params );
  2484. }
  2485. /**
  2486. * Add a filter to the database.
  2487. * This function does not perform any validation on access or inserted data
  2488. *
  2489. * @param string $p_filter_string Filter string in filter-serialized format
  2490. * @param integer $p_user_id User id owner of the filter
  2491. * @param integer $p_project_id Project id associated to the filter
  2492. * @param string $p_name Name of the filter
  2493. * @param boolean $p_is_public Boolean flag to set the filter public
  2494. * @return integer The id of the created row
  2495. */
  2496. function filter_db_create_filter( $p_filter_string, $p_user_id, $p_project_id, $p_name, $p_is_public ) {
  2497. $c_project_id = (int)$p_project_id;
  2498. $c_user_id = $p_user_id;
  2499. $c_is_public = (bool)$p_is_public;
  2500. db_param_push();
  2501. $t_query = 'INSERT INTO {filters} ( user_id, project_id, is_public, name, filter_string )'
  2502. . ' VALUES ( ' . db_param() . ', ' . db_param() . ', ' . db_param() . ', ' . db_param() . ', ' . db_param() . ' )';
  2503. $t_params = array( $c_user_id, $c_project_id, $c_is_public, $p_name, $p_filter_string );
  2504. db_query( $t_query, $t_params );
  2505. return db_insert_id( db_get_table( 'filters' ) );
  2506. }
  2507. /**
  2508. * Updates the default filter for a project and user.
  2509. * We only can have one filter of this kind, per project and user.
  2510. * These special filters are saved in database with a negative project id
  2511. * to differentiate from standard named filters.
  2512. *
  2513. * Note: currently this filter is how the current filter in use is persisted
  2514. * This means: the last used filter settings, for each project, are saved here.
  2515. * @TODO cproensa, theres some suggestions to clean this up:
  2516. * - working filters should not be tracked in database, at least not as unique per user
  2517. * - include a UI functionality to allow setting/clearing these default filters
  2518. * - ideally, the storage should be cleaner: either separated from standard filters
  2519. * or use a proper field in the table, instead of relying on the negative project id
  2520. *
  2521. * @param array $p_filter Filter array
  2522. * @param integer $p_project_id Project id
  2523. * @param integer $p_user_id User id
  2524. * @return integer The filter id that was updated or created
  2525. */
  2526. function filter_set_project_filter( array $p_filter, $p_project_id = null, $p_user_id = null ) {
  2527. if( null === $p_project_id ) {
  2528. $t_project_id = helper_get_current_project();
  2529. } else {
  2530. $t_project_id = (int)$p_project_id;
  2531. }
  2532. if( null === $p_user_id ) {
  2533. $t_user_id = auth_get_current_user_id();
  2534. } else {
  2535. $t_user_id = (int)$p_user_id;
  2536. }
  2537. $p_filter_string = filter_serialize( $p_filter );
  2538. # Check if a row already exists
  2539. $t_id = filter_db_get_project_current( $t_project_id, $p_user_id );
  2540. if( $t_id ) {
  2541. # A row already esxists
  2542. filter_db_update_filter( $t_id, $p_filter_string );
  2543. } else {
  2544. # Must create a row
  2545. $t_db_project_id = -1 * $t_project_id;
  2546. $t_id = filter_db_create_filter( $p_filter_string, $t_user_id, $t_db_project_id, '', false );
  2547. }
  2548. return $t_id;
  2549. }
  2550. /**
  2551. * This function returns the filter string that is tied to the unique id parameter. If the user
  2552. * does not have permission to see this filter, the function returns null
  2553. * @param integer $p_filter_id A Filter identifier.
  2554. * @param integer $p_user_id A valid user identifier.
  2555. * @return mixed
  2556. */
  2557. function filter_db_get_filter_string( $p_filter_id, $p_user_id = null ) {
  2558. $c_filter_id = (int)$p_filter_id;
  2559. if( !filter_is_accessible( $c_filter_id, $p_user_id ) ) {
  2560. return null;
  2561. }
  2562. $t_filter_row = filter_get_row( $c_filter_id );
  2563. return $t_filter_row['filter_string'];
  2564. }
  2565. /**
  2566. * get current filter for given project and user
  2567. * @param integer $p_project_id A project identifier.
  2568. * @param integer $p_user_id A valid user identifier.
  2569. * @return integer
  2570. */
  2571. function filter_db_get_project_current( $p_project_id = null, $p_user_id = null ) {
  2572. if( null === $p_project_id ) {
  2573. $c_project_id = helper_get_current_project();
  2574. } else {
  2575. $c_project_id = (int)$p_project_id;
  2576. }
  2577. if( null === $p_user_id ) {
  2578. $c_user_id = auth_get_current_user_id();
  2579. } else {
  2580. $c_user_id = (int)$p_user_id;
  2581. }
  2582. # we store current filters for each project with a special project index
  2583. $t_filter_project_id = $c_project_id * -1;
  2584. db_param_push();
  2585. $t_query = 'SELECT id FROM {filters} WHERE user_id = ' . db_param()
  2586. . ' AND project_id = ' . db_param() . ' AND name = ' . db_param();
  2587. $t_result = db_query( $t_query, array( $c_user_id, $t_filter_project_id, '' ) );
  2588. if( $t_row = db_fetch_array( $t_result ) ) {
  2589. return $t_row['id'];
  2590. }
  2591. return null;
  2592. }
  2593. /**
  2594. * Query for the filter name using the filter id
  2595. * @param integer $p_filter_id Filter id.
  2596. * @return string
  2597. */
  2598. function filter_db_get_name( $p_filter_id ) {
  2599. $c_filter_id = (int)$p_filter_id;
  2600. $t_filter_row = filter_get_row( $c_filter_id );
  2601. if( !$t_filter_row ) {
  2602. return null;
  2603. }
  2604. if( $t_filter_row['user_id'] != auth_get_current_user_id() ) {
  2605. if( $t_filter_row['is_public'] != true ) {
  2606. return null;
  2607. }
  2608. }
  2609. return $t_filter_row['name'];
  2610. }
  2611. /**
  2612. * Check if the current user has permissions to delete the stored query
  2613. * @param integer $p_filter_id Filter id.
  2614. * @param integer|null User id or null for logged in user.
  2615. * @return boolean
  2616. */
  2617. function filter_db_can_delete_filter( $p_filter_id, $p_user_id = null ) {
  2618. $c_filter_id = (int)$p_filter_id;
  2619. $t_user_id = $p_user_id != null ? $p_user_id : auth_get_current_user_id();
  2620. # Administrators can delete any filter
  2621. if( user_is_administrator( $t_user_id ) ) {
  2622. return true;
  2623. }
  2624. $t_filter_row = filter_get_row( $c_filter_id );
  2625. if( $t_filter_row
  2626. && $t_filter_row['user_id'] == $t_user_id
  2627. && $t_filter_row['project_id'] >= 0 ) {
  2628. return true;
  2629. } else {
  2630. return false;
  2631. }
  2632. }
  2633. /**
  2634. * Delete the filter specified by $p_filter_id
  2635. * @param integer $p_filter_id Filter identifier.
  2636. * @return boolean
  2637. */
  2638. function filter_db_delete_filter( $p_filter_id ) {
  2639. $c_filter_id = (int)$p_filter_id;
  2640. if( !filter_db_can_delete_filter( $c_filter_id ) ) {
  2641. return false;
  2642. }
  2643. db_param_push();
  2644. $t_query = 'DELETE FROM {filters} WHERE id=' . db_param();
  2645. db_query( $t_query, array( $c_filter_id ) );
  2646. return true;
  2647. }
  2648. /**
  2649. * Delete all the unnamed filters
  2650. * @return void
  2651. */
  2652. function filter_db_delete_current_filters() {
  2653. $t_all_id = ALL_PROJECTS;
  2654. db_param_push();
  2655. $t_query = 'DELETE FROM {filters} WHERE project_id<=' . db_param() . ' AND name=' . db_param();
  2656. db_query( $t_query, array( $t_all_id, '' ) );
  2657. }
  2658. /**
  2659. * Returns stored filters based on a combination of linked project, owner user and public status.
  2660. * Any parameter can be defaulted to null, to get all results for that field
  2661. * @param integer $p_project_id Project id linked to the filter
  2662. * @param integer $p_user_id User id, meaning filter owner
  2663. * @param boolean $p_public Public flag for filter
  2664. * @return array Array of filter ids and names
  2665. */
  2666. function filter_db_get_named_filters( $p_project_id = null, $p_user_id = null, $p_public = null ) {
  2667. db_param_push();
  2668. $t_params = array();
  2669. $t_query = 'SELECT id, name FROM {filters} WHERE project_id >= ' . db_param();
  2670. $t_params[] = 0;
  2671. # build where clauses
  2672. if( null !== $p_project_id ) {
  2673. $t_query .= ' AND project_id = ' . db_param();
  2674. $t_params[] = (int)$p_project_id;
  2675. }
  2676. if( null !== $p_user_id ) {
  2677. $t_query .= ' AND user_id = ' . db_param();
  2678. $t_params[] = (int)$p_user_id;
  2679. }
  2680. if( null !== $p_public ) {
  2681. $t_query .= ' AND is_public = ' . db_param();
  2682. # cast $p_public to strict true/false values
  2683. $t_params[] = $p_public ? true : false;
  2684. }
  2685. $t_result = db_query( $t_query, $t_params );
  2686. $t_query_arr = array();
  2687. while( $t_row = db_fetch_array( $t_result ) ) {
  2688. $t_query_arr[$t_row['id']] = $t_row['name'];
  2689. }
  2690. return $t_query_arr;
  2691. }
  2692. /**
  2693. * Get the list of available filters.
  2694. *
  2695. * @param integer|null $p_project_id A valid project identifier or null for current project.
  2696. * @param integer|null $p_user_id A valid user identifier or null for logged in user.
  2697. * @param boolean $p_filter_by_project Only return filters associated with specified project id or All Projects, otherwise return all filters for user.
  2698. * @param boolean $p_return_names_only true: return names of filters, false: return structures with filter header information.
  2699. * @return array Array of filters.
  2700. */
  2701. function filter_db_get_available_queries( $p_project_id = null, $p_user_id = null, $p_filter_by_project = true, $p_return_names_only = true ) {
  2702. if( null === $p_project_id ) {
  2703. $t_project_id = helper_get_current_project();
  2704. } else {
  2705. $t_project_id = (int)$p_project_id;
  2706. }
  2707. if( null === $p_user_id ) {
  2708. $t_user_id = auth_get_current_user_id();
  2709. } else {
  2710. $t_user_id = (int)$p_user_id;
  2711. }
  2712. # If the user doesn't have access rights to stored queries, just return
  2713. if( !access_has_project_level( config_get( 'stored_query_use_threshold' ) ) ) {
  2714. return array();
  2715. }
  2716. # Get the list of available queries. By sorting such that public queries are
  2717. # first, we can override any query that has the same name as a private query
  2718. # with that private one
  2719. db_param_push();
  2720. if( $p_filter_by_project ) {
  2721. $t_query = 'SELECT * FROM {filters}
  2722. WHERE (project_id = ' . db_param() . '
  2723. OR project_id = 0)
  2724. AND name != \'\'
  2725. AND (is_public = ' . db_param() . '
  2726. OR user_id = ' . db_param() . ')
  2727. ORDER BY is_public DESC, name ASC';
  2728. $t_result = db_query( $t_query, array( $t_project_id, true, $t_user_id ) );
  2729. } else {
  2730. $t_project_ids = user_get_all_accessible_projects( $t_user_id );
  2731. $t_project_ids[] = ALL_PROJECTS;
  2732. $t_query = 'SELECT * FROM {filters}
  2733. WHERE project_id in (' . implode( ',', $t_project_ids ) . ')
  2734. AND name != \'\'
  2735. AND (is_public = ' . db_param() . '
  2736. OR user_id = ' . db_param() . ')
  2737. ORDER BY is_public DESC, name ASC';
  2738. $t_result = db_query( $t_query, array( true, $t_user_id ) );
  2739. }
  2740. $t_filters = array();
  2741. # first build the id=>name array
  2742. while( $t_row = db_fetch_array( $t_result ) ) {
  2743. $t_filters[$t_row['id']] = $t_row['name'];
  2744. }
  2745. filter_cache_rows( array_keys( $t_filters ) );
  2746. if( $p_return_names_only ) {
  2747. asort( $t_filters );
  2748. return $t_filters;
  2749. }
  2750. # build an extended array of name=>{filter data}
  2751. $t_filter_data = array();
  2752. foreach( $t_filters as $t_filter_id => $t_filter_name ) {
  2753. $t_row = array();
  2754. $t_filter_obj = filter_get( $t_filter_id );
  2755. if( !$t_filter_obj ) {
  2756. continue;
  2757. }
  2758. $t_row = filter_get_row( $t_filter_id );
  2759. $t_row['criteria'] = $t_filter_obj;
  2760. $t_row['url'] = filter_get_url( $t_filter_obj );
  2761. $t_filter_data[$t_filter_name] = $t_row;
  2762. }
  2763. return $t_filter_data;
  2764. }
  2765. /**
  2766. * Check that the given filter name does not exceed the maximum filter length
  2767. * @param string $p_name Filter name.
  2768. * @return boolean true when under max_length (64) and false when over
  2769. */
  2770. function filter_name_valid_length( $p_name ) {
  2771. if( mb_strlen( $p_name ) > 64 ) {
  2772. return false;
  2773. } else {
  2774. return true;
  2775. }
  2776. }
  2777. /**
  2778. * Create a filter for getting issues modified in the last N days.
  2779. * @param integer $p_days Number of days counting from today
  2780. * @param array $p_filter Add the filter conditions over this filter array. Return a new one if null
  2781. * @return array Filter array
  2782. */
  2783. function filter_create_recently_modified( $p_days, $p_filter = null ) {
  2784. if( null === $p_filter ) {
  2785. $p_filter = filter_get_default();
  2786. # This filter overrides default "hide status" property
  2787. $p_filter[FILTER_PROPERTY_HIDE_STATUS] = META_FILTER_NONE;
  2788. }
  2789. $c_days = (int)$p_days;
  2790. $p_filter[FILTER_PROPERTY_FILTER_BY_LAST_UPDATED_DATE] = true;
  2791. $t_date = new DateTime('today');
  2792. $p_filter[FILTER_PROPERTY_LAST_UPDATED_END_DAY] = $t_date->format( 'j' );
  2793. $p_filter[FILTER_PROPERTY_LAST_UPDATED_END_MONTH] = $t_date->format( 'n' );
  2794. $p_filter[FILTER_PROPERTY_LAST_UPDATED_END_YEAR] = $t_date->format( 'Y' );
  2795. $t_date->modify( '-' . $c_days . ' days' );
  2796. $p_filter[FILTER_PROPERTY_LAST_UPDATED_START_DAY] = $t_date->format( 'j' );
  2797. $p_filter[FILTER_PROPERTY_LAST_UPDATED_START_MONTH] = $t_date->format( 'n' );
  2798. $p_filter[FILTER_PROPERTY_LAST_UPDATED_START_YEAR] = $t_date->format( 'Y' );
  2799. return filter_ensure_valid_filter( $p_filter );
  2800. }
  2801. /**
  2802. * Create a filter for getting issues assigned to the specified project and user that
  2803. * are not yet resolved.
  2804. *
  2805. * @param integer $p_project_id The project id or ALL_PROJECTS.
  2806. * @param integer $p_user_id The user id or 0 to get unassigned issues.
  2807. * @return mixed valid filter.
  2808. */
  2809. function filter_create_assigned_to_unresolved( $p_project_id, $p_user_id ) {
  2810. $t_filter = filter_get_default();
  2811. if( $p_user_id == 0 ) {
  2812. $t_filter[FILTER_PROPERTY_HANDLER_ID] = array( '0' => META_FILTER_NONE );
  2813. } else {
  2814. $t_filter[FILTER_PROPERTY_HANDLER_ID] = array( '0' => $p_user_id );
  2815. }
  2816. $t_bug_resolved_status_threshold = config_get( 'bug_resolved_status_threshold', null, $p_user_id, $p_project_id );
  2817. $t_filter[FILTER_PROPERTY_HIDE_STATUS] = array( '0' => $t_bug_resolved_status_threshold );
  2818. if( $p_project_id != ALL_PROJECTS ) {
  2819. $t_filter[FILTER_PROPERTY_PROJECT_ID] = array( '0' => $p_project_id );
  2820. }
  2821. return filter_ensure_valid_filter( $t_filter );
  2822. }
  2823. /**
  2824. * Create a filter for getting issues reported by the specified project and user.
  2825. * @param integer $p_project_id The project id or ALL_PROJECTS.
  2826. * @param integer $p_user_id A valid user identifier.
  2827. * @return array a valid filter.
  2828. */
  2829. function filter_create_reported_by( $p_project_id, $p_user_id ) {
  2830. $t_filter = filter_get_default();
  2831. $t_filter[FILTER_PROPERTY_REPORTER_ID] = array( '0' => $p_user_id );
  2832. if( $p_project_id != ALL_PROJECTS ) {
  2833. $t_filter[FILTER_PROPERTY_PROJECT_ID] = array( '0' => $p_project_id );
  2834. }
  2835. return filter_ensure_valid_filter( $t_filter );
  2836. }
  2837. /**
  2838. * Create a filter for getting issues monitored by the specified project and user.
  2839. * @param integer $p_project_id The project id or ALL_PROJECTS.
  2840. * @param integer $p_user_id The user id.
  2841. * @return array a valid filter.
  2842. */
  2843. function filter_create_monitored_by( $p_project_id, $p_user_id ) {
  2844. $t_filter = filter_get_default();
  2845. if( $p_user_id == 0 ) {
  2846. $t_filter[FILTER_PROPERTY_MONITOR_USER_ID] = array( '0' => META_FILTER_NONE );
  2847. } else {
  2848. $t_filter[FILTER_PROPERTY_MONITOR_USER_ID] = array( '0' => $p_user_id );
  2849. }
  2850. if( $p_project_id != ALL_PROJECTS ) {
  2851. $t_filter[FILTER_PROPERTY_PROJECT_ID] = array( '0' => $p_project_id );
  2852. }
  2853. return filter_ensure_valid_filter( $t_filter );
  2854. }
  2855. /**
  2856. * Performs the reading of parameters from get/post.
  2857. * If a filter array is passed as parameter, the read parameters will be appended,
  2858. * or everride existing ones.
  2859. * If no filter array is used as parameter, a default one will be used.
  2860. * @param array $p_filter An existing filter array
  2861. * @return array The resulting filter array
  2862. */
  2863. function filter_gpc_get( array $p_filter = null ) {
  2864. # Get or copy the view_type first as it's needed to get proper defaults
  2865. $f_view_type = gpc_get_string( 'view_type', null );
  2866. if( null === $f_view_type && is_array( $p_filter ) && isset( $p_filter['_view_type'] ) ) {
  2867. $f_view_type = $p_filter['_view_type'];
  2868. }
  2869. if( null === $p_filter ) {
  2870. $t_filter = filter_get_default_array( $f_view_type );
  2871. } else {
  2872. $t_filter = filter_ensure_fields( $p_filter );
  2873. }
  2874. # these are all possibly multiple selections for advanced filtering
  2875. # If a single value is provided, it will be normalized to an array with 'filter_ensure_valid_filter()'
  2876. $f_show_category = gpc_get( FILTER_PROPERTY_CATEGORY_ID, $t_filter[FILTER_PROPERTY_CATEGORY_ID] );
  2877. $f_platform = gpc_get( FILTER_PROPERTY_PLATFORM, $t_filter[FILTER_PROPERTY_PLATFORM] );
  2878. $f_os = gpc_get( FILTER_PROPERTY_OS, $t_filter[FILTER_PROPERTY_OS] );
  2879. $f_os_build = gpc_get( FILTER_PROPERTY_OS_BUILD, $t_filter[FILTER_PROPERTY_OS_BUILD] );
  2880. $f_show_severity = gpc_get( FILTER_PROPERTY_SEVERITY, $t_filter[FILTER_PROPERTY_SEVERITY] );
  2881. $f_show_status = gpc_get( FILTER_PROPERTY_STATUS, $t_filter[FILTER_PROPERTY_STATUS] );
  2882. $f_hide_status = gpc_get( FILTER_PROPERTY_HIDE_STATUS, $t_filter[FILTER_PROPERTY_HIDE_STATUS] );
  2883. $f_reporter_id = gpc_get( FILTER_PROPERTY_REPORTER_ID, $t_filter[FILTER_PROPERTY_REPORTER_ID] );
  2884. $f_handler_id = gpc_get( FILTER_PROPERTY_HANDLER_ID, $t_filter[FILTER_PROPERTY_HANDLER_ID] );
  2885. $f_project_id = gpc_get( FILTER_PROPERTY_PROJECT_ID, $t_filter[FILTER_PROPERTY_PROJECT_ID] );
  2886. $f_show_resolution = gpc_get( FILTER_PROPERTY_RESOLUTION, $t_filter[FILTER_PROPERTY_RESOLUTION] );
  2887. $f_show_build = gpc_get( FILTER_PROPERTY_BUILD, $t_filter[FILTER_PROPERTY_BUILD] );
  2888. $f_show_version = gpc_get( FILTER_PROPERTY_VERSION, $t_filter[FILTER_PROPERTY_VERSION] );
  2889. $f_fixed_in_version = gpc_get( FILTER_PROPERTY_FIXED_IN_VERSION, $t_filter[FILTER_PROPERTY_FIXED_IN_VERSION] );
  2890. $f_target_version = gpc_get( FILTER_PROPERTY_TARGET_VERSION, $t_filter[FILTER_PROPERTY_TARGET_VERSION] );
  2891. $f_show_profile = gpc_get( FILTER_PROPERTY_PROFILE_ID, $t_filter[FILTER_PROPERTY_PROFILE_ID] );
  2892. $f_show_priority = gpc_get( FILTER_PROPERTY_PRIORITY, $t_filter[FILTER_PROPERTY_PRIORITY] );
  2893. $f_user_monitor = gpc_get( FILTER_PROPERTY_MONITOR_USER_ID, $t_filter[FILTER_PROPERTY_MONITOR_USER_ID] );
  2894. $f_note_user_id = gpc_get( FILTER_PROPERTY_NOTE_USER_ID, $t_filter[FILTER_PROPERTY_NOTE_USER_ID] );
  2895. $f_match_type = gpc_get_int( FILTER_PROPERTY_MATCH_TYPE, $t_filter[FILTER_PROPERTY_MATCH_TYPE] );
  2896. # these are only single values, even when doing advanced filtering
  2897. $f_per_page = gpc_get_int( FILTER_PROPERTY_ISSUES_PER_PAGE, $t_filter[FILTER_PROPERTY_ISSUES_PER_PAGE] );
  2898. $f_highlight_changed = gpc_get_int( FILTER_PROPERTY_HIGHLIGHT_CHANGED, $t_filter[FILTER_PROPERTY_HIGHLIGHT_CHANGED] );
  2899. $f_sticky_issues = gpc_get_bool( FILTER_PROPERTY_STICKY, $t_filter[FILTER_PROPERTY_STICKY] );
  2900. # This sort parameter is a set of comma separated values, and can be an array of parameters.
  2901. # sort="c1,c2" as used by permalinks
  2902. # sort[]="c1" sort[]="c2" as used by filter form
  2903. gpc_make_array( FILTER_PROPERTY_SORT_FIELD_NAME );
  2904. $f_sort_array = gpc_get_string_array( FILTER_PROPERTY_SORT_FIELD_NAME, array() );
  2905. # This sort parameter is an incremental column addition to current sort set.
  2906. # Only one column/dir, which is added to the front.
  2907. $f_sort_add = gpc_get_string( FILTER_PROPERTY_SORT_FIELD_NAME . '_add', null );
  2908. if( !empty( $f_sort_array ) ) {
  2909. gpc_make_array( FILTER_PROPERTY_SORT_DIRECTION );
  2910. $f_dir_array = gpc_get_string_array( FILTER_PROPERTY_SORT_DIRECTION, array() );
  2911. $t_new_sort_array = array();
  2912. $t_new_dir_array = array();
  2913. # evaluate each parameter, checks that "dir" may be omitted in order to avoid shifting subsequent parameters
  2914. $t_count = count( $f_sort_array );
  2915. for( $ix = 0; $ix < $t_count; $ix++ ) {
  2916. $t_param_columns = explode( ',', $f_sort_array[$ix] );
  2917. if( isset( $f_dir_array[$ix] ) ) {
  2918. $t_param_dirs = explode( ',', $f_dir_array[$ix] );
  2919. } else {
  2920. $t_param_dirs = array();
  2921. }
  2922. # fill the gaps with dummy string, they will be defaulted by ensure_valid_filter
  2923. if( count( $t_param_dirs ) < count( $t_param_columns ) ) {
  2924. $t_param_dirs = array_pad( $t_param_dirs, count( $t_param_columns ), '' );
  2925. }
  2926. $t_new_sort_array = array_merge( $t_new_sort_array, $t_param_columns );
  2927. $t_new_dir_array = array_merge( $t_new_dir_array, $t_param_dirs );
  2928. }
  2929. $f_sort = implode( ',', $t_new_sort_array );
  2930. $f_dir = implode( ',', $t_new_dir_array );
  2931. } elseif( null !== $f_sort_add ) {
  2932. # this parameter has to be pushed in front of current sort set
  2933. $f_dir_add = gpc_get_string( FILTER_PROPERTY_SORT_DIRECTION . '_add', '' );
  2934. # Plain concatenation. Empty fields, or extra commas will be cleaned by ensure_valid_filter
  2935. $f_sort = $f_sort_add . ',' . $t_filter[FILTER_PROPERTY_SORT_FIELD_NAME];
  2936. $f_dir = $f_dir_add . ',' . $t_filter[FILTER_PROPERTY_SORT_DIRECTION];
  2937. } else {
  2938. # use the defaults
  2939. $f_sort = $t_filter[FILTER_PROPERTY_SORT_FIELD_NAME];
  2940. $f_dir = $t_filter[FILTER_PROPERTY_SORT_DIRECTION];
  2941. }
  2942. # date values
  2943. # creation date
  2944. $f_do_filter_by_date = gpc_get_bool( FILTER_PROPERTY_FILTER_BY_DATE_SUBMITTED, $t_filter[FILTER_PROPERTY_FILTER_BY_DATE_SUBMITTED] );
  2945. $f_start_month = gpc_get_int( FILTER_PROPERTY_DATE_SUBMITTED_START_MONTH, $t_filter[FILTER_PROPERTY_DATE_SUBMITTED_START_MONTH] );
  2946. $f_end_month = gpc_get_int( FILTER_PROPERTY_DATE_SUBMITTED_END_MONTH, $t_filter[FILTER_PROPERTY_DATE_SUBMITTED_END_MONTH] );
  2947. $f_start_day = gpc_get_int( FILTER_PROPERTY_DATE_SUBMITTED_START_DAY, $t_filter[FILTER_PROPERTY_DATE_SUBMITTED_START_DAY] );
  2948. $f_end_day = gpc_get_int( FILTER_PROPERTY_DATE_SUBMITTED_END_DAY, $t_filter[FILTER_PROPERTY_DATE_SUBMITTED_END_DAY] );
  2949. $f_start_year = gpc_get_int( FILTER_PROPERTY_DATE_SUBMITTED_START_YEAR, $t_filter[FILTER_PROPERTY_DATE_SUBMITTED_START_YEAR] );
  2950. $f_end_year = gpc_get_int( FILTER_PROPERTY_DATE_SUBMITTED_END_YEAR, $t_filter[FILTER_PROPERTY_DATE_SUBMITTED_END_YEAR] );
  2951. # last_updated date values
  2952. $f_do_filter_by_last_updated_date = gpc_get_bool( FILTER_PROPERTY_FILTER_BY_LAST_UPDATED_DATE, $t_filter[FILTER_PROPERTY_FILTER_BY_LAST_UPDATED_DATE] );
  2953. $f_last_updated_start_month = gpc_get_int( FILTER_PROPERTY_LAST_UPDATED_START_MONTH, $t_filter[FILTER_PROPERTY_LAST_UPDATED_START_MONTH] );
  2954. $f_last_updated_end_month = gpc_get_int( FILTER_PROPERTY_LAST_UPDATED_END_MONTH, $t_filter[FILTER_PROPERTY_LAST_UPDATED_END_MONTH] );
  2955. $f_last_updated_start_day = gpc_get_int( FILTER_PROPERTY_LAST_UPDATED_START_DAY, $t_filter[FILTER_PROPERTY_LAST_UPDATED_START_DAY] );
  2956. $f_last_updated_end_day = gpc_get_int( FILTER_PROPERTY_LAST_UPDATED_END_DAY, $t_filter[FILTER_PROPERTY_LAST_UPDATED_END_DAY] );
  2957. $f_last_updated_start_year = gpc_get_int( FILTER_PROPERTY_LAST_UPDATED_START_YEAR, $t_filter[FILTER_PROPERTY_LAST_UPDATED_START_YEAR] );
  2958. $f_last_updated_end_year = gpc_get_int( FILTER_PROPERTY_LAST_UPDATED_END_YEAR, $t_filter[FILTER_PROPERTY_LAST_UPDATED_END_YEAR] );
  2959. $f_search = gpc_get_string( FILTER_PROPERTY_SEARCH, $t_filter[FILTER_PROPERTY_SEARCH] );
  2960. $f_view_state = gpc_get_int( FILTER_PROPERTY_VIEW_STATE, $t_filter[FILTER_PROPERTY_VIEW_STATE] );
  2961. $f_tag_string = gpc_get_string( FILTER_PROPERTY_TAG_STRING, $t_filter[FILTER_PROPERTY_TAG_STRING] );
  2962. $f_tag_select = gpc_get_int( FILTER_PROPERTY_TAG_SELECT, $t_filter[FILTER_PROPERTY_TAG_SELECT] );
  2963. # plugin filter updates
  2964. $t_plugin_filters = filter_get_plugin_filters();
  2965. $t_filter_input = array();
  2966. foreach( $t_plugin_filters as $t_field_name => $t_filter_object ) {
  2967. switch( $t_filter_object->type ) {
  2968. case FILTER_TYPE_STRING:
  2969. $t_filter_input[$t_field_name] = gpc_get_string( $t_field_name, $t_filter[$t_field_name] );
  2970. break;
  2971. case FILTER_TYPE_INT:
  2972. $t_filter_input[$t_field_name] = gpc_get_int( $t_field_name, $t_filter[$t_field_name] );
  2973. break;
  2974. case FILTER_TYPE_BOOLEAN:
  2975. $t_filter_input[$t_field_name] = gpc_get_bool( $t_field_name, $t_filter[$t_field_name]);
  2976. break;
  2977. case FILTER_TYPE_MULTI_STRING:
  2978. $t_filter_input[$t_field_name] = gpc_get_string_array( $t_field_name, $t_filter[$t_field_name] );
  2979. break;
  2980. case FILTER_TYPE_MULTI_INT:
  2981. $t_filter_input[$t_field_name] = gpc_get_int_array( $t_field_name, $t_filter[$t_field_name] );
  2982. break;
  2983. }
  2984. }
  2985. # custom field updates
  2986. $t_custom_fields = custom_field_get_ids(); # @todo (thraxisp) This should really be the linked ids, but we don't know the project
  2987. $f_custom_fields_data = $t_filter['custom_fields'];
  2988. if( is_array( $t_custom_fields ) && ( count( $t_custom_fields ) > 0 ) ) {
  2989. foreach( $t_custom_fields as $t_cfid ) {
  2990. if( custom_field_type( $t_cfid ) == CUSTOM_FIELD_TYPE_DATE ) {
  2991. # check if gpc parameters are present, otherwise skip parsing.
  2992. if( !gpc_isset( 'custom_field_' . $t_cfid . '_control' ) ) {
  2993. continue;
  2994. }
  2995. $f_custom_fields_data[$t_cfid] = array();
  2996. # Get date control property
  2997. $t_control = gpc_get_string( 'custom_field_' . $t_cfid . '_control', null );
  2998. $f_custom_fields_data[$t_cfid][0] = $t_control;
  2999. $t_one_day = 86399;
  3000. # Get start date. If there is a timestamp input provided, use it,
  3001. # otherwise, look for individual date parts
  3002. $f_start_date = gpc_get( 'custom_field_' . $t_cfid . '_start_timestamp', null );
  3003. if( null !== $f_start_date ) {
  3004. $t_start_date = (int)$f_start_date;
  3005. $t_start = $t_start_date;
  3006. } else {
  3007. $t_year = gpc_get_int( 'custom_field_' . $t_cfid . '_start_year', null );
  3008. $t_month = gpc_get_int( 'custom_field_' . $t_cfid . '_start_month', null );
  3009. $t_day = gpc_get_int( 'custom_field_' . $t_cfid . '_start_day', null );
  3010. $t_start_date = mktime( 0, 0, 0, $t_month, $t_day, $t_year );
  3011. # calculate correct timestamps
  3012. $t_start = 1;
  3013. switch( $t_control ) {
  3014. case CUSTOM_FIELD_DATE_ANY:
  3015. case CUSTOM_FIELD_DATE_NONE:
  3016. case CUSTOM_FIELD_DATE_ONORBEFORE:
  3017. case CUSTOM_FIELD_DATE_BEFORE:
  3018. break ;
  3019. case CUSTOM_FIELD_DATE_BETWEEN:
  3020. $t_start = $t_start_date;
  3021. break ;
  3022. case CUSTOM_FIELD_DATE_ON:
  3023. $t_start = $t_start_date;
  3024. break;
  3025. case CUSTOM_FIELD_DATE_AFTER:
  3026. $t_start = $t_start_date + $t_one_day - 1;
  3027. break;
  3028. case CUSTOM_FIELD_DATE_ONORAFTER:
  3029. $t_start = $t_start_date;
  3030. break;
  3031. }
  3032. }
  3033. $f_custom_fields_data[$t_cfid][1] = $t_start;
  3034. # Get end date. If there is a timestamp input provided, use it,
  3035. # otherwise, look for individual date parts
  3036. $f_end_date = gpc_get( 'custom_field_' . $t_cfid . '_end_timestamp', null );
  3037. if( null !== $f_end_date ) {
  3038. $t_end_date = (int)$f_end_date;
  3039. $t_end = $t_end_date;
  3040. } else {
  3041. $t_year = gpc_get_int( 'custom_field_' . $t_cfid . '_end_year', null );
  3042. $t_month = gpc_get_int( 'custom_field_' . $t_cfid . '_end_month', null );
  3043. $t_day = gpc_get_int( 'custom_field_' . $t_cfid . '_end_day', null );
  3044. $t_end_date = mktime( 0, 0, 0, $t_month, $t_day, $t_year );
  3045. # calculate correct timestamps
  3046. $t_end = 1;
  3047. switch( $t_control ) {
  3048. case CUSTOM_FIELD_DATE_ANY:
  3049. case CUSTOM_FIELD_DATE_NONE:
  3050. break ;
  3051. case CUSTOM_FIELD_DATE_BETWEEN:
  3052. $t_end = $t_end_date + $t_one_day - 1;
  3053. break ;
  3054. case CUSTOM_FIELD_DATE_ONORBEFORE:
  3055. $t_end = $t_start_date + $t_one_day - 1;
  3056. break;
  3057. case CUSTOM_FIELD_DATE_BEFORE:
  3058. $t_end = $t_start_date;
  3059. break ;
  3060. case CUSTOM_FIELD_DATE_ON:
  3061. $t_end = $t_start_date + $t_one_day - 1;
  3062. break;
  3063. case CUSTOM_FIELD_DATE_AFTER:
  3064. $t_end = 2147483647; # Some time in 2038, max value of a signed int.
  3065. break;
  3066. case CUSTOM_FIELD_DATE_ONORAFTER:
  3067. $t_end = 2147483647; # Some time in 2038, max value of a signed int.
  3068. break;
  3069. }
  3070. }
  3071. $f_custom_fields_data[$t_cfid][2] = $t_end;
  3072. } else {
  3073. # check if gpc parameters are present, otherwise skip parsing.
  3074. if( !gpc_isset( 'custom_field_' . $t_cfid ) ) {
  3075. continue;
  3076. }
  3077. if( is_array( gpc_get( 'custom_field_' . $t_cfid, null ) ) ) {
  3078. $f_custom_fields_data[$t_cfid] = gpc_get_string_array( 'custom_field_' . $t_cfid, array( META_FILTER_ANY ) );
  3079. } else {
  3080. $f_custom_fields_data[$t_cfid] = gpc_get_string( 'custom_field_' . $t_cfid, META_FILTER_ANY );
  3081. $f_custom_fields_data[$t_cfid] = array( $f_custom_fields_data[$t_cfid] );
  3082. }
  3083. }
  3084. }
  3085. }
  3086. $f_relationship_type = gpc_get_int( FILTER_PROPERTY_RELATIONSHIP_TYPE, $t_filter[FILTER_PROPERTY_RELATIONSHIP_TYPE] );
  3087. $f_relationship_bug = gpc_get_int( FILTER_PROPERTY_RELATIONSHIP_BUG, $t_filter[FILTER_PROPERTY_RELATIONSHIP_BUG] );
  3088. log_event( LOG_FILTERING, 'filter_gpc_get: Update filters' );
  3089. $t_filter_input['_version'] = FILTER_VERSION;
  3090. $t_filter_input['_view_type'] = $f_view_type;
  3091. $t_filter_input[FILTER_PROPERTY_CATEGORY_ID] = $f_show_category;
  3092. $t_filter_input[FILTER_PROPERTY_SEVERITY] = $f_show_severity;
  3093. $t_filter_input[FILTER_PROPERTY_STATUS] = $f_show_status;
  3094. $t_filter_input[FILTER_PROPERTY_ISSUES_PER_PAGE] = $f_per_page;
  3095. $t_filter_input[FILTER_PROPERTY_HIGHLIGHT_CHANGED] = $f_highlight_changed;
  3096. $t_filter_input[FILTER_PROPERTY_REPORTER_ID] = $f_reporter_id;
  3097. $t_filter_input[FILTER_PROPERTY_HANDLER_ID] = $f_handler_id;
  3098. $t_filter_input[FILTER_PROPERTY_PROJECT_ID] = $f_project_id;
  3099. $t_filter_input[FILTER_PROPERTY_SORT_FIELD_NAME] = $f_sort;
  3100. $t_filter_input[FILTER_PROPERTY_SORT_DIRECTION] = $f_dir;
  3101. $t_filter_input[FILTER_PROPERTY_FILTER_BY_DATE_SUBMITTED] = $f_do_filter_by_date;
  3102. $t_filter_input[FILTER_PROPERTY_DATE_SUBMITTED_START_MONTH] = $f_start_month;
  3103. $t_filter_input[FILTER_PROPERTY_DATE_SUBMITTED_START_DAY] = $f_start_day;
  3104. $t_filter_input[FILTER_PROPERTY_DATE_SUBMITTED_START_YEAR] = $f_start_year;
  3105. $t_filter_input[FILTER_PROPERTY_DATE_SUBMITTED_END_MONTH] = $f_end_month;
  3106. $t_filter_input[FILTER_PROPERTY_DATE_SUBMITTED_END_DAY] = $f_end_day;
  3107. $t_filter_input[FILTER_PROPERTY_DATE_SUBMITTED_END_YEAR] = $f_end_year;
  3108. $t_filter_input[FILTER_PROPERTY_FILTER_BY_LAST_UPDATED_DATE] = $f_do_filter_by_last_updated_date;
  3109. $t_filter_input[FILTER_PROPERTY_LAST_UPDATED_START_MONTH] = $f_last_updated_start_month;
  3110. $t_filter_input[FILTER_PROPERTY_LAST_UPDATED_START_DAY] = $f_last_updated_start_day;
  3111. $t_filter_input[FILTER_PROPERTY_LAST_UPDATED_START_YEAR] = $f_last_updated_start_year;
  3112. $t_filter_input[FILTER_PROPERTY_LAST_UPDATED_END_MONTH] = $f_last_updated_end_month;
  3113. $t_filter_input[FILTER_PROPERTY_LAST_UPDATED_END_DAY] = $f_last_updated_end_day;
  3114. $t_filter_input[FILTER_PROPERTY_LAST_UPDATED_END_YEAR] = $f_last_updated_end_year;
  3115. $t_filter_input[FILTER_PROPERTY_SEARCH] = $f_search;
  3116. $t_filter_input[FILTER_PROPERTY_HIDE_STATUS] = $f_hide_status;
  3117. $t_filter_input[FILTER_PROPERTY_RESOLUTION] = $f_show_resolution;
  3118. $t_filter_input[FILTER_PROPERTY_BUILD] = $f_show_build;
  3119. $t_filter_input[FILTER_PROPERTY_VERSION] = $f_show_version;
  3120. $t_filter_input[FILTER_PROPERTY_FIXED_IN_VERSION] = $f_fixed_in_version;
  3121. $t_filter_input[FILTER_PROPERTY_TARGET_VERSION] = $f_target_version;
  3122. $t_filter_input[FILTER_PROPERTY_PRIORITY] = $f_show_priority;
  3123. $t_filter_input[FILTER_PROPERTY_MONITOR_USER_ID] = $f_user_monitor;
  3124. $t_filter_input[FILTER_PROPERTY_VIEW_STATE] = $f_view_state;
  3125. $t_filter_input['custom_fields'] = $f_custom_fields_data;
  3126. $t_filter_input[FILTER_PROPERTY_STICKY] = $f_sticky_issues;
  3127. $t_filter_input[FILTER_PROPERTY_RELATIONSHIP_TYPE] = $f_relationship_type;
  3128. $t_filter_input[FILTER_PROPERTY_RELATIONSHIP_BUG] = $f_relationship_bug;
  3129. $t_filter_input[FILTER_PROPERTY_PROFILE_ID] = $f_show_profile;
  3130. $t_filter_input[FILTER_PROPERTY_PLATFORM] = $f_platform;
  3131. $t_filter_input[FILTER_PROPERTY_OS] = $f_os;
  3132. $t_filter_input[FILTER_PROPERTY_OS_BUILD] = $f_os_build;
  3133. $t_filter_input[FILTER_PROPERTY_TAG_STRING] = $f_tag_string;
  3134. $t_filter_input[FILTER_PROPERTY_TAG_SELECT] = $f_tag_select;
  3135. $t_filter_input[FILTER_PROPERTY_NOTE_USER_ID] = $f_note_user_id;
  3136. $t_filter_input[FILTER_PROPERTY_MATCH_TYPE] = $f_match_type;
  3137. # copy runtime properties, if present
  3138. if( isset( $t_filter['_temporary_key'] ) ) {
  3139. $t_filter_input['_temporary_key'] = $t_filter['_temporary_key'];
  3140. }
  3141. if( isset( $t_filter['_filter_id'] ) ) {
  3142. $t_filter_input['_filter_id'] = $t_filter['_filter_id'];
  3143. }
  3144. return filter_ensure_valid_filter( $t_filter_input );
  3145. }
  3146. /**
  3147. * Returns the sort columns from a filter, with only those columns that are visible
  3148. * according to $p_columns_target user's configuration, and valid for sorting.
  3149. * Returns an array consisting of two respective properties of column names, and
  3150. * sort direction, each one already exploded into an array.
  3151. * Note: Filter array must be a valid filter
  3152. * @param array $p_filter Original filter array.
  3153. * @param integer $p_columns_target Target view for the columns.
  3154. * @return array Array of filtered columns and order
  3155. */
  3156. function filter_get_visible_sort_properties_array( array $p_filter, $p_columns_target = COLUMNS_TARGET_VIEW_PAGE ) {
  3157. # get visible columns
  3158. $t_visible_columns = helper_get_columns_to_view( $p_columns_target );
  3159. # filter out those that ar not sortable
  3160. $t_visible_columns = array_filter( $t_visible_columns, 'column_is_sortable' );
  3161. $t_sort_fields = explode( ',', $p_filter[FILTER_PROPERTY_SORT_FIELD_NAME] );
  3162. $t_dir_fields = explode( ',', $p_filter[FILTER_PROPERTY_SORT_DIRECTION] );
  3163. $t_sort_array = array();
  3164. $t_dir_array = array();
  3165. $t_count = count( $t_sort_fields );
  3166. for( $i = 0; $i < $t_count; $i++ ) {
  3167. $c_sort = $t_sort_fields[$i];
  3168. if( in_array( $c_sort, $t_visible_columns ) ) {
  3169. $t_sort_array[] = $t_sort_fields[$i];
  3170. $t_dir_array[] = $t_dir_fields[$i];
  3171. }
  3172. }
  3173. return array(
  3174. FILTER_PROPERTY_SORT_FIELD_NAME => $t_sort_array,
  3175. FILTER_PROPERTY_SORT_DIRECTION => $t_dir_array
  3176. );
  3177. }
  3178. /**
  3179. * Returns true if the filter id is a named stored filter, which can be managed and edited.
  3180. * Returns false if it's a temporary filter, or if the filter id does not exists
  3181. * @param integer $p_filter_id
  3182. * @return boolean
  3183. */
  3184. function filter_is_named_filter( $p_filter_id ) {
  3185. $t_filter_row = filter_get_row( $p_filter_id );
  3186. if( $t_filter_row ) {
  3187. return !empty( $t_filter_row['name'] ) && $t_filter_row['project_id'] >= 0;
  3188. }
  3189. return false;
  3190. }
  3191. /**
  3192. * Returns true if the filter is accessible by the user, which happens when the user
  3193. * is the owner of the filter, or the filter is public.
  3194. * @param integer $p_filter_id Filter id
  3195. * @param integer $p_user_id User id
  3196. * @return boolean true if the filter is accessible by the user
  3197. */
  3198. function filter_is_accessible( $p_filter_id, $p_user_id = null ) {
  3199. if( null === $p_user_id ) {
  3200. $t_user_id = auth_get_current_user_id();
  3201. } else {
  3202. $t_user_id = $p_user_id;
  3203. }
  3204. $t_filter_row = filter_get_row( $p_filter_id );
  3205. if( $t_filter_row ) {
  3206. if( $t_filter_row['user_id'] == $t_user_id || $t_filter_row['is_public'] ) {
  3207. # If the filter is a named filter, check the config options
  3208. if( $t_filter_row['project_id'] >= 0
  3209. && !is_blank( $t_filter_row['name'] ) ) {
  3210. return access_has_project_level( config_get( 'stored_query_use_threshold', null, $t_user_id, $t_filter_row['project_id'] ) );
  3211. }
  3212. # it it's a "current" filter, access is ok
  3213. return true;
  3214. }
  3215. }
  3216. return false;
  3217. }
  3218. /**
  3219. * Prints the simple/advanced menu item toggle if needed
  3220. * @param string $p_url Target URL, must end with 'view_type='
  3221. * @param string $p_view_type Filter view type (FILTER_VIEW_TYPE_SIMPLE or
  3222. * FILTER_VIEW_TYPE_ADVANCED)
  3223. */
  3224. function filter_print_view_type_toggle( $p_url, $p_view_type ) {
  3225. $t_view_filters = config_get( 'view_filters' );
  3226. if( $t_view_filters == SIMPLE_ONLY || $t_view_filters == ADVANCED_ONLY ) {
  3227. return;
  3228. }
  3229. if( $p_view_type == FILTER_VIEW_TYPE_ADVANCED ) {
  3230. $t_url = $p_url . FILTER_VIEW_TYPE_SIMPLE;
  3231. $t_icon = 'fa-toggle-off';
  3232. $t_lang_string = 'simple_filters';
  3233. } else {
  3234. $t_url = $p_url . FILTER_VIEW_TYPE_ADVANCED;
  3235. $t_icon = 'fa-toggle-on';
  3236. $t_lang_string = 'advanced_filters';
  3237. }
  3238. echo '<li>';
  3239. printf( '<a href="%s"><i class="ace-icon fa %s"></i>&#160;&#160;%s</a>',
  3240. $t_url,
  3241. $t_icon,
  3242. lang_get( $t_lang_string )
  3243. );
  3244. echo '</li>';
  3245. }
  3246. /**
  3247. * Returns an array of project ids which are included in the filter.
  3248. * This array includes all individual projects/subprojects that are in the search scope.
  3249. * If ALL_PROJECTS were included directly, or indirectly, and the parameter $p_return_all_projects
  3250. * is set to true, the value ALL_PROJECTS will be returned. Otherwise the array will be expanded
  3251. * to all actual accessible projects
  3252. * @param array $p_filter Filter array
  3253. * @param integer $p_project_id Project id to use in filtering, if applicable by filter type
  3254. * @param integer $p_user_id User id to use as current user when filtering
  3255. * @param boolean $p_return_all_projects If true, return ALL_PROJECTS directly if found, instead of
  3256. * expanding to individual project ids
  3257. * @return array|integer Array of project ids, or ALL_PROJECTS if applicable.
  3258. */
  3259. function filter_get_included_projects( array $p_filter, $p_project_id = null, $p_user_id = null, $p_return_all_projects = false ) {
  3260. if( null === $p_project_id ) {
  3261. $t_project_id = helper_get_current_project();
  3262. } else {
  3263. $t_project_id = $p_project_id;
  3264. }
  3265. if( !$p_user_id ) {
  3266. $t_user_id = auth_get_current_user_id();
  3267. } else {
  3268. $t_user_id = $p_user_id;
  3269. }
  3270. $t_view_type = $p_filter['_view_type'];
  3271. # normalize the project filtering into an array $t_project_ids
  3272. if( FILTER_VIEW_TYPE_SIMPLE == $t_view_type ) {
  3273. log_event( LOG_FILTERING, 'Simple Filter' );
  3274. $t_project_ids = array( $t_project_id );
  3275. $t_include_sub_projects = true;
  3276. } else {
  3277. log_event( LOG_FILTERING, 'Advanced Filter' );
  3278. $t_project_ids = $p_filter[FILTER_PROPERTY_PROJECT_ID];
  3279. $t_include_sub_projects = (( count( $t_project_ids ) == 1 ) && ( ( $t_project_ids[0] == META_FILTER_CURRENT ) || ( $t_project_ids[0] == ALL_PROJECTS ) ) );
  3280. }
  3281. log_event( LOG_FILTERING, 'project_ids = @P' . implode( ', @P', $t_project_ids ) );
  3282. log_event( LOG_FILTERING, 'include sub-projects = ' . ( $t_include_sub_projects ? '1' : '0' ) );
  3283. # if the array has ALL_PROJECTS, then reset the array to only contain ALL_PROJECTS.
  3284. # replace META_FILTER_CURRENT with the actual current project id.
  3285. $t_all_projects_found = false;
  3286. $t_new_project_ids = array();
  3287. foreach( $t_project_ids as $t_pid ) {
  3288. if( $t_pid == META_FILTER_CURRENT ) {
  3289. $t_pid = $t_project_id;
  3290. }
  3291. if( $t_pid == ALL_PROJECTS ) {
  3292. $t_all_projects_found = true;
  3293. log_event( LOG_FILTERING, 'all projects selected' );
  3294. break;
  3295. }
  3296. # filter out inaccessible projects.
  3297. if( !project_exists( $t_pid ) || !access_has_project_level( config_get( 'view_bug_threshold', null, $t_user_id, $t_pid ), $t_pid, $t_user_id ) ) {
  3298. log_event( LOG_FILTERING, 'Invalid or inaccessible project: ' . $t_pid );
  3299. continue;
  3300. }
  3301. $t_new_project_ids[] = $t_pid;
  3302. }
  3303. # if not expanding ALL_PROJECTS, shortcut return directly
  3304. if( $t_all_projects_found && $p_return_all_projects ) {
  3305. return ALL_PROJECTS;
  3306. }
  3307. if( $t_all_projects_found ) {
  3308. $t_project_ids = user_get_accessible_projects( $t_user_id );
  3309. } else {
  3310. $t_project_ids = $t_new_project_ids;
  3311. }
  3312. # expand project ids to include sub-projects
  3313. if( $t_include_sub_projects ) {
  3314. $t_top_project_ids = $t_project_ids;
  3315. foreach( $t_top_project_ids as $t_pid ) {
  3316. log_event( LOG_FILTERING, 'Getting sub-projects for project id @P' . $t_pid );
  3317. $t_subproject_ids = user_get_all_accessible_subprojects( $t_user_id, $t_pid );
  3318. if( !$t_subproject_ids ) {
  3319. continue;
  3320. }
  3321. $t_project_ids = array_merge( $t_project_ids, $t_subproject_ids );
  3322. }
  3323. $t_project_ids = array_unique( $t_project_ids );
  3324. }
  3325. if( count( $t_project_ids ) ) {
  3326. log_event( LOG_FILTERING, 'project_ids after including sub-projects = @P' . implode( ', @P', $t_project_ids ) );
  3327. } else {
  3328. log_event( LOG_FILTERING, 'no accessible projects' );
  3329. }
  3330. return $t_project_ids;
  3331. }
  3332. /**
  3333. * Returns a filter array structure for the given filter_id
  3334. * A default value can be provided to be used when the filter_id doesn't exists
  3335. * or is not accessible
  3336. *
  3337. * You may pass in any array as a default (including null) but if
  3338. * you pass in *no* default then an error will be triggered if the filter
  3339. * cannot be found
  3340. *
  3341. * @param integer $p_filter_id Filter id
  3342. * @param array $p_default A filter array to return when id is not found
  3343. * @return array A filter array
  3344. */
  3345. function filter_get( $p_filter_id, array $p_default = null ) {
  3346. # if no default was provided, we will trigger an error if not found
  3347. $t_trigger_error = func_num_args() == 1;
  3348. # This function checks for user access
  3349. $t_filter_string = filter_db_get_filter_string( $p_filter_id );
  3350. # If value is false, it either doesn't exists or is not accessible
  3351. if( !$t_filter_string ) {
  3352. if( $t_trigger_error ) {
  3353. error_parameters( $p_filter_id );
  3354. trigger_error( ERROR_FILTER_NOT_FOUND, ERROR );
  3355. } else {
  3356. return $p_default;
  3357. }
  3358. }
  3359. $t_filter = filter_deserialize( $t_filter_string );
  3360. # If the unserialez data is not an array, the some error happened, eg, invalid format
  3361. if( !is_array( $t_filter ) ) {
  3362. # Don't throw error, otherwise the user could not recover navigation easily
  3363. return filter_get_default();
  3364. }
  3365. $t_filter = filter_clean_runtime_properties( $t_filter );
  3366. $t_filter['_filter_id'] = $p_filter_id;
  3367. $t_filter = filter_update_source_properties( $t_filter );
  3368. return $t_filter;
  3369. }
  3370. /**
  3371. * Return a standard filter
  3372. * @param string $p_filter_name The name of the filter
  3373. * @param integer|null $p_user_id A user id to build this filter. Null for current user
  3374. * @return null|boolean|array null filter not found, false invalid filter, otherwise the filter.
  3375. */
  3376. function filter_standard_get( $p_filter_name, $p_user_id = null ) {
  3377. $p_filter_name = strtolower( $p_filter_name );
  3378. $t_project_id = helper_get_current_project();
  3379. if( null === $p_user_id ) {
  3380. $t_user_id = auth_get_current_user_id();
  3381. } else {
  3382. $t_user_id = $p_user_id;
  3383. }
  3384. switch( $p_filter_name ) {
  3385. case FILTER_STANDARD_ASSIGNED:
  3386. $t_filter = filter_create_assigned_to_unresolved( $t_project_id, $t_user_id );
  3387. break;
  3388. case FILTER_STANDARD_UNASSIGNED:
  3389. $t_filter = filter_create_assigned_to_unresolved( $t_project_id, NO_USER );
  3390. break;
  3391. case FILTER_STANDARD_REPORTED:
  3392. $t_filter = filter_create_reported_by( $t_project_id, $t_user_id );
  3393. break;
  3394. case FILTER_STANDARD_MONITORED:
  3395. $t_filter = filter_create_monitored_by( $t_project_id, $t_user_id );
  3396. break;
  3397. default:
  3398. return null;
  3399. }
  3400. return $t_filter;
  3401. }
  3402. /**
  3403. * Updates a filter's properties with those from another filter that is referenced
  3404. * by it's source-id property.
  3405. * This is used when an anonymous filter was created from a named filter. As long
  3406. * as this anonymous filter is not modified, it must be keep in sync with the
  3407. * referenced filter (source_id), because the source filter may have been modified
  3408. * at a later time.
  3409. * This is a side effect of always using anonymous filters even when selecting a
  3410. * named filter to be applied as current.
  3411. *
  3412. * @param array $p_filter Original filter array
  3413. * @return array Updated filter array
  3414. */
  3415. function filter_update_source_properties( array $p_filter ) {
  3416. # Check if the filter references a named filter
  3417. # This property only makes sense, and should be available on unnamed filters
  3418. if( isset( $p_filter['_filter_id'] ) ) {
  3419. $t_filter_id = $p_filter['_filter_id'];
  3420. } else {
  3421. $t_filter_id = null;
  3422. }
  3423. if( isset( $p_filter['_source_query_id'] ) && $t_filter_id != $p_filter['_source_query_id'] ) {
  3424. $t_source_query_id = $p_filter['_source_query_id'];
  3425. # check if filter id is a proper named filter, and is accessible
  3426. if( filter_is_named_filter( $t_source_query_id ) && filter_is_accessible( $t_source_query_id ) ){
  3427. # replace filter with the referenced one
  3428. $t_new_filter = filter_deserialize( filter_db_get_filter_string( $t_source_query_id ) );
  3429. if( is_array( $t_new_filter ) ) {
  3430. # update the referenced stored filter id for the new loaded filter
  3431. $t_new_filter['_source_query_id'] = $t_source_query_id;
  3432. $p_filter = filter_copy_runtime_properties( $t_new_filter, $p_filter );
  3433. } else {
  3434. # If the unserialez data is not an array, the some error happened, eg, invalid format
  3435. unset( $p_filter['_source_query_id'] );
  3436. }
  3437. } else {
  3438. # If the filter id is not valid, clean the referenced filter id
  3439. unset( $p_filter['_source_query_id'] );
  3440. }
  3441. }
  3442. return $p_filter;
  3443. }
  3444. /**
  3445. * Returns a filter which is stored in session data, indexed by the provided key.
  3446. * A default value can be provided to be used when the key doesn't exists
  3447. *
  3448. * You may pass in any array as a default (including null) but if
  3449. * you pass in *no* default then an error will be triggered if the key
  3450. * cannot be found
  3451. *
  3452. * @param string $p_filter_key Key to look up for in session data
  3453. * @param mixed $p_default A default value to return if key not found
  3454. * @return array A filter array.
  3455. */
  3456. function filter_temporary_get( $p_filter_key, $p_default = null ) {
  3457. # if no default was provided, we will trigger an error if not found
  3458. $t_trigger_error = func_num_args() == 1;
  3459. $t_session_filters = session_get( 'temporary_filters', array() );
  3460. if( isset( $t_session_filters[$p_filter_key] ) ) {
  3461. # setting here the key in the filter array only if the key exists
  3462. # this validates against receiving garbage input as XSS attacks
  3463. $t_filter = $t_session_filters[$p_filter_key];
  3464. $t_filter['_temporary_key'] = $p_filter_key;
  3465. return filter_ensure_valid_filter( $t_filter );
  3466. } else {
  3467. if( $t_trigger_error ) {
  3468. error_parameters( $p_filter_key );
  3469. trigger_error( ERROR_FILTER_NOT_FOUND, ERROR );
  3470. } else {
  3471. return $p_default;
  3472. }
  3473. }
  3474. }
  3475. /**
  3476. * Saves a filter as a temporary filter in session data.
  3477. * The filter will be updated or created, indexed by provided $p_filter_key,
  3478. * If no key is provided, it will search in the filter property that holds
  3479. * its key if it was loaded as a temporary filter.
  3480. * If neither key is found, a new one will be created
  3481. * @param array $p_filter Filter array
  3482. * @param string $p_filter_key Key to update, or null
  3483. * @return string The key used for storing the filter.
  3484. */
  3485. function filter_temporary_set( array $p_filter, $p_filter_key = null ) {
  3486. if( null === $p_filter_key ) {
  3487. $t_filter_key = filter_get_temporary_key( $p_filter );
  3488. if( !$t_filter_key ) {
  3489. $t_filter_key = uniqid();
  3490. }
  3491. } else {
  3492. $t_filter_key = $p_filter_key;
  3493. }
  3494. $p_filter = filter_clean_runtime_properties( $p_filter );
  3495. $t_session_filters = session_get( 'temporary_filters', array() );
  3496. $t_session_filters[$t_filter_key] = $p_filter;
  3497. session_set( 'temporary_filters', $t_session_filters );
  3498. return $t_filter_key;
  3499. }
  3500. /**
  3501. * Get the temporary key of the filter, if was loaded from temporary session store
  3502. * Return null otherwise
  3503. * @param array $p_filter Filter array
  3504. * @return string|null Key associated with this filter, null if none
  3505. */
  3506. function filter_get_temporary_key( array $p_filter ) {
  3507. if( isset( $p_filter['_temporary_key'] ) ) {
  3508. return $p_filter['_temporary_key'];
  3509. } else {
  3510. return null;
  3511. }
  3512. }
  3513. /**
  3514. * Returns true if the filter was loaded as temporary filter
  3515. * @param array $p_filter Filter array
  3516. * @return boolean Whether this filter is temporary
  3517. */
  3518. function filter_is_temporary( array $p_filter ) {
  3519. return isset( $p_filter['_temporary_key'] );
  3520. }
  3521. /**
  3522. * Returns a string formatted as GET parameter, suitable for tracking a
  3523. * temporary filter by its session key.
  3524. * The parameter can be ither an existing key, so its used directly,
  3525. * or a filter array, which can contain a property with the key
  3526. * If a filter is provided that does not contain the key property, an empty
  3527. * string is returned.
  3528. * @param array|string $p_key_or_filter Either a string key, or a filter array
  3529. * @return string|null Formatted parameter string, or null
  3530. */
  3531. function filter_get_temporary_key_param( $p_key_or_filter ) {
  3532. if( is_array( $p_key_or_filter ) ) {
  3533. $t_key = filter_get_temporary_key( $p_key_or_filter );
  3534. } else {
  3535. $t_key = $p_key_or_filter;
  3536. }
  3537. if( $t_key ) {
  3538. return 'filter=' . $t_key;
  3539. } else {
  3540. return null;
  3541. }
  3542. }
  3543. /**
  3544. * Removes runtime properties that are should not be saved as part of the filter
  3545. * Use this function before saving the filter.
  3546. * @param array $p_filter Filter array (passed as reference, it gets modified)
  3547. * @return array Modified filter array
  3548. */
  3549. function filter_clean_runtime_properties( array $p_filter ) {
  3550. if( isset( $p_filter['_temporary_key'] ) ) {
  3551. unset( $p_filter['_temporary_key'] );
  3552. }
  3553. if( isset( $p_filter['_filter_id'] ) ) {
  3554. unset( $p_filter['_filter_id'] );
  3555. }
  3556. return $p_filter;
  3557. }
  3558. /**
  3559. * Copy the runtime properties from one filter into another.
  3560. * @param array $p_filter_to Destination filter array
  3561. * @param array $p_filter_from Filter array from which properties are copied
  3562. * @return array Updated filter array
  3563. */
  3564. function filter_copy_runtime_properties( array $p_filter_to, array $p_filter_from ) {
  3565. if( isset( $p_filter_from['_temporary_key'] ) ) {
  3566. $p_filter_to['_temporary_key'] = $p_filter_from['_temporary_key'];
  3567. }
  3568. if( isset( $p_filter_from['_filter_id'] ) ) {
  3569. $p_filter_to['_filter_id'] = $p_filter_from['_filter_id'];
  3570. }
  3571. return $p_filter_to;
  3572. }
  3573. /**
  3574. * Returns true if the user can use peristent filters, in contexts such as view_all_bug_page.
  3575. * Persistent filters are remembered across sessions, and are not desirable when the user is
  3576. * a shared user, eg: anonymous user
  3577. * @param integer $p_user_id A valid user identifier.
  3578. * @return boolean true if the user can use persistent filters, false otherwise
  3579. */
  3580. function filter_user_can_use_persistent( $p_user_id = null ) {
  3581. return !user_is_anonymous( $p_user_id );
  3582. }