PageRenderTime 57ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/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

Large files files are truncated, but you can click here to view the full file

  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_tempo

Large files files are truncated, but you can click here to view the full file