PageRenderTime 35ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/core/filter_api.php

http://github.com/mantisbt/mantisbt
PHP | 2807 lines | 1837 code | 306 blank | 664 comment | 383 complexity | 98338e639df80d626dce598436f65169 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. # @global array $g_cache_filter_subquery
  97. # indexed by a hash of the filter array, contains a prebuilt BugFilterQuery object
  98. $g_cache_filter_subquery = array();
  99. /**
  100. * Initialize the filter API with the current filter.
  101. * @param array $p_filter The filter to set as the current filter.
  102. */
  103. function filter_init( $p_filter ) {
  104. global $g_filter;
  105. $g_filter = $p_filter;
  106. }
  107. /**
  108. * Allow plugins to define a set of class-based filters, and register/load
  109. * them here to be used by the rest of filter_api.
  110. * @return array Mapping of field name to filter object
  111. */
  112. function filter_get_plugin_filters() {
  113. static $s_field_array = null;
  114. if( is_null( $s_field_array ) ) {
  115. $s_field_array = array();
  116. $t_all_plugin_filters = event_signal( 'EVENT_FILTER_FIELDS' );
  117. foreach( $t_all_plugin_filters as $t_plugin => $t_plugin_filters ) {
  118. foreach( $t_plugin_filters as $t_callback => $t_plugin_filter_array ) {
  119. if( is_array( $t_plugin_filter_array ) ) {
  120. foreach( $t_plugin_filter_array as $t_filter_item ) {
  121. if( is_object( $t_filter_item ) && $t_filter_item instanceof MantisFilter ) {
  122. $t_filter_object = $t_filter_item;
  123. } elseif( class_exists( $t_filter_item ) && is_subclass_of( $t_filter_item, 'MantisFilter' ) ) {
  124. $t_filter_object = new $t_filter_item();
  125. } else {
  126. continue;
  127. }
  128. $t_filter_name = mb_strtolower( $t_plugin . '_' . $t_filter_object->field );
  129. $s_field_array[$t_filter_name] = $t_filter_object;
  130. }
  131. }
  132. }
  133. }
  134. }
  135. return $s_field_array;
  136. }
  137. /**
  138. * Get a permanent link for the current active filter. The results of using these fields by other users
  139. * can be inconsistent with the original results due to fields like "Myself", "Current Project",
  140. * and due to access level.
  141. * @param array $p_custom_filter Array containing a custom filter definition.
  142. * @return string the search.php?xxxx or an empty string if no criteria applied.
  143. */
  144. function filter_get_url( array $p_custom_filter ) {
  145. $t_query = array();
  146. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_PROJECT_ID] ) ) {
  147. $t_project_id = $p_custom_filter[FILTER_PROPERTY_PROJECT_ID];
  148. if( count( $t_project_id ) == 1 && $t_project_id[0] == META_FILTER_CURRENT ) {
  149. $t_project_id = array(
  150. helper_get_current_project(),
  151. );
  152. }
  153. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_PROJECT_ID, $t_project_id );
  154. }
  155. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_SEARCH] ) ) {
  156. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_SEARCH, $p_custom_filter[FILTER_PROPERTY_SEARCH] );
  157. }
  158. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_CATEGORY_ID] ) ) {
  159. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_CATEGORY_ID, $p_custom_filter[FILTER_PROPERTY_CATEGORY_ID] );
  160. }
  161. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_REPORTER_ID] ) ) {
  162. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_REPORTER_ID, $p_custom_filter[FILTER_PROPERTY_REPORTER_ID] );
  163. }
  164. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_STATUS] ) ) {
  165. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_STATUS, $p_custom_filter[FILTER_PROPERTY_STATUS] );
  166. }
  167. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_MONITOR_USER_ID] ) ) {
  168. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_MONITOR_USER_ID, $p_custom_filter[FILTER_PROPERTY_MONITOR_USER_ID] );
  169. }
  170. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_HANDLER_ID] ) ) {
  171. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_HANDLER_ID, $p_custom_filter[FILTER_PROPERTY_HANDLER_ID] );
  172. }
  173. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_NOTE_USER_ID] ) ) {
  174. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_NOTE_USER_ID, $p_custom_filter[FILTER_PROPERTY_NOTE_USER_ID] );
  175. }
  176. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_SEVERITY] ) ) {
  177. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_SEVERITY, $p_custom_filter[FILTER_PROPERTY_SEVERITY] );
  178. }
  179. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_RESOLUTION] ) ) {
  180. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_RESOLUTION, $p_custom_filter[FILTER_PROPERTY_RESOLUTION] );
  181. }
  182. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_PRIORITY] ) ) {
  183. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_PRIORITY, $p_custom_filter[FILTER_PROPERTY_PRIORITY] );
  184. }
  185. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_VIEW_STATE] ) ) {
  186. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_VIEW_STATE, $p_custom_filter[FILTER_PROPERTY_VIEW_STATE] );
  187. }
  188. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_STICKY] ) ) {
  189. $t_query[] = filter_encode_field_and_value(
  190. FILTER_PROPERTY_STICKY,
  191. $p_custom_filter[FILTER_PROPERTY_STICKY] ? 'on' : 'off' );
  192. }
  193. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_VERSION] ) ) {
  194. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_VERSION, $p_custom_filter[FILTER_PROPERTY_VERSION] );
  195. }
  196. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_BUILD] ) ) {
  197. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_BUILD, $p_custom_filter[FILTER_PROPERTY_BUILD] );
  198. }
  199. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_FIXED_IN_VERSION] ) ) {
  200. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_FIXED_IN_VERSION, $p_custom_filter[FILTER_PROPERTY_FIXED_IN_VERSION] );
  201. }
  202. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_TARGET_VERSION] ) ) {
  203. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_TARGET_VERSION, $p_custom_filter[FILTER_PROPERTY_TARGET_VERSION] );
  204. }
  205. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_SORT_FIELD_NAME] ) ) {
  206. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_SORT_FIELD_NAME, $p_custom_filter[FILTER_PROPERTY_SORT_FIELD_NAME] );
  207. }
  208. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_SORT_DIRECTION] ) ) {
  209. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_SORT_DIRECTION, $p_custom_filter[FILTER_PROPERTY_SORT_DIRECTION] );
  210. }
  211. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_ISSUES_PER_PAGE] ) ) {
  212. if( $p_custom_filter[FILTER_PROPERTY_ISSUES_PER_PAGE] != config_get( 'default_limit_view' ) ) {
  213. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_ISSUES_PER_PAGE, $p_custom_filter[FILTER_PROPERTY_ISSUES_PER_PAGE] );
  214. }
  215. }
  216. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_HIGHLIGHT_CHANGED] ) ) {
  217. if( $p_custom_filter[FILTER_PROPERTY_HIGHLIGHT_CHANGED] != config_get( 'default_show_changed' ) ) {
  218. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_HIGHLIGHT_CHANGED, $p_custom_filter[FILTER_PROPERTY_HIGHLIGHT_CHANGED] );
  219. }
  220. }
  221. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_HIDE_STATUS] ) ) {
  222. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_HIDE_STATUS, $p_custom_filter[FILTER_PROPERTY_HIDE_STATUS] );
  223. }
  224. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_FILTER_BY_DATE_SUBMITTED] ) ) {
  225. $t_query[] = filter_encode_field_and_value(
  226. FILTER_PROPERTY_FILTER_BY_DATE_SUBMITTED,
  227. $p_custom_filter[FILTER_PROPERTY_FILTER_BY_DATE_SUBMITTED] ? 'on' : 'off' );
  228. # The start and end dates are only applicable if filter by date is set.
  229. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_START_DAY] ) ) {
  230. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_DATE_SUBMITTED_START_DAY, $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_START_DAY] );
  231. }
  232. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_END_DAY] ) ) {
  233. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_DATE_SUBMITTED_END_DAY, $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_END_DAY] );
  234. }
  235. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_START_MONTH] ) ) {
  236. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_DATE_SUBMITTED_START_MONTH, $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_START_MONTH] );
  237. }
  238. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_END_MONTH] ) ) {
  239. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_DATE_SUBMITTED_END_MONTH, $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_END_MONTH] );
  240. }
  241. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_START_YEAR] ) ) {
  242. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_DATE_SUBMITTED_START_YEAR, $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_START_YEAR] );
  243. }
  244. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_END_YEAR] ) ) {
  245. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_DATE_SUBMITTED_END_YEAR, $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_END_YEAR] );
  246. }
  247. }
  248. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_FILTER_BY_LAST_UPDATED_DATE] ) ) {
  249. $t_query[] = filter_encode_field_and_value(
  250. FILTER_PROPERTY_FILTER_BY_LAST_UPDATED_DATE,
  251. $p_custom_filter[FILTER_PROPERTY_FILTER_BY_LAST_UPDATED_DATE] ? 'on' : 'off' );
  252. # The start and end dates are only applicable if filter by date is set.
  253. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_START_DAY] ) ) {
  254. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_LAST_UPDATED_START_DAY, $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_START_DAY] );
  255. }
  256. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_END_DAY] ) ) {
  257. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_LAST_UPDATED_END_DAY, $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_END_DAY] );
  258. }
  259. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_START_MONTH] ) ) {
  260. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_LAST_UPDATED_START_MONTH, $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_START_MONTH] );
  261. }
  262. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_END_MONTH] ) ) {
  263. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_LAST_UPDATED_END_MONTH, $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_END_MONTH] );
  264. }
  265. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_START_YEAR] ) ) {
  266. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_LAST_UPDATED_START_YEAR, $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_START_YEAR] );
  267. }
  268. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_END_YEAR] ) ) {
  269. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_LAST_UPDATED_END_YEAR, $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_END_YEAR] );
  270. }
  271. }
  272. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_RELATIONSHIP_TYPE] ) ) {
  273. if( $p_custom_filter[FILTER_PROPERTY_RELATIONSHIP_TYPE] != -1 ) {
  274. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_RELATIONSHIP_TYPE, $p_custom_filter[FILTER_PROPERTY_RELATIONSHIP_TYPE] );
  275. }
  276. }
  277. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_RELATIONSHIP_BUG] ) ) {
  278. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_RELATIONSHIP_BUG, $p_custom_filter[FILTER_PROPERTY_RELATIONSHIP_BUG] );
  279. }
  280. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_PLATFORM] ) ) {
  281. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_PLATFORM, $p_custom_filter[FILTER_PROPERTY_PLATFORM] );
  282. }
  283. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_OS] ) ) {
  284. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_OS, $p_custom_filter[FILTER_PROPERTY_OS] );
  285. }
  286. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_OS_BUILD] ) ) {
  287. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_OS_BUILD, $p_custom_filter[FILTER_PROPERTY_OS_BUILD] );
  288. }
  289. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_TAG_STRING] ) ) {
  290. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_TAG_STRING, $p_custom_filter[FILTER_PROPERTY_TAG_STRING] );
  291. }
  292. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_TAG_SELECT] ) ) {
  293. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_TAG_SELECT, $p_custom_filter[FILTER_PROPERTY_TAG_SELECT] );
  294. }
  295. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_MATCH_TYPE, $p_custom_filter[FILTER_PROPERTY_MATCH_TYPE] );
  296. if( isset( $p_custom_filter['custom_fields'] ) ) {
  297. foreach( $p_custom_filter['custom_fields'] as $t_custom_field_id => $t_custom_field_values ) {
  298. if( !filter_field_is_any( $t_custom_field_values ) ) {
  299. $t_query[] = filter_encode_field_and_value( 'custom_field_' . $t_custom_field_id, $t_custom_field_values );
  300. }
  301. }
  302. }
  303. # Allow plugins to add filter fields
  304. $t_plugin_filter_array = filter_get_plugin_filters();
  305. foreach( $t_plugin_filter_array as $t_field_name => $t_filter_object ) {
  306. if( !filter_field_is_any( $p_custom_filter[$t_field_name] ) ) {
  307. $t_query[] = filter_encode_field_and_value( $t_field_name, $p_custom_filter[$t_field_name], $t_filter_object->type );
  308. }
  309. }
  310. if( count( $t_query ) > 0 ) {
  311. $t_query_str = implode( '&', $t_query );
  312. $t_url = config_get_global( 'path' ) . 'search.php?' . $t_query_str;
  313. } else {
  314. $t_url = '';
  315. }
  316. return $t_url;
  317. }
  318. /**
  319. * Encodes a field and it's value for the filter URL. This handles the URL encoding and arrays.
  320. * @param string $p_field_name The field name.
  321. * @param string $p_field_value The field value (can be an array).
  322. * @param integer $p_field_type Field Type e.g. FILTER_TYPE_MULTI_STRING.
  323. * @return string url encoded string
  324. */
  325. function filter_encode_field_and_value( $p_field_name, $p_field_value, $p_field_type = null ) {
  326. $t_query_array = array();
  327. if( is_array( $p_field_value ) ) {
  328. $t_count = count( $p_field_value );
  329. if( $t_count > 1 || $p_field_type == FILTER_TYPE_MULTI_STRING || $p_field_type == FILTER_TYPE_MULTI_INT ) {
  330. foreach( $p_field_value as $t_value ) {
  331. $t_query_array[] = urlencode( $p_field_name . '[]' ) . '=' . urlencode( $t_value );
  332. }
  333. } else if( $t_count == 1 ) {
  334. $t_query_array[] = urlencode( $p_field_name ) . '=' . urlencode( $p_field_value[0] );
  335. }
  336. } else {
  337. $t_query_array[] = urlencode( $p_field_name ) . '=' . urlencode( $p_field_value );
  338. }
  339. return implode( '&', $t_query_array );
  340. }
  341. /**
  342. * Checks the supplied value to see if it is an ANY value.
  343. * @param string $p_field_value The value to check.
  344. * @return boolean true for "ANY" values and false for others. "ANY" means filter criteria not active.
  345. */
  346. function filter_field_is_any( $p_field_value ) {
  347. if( is_array( $p_field_value ) ) {
  348. if( count( $p_field_value ) == 0 ) {
  349. return true;
  350. }
  351. foreach( $p_field_value as $t_value ) {
  352. if( ( META_FILTER_ANY == $t_value ) && ( is_numeric( $t_value ) ) ) {
  353. return true;
  354. }
  355. }
  356. } else {
  357. if( is_string( $p_field_value ) && is_blank( $p_field_value ) ) {
  358. return true;
  359. }
  360. if( is_bool( $p_field_value ) && !$p_field_value ) {
  361. return true;
  362. }
  363. if( ( META_FILTER_ANY == $p_field_value ) && ( is_numeric( $p_field_value ) ) ) {
  364. return true;
  365. }
  366. }
  367. return false;
  368. }
  369. /**
  370. * Checks the supplied value to see if it is a NONE value.
  371. * @param string $p_field_value The value to check.
  372. * @return boolean true for "NONE" values and false for others.
  373. * @todo is a check for these necessary? if( ( $t_filter_value === 'none' ) || ( $t_filter_value === '[none]' ) )
  374. */
  375. function filter_field_is_none( $p_field_value ) {
  376. if( is_array( $p_field_value ) ) {
  377. foreach( $p_field_value as $t_value ) {
  378. if( ( META_FILTER_NONE == $t_value ) && ( is_numeric( $t_value ) ) ) {
  379. return true;
  380. }
  381. }
  382. } else {
  383. if( is_string( $p_field_value ) && is_blank( $p_field_value ) ) {
  384. return false;
  385. }
  386. if( ( META_FILTER_NONE == $p_field_value ) && ( is_numeric( $p_field_value ) ) ) {
  387. return true;
  388. }
  389. }
  390. return false;
  391. }
  392. /**
  393. * Checks the supplied value to see if it is a MYSELF value.
  394. * @param string $p_field_value The value to check.
  395. * @return boolean true for "MYSELF" values and false for others.
  396. */
  397. function filter_field_is_myself( $p_field_value ) {
  398. return( META_FILTER_MYSELF == $p_field_value ? true : false );
  399. }
  400. /**
  401. * Filter per page
  402. * @param array $p_filter Filter.
  403. * @param integer $p_count Count.
  404. * @param integer $p_per_page Per page.
  405. * @return integer
  406. */
  407. function filter_per_page( array $p_filter, $p_count, $p_per_page ) {
  408. $p_per_page = (( null == $p_per_page ) ? (int)$p_filter[FILTER_PROPERTY_ISSUES_PER_PAGE] : $p_per_page );
  409. $p_per_page = (( 0 == $p_per_page || -1 == $p_per_page ) ? $p_count : $p_per_page );
  410. return (int)abs( $p_per_page );
  411. }
  412. /**
  413. * Use $p_count and $p_per_page to determine how many pages to split this list up into.
  414. * For the sake of consistency have at least one page, even if it is empty.
  415. * @param integer $p_count Count.
  416. * @param integer $p_per_page Per page.
  417. * @return integer page count
  418. */
  419. function filter_page_count( $p_count, $p_per_page ) {
  420. $t_page_count = ceil( $p_count / $p_per_page );
  421. if( $t_page_count < 1 ) {
  422. $t_page_count = 1;
  423. }
  424. return $t_page_count;
  425. }
  426. /**
  427. * Checks to make sure $p_page_number isn't past the last page.
  428. * and that $p_page_number isn't before the first page
  429. * @param integer $p_page_number Page number.
  430. * @param integer $p_page_count Page count.
  431. * @return integer
  432. */
  433. function filter_valid_page_number( $p_page_number, $p_page_count ) {
  434. if( $p_page_number > $p_page_count ) {
  435. $p_page_number = $p_page_count;
  436. }
  437. if( $p_page_number < 1 ) {
  438. $p_page_number = 1;
  439. }
  440. return $p_page_number;
  441. }
  442. /**
  443. * Figure out the offset into the db query, offset is which record to start querying from
  444. * @param integer $p_page_number Page number.
  445. * @param integer $p_per_page Per page.
  446. * @return integer
  447. */
  448. function filter_offset( $p_page_number, $p_per_page ) {
  449. return(( (int)$p_page_number -1 ) * (int)$p_per_page );
  450. }
  451. /**
  452. * Make sure the filter array contains all the fields. If any field is missing,
  453. * create it with a default value.
  454. * @param array $p_filter_arr Input filter array
  455. * @return array Processed filter array
  456. */
  457. function filter_ensure_fields( array $p_filter_arr ) {
  458. # Fill missing filter properties with defaults
  459. if( isset( $p_filter_arr['_view_type'] ) ) {
  460. $t_filter_default = filter_get_default_array( $p_filter_arr['_view_type'] );
  461. } else {
  462. $t_filter_default = filter_get_default_array();
  463. }
  464. foreach( $t_filter_default as $t_key => $t_default_value ) {
  465. if( !isset( $p_filter_arr[$t_key] ) ) {
  466. $p_filter_arr[$t_key] = $t_default_value;
  467. }
  468. }
  469. # Veryfy custom fields
  470. foreach( $t_filter_default['custom_fields'] as $t_cfid => $t_cf_data ) {
  471. if( !isset( $p_filter_arr['custom_fields'][$t_cfid] ) ) {
  472. $p_filter_arr['custom_fields'][$t_cfid] = $t_cf_data;
  473. }
  474. }
  475. return $p_filter_arr;
  476. }
  477. /**
  478. * A wrapper to compare filter version syntax
  479. * Note: Currently, filter versions have this syntax: "vN", * where N is an integer number.
  480. * @param string $p_version1 First version number
  481. * @param string $p_version2 Second version number
  482. * @param string $p_operator Comparison test, if provided. As expected by version_compare()
  483. * @return mixed As returned by version_compare()
  484. */
  485. function filter_version_compare( $p_version1, $p_version2, $p_operator = null ) {
  486. return version_compare( $p_version1, $p_version2, $p_operator );
  487. }
  488. /**
  489. * Upgrade a filter array to the current filter structure, by converting properties
  490. * that have changed from previous filter versions
  491. * @param array $p_filter Filter array to upgrade
  492. * @return array Updgraded filter array
  493. */
  494. function filter_version_upgrade( array $p_filter ) {
  495. # This is a stub for future version upgrades
  496. # After conversions are made, update filter value to current version
  497. $p_filter['_version'] = FILTER_VERSION;
  498. return $p_filter;
  499. }
  500. /**
  501. * Make sure that our filters are entirely correct and complete (it is possible that they are not).
  502. * We need to do this to cover cases where we don't have complete control over the filters given.
  503. * @param array $p_filter_arr A filter array
  504. * @return array Validated filter array
  505. */
  506. function filter_ensure_valid_filter( array $p_filter_arr ) {
  507. if( !isset( $p_filter_arr['_version'] ) ) {
  508. $p_filter_arr['_version'] = FILTER_VERSION;
  509. }
  510. if( filter_version_compare( $p_filter_arr['_version'], FILTER_VERSION, '<' ) ) {
  511. $p_filter_arr = filter_version_upgrade( $p_filter_arr );
  512. }
  513. $p_filter_arr = filter_ensure_fields( $p_filter_arr );
  514. $t_config_view_filters = config_get( 'view_filters' );
  515. $t_view_type = $p_filter_arr['_view_type'];
  516. if( ADVANCED_ONLY == $t_config_view_filters ) {
  517. $t_view_type = FILTER_VIEW_TYPE_ADVANCED;
  518. }
  519. if( SIMPLE_ONLY == $t_config_view_filters ) {
  520. $t_view_type = FILTER_VIEW_TYPE_SIMPLE;
  521. }
  522. if( !in_array( $t_view_type, array( FILTER_VIEW_TYPE_SIMPLE, FILTER_VIEW_TYPE_ADVANCED ) ) ) {
  523. $t_view_type = filter_get_default_view_type();
  524. }
  525. $p_filter_arr['_view_type'] = $t_view_type;
  526. $t_sort_fields = explode( ',', $p_filter_arr[FILTER_PROPERTY_SORT_FIELD_NAME] );
  527. $t_dir_fields = explode( ',', $p_filter_arr[FILTER_PROPERTY_SORT_DIRECTION] );
  528. # both arrays should be equal length, just in case
  529. $t_sort_fields_count = min( count( $t_sort_fields ), count( $t_dir_fields ) );
  530. # clean up sort fields, remove invalid columns
  531. $t_new_sort_array = array();
  532. $t_new_dir_array = array();
  533. $t_all_columns = columns_get_all_active_columns();
  534. for( $ix = 0; $ix < $t_sort_fields_count; $ix++ ) {
  535. if( isset( $t_sort_fields[$ix] ) ) {
  536. $t_column = $t_sort_fields[$ix];
  537. # check that the column name exist
  538. if( !in_array( $t_column, $t_all_columns ) ) {
  539. continue;
  540. }
  541. # check that it has not been already used
  542. if( in_array( $t_column, $t_new_sort_array ) ) {
  543. continue;
  544. }
  545. # check that it is sortable
  546. if( !column_is_sortable( $t_column ) ) {
  547. continue;
  548. }
  549. $t_new_sort_array[] = $t_column;
  550. # if there is no dir field, set a dummy value
  551. if( isset( $t_dir_fields[$ix] ) ) {
  552. $t_dir = $t_dir_fields[$ix];
  553. } else {
  554. $t_dir = '';
  555. }
  556. # normalize sort_dir value
  557. $t_dir = ( $t_dir == 'ASC' ) ? 'ASC' : 'DESC';
  558. $t_new_dir_array[] = $t_dir;
  559. }
  560. }
  561. if( count( $t_new_sort_array ) > 0 ) {
  562. $p_filter_arr[FILTER_PROPERTY_SORT_FIELD_NAME] = implode( ',', $t_new_sort_array );
  563. $p_filter_arr[FILTER_PROPERTY_SORT_DIRECTION] = implode( ',', $t_new_dir_array );
  564. } else {
  565. $p_filter_arr[FILTER_PROPERTY_SORT_FIELD_NAME] = filter_get_default_property( FILTER_PROPERTY_SORT_FIELD_NAME, $t_view_type );
  566. $p_filter_arr[FILTER_PROPERTY_SORT_DIRECTION] = filter_get_default_property( FILTER_PROPERTY_SORT_DIRECTION, $t_view_type );
  567. }
  568. # Validate types for values.
  569. # helper function to validate types
  570. $t_function_validate_type = function( $p_value, $p_type ) {
  571. $t_value = stripslashes( $p_value );
  572. if( ( $t_value === 'any' ) || ( $t_value === '[any]' ) ) {
  573. $t_value = META_FILTER_ANY;
  574. }
  575. if( ( $t_value === 'none' ) || ( $t_value === '[none]' ) ) {
  576. $t_value = META_FILTER_NONE;
  577. }
  578. # Ensure the filter property has the right type - see #20087
  579. switch( $p_type ) {
  580. case 'string' :
  581. case 'int' :
  582. settype( $t_value, $p_type );
  583. break;
  584. }
  585. return $t_value;
  586. };
  587. # Validate properties that must not be arrays
  588. $t_single_value_list = array(
  589. FILTER_PROPERTY_VIEW_STATE => 'int',
  590. FILTER_PROPERTY_RELATIONSHIP_TYPE => 'int',
  591. FILTER_PROPERTY_RELATIONSHIP_BUG => 'int',
  592. );
  593. foreach( $t_single_value_list as $t_field_name => $t_field_type ) {
  594. $t_value = $p_filter_arr[$t_field_name];
  595. if( is_array( $t_value ) ) {
  596. if( count( $t_value ) > 0 ) {
  597. $p_filter_arr[$t_field_name] = reset( $t_value );
  598. } else {
  599. $p_filter_arr[$t_field_name] = filter_get_default_property( $t_field_name, $t_view_type );
  600. }
  601. }
  602. $p_filter_arr[$t_field_name] = $t_function_validate_type( $p_filter_arr[$t_field_name], $t_field_type );
  603. }
  604. # Validate properties that must be arrays, and the type of its elements
  605. $t_array_values_list = array(
  606. FILTER_PROPERTY_CATEGORY_ID => 'string',
  607. FILTER_PROPERTY_SEVERITY => 'int',
  608. FILTER_PROPERTY_STATUS => 'int',
  609. FILTER_PROPERTY_REPORTER_ID => 'int',
  610. FILTER_PROPERTY_HANDLER_ID => 'int',
  611. FILTER_PROPERTY_NOTE_USER_ID => 'int',
  612. FILTER_PROPERTY_RESOLUTION => 'int',
  613. FILTER_PROPERTY_PRIORITY => 'int',
  614. FILTER_PROPERTY_BUILD => 'string',
  615. FILTER_PROPERTY_VERSION => 'string',
  616. FILTER_PROPERTY_HIDE_STATUS => 'int',
  617. FILTER_PROPERTY_FIXED_IN_VERSION => 'string',
  618. FILTER_PROPERTY_TARGET_VERSION => 'string',
  619. FILTER_PROPERTY_MONITOR_USER_ID => 'int',
  620. FILTER_PROPERTY_PROFILE_ID => 'int',
  621. FILTER_PROPERTY_PLATFORM => 'string',
  622. FILTER_PROPERTY_OS => 'string',
  623. FILTER_PROPERTY_OS_BUILD => 'string',
  624. FILTER_PROPERTY_PROJECT_ID => 'int'
  625. );
  626. foreach( $t_array_values_list as $t_multi_field_name => $t_multi_field_type ) {
  627. if( !is_array( $p_filter_arr[$t_multi_field_name] ) ) {
  628. $p_filter_arr[$t_multi_field_name] = array(
  629. $p_filter_arr[$t_multi_field_name],
  630. );
  631. }
  632. $t_checked_array = array();
  633. foreach( $p_filter_arr[$t_multi_field_name] as $t_filter_value ) {
  634. $t_checked_array[] = $t_function_validate_type( $t_filter_value, $t_multi_field_type );
  635. }
  636. $p_filter_arr[$t_multi_field_name] = $t_checked_array;
  637. }
  638. $t_custom_fields = custom_field_get_ids();
  639. if( is_array( $t_custom_fields ) && ( count( $t_custom_fields ) > 0 ) ) {
  640. foreach( $t_custom_fields as $t_cfid ) {
  641. if( isset( $p_filter_arr['custom_fields'][$t_cfid]) ) {
  642. if( !is_array( $p_filter_arr['custom_fields'][$t_cfid] ) ) {
  643. $p_filter_arr['custom_fields'][$t_cfid] = array(
  644. $p_filter_arr['custom_fields'][$t_cfid],
  645. );
  646. }
  647. $t_checked_array = array();
  648. foreach( $p_filter_arr['custom_fields'][$t_cfid] as $t_filter_value ) {
  649. $t_filter_value = stripslashes( $t_filter_value );
  650. if( ( $t_filter_value === 'any' ) || ( $t_filter_value === '[any]' ) ) {
  651. $t_filter_value = META_FILTER_ANY;
  652. }
  653. $t_checked_array[] = $t_filter_value;
  654. }
  655. $p_filter_arr['custom_fields'][$t_cfid] = $t_checked_array;
  656. }
  657. }
  658. }
  659. # If view_type is advanced, and hide_status is present, modify status array
  660. # to remove hidden status. This may happen after switching from simple to advanced.
  661. # Then, remove hide_status property, as it does not apply to advanced filter
  662. if( $p_filter_arr['_view_type'] == FILTER_VIEW_TYPE_ADVANCED
  663. && !filter_field_is_none( $p_filter_arr[FILTER_PROPERTY_HIDE_STATUS] ) ) {
  664. if( filter_field_is_any( $p_filter_arr[FILTER_PROPERTY_STATUS] ) ) {
  665. $t_selected_status_array = MantisEnum::getValues( config_get( 'status_enum_string' ) );
  666. } else {
  667. $t_selected_status_array = $p_filter_arr[FILTER_PROPERTY_STATUS];
  668. }
  669. $t_hide_status = $p_filter_arr[FILTER_PROPERTY_HIDE_STATUS][0];
  670. $t_new_status_array = array();
  671. foreach( $t_selected_status_array as $t_status ) {
  672. if( $t_status < $t_hide_status ) {
  673. $t_new_status_array[] = $t_status;
  674. }
  675. }
  676. # If there is no status left, reset the status property to "any"
  677. if( empty( $t_new_status_array ) ) {
  678. $t_new_status_array[] = META_FILTER_ANY;
  679. }
  680. $p_filter_arr[FILTER_PROPERTY_STATUS] = $t_new_status_array;
  681. $p_filter_arr[FILTER_PROPERTY_HIDE_STATUS] = META_FILTER_NONE;
  682. }
  683. #If view_type is simple, resolve conflicts between show_status and hide_status
  684. if( $p_filter_arr['_view_type'] == FILTER_VIEW_TYPE_SIMPLE
  685. && !filter_field_is_none( $p_filter_arr[FILTER_PROPERTY_HIDE_STATUS] ) ) {
  686. # get array of hidden status ids
  687. $t_all_status = MantisEnum::getValues( config_get( 'status_enum_string' ) );
  688. $t_hidden_status = $p_filter_arr[FILTER_PROPERTY_HIDE_STATUS][0];
  689. $t_hidden_status_array = array();
  690. foreach( $t_all_status as $t_status ) {
  691. if( $t_status >= $t_hidden_status ) {
  692. $t_hidden_status_array[] = $t_status;
  693. }
  694. }
  695. # remove hidden status from show_status property array
  696. # note that this will keep the "any" meta value, if present
  697. $t_show_status_array = array_diff( $p_filter_arr[FILTER_PROPERTY_STATUS], $t_hidden_status_array );
  698. # If there is no status left, reset the status property previous values, and remove hide_status
  699. if( empty( $t_show_status_array ) ) {
  700. $t_show_status_array = $p_filter_arr[FILTER_PROPERTY_STATUS];
  701. $p_filter_arr[FILTER_PROPERTY_HIDE_STATUS] = META_FILTER_NONE;
  702. }
  703. $p_filter_arr[FILTER_PROPERTY_STATUS] = $t_show_status_array;
  704. }
  705. # validate relationship fields
  706. if( !(
  707. $p_filter_arr[FILTER_PROPERTY_RELATIONSHIP_BUG] > 0
  708. || $p_filter_arr[FILTER_PROPERTY_RELATIONSHIP_BUG] == META_FILTER_ANY
  709. || $p_filter_arr[FILTER_PROPERTY_RELATIONSHIP_BUG] == META_FILTER_NONE
  710. ) ) {
  711. $p_filter_arr[FILTER_PROPERTY_RELATIONSHIP_BUG] = filter_get_default_property( FILTER_PROPERTY_RELATIONSHIP_BUG, $t_view_type );
  712. }
  713. # all of our filter values are now guaranteed to be there, and correct.
  714. return $p_filter_arr;
  715. }
  716. /**
  717. * Get a filter array with default values
  718. * Optional view type parameter is used to initialize some fields properly,
  719. * as some may differ in the default content.
  720. * @param string $p_view_type FILTER_VIEW_TYPE_SIMPLE or FILTER_VIEW_TYPE_ADVANCED
  721. * @return array Filter array with default values
  722. */
  723. function filter_get_default_array( $p_view_type = null ) {
  724. static $t_cache_default_array = array();
  725. $t_default_view_type = filter_get_default_view_type();
  726. if( !in_array( $p_view_type, array( FILTER_VIEW_TYPE_SIMPLE, FILTER_VIEW_TYPE_ADVANCED ) ) ) {
  727. $p_view_type = $t_default_view_type;
  728. }
  729. # this function is called multiple times from filter api so return a cached value if possible
  730. if( isset( $t_cache_default_array[$p_view_type] ) ) {
  731. return $t_cache_default_array[$p_view_type];
  732. }
  733. $t_default_show_changed = config_get( 'default_show_changed' );
  734. $t_meta_filter_any_array = array( META_FILTER_ANY );
  735. $t_config_view_filters = config_get( 'view_filters' );
  736. if( ADVANCED_ONLY == $t_config_view_filters ) {
  737. $t_view_type = FILTER_VIEW_TYPE_ADVANCED;
  738. } elseif( SIMPLE_ONLY == $t_config_view_filters ) {
  739. $t_view_type = FILTER_VIEW_TYPE_SIMPLE;
  740. } else {
  741. $t_view_type = $p_view_type;
  742. }
  743. if( $t_view_type == FILTER_VIEW_TYPE_SIMPLE ) {
  744. $t_hide_status_default = config_get( 'hide_status_default' );
  745. } else {
  746. $t_hide_status_default = META_FILTER_NONE;
  747. }
  748. $t_filter = array(
  749. '_version' => FILTER_VERSION,
  750. '_view_type' => $t_view_type,
  751. FILTER_PROPERTY_CATEGORY_ID => $t_meta_filter_any_array,
  752. FILTER_PROPERTY_SEVERITY => $t_meta_filter_any_array,
  753. FILTER_PROPERTY_STATUS => $t_meta_filter_any_array,
  754. FILTER_PROPERTY_HIGHLIGHT_CHANGED => $t_default_show_changed,
  755. FILTER_PROPERTY_REPORTER_ID => $t_meta_filter_any_array,
  756. FILTER_PROPERTY_HANDLER_ID => $t_meta_filter_any_array,
  757. FILTER_PROPERTY_PROJECT_ID => array( META_FILTER_CURRENT ),
  758. FILTER_PROPERTY_RESOLUTION => $t_meta_filter_any_array,
  759. FILTER_PROPERTY_BUILD => $t_meta_filter_any_array,
  760. FILTER_PROPERTY_VERSION => $t_meta_filter_any_array,
  761. FILTER_PROPERTY_HIDE_STATUS => array( $t_hide_status_default ),
  762. FILTER_PROPERTY_MONITOR_USER_ID => $t_meta_filter_any_array,
  763. FILTER_PROPERTY_SORT_FIELD_NAME => 'last_updated',
  764. FILTER_PROPERTY_SORT_DIRECTION => 'DESC',
  765. FILTER_PROPERTY_ISSUES_PER_PAGE => config_get( 'default_limit_view' ),
  766. FILTER_PROPERTY_MATCH_TYPE => FILTER_MATCH_ALL,
  767. FILTER_PROPERTY_PLATFORM => $t_meta_filter_any_array,
  768. FILTER_PROPERTY_OS => $t_meta_filter_any_array,
  769. FILTER_PROPERTY_OS_BUILD => $t_meta_filter_any_array,
  770. FILTER_PROPERTY_FIXED_IN_VERSION => $t_meta_filter_any_array,
  771. FILTER_PROPERTY_TARGET_VERSION => $t_meta_filter_any_array,
  772. FILTER_PROPERTY_PROFILE_ID => $t_meta_filter_any_array,
  773. FILTER_PROPERTY_PRIORITY => $t_meta_filter_any_array,
  774. FILTER_PROPERTY_NOTE_USER_ID => $t_meta_filter_any_array,
  775. FILTER_PROPERTY_STICKY => gpc_string_to_bool( config_get( 'show_sticky_issues' ) ),
  776. FILTER_PROPERTY_FILTER_BY_DATE_SUBMITTED => false,
  777. FILTER_PROPERTY_DATE_SUBMITTED_START_MONTH => date( 'm' ),
  778. FILTER_PROPERTY_DATE_SUBMITTED_END_MONTH => date( 'm' ),
  779. FILTER_PROPERTY_DATE_SUBMITTED_START_DAY => 1,
  780. FILTER_PROPERTY_DATE_SUBMITTED_END_DAY => date( 'd' ),
  781. FILTER_PROPERTY_DATE_SUBMITTED_START_YEAR => date( 'Y' ),
  782. FILTER_PROPERTY_DATE_SUBMITTED_END_YEAR => date( 'Y' ),
  783. FILTER_PROPERTY_FILTER_BY_LAST_UPDATED_DATE => false,
  784. FILTER_PROPERTY_LAST_UPDATED_START_MONTH => date( 'm' ),
  785. FILTER_PROPERTY_LAST_UPDATED_END_MONTH => date( 'm' ),
  786. FILTER_PROPERTY_LAST_UPDATED_START_DAY => 1,
  787. FILTER_PROPERTY_LAST_UPDATED_END_DAY => date( 'd' ),
  788. FILTER_PROPERTY_LAST_UPDATED_START_YEAR => date( 'Y' ),
  789. FILTER_PROPERTY_LAST_UPDATED_END_YEAR => date( 'Y' ),
  790. FILTER_PROPERTY_SEARCH => '',
  791. FILTER_PROPERTY_VIEW_STATE => META_FILTER_ANY,
  792. FILTER_PROPERTY_TAG_STRING => '',
  793. FILTER_PROPERTY_TAG_SELECT => 0,
  794. FILTER_PROPERTY_RELATIONSHIP_TYPE => BUG_REL_ANY,
  795. FILTER_PROPERTY_RELATIONSHIP_BUG => META_FILTER_ANY,
  796. );
  797. # initialize plugin filters
  798. $t_plugin_filters = filter_get_plugin_filters();
  799. foreach( $t_plugin_filters as $t_field_name => $t_filter_object ) {
  800. switch( $t_filter_object->type ) {
  801. case FILTER_TYPE_STRING:
  802. $t_filter[$t_field_name] = $t_filter_object->default;
  803. break;
  804. case FILTER_TYPE_INT:
  805. $t_filter[$t_field_name] = (int)$t_filter_object->default;
  806. break;
  807. case FILTER_TYPE_BOOLEAN:
  808. $t_filter[$t_field_name] = (bool)$t_filter_object->default;
  809. break;
  810. case FILTER_TYPE_MULTI_STRING:
  811. $t_filter[$t_field_name] = array( (string)META_FILTER_ANY );
  812. break;
  813. case FILTER_TYPE_MULTI_INT:
  814. $t_filter[$t_field_name] = array( META_FILTER_ANY );
  815. break;
  816. default:
  817. $t_filter[$t_field_name] = (string)META_FILTER_ANY;
  818. }
  819. if( !$t_filter_object->validate( $t_filter[$t_field_name] ) ) {
  820. $t_filter[$t_field_name] = $t_filter_object->default;
  821. }
  822. }
  823. $t_custom_fields = custom_field_get_ids();
  824. # @@@ (thraxisp) This should really be the linked ids, but we don't know the project
  825. $f_custom_fields_data = array();
  826. if( is_array( $t_custom_fields ) && ( count( $t_custom_fields ) > 0 ) ) {
  827. foreach( $t_custom_fields as $t_cfid ) {
  828. $f_custom_fields_data[$t_cfid] = array( (string)META_FILTER_ANY );
  829. }
  830. }
  831. $t_filter['custom_fields'] = $f_custom_fields_data;
  832. $t_cache_default_array[$p_view_type] = $t_filter;
  833. return $t_filter;
  834. }
  835. /**
  836. * Returns the default view type for filters
  837. * @return string Default view type
  838. */
  839. function filter_get_default_view_type() {
  840. if( ADVANCED_DEFAULT == config_get( 'view_filters' ) ) {
  841. return FILTER_VIEW_TYPE_ADVANCED;
  842. } else {
  843. return FILTER_VIEW_TYPE_SIMPLE;
  844. }
  845. }
  846. /**
  847. * Returns the default value for a filter property.
  848. * Relies on filter_get_default_array() to get a defaulted filter.
  849. * @param string $p_filter_property The requested filter property name
  850. * @param string $p_view_type Optional, view type for the defaulted filter (simple/advanced)
  851. * @return mixed The property default value, or null if it doesn't exist
  852. */
  853. function filter_get_default_property( $p_filter_property, $p_view_type = null ) {
  854. $t_default_array = filter_get_default_array( $p_view_type );
  855. if( isset( $t_default_array[$p_filter_property] ) ) {
  856. return $t_default_array[$p_filter_property];
  857. } else {
  858. return null;
  859. }
  860. }
  861. /**
  862. * Get the standard filter that is to be used when no filter was previously saved.
  863. * When creating specific filters, this can be used as a basis for the filter, where
  864. * specific entries can be overridden.
  865. * @return mixed
  866. */
  867. function filter_get_default() {
  868. # Create empty array, validation will fill it with defaults
  869. $t_filter = array();
  870. return filter_ensure_valid_filter( $t_filter );
  871. }
  872. /**
  873. * Deserialize filter string
  874. * Expected strings have this format: "<version>#<json string>" where:
  875. * - <version> is the versio number of the filter structure used. See constant FILTER_VERSION
  876. * - # is a separator
  877. * - <json string> is the json encoded filter array.
  878. * @param string $p_serialized_filter Serialized filter string.
  879. * @return mixed $t_filter array
  880. * @see filter_ensure_valid_filter
  881. */
  882. function filter_deserialize( $p_serialized_filter ) {
  883. if( is_blank( $p_serialized_filter ) ) {
  884. return false;
  885. }
  886. #@TODO cproensa, we could accept a pure json array, without version prefix
  887. # in this case, the filter version field inside the array is to be used
  888. # and if not present, set the current filter version
  889. # check filter version mark
  890. $t_setting_arr = explode( '#', $p_serialized_filter, 2 );
  891. $t_version_string = $t_setting_arr[0];
  892. if( in_array( $t_version_string, array( 'v1', 'v2', 'v3', 'v4' ) ) ) {
  893. # these versions can't be salvaged, they are too old to update
  894. return false;
  895. } elseif( in_array( $t_version_string, array( 'v5', 'v6', 'v7', 'v8' ) ) ) {
  896. # filters from v5 onwards should cope with changing filter indices dynamically
  897. $t_filter_array = unserialize( $t_setting_arr[1] );
  898. } else {
  899. # filters from v9 onwards are stored as json
  900. $t_filter_array = json_decode( $t_setting_arr[1], /* assoc array */ true );
  901. }
  902. # If the unserialez data is not an array, the some error happened, eg, invalid format
  903. if( !is_array( $t_filter_array ) ) {
  904. return false;
  905. }
  906. # Set the filter version that was loaded in the array
  907. $t_filter_array['_version'] = $t_setting_arr[0];
  908. # If upgrade in filter content is needed, it will be done in filter_ensure_valid_filter()
  909. return filter_ensure_valid_filter( $t_filter_array );
  910. }
  911. /**
  912. * Creates a serialized filter with the correct format
  913. * @param array $p_filter_array Filter array to be serialized
  914. * @return string Serialized filter string
  915. */
  916. function filter_serialize( $p_filter_array ) {
  917. $t_cookie_version = FILTER_VERSION;
  918. $p_filter_array = filter_clean_runtime_properties( $p_filter_array );
  919. $t_settings_serialized = json_encode( $p_filter_array );
  920. $t_settings_string = $t_cookie_version . '#' . $t_settings_serialized;
  921. return $t_settings_string;
  922. }
  923. /**
  924. * Get the filter db row $p_filter_id
  925. * using the cached row if it's available
  926. * @global array $g_cache_filter_db_rows
  927. * @param integer $p_filter_id A filter identifier to look up in the database.
  928. * @return array|boolean The row of filter data as stored in db table, or false if does not exist
  929. */
  930. function filter_get_row( $p_filter_id ) {
  931. global $g_cache_filter_db_rows;
  932. if( !isset( $g_cache_filter_db_rows[$p_filter_id] ) ) {
  933. filter_cache_rows( array($p_filter_id) );
  934. }
  935. $t_row = $g_cache_filter_db_rows[$p_filter_id];
  936. return $t_row;
  937. }
  938. /**
  939. * Get the value of the filter field specified by filter id and field name
  940. * @param integer $p_filter_id A filter identifier to look up in the database.
  941. * @param string $p_field_name Name of the filter field to retrieve.
  942. * @return string
  943. */
  944. function filter_get_field( $p_filter_id, $p_field_name ) {
  945. $t_row = filter_get_row( $p_filter_id );
  946. if( isset( $t_row[$p_field_name] ) ) {
  947. return $t_row[$p_field_name];
  948. } else {
  949. error_parameters( $p_field_name );
  950. trigger_error( ERROR_DB_FIELD_NOT_FOUND, WARNING );
  951. return '';
  952. }
  953. }
  954. /**
  955. * Get set of bug rows from given filter
  956. * @todo Had to make all these parameters required because we can't use call-time pass by reference anymore.
  957. * I really preferred not having to pass all the params in if you didn't want to, but I wanted to get
  958. * rid of the errors for now. If we can think of a better way later (maybe return an object) that would be great.
  959. *
  960. * @param integer &$p_page_number Page number of the page you want to see (set to the actual page on return).
  961. * @param integer &$p_per_page The number of bugs to see per page (set to actual on return)
  962. * -1 indicates you want to see all bugs
  963. * null indicates you want to use the value specified in the filter.
  964. * @param integer &$p_page_count You don't need to give a value here, the number of pages will be stored here on return.
  965. * @param integer &$p_bug_count You don't need to give a value here, the number of bugs will be stored here on return.
  966. * @param mixed $p_custom_filter Custom Filter to use.
  967. * @param integer $p_project_id Project id to use in filtering.
  968. * @param integer $p_user_id User id to use as current user when filtering.
  969. * @param boolean $p_show_sticky True/false - get sticky issues only.
  970. * @return boolean|array
  971. */
  972. 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 ) {
  973. # assigning to $p_* for this function writes the values back in case the caller wants to know
  974. if( $p_custom_filter === null ) {
  975. $t_filter = filter_get_bug_rows_filter( $p_project_id, $p_user_id );
  976. } else {
  977. $t_filter = filter_ensure_valid_filter( $p_custom_filter );
  978. }
  979. # build a filter query, here for counting results
  980. $t_filter_query = new BugFilterQuery(
  981. $t_filter,
  982. array(
  983. 'query_type' => BugFilterQuery::QUERY_TYPE_LIST,
  984. 'project_id' => $p_project_id,
  985. 'user_id' => $p_user_id,
  986. 'use_sticky' => $p_show_sticky
  987. )
  988. );
  989. $p_bug_count = $t_filter_query->get_bug_count();
  990. if( 0 == $p_bug_count ) {
  991. return array();
  992. }
  993. # Calculate pagination
  994. $p_per_page = filter_per_page( $t_filter, $p_bug_count, $p_per_page );
  995. $p_page_count = filter_page_count( $p_bug_count, $p_per_page );
  996. $p_page_number = filter_valid_page_number( $p_page_number, $p_page_count );
  997. $t_offset = filter_offset( $p_page_number, $p_per_page );
  998. $t_filter_query->set_limit( $p_per_page );
  999. $t_filter_query->set_offset( $t_offset );
  1000. # Execute query
  1001. $t_rows = $t_filter_query->fetch_all();
  1002. $t_bug_id_array = array_column( $t_rows, 'id' );
  1003. # Return the processed rows: cache data, convert to bug objects
  1004. return filter_cache_result( $t_rows, $t_bug_id_array );
  1005. }
  1006. /**
  1007. * Get the filter defined by user and project.
  1008. * @param integer $p_project_id Project id to use in filtering.
  1009. * @param integer $p_user_id User id to use as current user when filtering.
  1010. * @return array
  1011. */
  1012. function filter_get_bug_rows_filter( $p_project_id = null, $p_user_id = null ) {
  1013. $t_current_user_id = auth_get_current_user_id();
  1014. if( $p_user_id === null || $p_user_id === 0 ) {
  1015. $t_user_id = $t_current_user_id;
  1016. } else {
  1017. $t_user_id = $p_user_id;
  1018. }
  1019. if( null === $p_project_id ) {
  1020. # @@@ If project_id is not specified, then use the project id(s) in the filter if set, otherwise, use current project.
  1021. $t_project_id = helper_get_current_project();
  1022. } else {
  1023. $t_project_id = $p_project_id;
  1024. }
  1025. if( $t_user_id == $t_current_user_id ) {
  1026. $t_filter = current_user_get_bug_filter();
  1027. } else {
  1028. $t_filter = user_get_bug_filter( $t_user_id, $t_project_id );
  1029. }
  1030. # if filter isn't return above, create a new filter from an empty array.
  1031. if( false === $t_filter ) {
  1032. $t_filter = array();
  1033. }
  1034. return $t_filter;
  1035. }
  1036. /**
  1037. * Cache the filter results with bugnote stats for later use
  1038. * @param array $p_rows Results of the filter query.
  1039. * @param array $p_id_array_lastmod Array of bug ids.
  1040. * @return array
  1041. */
  1042. function filter_cache_result( array $p_rows, array $p_id_array_lastmod ) {
  1043. $t_stats = bug_get_bugnote_stats_array( $p_id_array_lastmod );
  1044. $t_rows = array();
  1045. foreach( $p_rows as $t_row ) {
  1046. if( array_key_exists( $t_row['id'], $t_stats ) ) {
  1047. $t_rows[] = bug_row_to_object( bug_cache_database_result( $t_row, $t_stats[$t_row['id']] ) );
  1048. } else {
  1049. $t_rows[] = bug_row_to_object( bug_cache_database_result( $t_row ) );
  1050. }
  1051. }
  1052. return $t_rows;
  1053. }
  1054. /**
  1055. * Prints the filter selection area for both the bug list view screen and
  1056. * the bug list print screen. This function was an attempt to make it easier to
  1057. * add new filters and rearrange them on screen for both pages.
  1058. * @return void
  1059. */
  1060. function filter_draw_selection_area() {
  1061. $t_form_name_suffix = '_open';
  1062. $t_filter = current_user_get_bug_filter();
  1063. $t_filter = filter_ensure_valid_filter( $t_filter === false ? array() : $t_filter );
  1064. $t_view_type = $t_filter['_view_type'];
  1065. ?>
  1066. <div class="col-md-12 col-xs-12">
  1067. <div class="filter-box">
  1068. <?php
  1069. $t_stored_queries_arr = filter_db_get_available_queries();
  1070. $t_is_temporary = filter_is_temporary( $t_filter );
  1071. $t_tmp_filter_param = $t_is_temporary ? '&filter=' . filter_get_temporary_key( $t_filter ) : '';
  1072. $t_can_persist = filter_user_can_use_persistent( auth_get_current_user_id() );
  1073. $t_collapse_block = is_collapsed( 'filter' );
  1074. $t_block_css = $t_collapse_block ? 'collapsed' : '';
  1075. $t_block_icon = $t_collapse_block ? 'fa-chevron-down' : 'fa-chevron-up';
  1076. # further use of this icon must be inlined to avoid spaces in rendered html
  1077. $t_temporary_icon_html = ( $t_is_temporary && $t_can_persist ) ?
  1078. '<i class="fa fa-clock-o fa-xs-top" title="' . lang_get( 'temporary_filter' ) . '"></i>'
  1079. : '';
  1080. $t_url_reset_filter = 'view_all_set.php?type=' . FILTER_ACTION_RESET;
  1081. $t_url_persist_filter = 'view_all_set.php?temporary=n' . $t_tmp_filter_param . '&set_project_id=' . helper_get_current_project();
  1082. ?>
  1083. <div id="filter" class="widget-box widget-color-blue2 <?php echo $t_block_css ?>">
  1084. <div class="widget-header widget-header-small">
  1085. <h4 class="widget-title lighter">
  1086. <i class="ace-icon fa fa-filter"><?php echo $t_temporary_icon_html ?>
  1087. </i>
  1088. <?php echo lang_get( 'filters' ) ?>
  1089. </h4>
  1090. <div class="widget-toolbar">
  1091. <?php
  1092. $t_view_filters = config_get('view_filters');
  1093. if( ( ( SIMPLE_ONLY != $t_view_filters ) && ( ADVANCED_ONLY != $t_view_filters ) ) ||
  1094. access_has_project_level( config_get( 'create_permalink_threshold' ) ) ||
  1095. count( $t_stored_queries_arr ) > 0 ) { ?>
  1096. <div class="widget-menu">
  1097. <a href="#" data-action="settings" data-toggle="dropdown">
  1098. <i class="ace-icon fa fa-bars bigger-125"></i>
  1099. </a>
  1100. <ul class="dropdown-menu dropdown-menu-right dropdown-yellow dropdown-caret dropdown-closer">
  1101. <?php
  1102. $t_url = config_get( 'use_dynamic_filters' )
  1103. ? 'view_all_set.php?type=' . FILTER_ACTION_PARSE_ADD . $t_tmp_filter_param . '&view_type='
  1104. : 'view_filters_page.php?view_type=';
  1105. filter_print_view_type_toggle( $t_url, $t_filter['_view_type'] );
  1106. if( access_has_project_level( config_get( 'create_permalink_threshold' ) ) ) {
  1107. # Add CSRF protection, see #22702
  1108. $t_permalink_url = urlencode( filter_get_url( $t_filter ) )
  1109. . form_security_param( 'permalink' );
  1110. echo '<li>';
  1111. echo '<a href="permalink_page.php?url=' . $t_permalink_url . '">';
  1112. echo '<i class="ace-icon fa fa-link"></i>&#160;&#160;' . lang_get( 'create_filter_link' );
  1113. echo '</a>';
  1114. echo '</li>';
  1115. }
  1116. if( count( $t_stored_queries_arr ) > 0 ) {
  1117. echo '<li>';
  1118. echo '<a href="manage_filter_page.php">';
  1119. echo '<i class="ace-icon fa fa-wrench"></i>&#160;&#160;' . lang_get( 'open_queries' );
  1120. echo '</a>';
  1121. echo '</li>';
  1122. }
  1123. if( $t_is_temporary && $t_can_persist ) {
  1124. echo '<li>';
  1125. echo '<a href="' . $t_url_persist_filter . '">';
  1126. echo '<i class="ace-icon fa fa-thumb-tack"></i>&#160;&#160;' . lang_get( 'set_as_persistent_filter' );
  1127. echo '</a>';
  1128. echo '</li>';
  1129. }
  1130. ?>
  1131. </ul>
  1132. </div>
  1133. <?php } ?>
  1134. <a id="filter-toggle" data-action="collapse" href="#">
  1135. <i class="1 ace-icon fa bigger-125 <?php echo $t_block_icon ?>"></i>
  1136. </a>
  1137. </div>
  1138. <div id="filter-bar-queries" class="widget-toolbar no-border" style="display: <?php echo $t_collapse_block ? 'block' : 'none' ?>">
  1139. <div class="widget-menu margin-left-8">
  1140. <?php
  1141. if( $t_is_temporary && $t_can_persist ) {
  1142. ?>
  1143. <a class="btn btn-primary btn-white btn-round btn-xs"
  1144. title="<?php echo lang_get( 'set_as_persistent_filter' ) ?>"
  1145. href="<?php echo $t_url_persist_filter ?>">
  1146. <i class="ace-icon fa fa-thumb-tack"></i>
  1147. </a>
  1148. <?php
  1149. }
  1150. ?>
  1151. <a class="btn btn-primary btn-white btn-round btn-xs"
  1152. title="<?php echo lang_get( 'reset_query' ) ?>"
  1153. href="<?php echo $t_url_reset_filter ?>">
  1154. <i class="ace-icon fa fa-times"></i>
  1155. </a>
  1156. </div>
  1157. <?php if( count( $t_stored_queries_arr ) > 0 ) { ?>
  1158. <div class="widget-menu hidden-xs">
  1159. <form method="post" action="view_all_set.php">
  1160. <input type="hidden" name="type" value="<?php echo FILTER_ACTION_LOAD ?>" />
  1161. <select id="filter-bar-query-id" class="input-xs">
  1162. <option value="-1"></option>
  1163. <?php
  1164. $t_source_query_id = isset( $t_filter['_source_query_id'] ) ? (int)$t_filter['_source_query_id'] : -1;
  1165. foreach( $t_stored_queries_arr as $t_query_id => $t_query_name ) {
  1166. echo '<option value="' . $t_query_id . '" ';
  1167. check_selected( $t_query_id, $t_source_query_id );
  1168. echo '>' . string_display_line( $t_query_name ) . '</option>';
  1169. }
  1170. ?>
  1171. </select>
  1172. </form>
  1173. </div>
  1174. <?php } ?>
  1175. <div class="widget-menu margin-right-8">
  1176. <form method="post" action="view_all_set.php">
  1177. <input type="hidden" name="type" value="<?php echo FILTER_ACTION_PARSE_ADD ?>" />
  1178. <input id="filter-bar-search-txt" type="text" size="16" class="input-xs"
  1179. placeholder="<?php echo lang_get( 'search' ) ?>"
  1180. name="<?php echo FILTER_PROPERTY_SEARCH ?>"
  1181. value="<?php echo string_attribute( $t_filter[FILTER_PROPERTY_SEARCH] ); ?>" />
  1182. <button id="filter-bar-search-btn" type="submit" name="filter_submit" class="btn btn-primary btn-white btn-round btn-xs"
  1183. title="<?php echo lang_get( 'filter_button' ) ?>">
  1184. <i class="ace-icon fa fa-search"></i>
  1185. </button>
  1186. </form>
  1187. </div>
  1188. </div>
  1189. </div>
  1190. <div class="widget-body">
  1191. <div class="widget-toolbox padding-4 clearfix">
  1192. <div class="btn-toolbar">
  1193. <div class="form-inline">
  1194. <div class="btn-group pull-left">
  1195. <?php
  1196. # Top left toolbar for buttons
  1197. $t_url_reset_filter = 'view_all_set.php?type=' . FILTER_ACTION_RESET;
  1198. if( $t_is_temporary && $t_can_persist ) {
  1199. ?>
  1200. <a class="btn btn-sm btn-primary btn-white btn-round" href="<?php echo $t_url_persist_filter ?>">
  1201. <i class="ace-icon fa fa-thumb-tack"></i>
  1202. <?php echo lang_get( 'persist' ) ?>
  1203. </a>
  1204. <?php
  1205. }
  1206. ?>
  1207. <a class="btn btn-sm btn-primary btn-white btn-round" href="<?php echo $t_url_reset_filter ?>">
  1208. <i class="ace-icon fa fa-times"></i>
  1209. <?php echo lang_get( 'reset' ) ?>
  1210. </a>
  1211. <?php
  1212. if( access_has_project_level( config_get( 'stored_query_create_threshold' ) ) ) {
  1213. $t_url_save_filter = 'query_store_page.php';
  1214. if( filter_is_temporary( $t_filter ) ) {
  1215. $t_url_save_filter .= '?filter=' . filter_get_temporary_key( $t_filter );
  1216. }
  1217. ?>
  1218. <a class="btn btn-sm btn-primary btn-white btn-round" href="<?php echo $t_url_save_filter ?>">
  1219. <i class="ace-icon fa fa-floppy-o"></i>
  1220. <?php echo lang_get( 'save' ) ?>
  1221. </a>
  1222. <?php
  1223. }
  1224. ?>
  1225. </div>
  1226. <?php
  1227. if( count( $t_stored_queries_arr ) > 0 ) { ?>
  1228. <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">
  1229. <?php # CSRF protection not required here - form does not result in modifications?>
  1230. <input type="hidden" name="type" value="<?php echo FILTER_ACTION_LOAD ?>" />
  1231. <label><?php echo lang_get( 'load' ) ?>
  1232. <select class="input-s" name="source_query_id">
  1233. <option value="-1"></option>
  1234. <?php
  1235. $t_source_query_id = isset( $t_filter['_source_query_id'] ) ? (int)$t_filter['_source_query_id'] : -1;
  1236. foreach( $t_stored_queries_arr as $t_query_id => $t_query_name ) {
  1237. echo '<option value="' . $t_query_id . '" ';
  1238. check_selected( $t_query_id, $t_source_query_id );
  1239. echo '>' . string_display_line( $t_query_name ) . '</option>';
  1240. }
  1241. ?>
  1242. </select>
  1243. </label>
  1244. </form>
  1245. <?php
  1246. }
  1247. ?>
  1248. </div>
  1249. </div>
  1250. </div>
  1251. <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">
  1252. <?php # CSRF protection not required here - form does not result in modifications ?>
  1253. <input type="hidden" name="type" value="<?php echo FILTER_ACTION_PARSE_NEW ?>" />
  1254. <?php
  1255. if( filter_is_temporary( $t_filter ) ) {
  1256. echo '<input type="hidden" name="filter" value="' . filter_get_temporary_key( $t_filter ) . '" />';
  1257. }
  1258. ?>
  1259. <input type="hidden" name="view_type" value="<?php echo $t_view_type?>" />
  1260. <div class="widget-main no-padding">
  1261. <div class="table-responsive">
  1262. <?php
  1263. filter_form_draw_inputs( $t_filter, true, false, 'view_filters_page.php', false /* don't show search */ );
  1264. ?>
  1265. </div>
  1266. </div>
  1267. <div class="widget-toolbox padding-8 clearfix">
  1268. <div class="btn-toolbar pull-left">
  1269. <div class="form-inline">
  1270. <?php echo '<input type="text" id="filter-search-txt" class="input-sm" size="16" name="', FILTER_PROPERTY_SEARCH, '"'
  1271. , ' placeholder="' . lang_get( 'search' ) . '" value="', string_attribute( $t_filter[FILTER_PROPERTY_SEARCH] ), '" />';
  1272. ?>
  1273. <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' )?>" />
  1274. </div>
  1275. </div>
  1276. </div>
  1277. </form>
  1278. </div>
  1279. </div>
  1280. </div>
  1281. </div>
  1282. <?php
  1283. }
  1284. function filter_cache_rows( array $p_filter_ids ) {
  1285. global $g_cache_filter_db_rows;
  1286. if( empty( $p_filter_ids ) ) {
  1287. return;
  1288. }
  1289. $t_ids_not_found = array();
  1290. $t_params = array();
  1291. $t_sql_params = array();
  1292. db_param_push();
  1293. foreach( $p_filter_ids as $t_id ) {
  1294. $t_sql_params[] = db_param();
  1295. $t_params[] = (int)$t_id;
  1296. $t_ids_not_found[$t_id] = $t_id;
  1297. }
  1298. $t_query = 'SELECT * FROM {filters} WHERE id IN ('
  1299. . implode( ',', $t_sql_params ) . ')';
  1300. $t_result = db_query( $t_query, $t_params );
  1301. while( $t_row = db_fetch_array( $t_result ) ) {
  1302. $g_cache_filter_db_rows[$t_row['id']] = $t_row;
  1303. unset( $t_ids_not_found[$t_row['id']] );
  1304. }
  1305. foreach( $t_ids_not_found as $t_id ) {
  1306. $g_cache_filter_db_rows[$t_id] = false;
  1307. }
  1308. }
  1309. /**
  1310. * Clear the filter cache (or just the given id if specified)
  1311. * @param integer $p_filter_id Filter id.
  1312. * @return boolean
  1313. */
  1314. function filter_clear_cache( $p_filter_id = null ) {
  1315. global $g_cache_filter_db_rows;
  1316. if( null === $p_filter_id ) {
  1317. $g_cache_filter_db_rows = array();
  1318. } else {
  1319. unset( $g_cache_filter_db_rows[(int)$p_filter_id] );
  1320. }
  1321. return true;
  1322. }
  1323. /**
  1324. * Update a filter identified by its id
  1325. * Some parameters are optional and only will be updated if provided
  1326. * Note that values are not validated
  1327. * @param int $p_filter_id Filter id
  1328. * @param string $p_filter_string Filter string in custom serialized format
  1329. * @param int $p_project_id
  1330. * @param bool $p_is_public
  1331. * @param string $p_name
  1332. */
  1333. function filter_db_update_filter( $p_filter_id, $p_filter_string, $p_project_id = null, $p_is_public = null, $p_name = null ) {
  1334. db_param_push();
  1335. $t_params = array();
  1336. $t_query = 'UPDATE {filters} SET filter_string=' . db_param();
  1337. $t_params[] = $p_filter_string;
  1338. if( null !== $p_project_id ) {
  1339. $t_query .= ', project_id=' . db_param();
  1340. $t_params[] = (int)$p_project_id;
  1341. }
  1342. if( null !== $p_is_public ) {
  1343. $t_query .= ', is_public=' . db_param();
  1344. $t_params[] = (bool)$p_is_public;
  1345. }
  1346. if( null !== $p_name ) {
  1347. $t_query .= ', name=' . db_param();
  1348. $t_params[] = $p_name;
  1349. }
  1350. $t_query .= ' WHERE id=' . db_param();
  1351. $t_params[] = (int)$p_filter_id;
  1352. db_query( $t_query, $t_params );
  1353. }
  1354. /**
  1355. * Add a filter to the database.
  1356. * This function does not perform any validation on access or inserted data
  1357. *
  1358. * @param string $p_filter_string Filter string in filter-serialized format
  1359. * @param integer $p_user_id User id owner of the filter
  1360. * @param integer $p_project_id Project id associated to the filter
  1361. * @param string $p_name Name of the filter
  1362. * @param boolean $p_is_public Boolean flag to set the filter public
  1363. * @return integer The id of the created row
  1364. */
  1365. function filter_db_create_filter( $p_filter_string, $p_user_id, $p_project_id, $p_name, $p_is_public ) {
  1366. $c_project_id = (int)$p_project_id;
  1367. $c_user_id = $p_user_id;
  1368. $c_is_public = (bool)$p_is_public;
  1369. db_param_push();
  1370. $t_query = 'INSERT INTO {filters} ( user_id, project_id, is_public, name, filter_string )'
  1371. . ' VALUES ( ' . db_param() . ', ' . db_param() . ', ' . db_param() . ', ' . db_param() . ', ' . db_param() . ' )';
  1372. $t_params = array( $c_user_id, $c_project_id, $c_is_public, $p_name, $p_filter_string );
  1373. db_query( $t_query, $t_params );
  1374. return db_insert_id( db_get_table( 'filters' ) );
  1375. }
  1376. /**
  1377. * Updates the default filter for a project and user.
  1378. * We only can have one filter of this kind, per project and user.
  1379. * These special filters are saved in database with a negative project id
  1380. * to differentiate from standard named filters.
  1381. *
  1382. * Note: currently this filter is how the current filter in use is persisted
  1383. * This means: the last used filter settings, for each project, are saved here.
  1384. * @TODO cproensa, theres some suggestions to clean this up:
  1385. * - working filters should not be tracked in database, at least not as unique per user
  1386. * - include a UI functionality to allow setting/clearing these default filters
  1387. * - ideally, the storage should be cleaner: either separated from standard filters
  1388. * or use a proper field in the table, instead of relying on the negative project id
  1389. *
  1390. * @param array $p_filter Filter array
  1391. * @param integer $p_project_id Project id
  1392. * @param integer $p_user_id User id
  1393. * @return integer The filter id that was updated or created
  1394. */
  1395. function filter_set_project_filter( array $p_filter, $p_project_id = null, $p_user_id = null ) {
  1396. if( null === $p_project_id ) {
  1397. $t_project_id = helper_get_current_project();
  1398. } else {
  1399. $t_project_id = (int)$p_project_id;
  1400. }
  1401. if( null === $p_user_id ) {
  1402. $t_user_id = auth_get_current_user_id();
  1403. } else {
  1404. $t_user_id = (int)$p_user_id;
  1405. }
  1406. $p_filter_string = filter_serialize( $p_filter );
  1407. # Check if a row already exists
  1408. $t_id = filter_db_get_project_current( $t_project_id, $p_user_id );
  1409. if( $t_id ) {
  1410. # A row already esxists
  1411. filter_db_update_filter( $t_id, $p_filter_string );
  1412. } else {
  1413. # Must create a row
  1414. $t_db_project_id = -1 * $t_project_id;
  1415. $t_id = filter_db_create_filter( $p_filter_string, $t_user_id, $t_db_project_id, '', false );
  1416. }
  1417. return $t_id;
  1418. }
  1419. /**
  1420. * This function returns the filter string that is tied to the unique id parameter. If the user
  1421. * does not have permission to see this filter, the function returns null
  1422. * @param integer $p_filter_id A Filter identifier.
  1423. * @param integer $p_user_id A valid user identifier.
  1424. * @return mixed
  1425. */
  1426. function filter_db_get_filter_string( $p_filter_id, $p_user_id = null ) {
  1427. $c_filter_id = (int)$p_filter_id;
  1428. if( !filter_is_accessible( $c_filter_id, $p_user_id ) ) {
  1429. return null;
  1430. }
  1431. $t_filter_row = filter_get_row( $c_filter_id );
  1432. return $t_filter_row['filter_string'];
  1433. }
  1434. /**
  1435. * get current filter for given project and user
  1436. * @param integer $p_project_id A project identifier.
  1437. * @param integer $p_user_id A valid user identifier.
  1438. * @return integer
  1439. */
  1440. function filter_db_get_project_current( $p_project_id = null, $p_user_id = null ) {
  1441. if( null === $p_project_id ) {
  1442. $c_project_id = helper_get_current_project();
  1443. } else {
  1444. $c_project_id = (int)$p_project_id;
  1445. }
  1446. if( null === $p_user_id ) {
  1447. $c_user_id = auth_get_current_user_id();
  1448. } else {
  1449. $c_user_id = (int)$p_user_id;
  1450. }
  1451. # we store current filters for each project with a special project index
  1452. $t_filter_project_id = $c_project_id * -1;
  1453. db_param_push();
  1454. $t_query = 'SELECT id FROM {filters} WHERE user_id = ' . db_param()
  1455. . ' AND project_id = ' . db_param() . ' AND name = ' . db_param();
  1456. $t_result = db_query( $t_query, array( $c_user_id, $t_filter_project_id, '' ) );
  1457. if( $t_row = db_fetch_array( $t_result ) ) {
  1458. return $t_row['id'];
  1459. }
  1460. return null;
  1461. }
  1462. /**
  1463. * Query for the filter name using the filter id
  1464. * @param integer $p_filter_id Filter id.
  1465. * @return string
  1466. */
  1467. function filter_db_get_name( $p_filter_id ) {
  1468. $c_filter_id = (int)$p_filter_id;
  1469. $t_filter_row = filter_get_row( $c_filter_id );
  1470. if( !$t_filter_row ) {
  1471. return null;
  1472. }
  1473. if( $t_filter_row['user_id'] != auth_get_current_user_id() ) {
  1474. if( $t_filter_row['is_public'] != true ) {
  1475. return null;
  1476. }
  1477. }
  1478. return $t_filter_row['name'];
  1479. }
  1480. /**
  1481. * Check if the current user has permissions to delete the stored query
  1482. * @param integer $p_filter_id Filter id.
  1483. * @param integer|null User id or null for logged in user.
  1484. * @return boolean
  1485. */
  1486. function filter_db_can_delete_filter( $p_filter_id, $p_user_id = null ) {
  1487. $c_filter_id = (int)$p_filter_id;
  1488. $t_user_id = $p_user_id != null ? $p_user_id : auth_get_current_user_id();
  1489. # Administrators can delete any filter
  1490. if( user_is_administrator( $t_user_id ) ) {
  1491. return true;
  1492. }
  1493. $t_filter_row = filter_get_row( $c_filter_id );
  1494. if( $t_filter_row
  1495. && $t_filter_row['user_id'] == $t_user_id
  1496. && $t_filter_row['project_id'] >= 0 ) {
  1497. return true;
  1498. } else {
  1499. return false;
  1500. }
  1501. }
  1502. /**
  1503. * Delete the filter specified by $p_filter_id
  1504. * @param integer $p_filter_id Filter identifier.
  1505. * @return boolean
  1506. */
  1507. function filter_db_delete_filter( $p_filter_id ) {
  1508. $c_filter_id = (int)$p_filter_id;
  1509. if( !filter_db_can_delete_filter( $c_filter_id ) ) {
  1510. return false;
  1511. }
  1512. db_param_push();
  1513. $t_query = 'DELETE FROM {filters} WHERE id=' . db_param();
  1514. db_query( $t_query, array( $c_filter_id ) );
  1515. return true;
  1516. }
  1517. /**
  1518. * Delete all the unnamed filters
  1519. * @return void
  1520. */
  1521. function filter_db_delete_current_filters() {
  1522. $t_all_id = ALL_PROJECTS;
  1523. db_param_push();
  1524. $t_query = 'DELETE FROM {filters} WHERE project_id<=' . db_param() . ' AND name=' . db_param();
  1525. db_query( $t_query, array( $t_all_id, '' ) );
  1526. }
  1527. /**
  1528. * Returns stored filters based on a combination of linked project, owner user and public status.
  1529. * Any parameter can be defaulted to null, to get all results for that field
  1530. * @param integer $p_project_id Project id linked to the filter
  1531. * @param integer $p_user_id User id, meaning filter owner
  1532. * @param boolean $p_public Public flag for filter
  1533. * @return array Array of filter ids and names
  1534. */
  1535. function filter_db_get_named_filters( $p_project_id = null, $p_user_id = null, $p_public = null ) {
  1536. db_param_push();
  1537. $t_params = array();
  1538. $t_query = 'SELECT id, name FROM {filters} WHERE project_id >= ' . db_param();
  1539. $t_params[] = 0;
  1540. # build where clauses
  1541. if( null !== $p_project_id ) {
  1542. $t_query .= ' AND project_id = ' . db_param();
  1543. $t_params[] = (int)$p_project_id;
  1544. }
  1545. if( null !== $p_user_id ) {
  1546. $t_query .= ' AND user_id = ' . db_param();
  1547. $t_params[] = (int)$p_user_id;
  1548. }
  1549. if( null !== $p_public ) {
  1550. $t_query .= ' AND is_public = ' . db_param();
  1551. # cast $p_public to strict true/false values
  1552. $t_params[] = $p_public ? true : false;
  1553. }
  1554. $t_result = db_query( $t_query, $t_params );
  1555. $t_query_arr = array();
  1556. while( $t_row = db_fetch_array( $t_result ) ) {
  1557. $t_query_arr[$t_row['id']] = $t_row['name'];
  1558. }
  1559. return $t_query_arr;
  1560. }
  1561. /**
  1562. * Get the list of available filters.
  1563. *
  1564. * @param integer|null $p_project_id A valid project identifier or null for current project.
  1565. * @param integer|null $p_user_id A valid user identifier or null for logged in user.
  1566. * @param boolean $p_filter_by_project Only return filters associated with specified project id or All Projects, otherwise return all filters for user.
  1567. * @param boolean $p_return_names_only true: return names of filters, false: return structures with filter header information.
  1568. * @return array Array of filters.
  1569. */
  1570. function filter_db_get_available_queries( $p_project_id = null, $p_user_id = null, $p_filter_by_project = true, $p_return_names_only = true ) {
  1571. if( null === $p_project_id ) {
  1572. $t_project_id = helper_get_current_project();
  1573. } else {
  1574. $t_project_id = (int)$p_project_id;
  1575. }
  1576. if( null === $p_user_id ) {
  1577. $t_user_id = auth_get_current_user_id();
  1578. } else {
  1579. $t_user_id = (int)$p_user_id;
  1580. }
  1581. # If the user doesn't have access rights to stored queries, just return
  1582. if( !access_has_project_level( config_get( 'stored_query_use_threshold' ) ) ) {
  1583. return array();
  1584. }
  1585. # Get the list of available queries. By sorting such that public queries are
  1586. # first, we can override any query that has the same name as a private query
  1587. # with that private one
  1588. db_param_push();
  1589. if( $p_filter_by_project ) {
  1590. $t_query = 'SELECT * FROM {filters}
  1591. WHERE (project_id = ' . db_param() . '
  1592. OR project_id = 0)
  1593. AND name != \'\'
  1594. AND (is_public = ' . db_param() . '
  1595. OR user_id = ' . db_param() . ')
  1596. ORDER BY is_public DESC, name ASC';
  1597. $t_result = db_query( $t_query, array( $t_project_id, true, $t_user_id ) );
  1598. } else {
  1599. $t_project_ids = user_get_all_accessible_projects( $t_user_id );
  1600. $t_project_ids[] = ALL_PROJECTS;
  1601. $t_query = 'SELECT * FROM {filters}
  1602. WHERE project_id in (' . implode( ',', $t_project_ids ) . ')
  1603. AND name != \'\'
  1604. AND (is_public = ' . db_param() . '
  1605. OR user_id = ' . db_param() . ')
  1606. ORDER BY is_public DESC, name ASC';
  1607. $t_result = db_query( $t_query, array( true, $t_user_id ) );
  1608. }
  1609. $t_filters = array();
  1610. # first build the id=>name array
  1611. while( $t_row = db_fetch_array( $t_result ) ) {
  1612. $t_filters[$t_row['id']] = $t_row['name'];
  1613. }
  1614. filter_cache_rows( array_keys( $t_filters ) );
  1615. if( $p_return_names_only ) {
  1616. asort( $t_filters );
  1617. return $t_filters;
  1618. }
  1619. # build an extended array of name=>{filter data}
  1620. $t_filter_data = array();
  1621. foreach( $t_filters as $t_filter_id => $t_filter_name ) {
  1622. $t_row = array();
  1623. $t_filter_obj = filter_get( $t_filter_id );
  1624. if( !$t_filter_obj ) {
  1625. continue;
  1626. }
  1627. $t_row = filter_get_row( $t_filter_id );
  1628. $t_row['criteria'] = $t_filter_obj;
  1629. $t_row['url'] = filter_get_url( $t_filter_obj );
  1630. $t_filter_data[$t_filter_name] = $t_row;
  1631. }
  1632. return $t_filter_data;
  1633. }
  1634. /**
  1635. * Check that the given filter name does not exceed the maximum filter length
  1636. * @param string $p_name Filter name.
  1637. * @return boolean true when under max_length (64) and false when over
  1638. */
  1639. function filter_name_valid_length( $p_name ) {
  1640. if( mb_strlen( $p_name ) > 64 ) {
  1641. return false;
  1642. } else {
  1643. return true;
  1644. }
  1645. }
  1646. /**
  1647. * Create a filter for getting issues modified in the last N days.
  1648. * @param integer $p_days Number of days counting from today
  1649. * @param array $p_filter Add the filter conditions over this filter array. Return a new one if null
  1650. * @return array Filter array
  1651. */
  1652. function filter_create_recently_modified( $p_days, $p_filter = null ) {
  1653. if( null === $p_filter ) {
  1654. $p_filter = filter_get_default();
  1655. # This filter overrides default "hide status" property
  1656. $p_filter[FILTER_PROPERTY_HIDE_STATUS] = META_FILTER_NONE;
  1657. }
  1658. $c_days = (int)$p_days;
  1659. $p_filter[FILTER_PROPERTY_FILTER_BY_LAST_UPDATED_DATE] = true;
  1660. $t_date = new DateTime('today');
  1661. $p_filter[FILTER_PROPERTY_LAST_UPDATED_END_DAY] = $t_date->format( 'j' );
  1662. $p_filter[FILTER_PROPERTY_LAST_UPDATED_END_MONTH] = $t_date->format( 'n' );
  1663. $p_filter[FILTER_PROPERTY_LAST_UPDATED_END_YEAR] = $t_date->format( 'Y' );
  1664. $t_date->modify( '-' . $c_days . ' days' );
  1665. $p_filter[FILTER_PROPERTY_LAST_UPDATED_START_DAY] = $t_date->format( 'j' );
  1666. $p_filter[FILTER_PROPERTY_LAST_UPDATED_START_MONTH] = $t_date->format( 'n' );
  1667. $p_filter[FILTER_PROPERTY_LAST_UPDATED_START_YEAR] = $t_date->format( 'Y' );
  1668. return filter_ensure_valid_filter( $p_filter );
  1669. }
  1670. /**
  1671. * Create a filter for getting any issues without restrictions
  1672. * @return mixed A valid filter.
  1673. */
  1674. function filter_create_any() {
  1675. $t_filter = filter_get_default();
  1676. $t_filter[FILTER_PROPERTY_HIDE_STATUS] = META_FILTER_NONE;
  1677. return filter_ensure_valid_filter( $t_filter );
  1678. }
  1679. /**
  1680. * Create a filter for getting issues assigned to the specified project and user that
  1681. * are not yet resolved.
  1682. *
  1683. * @param integer $p_project_id The project id or ALL_PROJECTS.
  1684. * @param integer $p_user_id The user id or 0 to get unassigned issues.
  1685. * @return mixed valid filter.
  1686. */
  1687. function filter_create_assigned_to_unresolved( $p_project_id, $p_user_id ) {
  1688. $t_filter = filter_get_default();
  1689. if( $p_user_id == 0 ) {
  1690. $t_filter[FILTER_PROPERTY_HANDLER_ID] = array( '0' => META_FILTER_NONE );
  1691. } else {
  1692. $t_filter[FILTER_PROPERTY_HANDLER_ID] = array( '0' => $p_user_id );
  1693. }
  1694. $t_bug_resolved_status_threshold = config_get( 'bug_resolved_status_threshold', null, $p_user_id, $p_project_id );
  1695. $t_filter[FILTER_PROPERTY_HIDE_STATUS] = array( '0' => $t_bug_resolved_status_threshold );
  1696. if( $p_project_id != ALL_PROJECTS ) {
  1697. $t_filter[FILTER_PROPERTY_PROJECT_ID] = array( '0' => $p_project_id );
  1698. }
  1699. return filter_ensure_valid_filter( $t_filter );
  1700. }
  1701. /**
  1702. * Create a filter for getting issues reported by the specified project and user.
  1703. * @param integer $p_project_id The project id or ALL_PROJECTS.
  1704. * @param integer $p_user_id A valid user identifier.
  1705. * @return array a valid filter.
  1706. */
  1707. function filter_create_reported_by( $p_project_id, $p_user_id ) {
  1708. $t_filter = filter_get_default();
  1709. $t_filter[FILTER_PROPERTY_REPORTER_ID] = array( '0' => $p_user_id );
  1710. if( $p_project_id != ALL_PROJECTS ) {
  1711. $t_filter[FILTER_PROPERTY_PROJECT_ID] = array( '0' => $p_project_id );
  1712. }
  1713. return filter_ensure_valid_filter( $t_filter );
  1714. }
  1715. /**
  1716. * Create a filter for getting issues monitored by the specified project and user.
  1717. * @param integer $p_project_id The project id or ALL_PROJECTS.
  1718. * @param integer $p_user_id The user id.
  1719. * @return array a valid filter.
  1720. */
  1721. function filter_create_monitored_by( $p_project_id, $p_user_id ) {
  1722. $t_filter = filter_get_default();
  1723. if( $p_user_id == 0 ) {
  1724. $t_filter[FILTER_PROPERTY_MONITOR_USER_ID] = array( '0' => META_FILTER_NONE );
  1725. } else {
  1726. $t_filter[FILTER_PROPERTY_MONITOR_USER_ID] = array( '0' => $p_user_id );
  1727. }
  1728. if( $p_project_id != ALL_PROJECTS ) {
  1729. $t_filter[FILTER_PROPERTY_PROJECT_ID] = array( '0' => $p_project_id );
  1730. }
  1731. return filter_ensure_valid_filter( $t_filter );
  1732. }
  1733. /**
  1734. * Performs the reading of parameters from get/post.
  1735. * If a filter array is passed as parameter, the read parameters will be appended,
  1736. * or everride existing ones.
  1737. * If no filter array is used as parameter, a default one will be used.
  1738. * @param array $p_filter An existing filter array
  1739. * @return array The resulting filter array
  1740. */
  1741. function filter_gpc_get( array $p_filter = null ) {
  1742. # Get or copy the view_type first as it's needed to get proper defaults
  1743. $f_view_type = gpc_get_string( 'view_type', null );
  1744. if( null === $f_view_type && is_array( $p_filter ) && isset( $p_filter['_view_type'] ) ) {
  1745. $f_view_type = $p_filter['_view_type'];
  1746. }
  1747. if( null === $p_filter ) {
  1748. $t_filter = filter_get_default_array( $f_view_type );
  1749. } else {
  1750. $t_filter = filter_ensure_fields( $p_filter );
  1751. }
  1752. # these are all possibly multiple selections for advanced filtering
  1753. # If a single value is provided, it will be normalized to an array with 'filter_ensure_valid_filter()'
  1754. $f_show_category = gpc_get( FILTER_PROPERTY_CATEGORY_ID, $t_filter[FILTER_PROPERTY_CATEGORY_ID] );
  1755. $f_platform = gpc_get( FILTER_PROPERTY_PLATFORM, $t_filter[FILTER_PROPERTY_PLATFORM] );
  1756. $f_os = gpc_get( FILTER_PROPERTY_OS, $t_filter[FILTER_PROPERTY_OS] );
  1757. $f_os_build = gpc_get( FILTER_PROPERTY_OS_BUILD, $t_filter[FILTER_PROPERTY_OS_BUILD] );
  1758. $f_show_severity = gpc_get( FILTER_PROPERTY_SEVERITY, $t_filter[FILTER_PROPERTY_SEVERITY] );
  1759. $f_show_status = gpc_get( FILTER_PROPERTY_STATUS, $t_filter[FILTER_PROPERTY_STATUS] );
  1760. $f_hide_status = gpc_get( FILTER_PROPERTY_HIDE_STATUS, $t_filter[FILTER_PROPERTY_HIDE_STATUS] );
  1761. $f_reporter_id = gpc_get( FILTER_PROPERTY_REPORTER_ID, $t_filter[FILTER_PROPERTY_REPORTER_ID] );
  1762. $f_handler_id = gpc_get( FILTER_PROPERTY_HANDLER_ID, $t_filter[FILTER_PROPERTY_HANDLER_ID] );
  1763. $f_project_id = gpc_get( FILTER_PROPERTY_PROJECT_ID, $t_filter[FILTER_PROPERTY_PROJECT_ID] );
  1764. $f_show_resolution = gpc_get( FILTER_PROPERTY_RESOLUTION, $t_filter[FILTER_PROPERTY_RESOLUTION] );
  1765. $f_show_build = gpc_get( FILTER_PROPERTY_BUILD, $t_filter[FILTER_PROPERTY_BUILD] );
  1766. $f_show_version = gpc_get( FILTER_PROPERTY_VERSION, $t_filter[FILTER_PROPERTY_VERSION] );
  1767. $f_fixed_in_version = gpc_get( FILTER_PROPERTY_FIXED_IN_VERSION, $t_filter[FILTER_PROPERTY_FIXED_IN_VERSION] );
  1768. $f_target_version = gpc_get( FILTER_PROPERTY_TARGET_VERSION, $t_filter[FILTER_PROPERTY_TARGET_VERSION] );
  1769. $f_show_profile = gpc_get( FILTER_PROPERTY_PROFILE_ID, $t_filter[FILTER_PROPERTY_PROFILE_ID] );
  1770. $f_show_priority = gpc_get( FILTER_PROPERTY_PRIORITY, $t_filter[FILTER_PROPERTY_PRIORITY] );
  1771. $f_user_monitor = gpc_get( FILTER_PROPERTY_MONITOR_USER_ID, $t_filter[FILTER_PROPERTY_MONITOR_USER_ID] );
  1772. $f_note_user_id = gpc_get( FILTER_PROPERTY_NOTE_USER_ID, $t_filter[FILTER_PROPERTY_NOTE_USER_ID] );
  1773. $f_match_type = gpc_get_int( FILTER_PROPERTY_MATCH_TYPE, $t_filter[FILTER_PROPERTY_MATCH_TYPE] );
  1774. # these are only single values, even when doing advanced filtering
  1775. $f_per_page = gpc_get_int( FILTER_PROPERTY_ISSUES_PER_PAGE, $t_filter[FILTER_PROPERTY_ISSUES_PER_PAGE] );
  1776. $f_highlight_changed = gpc_get_int( FILTER_PROPERTY_HIGHLIGHT_CHANGED, $t_filter[FILTER_PROPERTY_HIGHLIGHT_CHANGED] );
  1777. $f_sticky_issues = gpc_get_bool( FILTER_PROPERTY_STICKY, $t_filter[FILTER_PROPERTY_STICKY] );
  1778. # This sort parameter is a set of comma separated values, and can be an array of parameters.
  1779. # sort="c1,c2" as used by permalinks
  1780. # sort[]="c1" sort[]="c2" as used by filter form
  1781. gpc_make_array( FILTER_PROPERTY_SORT_FIELD_NAME );
  1782. $f_sort_array = gpc_get_string_array( FILTER_PROPERTY_SORT_FIELD_NAME, array() );
  1783. # This sort parameter is an incremental column addition to current sort set.
  1784. # Only one column/dir, which is added to the front.
  1785. $f_sort_add = gpc_get_string( FILTER_PROPERTY_SORT_FIELD_NAME . '_add', null );
  1786. if( !empty( $f_sort_array ) ) {
  1787. gpc_make_array( FILTER_PROPERTY_SORT_DIRECTION );
  1788. $f_dir_array = gpc_get_string_array( FILTER_PROPERTY_SORT_DIRECTION, array() );
  1789. $t_new_sort_array = array();
  1790. $t_new_dir_array = array();
  1791. # evaluate each parameter, checks that "dir" may be omitted in order to avoid shifting subsequent parameters
  1792. $t_count = count( $f_sort_array );
  1793. for( $ix = 0; $ix < $t_count; $ix++ ) {
  1794. $t_param_columns = explode( ',', $f_sort_array[$ix] );
  1795. if( isset( $f_dir_array[$ix] ) ) {
  1796. $t_param_dirs = explode( ',', $f_dir_array[$ix] );
  1797. } else {
  1798. $t_param_dirs = array();
  1799. }
  1800. # fill the gaps with dummy string, they will be defaulted by ensure_valid_filter
  1801. if( count( $t_param_dirs ) < count( $t_param_columns ) ) {
  1802. $t_param_dirs = array_pad( $t_param_dirs, count( $t_param_columns ), '' );
  1803. }
  1804. $t_new_sort_array = array_merge( $t_new_sort_array, $t_param_columns );
  1805. $t_new_dir_array = array_merge( $t_new_dir_array, $t_param_dirs );
  1806. }
  1807. $f_sort = implode( ',', $t_new_sort_array );
  1808. $f_dir = implode( ',', $t_new_dir_array );
  1809. } elseif( null !== $f_sort_add ) {
  1810. # this parameter has to be pushed in front of current sort set
  1811. $f_dir_add = gpc_get_string( FILTER_PROPERTY_SORT_DIRECTION . '_add', '' );
  1812. # Plain concatenation. Empty fields, or extra commas will be cleaned by ensure_valid_filter
  1813. $f_sort = $f_sort_add . ',' . $t_filter[FILTER_PROPERTY_SORT_FIELD_NAME];
  1814. $f_dir = $f_dir_add . ',' . $t_filter[FILTER_PROPERTY_SORT_DIRECTION];
  1815. } else {
  1816. # use the defaults
  1817. $f_sort = $t_filter[FILTER_PROPERTY_SORT_FIELD_NAME];
  1818. $f_dir = $t_filter[FILTER_PROPERTY_SORT_DIRECTION];
  1819. }
  1820. # date values
  1821. # creation date
  1822. $f_do_filter_by_date = gpc_get_bool( FILTER_PROPERTY_FILTER_BY_DATE_SUBMITTED, $t_filter[FILTER_PROPERTY_FILTER_BY_DATE_SUBMITTED] );
  1823. $f_start_month = gpc_get_int( FILTER_PROPERTY_DATE_SUBMITTED_START_MONTH, $t_filter[FILTER_PROPERTY_DATE_SUBMITTED_START_MONTH] );
  1824. $f_end_month = gpc_get_int( FILTER_PROPERTY_DATE_SUBMITTED_END_MONTH, $t_filter[FILTER_PROPERTY_DATE_SUBMITTED_END_MONTH] );
  1825. $f_start_day = gpc_get_int( FILTER_PROPERTY_DATE_SUBMITTED_START_DAY, $t_filter[FILTER_PROPERTY_DATE_SUBMITTED_START_DAY] );
  1826. $f_end_day = gpc_get_int( FILTER_PROPERTY_DATE_SUBMITTED_END_DAY, $t_filter[FILTER_PROPERTY_DATE_SUBMITTED_END_DAY] );
  1827. $f_start_year = gpc_get_int( FILTER_PROPERTY_DATE_SUBMITTED_START_YEAR, $t_filter[FILTER_PROPERTY_DATE_SUBMITTED_START_YEAR] );
  1828. $f_end_year = gpc_get_int( FILTER_PROPERTY_DATE_SUBMITTED_END_YEAR, $t_filter[FILTER_PROPERTY_DATE_SUBMITTED_END_YEAR] );
  1829. # last_updated date values
  1830. $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] );
  1831. $f_last_updated_start_month = gpc_get_int( FILTER_PROPERTY_LAST_UPDATED_START_MONTH, $t_filter[FILTER_PROPERTY_LAST_UPDATED_START_MONTH] );
  1832. $f_last_updated_end_month = gpc_get_int( FILTER_PROPERTY_LAST_UPDATED_END_MONTH, $t_filter[FILTER_PROPERTY_LAST_UPDATED_END_MONTH] );
  1833. $f_last_updated_start_day = gpc_get_int( FILTER_PROPERTY_LAST_UPDATED_START_DAY, $t_filter[FILTER_PROPERTY_LAST_UPDATED_START_DAY] );
  1834. $f_last_updated_end_day = gpc_get_int( FILTER_PROPERTY_LAST_UPDATED_END_DAY, $t_filter[FILTER_PROPERTY_LAST_UPDATED_END_DAY] );
  1835. $f_last_updated_start_year = gpc_get_int( FILTER_PROPERTY_LAST_UPDATED_START_YEAR, $t_filter[FILTER_PROPERTY_LAST_UPDATED_START_YEAR] );
  1836. $f_last_updated_end_year = gpc_get_int( FILTER_PROPERTY_LAST_UPDATED_END_YEAR, $t_filter[FILTER_PROPERTY_LAST_UPDATED_END_YEAR] );
  1837. $f_search = gpc_get_string( FILTER_PROPERTY_SEARCH, $t_filter[FILTER_PROPERTY_SEARCH] );
  1838. $f_view_state = gpc_get_int( FILTER_PROPERTY_VIEW_STATE, $t_filter[FILTER_PROPERTY_VIEW_STATE] );
  1839. $f_tag_string = gpc_get_string( FILTER_PROPERTY_TAG_STRING, $t_filter[FILTER_PROPERTY_TAG_STRING] );
  1840. $f_tag_select = gpc_get_int( FILTER_PROPERTY_TAG_SELECT, $t_filter[FILTER_PROPERTY_TAG_SELECT] );
  1841. # plugin filter updates
  1842. $t_plugin_filters = filter_get_plugin_filters();
  1843. $t_filter_input = array();
  1844. foreach( $t_plugin_filters as $t_field_name => $t_filter_object ) {
  1845. switch( $t_filter_object->type ) {
  1846. case FILTER_TYPE_STRING:
  1847. $t_filter_input[$t_field_name] = gpc_get_string( $t_field_name, $t_filter[$t_field_name] );
  1848. break;
  1849. case FILTER_TYPE_INT:
  1850. $t_filter_input[$t_field_name] = gpc_get_int( $t_field_name, $t_filter[$t_field_name] );
  1851. break;
  1852. case FILTER_TYPE_BOOLEAN:
  1853. $t_filter_input[$t_field_name] = gpc_get_bool( $t_field_name, $t_filter[$t_field_name]);
  1854. break;
  1855. case FILTER_TYPE_MULTI_STRING:
  1856. $t_filter_input[$t_field_name] = gpc_get_string_array( $t_field_name, $t_filter[$t_field_name] );
  1857. break;
  1858. case FILTER_TYPE_MULTI_INT:
  1859. $t_filter_input[$t_field_name] = gpc_get_int_array( $t_field_name, $t_filter[$t_field_name] );
  1860. break;
  1861. }
  1862. }
  1863. # custom field updates
  1864. $t_custom_fields = custom_field_get_ids(); # @todo (thraxisp) This should really be the linked ids, but we don't know the project
  1865. $f_custom_fields_data = $t_filter['custom_fields'];
  1866. if( is_array( $t_custom_fields ) && ( count( $t_custom_fields ) > 0 ) ) {
  1867. foreach( $t_custom_fields as $t_cfid ) {
  1868. if( custom_field_type( $t_cfid ) == CUSTOM_FIELD_TYPE_DATE ) {
  1869. # check if gpc parameters are present, otherwise skip parsing.
  1870. if( !gpc_isset( 'custom_field_' . $t_cfid . '_control' ) ) {
  1871. continue;
  1872. }
  1873. $f_custom_fields_data[$t_cfid] = array();
  1874. # Get date control property
  1875. $t_control = gpc_get_string( 'custom_field_' . $t_cfid . '_control', null );
  1876. $f_custom_fields_data[$t_cfid][0] = $t_control;
  1877. $t_one_day = 86399;
  1878. # Get start date. If there is a timestamp input provided, use it,
  1879. # otherwise, look for individual date parts
  1880. $f_start_date = gpc_get( 'custom_field_' . $t_cfid . '_start_timestamp', null );
  1881. if( null !== $f_start_date ) {
  1882. $t_start_date = (int)$f_start_date;
  1883. $t_start = $t_start_date;
  1884. } else {
  1885. $t_year = gpc_get_int( 'custom_field_' . $t_cfid . '_start_year', null );
  1886. $t_month = gpc_get_int( 'custom_field_' . $t_cfid . '_start_month', null );
  1887. $t_day = gpc_get_int( 'custom_field_' . $t_cfid . '_start_day', null );
  1888. $t_start_date = mktime( 0, 0, 0, $t_month, $t_day, $t_year );
  1889. # calculate correct timestamps
  1890. $t_start = 1;
  1891. switch( $t_control ) {
  1892. case CUSTOM_FIELD_DATE_ANY:
  1893. case CUSTOM_FIELD_DATE_NONE:
  1894. case CUSTOM_FIELD_DATE_ONORBEFORE:
  1895. case CUSTOM_FIELD_DATE_BEFORE:
  1896. break ;
  1897. case CUSTOM_FIELD_DATE_BETWEEN:
  1898. $t_start = $t_start_date;
  1899. break ;
  1900. case CUSTOM_FIELD_DATE_ON:
  1901. $t_start = $t_start_date;
  1902. break;
  1903. case CUSTOM_FIELD_DATE_AFTER:
  1904. $t_start = $t_start_date + $t_one_day - 1;
  1905. break;
  1906. case CUSTOM_FIELD_DATE_ONORAFTER:
  1907. $t_start = $t_start_date;
  1908. break;
  1909. }
  1910. }
  1911. $f_custom_fields_data[$t_cfid][1] = $t_start;
  1912. # Get end date. If there is a timestamp input provided, use it,
  1913. # otherwise, look for individual date parts
  1914. $f_end_date = gpc_get( 'custom_field_' . $t_cfid . '_end_timestamp', null );
  1915. if( null !== $f_end_date ) {
  1916. $t_end_date = (int)$f_end_date;
  1917. $t_end = $t_end_date;
  1918. } else {
  1919. $t_year = gpc_get_int( 'custom_field_' . $t_cfid . '_end_year', null );
  1920. $t_month = gpc_get_int( 'custom_field_' . $t_cfid . '_end_month', null );
  1921. $t_day = gpc_get_int( 'custom_field_' . $t_cfid . '_end_day', null );
  1922. $t_end_date = mktime( 0, 0, 0, $t_month, $t_day, $t_year );
  1923. # calculate correct timestamps
  1924. $t_end = 1;
  1925. switch( $t_control ) {
  1926. case CUSTOM_FIELD_DATE_ANY:
  1927. case CUSTOM_FIELD_DATE_NONE:
  1928. break ;
  1929. case CUSTOM_FIELD_DATE_BETWEEN:
  1930. $t_end = $t_end_date + $t_one_day - 1;
  1931. break ;
  1932. case CUSTOM_FIELD_DATE_ONORBEFORE:
  1933. $t_end = $t_start_date + $t_one_day - 1;
  1934. break;
  1935. case CUSTOM_FIELD_DATE_BEFORE:
  1936. $t_end = $t_start_date;
  1937. break ;
  1938. case CUSTOM_FIELD_DATE_ON:
  1939. $t_end = $t_start_date + $t_one_day - 1;
  1940. break;
  1941. case CUSTOM_FIELD_DATE_AFTER:
  1942. $t_end = 2147483647; # Some time in 2038, max value of a signed int.
  1943. break;
  1944. case CUSTOM_FIELD_DATE_ONORAFTER:
  1945. $t_end = 2147483647; # Some time in 2038, max value of a signed int.
  1946. break;
  1947. }
  1948. }
  1949. $f_custom_fields_data[$t_cfid][2] = $t_end;
  1950. } else {
  1951. # check if gpc parameters are present, otherwise skip parsing.
  1952. if( !gpc_isset( 'custom_field_' . $t_cfid ) ) {
  1953. continue;
  1954. }
  1955. if( is_array( gpc_get( 'custom_field_' . $t_cfid, null ) ) ) {
  1956. $f_custom_fields_data[$t_cfid] = gpc_get_string_array( 'custom_field_' . $t_cfid, array( META_FILTER_ANY ) );
  1957. } else {
  1958. $f_custom_fields_data[$t_cfid] = gpc_get_string( 'custom_field_' . $t_cfid, META_FILTER_ANY );
  1959. $f_custom_fields_data[$t_cfid] = array( $f_custom_fields_data[$t_cfid] );
  1960. }
  1961. }
  1962. }
  1963. }
  1964. $f_relationship_type = gpc_get_int( FILTER_PROPERTY_RELATIONSHIP_TYPE, $t_filter[FILTER_PROPERTY_RELATIONSHIP_TYPE] );
  1965. $f_relationship_bug = gpc_get_int( FILTER_PROPERTY_RELATIONSHIP_BUG, $t_filter[FILTER_PROPERTY_RELATIONSHIP_BUG] );
  1966. log_event( LOG_FILTERING, 'filter_gpc_get: Update filters' );
  1967. $t_filter_input['_version'] = FILTER_VERSION;
  1968. $t_filter_input['_view_type'] = $f_view_type;
  1969. $t_filter_input[FILTER_PROPERTY_CATEGORY_ID] = $f_show_category;
  1970. $t_filter_input[FILTER_PROPERTY_SEVERITY] = $f_show_severity;
  1971. $t_filter_input[FILTER_PROPERTY_STATUS] = $f_show_status;
  1972. $t_filter_input[FILTER_PROPERTY_ISSUES_PER_PAGE] = $f_per_page;
  1973. $t_filter_input[FILTER_PROPERTY_HIGHLIGHT_CHANGED] = $f_highlight_changed;
  1974. $t_filter_input[FILTER_PROPERTY_REPORTER_ID] = $f_reporter_id;
  1975. $t_filter_input[FILTER_PROPERTY_HANDLER_ID] = $f_handler_id;
  1976. $t_filter_input[FILTER_PROPERTY_PROJECT_ID] = $f_project_id;
  1977. $t_filter_input[FILTER_PROPERTY_SORT_FIELD_NAME] = $f_sort;
  1978. $t_filter_input[FILTER_PROPERTY_SORT_DIRECTION] = $f_dir;
  1979. $t_filter_input[FILTER_PROPERTY_FILTER_BY_DATE_SUBMITTED] = $f_do_filter_by_date;
  1980. $t_filter_input[FILTER_PROPERTY_DATE_SUBMITTED_START_MONTH] = $f_start_month;
  1981. $t_filter_input[FILTER_PROPERTY_DATE_SUBMITTED_START_DAY] = $f_start_day;
  1982. $t_filter_input[FILTER_PROPERTY_DATE_SUBMITTED_START_YEAR] = $f_start_year;
  1983. $t_filter_input[FILTER_PROPERTY_DATE_SUBMITTED_END_MONTH] = $f_end_month;
  1984. $t_filter_input[FILTER_PROPERTY_DATE_SUBMITTED_END_DAY] = $f_end_day;
  1985. $t_filter_input[FILTER_PROPERTY_DATE_SUBMITTED_END_YEAR] = $f_end_year;
  1986. $t_filter_input[FILTER_PROPERTY_FILTER_BY_LAST_UPDATED_DATE] = $f_do_filter_by_last_updated_date;
  1987. $t_filter_input[FILTER_PROPERTY_LAST_UPDATED_START_MONTH] = $f_last_updated_start_month;
  1988. $t_filter_input[FILTER_PROPERTY_LAST_UPDATED_START_DAY] = $f_last_updated_start_day;
  1989. $t_filter_input[FILTER_PROPERTY_LAST_UPDATED_START_YEAR] = $f_last_updated_start_year;
  1990. $t_filter_input[FILTER_PROPERTY_LAST_UPDATED_END_MONTH] = $f_last_updated_end_month;
  1991. $t_filter_input[FILTER_PROPERTY_LAST_UPDATED_END_DAY] = $f_last_updated_end_day;
  1992. $t_filter_input[FILTER_PROPERTY_LAST_UPDATED_END_YEAR] = $f_last_updated_end_year;
  1993. $t_filter_input[FILTER_PROPERTY_SEARCH] = $f_search;
  1994. $t_filter_input[FILTER_PROPERTY_HIDE_STATUS] = $f_hide_status;
  1995. $t_filter_input[FILTER_PROPERTY_RESOLUTION] = $f_show_resolution;
  1996. $t_filter_input[FILTER_PROPERTY_BUILD] = $f_show_build;
  1997. $t_filter_input[FILTER_PROPERTY_VERSION] = $f_show_version;
  1998. $t_filter_input[FILTER_PROPERTY_FIXED_IN_VERSION] = $f_fixed_in_version;
  1999. $t_filter_input[FILTER_PROPERTY_TARGET_VERSION] = $f_target_version;
  2000. $t_filter_input[FILTER_PROPERTY_PRIORITY] = $f_show_priority;
  2001. $t_filter_input[FILTER_PROPERTY_MONITOR_USER_ID] = $f_user_monitor;
  2002. $t_filter_input[FILTER_PROPERTY_VIEW_STATE] = $f_view_state;
  2003. $t_filter_input['custom_fields'] = $f_custom_fields_data;
  2004. $t_filter_input[FILTER_PROPERTY_STICKY] = $f_sticky_issues;
  2005. $t_filter_input[FILTER_PROPERTY_RELATIONSHIP_TYPE] = $f_relationship_type;
  2006. $t_filter_input[FILTER_PROPERTY_RELATIONSHIP_BUG] = $f_relationship_bug;
  2007. $t_filter_input[FILTER_PROPERTY_PROFILE_ID] = $f_show_profile;
  2008. $t_filter_input[FILTER_PROPERTY_PLATFORM] = $f_platform;
  2009. $t_filter_input[FILTER_PROPERTY_OS] = $f_os;
  2010. $t_filter_input[FILTER_PROPERTY_OS_BUILD] = $f_os_build;
  2011. $t_filter_input[FILTER_PROPERTY_TAG_STRING] = $f_tag_string;
  2012. $t_filter_input[FILTER_PROPERTY_TAG_SELECT] = $f_tag_select;
  2013. $t_filter_input[FILTER_PROPERTY_NOTE_USER_ID] = $f_note_user_id;
  2014. $t_filter_input[FILTER_PROPERTY_MATCH_TYPE] = $f_match_type;
  2015. # copy runtime properties, if present
  2016. if( isset( $t_filter['_temporary_key'] ) ) {
  2017. $t_filter_input['_temporary_key'] = $t_filter['_temporary_key'];
  2018. }
  2019. if( isset( $t_filter['_filter_id'] ) ) {
  2020. $t_filter_input['_filter_id'] = $t_filter['_filter_id'];
  2021. }
  2022. # Don't copy cached subquery '_subquery' property
  2023. return filter_ensure_valid_filter( $t_filter_input );
  2024. }
  2025. /**
  2026. * Returns the sort columns from a filter, with only those columns that are visible
  2027. * according to $p_columns_target user's configuration, and valid for sorting.
  2028. * Returns an array consisting of two respective properties of column names, and
  2029. * sort direction, each one already exploded into an array.
  2030. * Note: Filter array must be a valid filter
  2031. * @param array $p_filter Original filter array.
  2032. * @param integer $p_columns_target Target view for the columns.
  2033. * @return array Array of filtered columns and order
  2034. */
  2035. function filter_get_visible_sort_properties_array( array $p_filter, $p_columns_target = COLUMNS_TARGET_VIEW_PAGE ) {
  2036. # get visible columns
  2037. $t_visible_columns = helper_get_columns_to_view( $p_columns_target );
  2038. # filter out those that ar not sortable
  2039. $t_visible_columns = array_filter( $t_visible_columns, 'column_is_sortable' );
  2040. $t_sort_fields = explode( ',', $p_filter[FILTER_PROPERTY_SORT_FIELD_NAME] );
  2041. $t_dir_fields = explode( ',', $p_filter[FILTER_PROPERTY_SORT_DIRECTION] );
  2042. $t_sort_array = array();
  2043. $t_dir_array = array();
  2044. $t_count = count( $t_sort_fields );
  2045. for( $i = 0; $i < $t_count; $i++ ) {
  2046. $c_sort = $t_sort_fields[$i];
  2047. if( in_array( $c_sort, $t_visible_columns ) ) {
  2048. $t_sort_array[] = $t_sort_fields[$i];
  2049. $t_dir_array[] = $t_dir_fields[$i];
  2050. }
  2051. }
  2052. return array(
  2053. FILTER_PROPERTY_SORT_FIELD_NAME => $t_sort_array,
  2054. FILTER_PROPERTY_SORT_DIRECTION => $t_dir_array
  2055. );
  2056. }
  2057. /**
  2058. * Returns true if the filter id is a named stored filter, which can be managed and edited.
  2059. * Returns false if it's a temporary filter, or if the filter id does not exists
  2060. * @param integer $p_filter_id
  2061. * @return boolean
  2062. */
  2063. function filter_is_named_filter( $p_filter_id ) {
  2064. $t_filter_row = filter_get_row( $p_filter_id );
  2065. if( $t_filter_row ) {
  2066. return !empty( $t_filter_row['name'] ) && $t_filter_row['project_id'] >= 0;
  2067. }
  2068. return false;
  2069. }
  2070. /**
  2071. * Returns true if the filter is accessible by the user, which happens when the user
  2072. * is the owner of the filter, or the filter is public.
  2073. * @param integer $p_filter_id Filter id
  2074. * @param integer $p_user_id User id
  2075. * @return boolean true if the filter is accessible by the user
  2076. */
  2077. function filter_is_accessible( $p_filter_id, $p_user_id = null ) {
  2078. if( null === $p_user_id ) {
  2079. $t_user_id = auth_get_current_user_id();
  2080. } else {
  2081. $t_user_id = $p_user_id;
  2082. }
  2083. $t_filter_row = filter_get_row( $p_filter_id );
  2084. if( $t_filter_row ) {
  2085. if( $t_filter_row['user_id'] == $t_user_id || $t_filter_row['is_public'] ) {
  2086. # If the filter is a named filter, check the config options
  2087. if( $t_filter_row['project_id'] >= 0
  2088. && !is_blank( $t_filter_row['name'] ) ) {
  2089. return access_has_project_level( config_get( 'stored_query_use_threshold', null, $t_user_id, $t_filter_row['project_id'] ) );
  2090. }
  2091. # it it's a "current" filter, access is ok
  2092. return true;
  2093. }
  2094. }
  2095. return false;
  2096. }
  2097. /**
  2098. * Prints the simple/advanced menu item toggle if needed
  2099. * @param string $p_url Target URL, must end with 'view_type='
  2100. * @param string $p_view_type Filter view type (FILTER_VIEW_TYPE_SIMPLE or
  2101. * FILTER_VIEW_TYPE_ADVANCED)
  2102. */
  2103. function filter_print_view_type_toggle( $p_url, $p_view_type ) {
  2104. $t_view_filters = config_get( 'view_filters' );
  2105. if( $t_view_filters == SIMPLE_ONLY || $t_view_filters == ADVANCED_ONLY ) {
  2106. return;
  2107. }
  2108. if( $p_view_type == FILTER_VIEW_TYPE_ADVANCED ) {
  2109. $t_url = $p_url . FILTER_VIEW_TYPE_SIMPLE;
  2110. $t_icon = 'fa-toggle-off';
  2111. $t_lang_string = 'simple_filters';
  2112. } else {
  2113. $t_url = $p_url . FILTER_VIEW_TYPE_ADVANCED;
  2114. $t_icon = 'fa-toggle-on';
  2115. $t_lang_string = 'advanced_filters';
  2116. }
  2117. echo '<li>';
  2118. printf( '<a href="%s"><i class="ace-icon fa %s"></i>&#160;&#160;%s</a>',
  2119. $t_url,
  2120. $t_icon,
  2121. lang_get( $t_lang_string )
  2122. );
  2123. echo '</li>';
  2124. }
  2125. /**
  2126. * Returns an array of project ids which are included in the filter.
  2127. * This array includes all individual projects/subprojects that are in the search scope.
  2128. * If ALL_PROJECTS were included directly, or indirectly, and the parameter $p_return_all_projects
  2129. * is set to true, the value ALL_PROJECTS will be returned. Otherwise the array will be expanded
  2130. * to all actual accessible projects
  2131. * @param array $p_filter Filter array
  2132. * @param integer $p_project_id Project id to use in filtering, if applicable by filter type
  2133. * @param integer $p_user_id User id to use as current user when filtering
  2134. * @param boolean $p_return_all_projects If true, return ALL_PROJECTS directly if found, instead of
  2135. * expanding to individual project ids
  2136. * @return array|integer Array of project ids, or ALL_PROJECTS if applicable.
  2137. */
  2138. function filter_get_included_projects( array $p_filter, $p_project_id = null, $p_user_id = null, $p_return_all_projects = false ) {
  2139. if( null === $p_project_id ) {
  2140. $t_project_id = helper_get_current_project();
  2141. } else {
  2142. $t_project_id = $p_project_id;
  2143. }
  2144. if( !$p_user_id ) {
  2145. $t_user_id = auth_get_current_user_id();
  2146. } else {
  2147. $t_user_id = $p_user_id;
  2148. }
  2149. $t_view_type = $p_filter['_view_type'];
  2150. # normalize the project filtering into an array $t_project_ids
  2151. if( FILTER_VIEW_TYPE_SIMPLE == $t_view_type ) {
  2152. log_event( LOG_FILTERING, 'Simple Filter' );
  2153. $t_project_ids = array( $t_project_id );
  2154. $t_include_sub_projects = true;
  2155. } else {
  2156. log_event( LOG_FILTERING, 'Advanced Filter' );
  2157. $t_project_ids = $p_filter[FILTER_PROPERTY_PROJECT_ID];
  2158. $t_include_sub_projects = (( count( $t_project_ids ) == 1 ) && ( ( $t_project_ids[0] == META_FILTER_CURRENT ) || ( $t_project_ids[0] == ALL_PROJECTS ) ) );
  2159. }
  2160. log_event( LOG_FILTERING, 'project_ids = @P' . implode( ', @P', $t_project_ids ) );
  2161. log_event( LOG_FILTERING, 'include sub-projects = ' . ( $t_include_sub_projects ? '1' : '0' ) );
  2162. # if the array has ALL_PROJECTS, then reset the array to only contain ALL_PROJECTS.
  2163. # replace META_FILTER_CURRENT with the actual current project id.
  2164. $t_all_projects_found = false;
  2165. $t_new_project_ids = array();
  2166. foreach( $t_project_ids as $t_pid ) {
  2167. if( $t_pid == META_FILTER_CURRENT ) {
  2168. $t_pid = $t_project_id;
  2169. }
  2170. if( $t_pid == ALL_PROJECTS ) {
  2171. $t_all_projects_found = true;
  2172. log_event( LOG_FILTERING, 'all projects selected' );
  2173. break;
  2174. }
  2175. # filter out inaccessible projects.
  2176. 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 ) ) {
  2177. log_event( LOG_FILTERING, 'Invalid or inaccessible project: ' . $t_pid );
  2178. continue;
  2179. }
  2180. $t_new_project_ids[] = $t_pid;
  2181. }
  2182. # if not expanding ALL_PROJECTS, shortcut return directly
  2183. if( $t_all_projects_found && $p_return_all_projects ) {
  2184. return ALL_PROJECTS;
  2185. }
  2186. if( $t_all_projects_found ) {
  2187. $t_project_ids = user_get_accessible_projects( $t_user_id );
  2188. } else {
  2189. $t_project_ids = $t_new_project_ids;
  2190. }
  2191. # expand project ids to include sub-projects
  2192. if( $t_include_sub_projects ) {
  2193. $t_top_project_ids = $t_project_ids;
  2194. foreach( $t_top_project_ids as $t_pid ) {
  2195. log_event( LOG_FILTERING, 'Getting sub-projects for project id @P' . $t_pid );
  2196. $t_subproject_ids = user_get_all_accessible_subprojects( $t_user_id, $t_pid );
  2197. if( !$t_subproject_ids ) {
  2198. continue;
  2199. }
  2200. $t_project_ids = array_merge( $t_project_ids, $t_subproject_ids );
  2201. }
  2202. $t_project_ids = array_unique( $t_project_ids );
  2203. }
  2204. if( count( $t_project_ids ) ) {
  2205. log_event( LOG_FILTERING, 'project_ids after including sub-projects = @P' . implode( ', @P', $t_project_ids ) );
  2206. } else {
  2207. log_event( LOG_FILTERING, 'no accessible projects' );
  2208. }
  2209. return $t_project_ids;
  2210. }
  2211. /**
  2212. * Returns a filter array structure for the given filter_id
  2213. * A default value can be provided to be used when the filter_id doesn't exists
  2214. * or is not accessible
  2215. *
  2216. * You may pass in any array as a default (including null) but if
  2217. * you pass in *no* default then an error will be triggered if the filter
  2218. * cannot be found
  2219. *
  2220. * @param integer $p_filter_id Filter id
  2221. * @param array $p_default A filter array to return when id is not found
  2222. * @return array A filter array
  2223. */
  2224. function filter_get( $p_filter_id, array $p_default = null ) {
  2225. # if no default was provided, we will trigger an error if not found
  2226. $t_trigger_error = func_num_args() == 1;
  2227. # This function checks for user access
  2228. $t_filter_string = filter_db_get_filter_string( $p_filter_id );
  2229. # If value is false, it either doesn't exists or is not accessible
  2230. if( !$t_filter_string ) {
  2231. if( $t_trigger_error ) {
  2232. error_parameters( $p_filter_id );
  2233. trigger_error( ERROR_FILTER_NOT_FOUND, ERROR );
  2234. } else {
  2235. return $p_default;
  2236. }
  2237. }
  2238. $t_filter = filter_deserialize( $t_filter_string );
  2239. # If the unserialez data is not an array, the some error happened, eg, invalid format
  2240. if( !is_array( $t_filter ) ) {
  2241. # Don't throw error, otherwise the user could not recover navigation easily
  2242. return filter_get_default();
  2243. }
  2244. $t_filter = filter_clean_runtime_properties( $t_filter );
  2245. $t_filter['_filter_id'] = $p_filter_id;
  2246. $t_filter = filter_update_source_properties( $t_filter );
  2247. return $t_filter;
  2248. }
  2249. /**
  2250. * Return a standard filter
  2251. * @param string $p_filter_name The name of the filter
  2252. * @param integer|null $p_user_id A user id to build this filter. Null for current user
  2253. * @param integer|null $p_project_id A project id to build this filter. Null for current project
  2254. * @return null|boolean|array null filter not found, false invalid filter, otherwise the filter.
  2255. */
  2256. function filter_standard_get( $p_filter_name, $p_user_id = null, $p_project_id = null ) {
  2257. $p_filter_name = strtolower( $p_filter_name );
  2258. if( null === $p_project_id ) {
  2259. $t_project_id = helper_get_current_project();
  2260. } else {
  2261. $t_project_id = $p_project_id;
  2262. }
  2263. if( null === $p_user_id ) {
  2264. $t_user_id = auth_get_current_user_id();
  2265. } else {
  2266. $t_user_id = $p_user_id;
  2267. }
  2268. switch( $p_filter_name ) {
  2269. case FILTER_STANDARD_ANY:
  2270. $t_filter = filter_create_any();
  2271. break;
  2272. case FILTER_STANDARD_ASSIGNED:
  2273. $t_filter = filter_create_assigned_to_unresolved( $t_project_id, $t_user_id );
  2274. break;
  2275. case FILTER_STANDARD_UNASSIGNED:
  2276. $t_filter = filter_create_assigned_to_unresolved( $t_project_id, NO_USER );
  2277. break;
  2278. case FILTER_STANDARD_REPORTED:
  2279. $t_filter = filter_create_reported_by( $t_project_id, $t_user_id );
  2280. break;
  2281. case FILTER_STANDARD_MONITORED:
  2282. $t_filter = filter_create_monitored_by( $t_project_id, $t_user_id );
  2283. break;
  2284. default:
  2285. return null;
  2286. }
  2287. return $t_filter;
  2288. }
  2289. /**
  2290. * Updates a filter's properties with those from another filter that is referenced
  2291. * by it's source-id property.
  2292. * This is used when an anonymous filter was created from a named filter. As long
  2293. * as this anonymous filter is not modified, it must be keep in sync with the
  2294. * referenced filter (source_id), because the source filter may have been modified
  2295. * at a later time.
  2296. * This is a side effect of always using anonymous filters even when selecting a
  2297. * named filter to be applied as current.
  2298. *
  2299. * @param array $p_filter Original filter array
  2300. * @return array Updated filter array
  2301. */
  2302. function filter_update_source_properties( array $p_filter ) {
  2303. # Check if the filter references a named filter
  2304. # This property only makes sense, and should be available on unnamed filters
  2305. if( isset( $p_filter['_filter_id'] ) ) {
  2306. $t_filter_id = $p_filter['_filter_id'];
  2307. } else {
  2308. $t_filter_id = null;
  2309. }
  2310. if( isset( $p_filter['_source_query_id'] ) && $t_filter_id != $p_filter['_source_query_id'] ) {
  2311. $t_source_query_id = $p_filter['_source_query_id'];
  2312. # check if filter id is a proper named filter, and is accessible
  2313. if( filter_is_named_filter( $t_source_query_id ) && filter_is_accessible( $t_source_query_id ) ){
  2314. # replace filter with the referenced one
  2315. $t_new_filter = filter_deserialize( filter_db_get_filter_string( $t_source_query_id ) );
  2316. if( is_array( $t_new_filter ) ) {
  2317. # update the referenced stored filter id for the new loaded filter
  2318. $t_new_filter['_source_query_id'] = $t_source_query_id;
  2319. $p_filter = filter_copy_runtime_properties( $t_new_filter, $p_filter );
  2320. } else {
  2321. # If the unserialez data is not an array, the some error happened, eg, invalid format
  2322. unset( $p_filter['_source_query_id'] );
  2323. }
  2324. } else {
  2325. # If the filter id is not valid, clean the referenced filter id
  2326. unset( $p_filter['_source_query_id'] );
  2327. }
  2328. }
  2329. return $p_filter;
  2330. }
  2331. /**
  2332. * Returns a filter which is stored in session data, indexed by the provided key.
  2333. * A default value can be provided to be used when the key doesn't exists
  2334. *
  2335. * You may pass in any array as a default (including null) but if
  2336. * you pass in *no* default then an error will be triggered if the key
  2337. * cannot be found
  2338. *
  2339. * @param string $p_filter_key Key to look up for in session data
  2340. * @param mixed $p_default A default value to return if key not found
  2341. * @return array A filter array.
  2342. */
  2343. function filter_temporary_get( $p_filter_key, $p_default = null ) {
  2344. # if no default was provided, we will trigger an error if not found
  2345. $t_trigger_error = func_num_args() == 1;
  2346. $t_session_filters = session_get( 'temporary_filters', array() );
  2347. if( isset( $t_session_filters[$p_filter_key] ) ) {
  2348. # setting here the key in the filter array only if the key exists
  2349. # this validates against receiving garbage input as XSS attacks
  2350. $t_filter = $t_session_filters[$p_filter_key];
  2351. $t_filter['_temporary_key'] = $p_filter_key;
  2352. return filter_ensure_valid_filter( $t_filter );
  2353. } else {
  2354. if( $t_trigger_error ) {
  2355. error_parameters( $p_filter_key );
  2356. trigger_error( ERROR_FILTER_NOT_FOUND, ERROR );
  2357. } else {
  2358. return $p_default;
  2359. }
  2360. }
  2361. }
  2362. /**
  2363. * Saves a filter as a temporary filter in session data.
  2364. * The filter will be updated or created, indexed by provided $p_filter_key,
  2365. * If no key is provided, it will search in the filter property that holds
  2366. * its key if it was loaded as a temporary filter.
  2367. * If neither key is found, a new one will be created
  2368. * @param array $p_filter Filter array
  2369. * @param string $p_filter_key Key to update, or null
  2370. * @return string The key used for storing the filter.
  2371. */
  2372. function filter_temporary_set( array $p_filter, $p_filter_key = null ) {
  2373. if( null === $p_filter_key ) {
  2374. $t_filter_key = filter_get_temporary_key( $p_filter );
  2375. if( !$t_filter_key ) {
  2376. $t_filter_key = uniqid();
  2377. }
  2378. } else {
  2379. $t_filter_key = $p_filter_key;
  2380. }
  2381. $p_filter = filter_clean_runtime_properties( $p_filter );
  2382. $t_session_filters = session_get( 'temporary_filters', array() );
  2383. $t_session_filters[$t_filter_key] = $p_filter;
  2384. session_set( 'temporary_filters', $t_session_filters );
  2385. return $t_filter_key;
  2386. }
  2387. /**
  2388. * Get the temporary key of the filter, if was loaded from temporary session store
  2389. * Return null otherwise
  2390. * @param array $p_filter Filter array
  2391. * @return string|null Key associated with this filter, null if none
  2392. */
  2393. function filter_get_temporary_key( array $p_filter ) {
  2394. if( isset( $p_filter['_temporary_key'] ) ) {
  2395. return $p_filter['_temporary_key'];
  2396. } else {
  2397. return null;
  2398. }
  2399. }
  2400. /**
  2401. * Returns true if the filter was loaded as temporary filter
  2402. * @param array $p_filter Filter array
  2403. * @return boolean Whether this filter is temporary
  2404. */
  2405. function filter_is_temporary( array $p_filter ) {
  2406. return isset( $p_filter['_temporary_key'] );
  2407. }
  2408. /**
  2409. * Returns a string formatted as GET parameter, suitable for tracking a
  2410. * temporary filter by its session key.
  2411. * The parameter can be either:
  2412. * - an existing key to be used directly, or
  2413. * - a filter array, which can contain a property with the key.
  2414. * If the provided filter does not contain the key property, the function
  2415. * returns null.
  2416. *
  2417. * @param array|string $p_key_or_filter Either a string key, or a filter array
  2418. *
  2419. * @return string|null Formatted parameter string, or null
  2420. */
  2421. function filter_get_temporary_key_param( $p_key_or_filter ) {
  2422. if( is_array( $p_key_or_filter ) ) {
  2423. $t_key = filter_get_temporary_key( $p_key_or_filter );
  2424. } else {
  2425. $t_key = $p_key_or_filter;
  2426. }
  2427. if( $t_key ) {
  2428. return 'filter=' . $t_key;
  2429. } else {
  2430. return null;
  2431. }
  2432. }
  2433. /**
  2434. * Removes runtime properties that are should not be saved as part of the filter
  2435. * Use this function before saving the filter.
  2436. * @param array $p_filter Filter array (passed as reference, it gets modified)
  2437. * @return array Modified filter array
  2438. */
  2439. function filter_clean_runtime_properties( array $p_filter ) {
  2440. if( isset( $p_filter['_temporary_key'] ) ) {
  2441. unset( $p_filter['_temporary_key'] );
  2442. }
  2443. if( isset( $p_filter['_filter_id'] ) ) {
  2444. unset( $p_filter['_filter_id'] );
  2445. }
  2446. if( isset( $p_filter['_subquery'] ) ) {
  2447. unset( $p_filter['_subquery'] );
  2448. }
  2449. return $p_filter;
  2450. }
  2451. /**
  2452. * Copy the runtime properties from one filter into another.
  2453. * @param array $p_filter_to Destination filter array
  2454. * @param array $p_filter_from Filter array from which properties are copied
  2455. * @return array Updated filter array
  2456. */
  2457. function filter_copy_runtime_properties( array $p_filter_to, array $p_filter_from ) {
  2458. if( isset( $p_filter_from['_temporary_key'] ) ) {
  2459. $p_filter_to['_temporary_key'] = $p_filter_from['_temporary_key'];
  2460. }
  2461. if( isset( $p_filter_from['_filter_id'] ) ) {
  2462. $p_filter_to['_filter_id'] = $p_filter_from['_filter_id'];
  2463. }
  2464. # we don't copy '_subquery' property, which is a cached subquery object,
  2465. # and can be regenerated at demand
  2466. return $p_filter_to;
  2467. }
  2468. /**
  2469. * Return a cached BugFilterQuery object for the provided filter, configured and
  2470. * ready to be used as a subquery for building other queries.
  2471. * If the query is not in the cache, creates a new one and store it for later reuse.
  2472. * Note: Query objects are indexed by a hash value over the serialized contents of the
  2473. * filter array.
  2474. *
  2475. * Warning: Since the returned query is an object, it should not be modified in any way
  2476. * that changes the expected behavior from the original filter array, as any further
  2477. * reuse of this chached query will share the same instanced object.
  2478. * If such a modification is needed over the query object, a clone should be used
  2479. * instead, to avoid said side effects.
  2480. *
  2481. * @param array $p_filter Filter array
  2482. * @return BugFilterQuery A query object for the filter
  2483. */
  2484. function filter_cache_subquery( array $p_filter ) {
  2485. global $g_cache_filter_subquery;
  2486. $t_hash = md5( json_encode( $p_filter ) );
  2487. if( !isset( $g_cache_filter_subquery[$t_hash] ) ) {
  2488. $g_cache_filter_subquery[$t_hash] = new BugFilterQuery( $p_filter, BugFilterQuery::QUERY_TYPE_IDS );
  2489. }
  2490. return $g_cache_filter_subquery[$t_hash];
  2491. }
  2492. /**
  2493. * Returns true if the user can use peristent filters, in contexts such as view_all_bug_page.
  2494. * Persistent filters are remembered across sessions, and are not desirable when the user is
  2495. * a shared user, eg: anonymous user
  2496. * @param integer $p_user_id A valid user identifier.
  2497. * @return boolean true if the user can use persistent filters, false otherwise
  2498. */
  2499. function filter_user_can_use_persistent( $p_user_id = null ) {
  2500. return !user_is_anonymous( $p_user_id );
  2501. }