PageRenderTime 65ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

/core/filter_api.php

https://github.com/Kirill/mantisbt
PHP | 4032 lines | 3307 code | 282 blank | 443 comment | 413 complexity | f5aa88adf8f0e19613695b08e5d9c349 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1

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. /**
  97. * Initialize the filter API with the current filter.
  98. * @param array $p_filter The filter to set as the current filter.
  99. */
  100. function filter_init( $p_filter ) {
  101. global $g_filter;
  102. $g_filter = $p_filter;
  103. }
  104. /**
  105. * Allow plugins to define a set of class-based filters, and register/load
  106. * them here to be used by the rest of filter_api.
  107. * @return array Mapping of field name to filter object
  108. */
  109. function filter_get_plugin_filters() {
  110. static $s_field_array = null;
  111. if( is_null( $s_field_array ) ) {
  112. $s_field_array = array();
  113. $t_all_plugin_filters = event_signal( 'EVENT_FILTER_FIELDS' );
  114. foreach( $t_all_plugin_filters as $t_plugin => $t_plugin_filters ) {
  115. foreach( $t_plugin_filters as $t_callback => $t_plugin_filter_array ) {
  116. if( is_array( $t_plugin_filter_array ) ) {
  117. foreach( $t_plugin_filter_array as $t_filter_item ) {
  118. if( is_object( $t_filter_item ) && $t_filter_item instanceof MantisFilter ) {
  119. $t_filter_object = $t_filter_item;
  120. } elseif( class_exists( $t_filter_item ) && is_subclass_of( $t_filter_item, 'MantisFilter' ) ) {
  121. $t_filter_object = new $t_filter_item();
  122. } else {
  123. continue;
  124. }
  125. $t_filter_name = mb_strtolower( $t_plugin . '_' . $t_filter_object->field );
  126. $s_field_array[$t_filter_name] = $t_filter_object;
  127. }
  128. }
  129. }
  130. }
  131. }
  132. return $s_field_array;
  133. }
  134. /**
  135. * Get a permanent link for the current active filter. The results of using these fields by other users
  136. * can be inconsistent with the original results due to fields like "Myself", "Current Project",
  137. * and due to access level.
  138. * @param array $p_custom_filter Array containing a custom filter definition.
  139. * @return string the search.php?xxxx or an empty string if no criteria applied.
  140. */
  141. function filter_get_url( array $p_custom_filter ) {
  142. $t_query = array();
  143. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_PROJECT_ID] ) ) {
  144. $t_project_id = $p_custom_filter[FILTER_PROPERTY_PROJECT_ID];
  145. if( count( $t_project_id ) == 1 && $t_project_id[0] == META_FILTER_CURRENT ) {
  146. $t_project_id = array(
  147. helper_get_current_project(),
  148. );
  149. }
  150. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_PROJECT_ID, $t_project_id );
  151. }
  152. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_SEARCH] ) ) {
  153. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_SEARCH, $p_custom_filter[FILTER_PROPERTY_SEARCH] );
  154. }
  155. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_CATEGORY_ID] ) ) {
  156. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_CATEGORY_ID, $p_custom_filter[FILTER_PROPERTY_CATEGORY_ID] );
  157. }
  158. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_REPORTER_ID] ) ) {
  159. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_REPORTER_ID, $p_custom_filter[FILTER_PROPERTY_REPORTER_ID] );
  160. }
  161. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_STATUS] ) ) {
  162. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_STATUS, $p_custom_filter[FILTER_PROPERTY_STATUS] );
  163. }
  164. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_MONITOR_USER_ID] ) ) {
  165. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_MONITOR_USER_ID, $p_custom_filter[FILTER_PROPERTY_MONITOR_USER_ID] );
  166. }
  167. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_HANDLER_ID] ) ) {
  168. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_HANDLER_ID, $p_custom_filter[FILTER_PROPERTY_HANDLER_ID] );
  169. }
  170. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_NOTE_USER_ID] ) ) {
  171. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_NOTE_USER_ID, $p_custom_filter[FILTER_PROPERTY_NOTE_USER_ID] );
  172. }
  173. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_SEVERITY] ) ) {
  174. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_SEVERITY, $p_custom_filter[FILTER_PROPERTY_SEVERITY] );
  175. }
  176. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_RESOLUTION] ) ) {
  177. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_RESOLUTION, $p_custom_filter[FILTER_PROPERTY_RESOLUTION] );
  178. }
  179. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_PRIORITY] ) ) {
  180. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_PRIORITY, $p_custom_filter[FILTER_PROPERTY_PRIORITY] );
  181. }
  182. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_VIEW_STATE] ) ) {
  183. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_VIEW_STATE, $p_custom_filter[FILTER_PROPERTY_VIEW_STATE] );
  184. }
  185. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_STICKY] ) ) {
  186. $t_query[] = filter_encode_field_and_value(
  187. FILTER_PROPERTY_STICKY,
  188. $p_custom_filter[FILTER_PROPERTY_STICKY] ? 'on' : 'off' );
  189. }
  190. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_VERSION] ) ) {
  191. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_VERSION, $p_custom_filter[FILTER_PROPERTY_VERSION] );
  192. }
  193. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_BUILD] ) ) {
  194. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_BUILD, $p_custom_filter[FILTER_PROPERTY_BUILD] );
  195. }
  196. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_FIXED_IN_VERSION] ) ) {
  197. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_FIXED_IN_VERSION, $p_custom_filter[FILTER_PROPERTY_FIXED_IN_VERSION] );
  198. }
  199. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_TARGET_VERSION] ) ) {
  200. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_TARGET_VERSION, $p_custom_filter[FILTER_PROPERTY_TARGET_VERSION] );
  201. }
  202. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_SORT_FIELD_NAME] ) ) {
  203. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_SORT_FIELD_NAME, $p_custom_filter[FILTER_PROPERTY_SORT_FIELD_NAME] );
  204. }
  205. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_SORT_DIRECTION] ) ) {
  206. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_SORT_DIRECTION, $p_custom_filter[FILTER_PROPERTY_SORT_DIRECTION] );
  207. }
  208. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_ISSUES_PER_PAGE] ) ) {
  209. if( $p_custom_filter[FILTER_PROPERTY_ISSUES_PER_PAGE] != config_get( 'default_limit_view' ) ) {
  210. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_ISSUES_PER_PAGE, $p_custom_filter[FILTER_PROPERTY_ISSUES_PER_PAGE] );
  211. }
  212. }
  213. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_HIGHLIGHT_CHANGED] ) ) {
  214. if( $p_custom_filter[FILTER_PROPERTY_HIGHLIGHT_CHANGED] != config_get( 'default_show_changed' ) ) {
  215. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_HIGHLIGHT_CHANGED, $p_custom_filter[FILTER_PROPERTY_HIGHLIGHT_CHANGED] );
  216. }
  217. }
  218. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_HIDE_STATUS] ) ) {
  219. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_HIDE_STATUS, $p_custom_filter[FILTER_PROPERTY_HIDE_STATUS] );
  220. }
  221. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_FILTER_BY_DATE_SUBMITTED] ) ) {
  222. $t_query[] = filter_encode_field_and_value(
  223. FILTER_PROPERTY_FILTER_BY_DATE_SUBMITTED,
  224. $p_custom_filter[FILTER_PROPERTY_FILTER_BY_DATE_SUBMITTED] ? 'on' : 'off' );
  225. # The start and end dates are only applicable if filter by date is set.
  226. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_START_DAY] ) ) {
  227. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_DATE_SUBMITTED_START_DAY, $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_START_DAY] );
  228. }
  229. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_END_DAY] ) ) {
  230. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_DATE_SUBMITTED_END_DAY, $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_END_DAY] );
  231. }
  232. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_START_MONTH] ) ) {
  233. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_DATE_SUBMITTED_START_MONTH, $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_START_MONTH] );
  234. }
  235. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_END_MONTH] ) ) {
  236. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_DATE_SUBMITTED_END_MONTH, $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_END_MONTH] );
  237. }
  238. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_START_YEAR] ) ) {
  239. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_DATE_SUBMITTED_START_YEAR, $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_START_YEAR] );
  240. }
  241. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_END_YEAR] ) ) {
  242. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_DATE_SUBMITTED_END_YEAR, $p_custom_filter[FILTER_PROPERTY_DATE_SUBMITTED_END_YEAR] );
  243. }
  244. }
  245. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_FILTER_BY_LAST_UPDATED_DATE] ) ) {
  246. $t_query[] = filter_encode_field_and_value(
  247. FILTER_PROPERTY_FILTER_BY_LAST_UPDATED_DATE,
  248. $p_custom_filter[FILTER_PROPERTY_FILTER_BY_LAST_UPDATED_DATE] ? 'on' : 'off' );
  249. # The start and end dates are only applicable if filter by date is set.
  250. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_START_DAY] ) ) {
  251. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_LAST_UPDATED_START_DAY, $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_START_DAY] );
  252. }
  253. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_END_DAY] ) ) {
  254. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_LAST_UPDATED_END_DAY, $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_END_DAY] );
  255. }
  256. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_START_MONTH] ) ) {
  257. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_LAST_UPDATED_START_MONTH, $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_START_MONTH] );
  258. }
  259. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_END_MONTH] ) ) {
  260. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_LAST_UPDATED_END_MONTH, $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_END_MONTH] );
  261. }
  262. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_START_YEAR] ) ) {
  263. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_LAST_UPDATED_START_YEAR, $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_START_YEAR] );
  264. }
  265. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_END_YEAR] ) ) {
  266. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_LAST_UPDATED_END_YEAR, $p_custom_filter[FILTER_PROPERTY_LAST_UPDATED_END_YEAR] );
  267. }
  268. }
  269. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_RELATIONSHIP_TYPE] ) ) {
  270. if( $p_custom_filter[FILTER_PROPERTY_RELATIONSHIP_TYPE] != -1 ) {
  271. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_RELATIONSHIP_TYPE, $p_custom_filter[FILTER_PROPERTY_RELATIONSHIP_TYPE] );
  272. }
  273. }
  274. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_RELATIONSHIP_BUG] ) ) {
  275. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_RELATIONSHIP_BUG, $p_custom_filter[FILTER_PROPERTY_RELATIONSHIP_BUG] );
  276. }
  277. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_PLATFORM] ) ) {
  278. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_PLATFORM, $p_custom_filter[FILTER_PROPERTY_PLATFORM] );
  279. }
  280. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_OS] ) ) {
  281. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_OS, $p_custom_filter[FILTER_PROPERTY_OS] );
  282. }
  283. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_OS_BUILD] ) ) {
  284. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_OS_BUILD, $p_custom_filter[FILTER_PROPERTY_OS_BUILD] );
  285. }
  286. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_TAG_STRING] ) ) {
  287. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_TAG_STRING, $p_custom_filter[FILTER_PROPERTY_TAG_STRING] );
  288. }
  289. if( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_TAG_SELECT] ) ) {
  290. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_TAG_SELECT, $p_custom_filter[FILTER_PROPERTY_TAG_SELECT] );
  291. }
  292. $t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_MATCH_TYPE, $p_custom_filter[FILTER_PROPERTY_MATCH_TYPE] );
  293. if( isset( $p_custom_filter['custom_fields'] ) ) {
  294. foreach( $p_custom_filter['custom_fields'] as $t_custom_field_id => $t_custom_field_values ) {
  295. if( !filter_field_is_any( $t_custom_field_values ) ) {
  296. $t_query[] = filter_encode_field_and_value( 'custom_field_' . $t_custom_field_id, $t_custom_field_values );
  297. }
  298. }
  299. }
  300. # Allow plugins to add filter fields
  301. $t_plugin_filter_array = filter_get_plugin_filters();
  302. foreach( $t_plugin_filter_array as $t_field_name => $t_filter_object ) {
  303. if( !filter_field_is_any( $p_custom_filter[$t_field_name] ) ) {
  304. $t_query[] = filter_encode_field_and_value( $t_field_name, $p_custom_filter[$t_field_name], $t_filter_object->type );
  305. }
  306. }
  307. if( count( $t_query ) > 0 ) {
  308. $t_query_str = implode( $t_query, '&' );
  309. $t_url = config_get_global( 'path' ) . 'search.php?' . $t_query_str;
  310. } else {
  311. $t_url = '';
  312. }
  313. return $t_url;
  314. }
  315. /**
  316. * Encodes a field and it's value for the filter URL. This handles the URL encoding and arrays.
  317. * @param string $p_field_name The field name.
  318. * @param string $p_field_value The field value (can be an array).
  319. * @param integer $p_field_type Field Type e.g. FILTER_TYPE_MULTI_STRING.
  320. * @return string url encoded string
  321. */
  322. function filter_encode_field_and_value( $p_field_name, $p_field_value, $p_field_type = null ) {
  323. $t_query_array = array();
  324. if( is_array( $p_field_value ) ) {
  325. $t_count = count( $p_field_value );
  326. if( $t_count > 1 || $p_field_type == FILTER_TYPE_MULTI_STRING || $p_field_type == FILTER_TYPE_MULTI_INT ) {
  327. foreach( $p_field_value as $t_value ) {
  328. $t_query_array[] = urlencode( $p_field_name . '[]' ) . '=' . urlencode( $t_value );
  329. }
  330. } else if( $t_count == 1 ) {
  331. $t_query_array[] = urlencode( $p_field_name ) . '=' . urlencode( $p_field_value[0] );
  332. }
  333. } else {
  334. $t_query_array[] = urlencode( $p_field_name ) . '=' . urlencode( $p_field_value );
  335. }
  336. return implode( $t_query_array, '&' );
  337. }
  338. /**
  339. * Checks the supplied value to see if it is an ANY value.
  340. * @param string $p_field_value The value to check.
  341. * @return boolean true for "ANY" values and false for others. "ANY" means filter criteria not active.
  342. */
  343. function filter_field_is_any( $p_field_value ) {
  344. if( is_array( $p_field_value ) ) {
  345. if( count( $p_field_value ) == 0 ) {
  346. return true;
  347. }
  348. foreach( $p_field_value as $t_value ) {
  349. if( ( META_FILTER_ANY == $t_value ) && ( is_numeric( $t_value ) ) ) {
  350. return true;
  351. }
  352. }
  353. } else {
  354. if( is_string( $p_field_value ) && is_blank( $p_field_value ) ) {
  355. return true;
  356. }
  357. if( is_bool( $p_field_value ) && !$p_field_value ) {
  358. return true;
  359. }
  360. if( ( META_FILTER_ANY == $p_field_value ) && ( is_numeric( $p_field_value ) ) ) {
  361. return true;
  362. }
  363. }
  364. return false;
  365. }
  366. /**
  367. * Checks the supplied value to see if it is a NONE value.
  368. * @param string $p_field_value The value to check.
  369. * @return boolean true for "NONE" values and false for others.
  370. * @todo is a check for these necessary? if( ( $t_filter_value === 'none' ) || ( $t_filter_value === '[none]' ) )
  371. */
  372. function filter_field_is_none( $p_field_value ) {
  373. if( is_array( $p_field_value ) ) {
  374. foreach( $p_field_value as $t_value ) {
  375. if( ( META_FILTER_NONE == $t_value ) && ( is_numeric( $t_value ) ) ) {
  376. return true;
  377. }
  378. }
  379. } else {
  380. if( is_string( $p_field_value ) && is_blank( $p_field_value ) ) {
  381. return false;
  382. }
  383. if( ( META_FILTER_NONE == $p_field_value ) && ( is_numeric( $p_field_value ) ) ) {
  384. return true;
  385. }
  386. }
  387. return false;
  388. }
  389. /**
  390. * Checks the supplied value to see if it is a MYSELF value.
  391. * @param string $p_field_value The value to check.
  392. * @return boolean true for "MYSELF" values and false for others.
  393. */
  394. function filter_field_is_myself( $p_field_value ) {
  395. return( META_FILTER_MYSELF == $p_field_value ? true : false );
  396. }
  397. /**
  398. * Filter per page
  399. * @param array $p_filter Filter.
  400. * @param integer $p_count Count.
  401. * @param integer $p_per_page Per page.
  402. * @return integer
  403. */
  404. function filter_per_page( array $p_filter, $p_count, $p_per_page ) {
  405. $p_per_page = (( null == $p_per_page ) ? (int)$p_filter[FILTER_PROPERTY_ISSUES_PER_PAGE] : $p_per_page );
  406. $p_per_page = (( 0 == $p_per_page || -1 == $p_per_page ) ? $p_count : $p_per_page );
  407. return (int)abs( $p_per_page );
  408. }
  409. /**
  410. * Use $p_count and $p_per_page to determine how many pages to split this list up into.
  411. * For the sake of consistency have at least one page, even if it is empty.
  412. * @param integer $p_count Count.
  413. * @param integer $p_per_page Per page.
  414. * @return integer page count
  415. */
  416. function filter_page_count( $p_count, $p_per_page ) {
  417. $t_page_count = ceil( $p_count / $p_per_page );
  418. if( $t_page_count < 1 ) {
  419. $t_page_count = 1;
  420. }
  421. return $t_page_count;
  422. }
  423. /**
  424. * Checks to make sure $p_page_number isn't past the last page.
  425. * and that $p_page_number isn't before the first page
  426. * @param integer $p_page_number Page number.
  427. * @param integer $p_page_count Page count.
  428. * @return integer
  429. */
  430. function filter_valid_page_number( $p_page_number, $p_page_count ) {
  431. if( $p_page_number > $p_page_count ) {
  432. $p_page_number = $p_page_count;
  433. }
  434. if( $p_page_number < 1 ) {
  435. $p_page_number = 1;
  436. }
  437. return $p_page_number;
  438. }
  439. /**
  440. * Figure out the offset into the db query, offset is which record to start querying from
  441. * @param integer $p_page_number Page number.
  442. * @param integer $p_per_page Per page.
  443. * @return integer
  444. */
  445. function filter_offset( $p_page_number, $p_per_page ) {
  446. return(( (int)$p_page_number -1 ) * (int)$p_per_page );
  447. }
  448. /**
  449. * Make sure the filter array contains all the fields. If any field is missing,
  450. * create it with a default value.
  451. * @param array $p_filter_arr Input filter array
  452. * @return array Processed filter array
  453. */
  454. function filter_ensure_fields( array $p_filter_arr ) {
  455. # Fill missing filter properties with defaults
  456. if( isset( $p_filter_arr['_view_type'] ) ) {
  457. $t_filter_default = filter_get_default_array( $p_filter_arr['_view_type'] );
  458. } else {
  459. $t_filter_default = filter_get_default_array();
  460. }
  461. foreach( $t_filter_default as $t_key => $t_default_value ) {
  462. if( !isset( $p_filter_arr[$t_key] ) ) {
  463. $p_filter_arr[$t_key] = $t_default_value;
  464. }
  465. }
  466. # Veryfy custom fields
  467. foreach( $t_filter_default['custom_fields'] as $t_cfid => $t_cf_data ) {
  468. if( !isset( $p_filter_arr['custom_fields'][$t_cfid] ) ) {
  469. $p_filter_arr['custom_fields'][$t_cfid] = $t_cf_data;
  470. }
  471. }
  472. return $p_filter_arr;
  473. }
  474. /**
  475. * A wrapper to compare filter version syntax
  476. * Note: Currently, filter versions have this syntax: "vN", * where N is an integer number.
  477. * @param string $p_version1 First version number
  478. * @param string $p_version2 Second version number
  479. * @param string $p_operator Comparison test, if provided. As expected by version_compare()
  480. * @return mixed As returned by version_compare()
  481. */
  482. function filter_version_compare( $p_version1, $p_version2, $p_operator = null ) {
  483. return version_compare( $p_version1, $p_version2, $p_operator );
  484. }
  485. /**
  486. * Upgrade a filter array to the current filter structure, by converting properties
  487. * that have changed from previous filter versions
  488. * @param array $p_filter Filter array to upgrade
  489. * @return array Updgraded filter array
  490. */
  491. function filter_version_upgrade( array $p_filter ) {
  492. # This is a stub for future version upgrades
  493. # After conversions are made, update filter value to current version
  494. $p_filter['_version'] = FILTER_VERSION;
  495. return $p_filter;
  496. }
  497. /**
  498. * Make sure that our filters are entirely correct and complete (it is possible that they are not).
  499. * We need to do this to cover cases where we don't have complete control over the filters given.
  500. * @param array $p_filter_arr A filter array
  501. * @return array Validated filter array
  502. */
  503. function filter_ensure_valid_filter( array $p_filter_arr ) {
  504. if( !isset( $p_filter_arr['_version'] ) ) {
  505. $p_filter_arr['_version'] = FILTER_VERSION;
  506. }
  507. if( filter_version_compare( $p_filter_arr['_version'], FILTER_VERSION, '<' ) ) {
  508. $p_filter_arr = filter_version_upgrade( $p_filter_arr );
  509. }
  510. $p_filter_arr = filter_ensure_fields( $p_filter_arr );
  511. $t_config_view_filters = config_get( 'view_filters' );
  512. $t_view_type = $p_filter_arr['_view_type'];
  513. if( ADVANCED_ONLY == $t_config_view_filters ) {
  514. $t_view_type = FILTER_VIEW_TYPE_ADVANCED;
  515. }
  516. if( SIMPLE_ONLY == $t_config_view_filters ) {
  517. $t_view_type = FILTER_VIEW_TYPE_SIMPLE;
  518. }
  519. if( !in_array( $t_view_type, array( FILTER_VIEW_TYPE_SIMPLE, FILTER_VIEW_TYPE_ADVANCED ) ) ) {
  520. $t_view_type = filter_get_default_view_type();
  521. }
  522. $p_filter_arr['_view_type'] = $t_view_type;
  523. $t_sort_fields = explode( ',', $p_filter_arr[FILTER_PROPERTY_SORT_FIELD_NAME] );
  524. $t_dir_fields = explode( ',', $p_filter_arr[FILTER_PROPERTY_SORT_DIRECTION] );
  525. # both arrays should be equal length, just in case
  526. $t_sort_fields_count = min( count( $t_sort_fields ), count( $t_dir_fields ) );
  527. # clean up sort fields, remove invalid columns
  528. $t_new_sort_array = array();
  529. $t_new_dir_array = array();
  530. $t_all_columns = columns_get_all_active_columns();
  531. for( $ix = 0; $ix < $t_sort_fields_count; $ix++ ) {
  532. if( isset( $t_sort_fields[$ix] ) ) {
  533. $t_column = $t_sort_fields[$ix];
  534. # check that the column name exist
  535. if( !in_array( $t_column, $t_all_columns ) ) {
  536. continue;
  537. }
  538. # check that it has not been already used
  539. if( in_array( $t_column, $t_new_sort_array ) ) {
  540. continue;
  541. }
  542. # check that it is sortable
  543. if( !column_is_sortable( $t_column ) ) {
  544. continue;
  545. }
  546. $t_new_sort_array[] = $t_column;
  547. # if there is no dir field, set a dummy value
  548. if( isset( $t_dir_fields[$ix] ) ) {
  549. $t_dir = $t_dir_fields[$ix];
  550. } else {
  551. $t_dir = '';
  552. }
  553. # normalize sort_dir value
  554. $t_dir = ( $t_dir == 'ASC' ) ? 'ASC' : 'DESC';
  555. $t_new_dir_array[] = $t_dir;
  556. }
  557. }
  558. if( count( $t_new_sort_array ) > 0 ) {
  559. $p_filter_arr[FILTER_PROPERTY_SORT_FIELD_NAME] = implode( ',', $t_new_sort_array );
  560. $p_filter_arr[FILTER_PROPERTY_SORT_DIRECTION] = implode( ',', $t_new_dir_array );
  561. } else {
  562. $p_filter_arr[FILTER_PROPERTY_SORT_FIELD_NAME] = filter_get_default_property( FILTER_PROPERTY_SORT_FIELD_NAME, $t_view_type );
  563. $p_filter_arr[FILTER_PROPERTY_SORT_DIRECTION] = filter_get_default_property( FILTER_PROPERTY_SORT_DIRECTION, $t_view_type );
  564. }
  565. # Validate types for values.
  566. # helper function to validate types
  567. $t_function_validate_type = function( $p_value, $p_type ) {
  568. $t_value = stripslashes( $p_value );
  569. if( ( $t_value === 'any' ) || ( $t_value === '[any]' ) ) {
  570. $t_value = META_FILTER_ANY;
  571. }
  572. if( ( $t_value === 'none' ) || ( $t_value === '[none]' ) ) {
  573. $t_value = META_FILTER_NONE;
  574. }
  575. # Ensure the filter property has the right type - see #20087
  576. switch( $p_type ) {
  577. case 'string' :
  578. case 'int' :
  579. settype( $t_value, $p_type );
  580. break;
  581. }
  582. return $t_value;
  583. };
  584. # Validate properties that must not be arrays
  585. $t_single_value_list = array(
  586. FILTER_PROPERTY_VIEW_STATE => 'int',
  587. FILTER_PROPERTY_RELATIONSHIP_TYPE => 'int',
  588. FILTER_PROPERTY_RELATIONSHIP_BUG => 'int',
  589. );
  590. foreach( $t_single_value_list as $t_field_name => $t_field_type ) {
  591. $t_value = $p_filter_arr[$t_field_name];
  592. if( is_array( $t_value ) ) {
  593. if( count( $t_value ) > 0 ) {
  594. $p_filter_arr[$t_field_name] = reset( $t_value );
  595. } else {
  596. $p_filter_arr[$t_field_name] = filter_get_default_property( $t_field_name, $t_view_type );
  597. }
  598. }
  599. $p_filter_arr[$t_field_name] = $t_function_validate_type( $p_filter_arr[$t_field_name], $t_field_type );
  600. }
  601. # Validate properties that must be arrays, and the type of its elements
  602. $t_array_values_list = array(
  603. FILTER_PROPERTY_CATEGORY_ID => 'string',
  604. FILTER_PROPERTY_SEVERITY => 'int',
  605. FILTER_PROPERTY_STATUS => 'int',
  606. FILTER_PROPERTY_REPORTER_ID => 'int',
  607. FILTER_PROPERTY_HANDLER_ID => 'int',
  608. FILTER_PROPERTY_NOTE_USER_ID => 'int',
  609. FILTER_PROPERTY_RESOLUTION => 'int',
  610. FILTER_PROPERTY_PRIORITY => 'int',
  611. FILTER_PROPERTY_BUILD => 'string',
  612. FILTER_PROPERTY_VERSION => 'string',
  613. FILTER_PROPERTY_HIDE_STATUS => 'int',
  614. FILTER_PROPERTY_FIXED_IN_VERSION => 'string',
  615. FILTER_PROPERTY_TARGET_VERSION => 'string',
  616. FILTER_PROPERTY_MONITOR_USER_ID => 'int',
  617. FILTER_PROPERTY_PROFILE_ID => 'int',
  618. FILTER_PROPERTY_PLATFORM => 'string',
  619. FILTER_PROPERTY_OS => 'string',
  620. FILTER_PROPERTY_OS_BUILD => 'string',
  621. FILTER_PROPERTY_PROJECT_ID => 'int'
  622. );
  623. foreach( $t_array_values_list as $t_multi_field_name => $t_multi_field_type ) {
  624. if( !is_array( $p_filter_arr[$t_multi_field_name] ) ) {
  625. $p_filter_arr[$t_multi_field_name] = array(
  626. $p_filter_arr[$t_multi_field_name],
  627. );
  628. }
  629. $t_checked_array = array();
  630. foreach( $p_filter_arr[$t_multi_field_name] as $t_filter_value ) {
  631. $t_checked_array[] = $t_function_validate_type( $t_filter_value, $t_multi_field_type );
  632. }
  633. $p_filter_arr[$t_multi_field_name] = $t_checked_array;
  634. }
  635. $t_custom_fields = custom_field_get_ids();
  636. if( is_array( $t_custom_fields ) && ( count( $t_custom_fields ) > 0 ) ) {
  637. foreach( $t_custom_fields as $t_cfid ) {
  638. if( isset( $p_filter_arr['custom_fields'][$t_cfid]) ) {
  639. if( !is_array( $p_filter_arr['custom_fields'][$t_cfid] ) ) {
  640. $p_filter_arr['custom_fields'][$t_cfid] = array(
  641. $p_filter_arr['custom_fields'][$t_cfid],
  642. );
  643. }
  644. $t_checked_array = array();
  645. foreach( $p_filter_arr['custom_fields'][$t_cfid] as $t_filter_value ) {
  646. $t_filter_value = stripslashes( $t_filter_value );
  647. if( ( $t_filter_value === 'any' ) || ( $t_filter_value === '[any]' ) ) {
  648. $t_filter_value = META_FILTER_ANY;
  649. }
  650. $t_checked_array[] = $t_filter_value;
  651. }
  652. $p_filter_arr['custom_fields'][$t_cfid] = $t_checked_array;
  653. }
  654. }
  655. }
  656. # If view_type is advanced, and hide_status is present, modify status array
  657. # to remove hidden status. This may happen after switching from simple to advanced.
  658. # Then, remove hide_status property, as it does not apply to advanced filter
  659. if( $p_filter_arr['_view_type'] == FILTER_VIEW_TYPE_ADVANCED
  660. && !filter_field_is_none( $p_filter_arr[FILTER_PROPERTY_HIDE_STATUS] ) ) {
  661. if( filter_field_is_any( $p_filter_arr[FILTER_PROPERTY_STATUS] ) ) {
  662. $t_selected_status_array = MantisEnum::getValues( config_get( 'status_enum_string' ) );
  663. } else {
  664. $t_selected_status_array = $p_filter_arr[FILTER_PROPERTY_STATUS];
  665. }
  666. $t_hide_status = $p_filter_arr[FILTER_PROPERTY_HIDE_STATUS][0];
  667. $t_new_status_array = array();
  668. foreach( $t_selected_status_array as $t_status ) {
  669. if( $t_status < $t_hide_status ) {
  670. $t_new_status_array[] = $t_status;
  671. }
  672. }
  673. # If there is no status left, reset the status property to "any"
  674. if( empty( $t_new_status_array ) ) {
  675. $t_new_status_array[] = META_FILTER_ANY;
  676. }
  677. $p_filter_arr[FILTER_PROPERTY_STATUS] = $t_new_status_array;
  678. $p_filter_arr[FILTER_PROPERTY_HIDE_STATUS] = META_FILTER_NONE;
  679. }
  680. #If view_type is simple, resolve conflicts between show_status and hide_status
  681. if( $p_filter_arr['_view_type'] == FILTER_VIEW_TYPE_SIMPLE
  682. && !filter_field_is_none( $p_filter_arr[FILTER_PROPERTY_HIDE_STATUS] ) ) {
  683. # get array of hidden status ids
  684. $t_all_status = MantisEnum::getValues( config_get( 'status_enum_string' ) );
  685. $t_hidden_status = $p_filter_arr[FILTER_PROPERTY_HIDE_STATUS][0];
  686. $t_hidden_status_array = array();
  687. foreach( $t_all_status as $t_status ) {
  688. if( $t_status >= $t_hidden_status ) {
  689. $t_hidden_status_array[] = $t_status;
  690. }
  691. }
  692. # remove hidden status from show_status property array
  693. # note that this will keep the "any" meta value, if present
  694. $t_show_status_array = array_diff( $p_filter_arr[FILTER_PROPERTY_STATUS], $t_hidden_status_array );
  695. # If there is no status left, reset the status property previous values, and remove hide_status
  696. if( empty( $t_show_status_array ) ) {
  697. $t_show_status_array = $p_filter_arr[FILTER_PROPERTY_STATUS];
  698. $p_filter_arr[FILTER_PROPERTY_HIDE_STATUS] = META_FILTER_NONE;
  699. }
  700. $p_filter_arr[FILTER_PROPERTY_STATUS] = $t_show_status_array;
  701. }
  702. # validate relationship fields
  703. if( !(
  704. $p_filter_arr[FILTER_PROPERTY_RELATIONSHIP_BUG] > 0
  705. || $p_filter_arr[FILTER_PROPERTY_RELATIONSHIP_BUG] == META_FILTER_ANY
  706. || $p_filter_arr[FILTER_PROPERTY_RELATIONSHIP_BUG] == META_FILTER_NONE
  707. ) ) {
  708. $p_filter_arr[FILTER_PROPERTY_RELATIONSHIP_BUG] = filter_get_default_property( FILTER_PROPERTY_RELATIONSHIP_BUG, $t_view_type );
  709. }
  710. # all of our filter values are now guaranteed to be there, and correct.
  711. return $p_filter_arr;
  712. }
  713. /**
  714. * Get a filter array with default values
  715. * Optional view type parameter is used to initialize some fields properly,
  716. * as some may differ in the default content.
  717. * @param string $p_view_type FILTER_VIEW_TYPE_SIMPLE or FILTER_VIEW_TYPE_ADVANCED
  718. * @return array Filter array with default values
  719. */
  720. function filter_get_default_array( $p_view_type = null ) {
  721. static $t_cache_default_array = array();
  722. $t_default_view_type = filter_get_default_view_type();
  723. if( !in_array( $p_view_type, array( FILTER_VIEW_TYPE_SIMPLE, FILTER_VIEW_TYPE_ADVANCED ) ) ) {
  724. $p_view_type = $t_default_view_type;
  725. }
  726. # this function is called multiple times from filter api so return a cached value if possible
  727. if( isset( $t_cache_default_array[$p_view_type] ) ) {
  728. return $t_cache_default_array[$p_view_type];
  729. }
  730. $t_default_show_changed = config_get( 'default_show_changed' );
  731. $t_meta_filter_any_array = array( META_FILTER_ANY );
  732. $t_config_view_filters = config_get( 'view_filters' );
  733. if( ADVANCED_ONLY == $t_config_view_filters ) {
  734. $t_view_type = FILTER_VIEW_TYPE_ADVANCED;
  735. } elseif( SIMPLE_ONLY == $t_config_view_filters ) {
  736. $t_view_type = FILTER_VIEW_TYPE_SIMPLE;
  737. } else {
  738. $t_view_type = $p_view_type;
  739. }
  740. if( $t_view_type == FILTER_VIEW_TYPE_SIMPLE ) {
  741. $t_hide_status_default = config_get( 'hide_status_default' );
  742. } else {
  743. $t_hide_status_default = META_FILTER_NONE;
  744. }
  745. $t_filter = array(
  746. '_version' => FILTER_VERSION,
  747. '_view_type' => $t_view_type,
  748. FILTER_PROPERTY_CATEGORY_ID => $t_meta_filter_any_array,
  749. FILTER_PROPERTY_SEVERITY => $t_meta_filter_any_array,
  750. FILTER_PROPERTY_STATUS => $t_meta_filter_any_array,
  751. FILTER_PROPERTY_HIGHLIGHT_CHANGED => $t_default_show_changed,
  752. FILTER_PROPERTY_REPORTER_ID => $t_meta_filter_any_array,
  753. FILTER_PROPERTY_HANDLER_ID => $t_meta_filter_any_array,
  754. FILTER_PROPERTY_PROJECT_ID => array( META_FILTER_CURRENT ),
  755. FILTER_PROPERTY_RESOLUTION => $t_meta_filter_any_array,
  756. FILTER_PROPERTY_BUILD => $t_meta_filter_any_array,
  757. FILTER_PROPERTY_VERSION => $t_meta_filter_any_array,
  758. FILTER_PROPERTY_HIDE_STATUS => array( $t_hide_status_default ),
  759. FILTER_PROPERTY_MONITOR_USER_ID => $t_meta_filter_any_array,
  760. FILTER_PROPERTY_SORT_FIELD_NAME => 'last_updated',
  761. FILTER_PROPERTY_SORT_DIRECTION => 'DESC',
  762. FILTER_PROPERTY_ISSUES_PER_PAGE => config_get( 'default_limit_view' ),
  763. FILTER_PROPERTY_MATCH_TYPE => FILTER_MATCH_ALL,
  764. FILTER_PROPERTY_PLATFORM => $t_meta_filter_any_array,
  765. FILTER_PROPERTY_OS => $t_meta_filter_any_array,
  766. FILTER_PROPERTY_OS_BUILD => $t_meta_filter_any_array,
  767. FILTER_PROPERTY_FIXED_IN_VERSION => $t_meta_filter_any_array,
  768. FILTER_PROPERTY_TARGET_VERSION => $t_meta_filter_any_array,
  769. FILTER_PROPERTY_PROFILE_ID => $t_meta_filter_any_array,
  770. FILTER_PROPERTY_PRIORITY => $t_meta_filter_any_array,
  771. FILTER_PROPERTY_NOTE_USER_ID => $t_meta_filter_any_array,
  772. FILTER_PROPERTY_STICKY => gpc_string_to_bool( config_get( 'show_sticky_issues' ) ),
  773. FILTER_PROPERTY_FILTER_BY_DATE_SUBMITTED => false,
  774. FILTER_PROPERTY_DATE_SUBMITTED_START_MONTH => date( 'm' ),
  775. FILTER_PROPERTY_DATE_SUBMITTED_END_MONTH => date( 'm' ),
  776. FILTER_PROPERTY_DATE_SUBMITTED_START_DAY => 1,
  777. FILTER_PROPERTY_DATE_SUBMITTED_END_DAY => date( 'd' ),
  778. FILTER_PROPERTY_DATE_SUBMITTED_START_YEAR => date( 'Y' ),
  779. FILTER_PROPERTY_DATE_SUBMITTED_END_YEAR => date( 'Y' ),
  780. FILTER_PROPERTY_FILTER_BY_LAST_UPDATED_DATE => false,
  781. FILTER_PROPERTY_LAST_UPDATED_START_MONTH => date( 'm' ),
  782. FILTER_PROPERTY_LAST_UPDATED_END_MONTH => date( 'm' ),
  783. FILTER_PROPERTY_LAST_UPDATED_START_DAY => 1,
  784. FILTER_PROPERTY_LAST_UPDATED_END_DAY => date( 'd' ),
  785. FILTER_PROPERTY_LAST_UPDATED_START_YEAR => date( 'Y' ),
  786. FILTER_PROPERTY_LAST_UPDATED_END_YEAR => date( 'Y' ),
  787. FILTER_PROPERTY_SEARCH => '',
  788. FILTER_PROPERTY_VIEW_STATE => META_FILTER_ANY,
  789. FILTER_PROPERTY_TAG_STRING => '',
  790. FILTER_PROPERTY_TAG_SELECT => 0,
  791. FILTER_PROPERTY_RELATIONSHIP_TYPE => BUG_REL_ANY,
  792. FILTER_PROPERTY_RELATIONSHIP_BUG => META_FILTER_ANY,
  793. );
  794. # initialize plugin filters
  795. $t_plugin_filters = filter_get_plugin_filters();
  796. foreach( $t_plugin_filters as $t_field_name => $t_filter_object ) {
  797. switch( $t_filter_object->type ) {
  798. case FILTER_TYPE_STRING:
  799. $t_filter[$t_field_name] = $t_filter_object->default;
  800. break;
  801. case FILTER_TYPE_INT:
  802. $t_filter[$t_field_name] = (int)$t_filter_object->default;
  803. break;
  804. case FILTER_TYPE_BOOLEAN:
  805. $t_filter[$t_field_name] = (bool)$t_filter_object->default;
  806. break;
  807. case FILTER_TYPE_MULTI_STRING:
  808. $t_filter[$t_field_name] = array( (string)META_FILTER_ANY );
  809. break;
  810. case FILTER_TYPE_MULTI_INT:
  811. $t_filter[$t_field_name] = array( META_FILTER_ANY );
  812. break;
  813. default:
  814. $t_filter[$t_field_name] = (string)META_FILTER_ANY;
  815. }
  816. if( !$t_filter_object->validate( $t_filter[$t_field_name] ) ) {
  817. $t_filter[$t_field_name] = $t_filter_object->default;
  818. }
  819. }
  820. $t_custom_fields = custom_field_get_ids();
  821. # @@@ (thraxisp) This should really be the linked ids, but we don't know the project
  822. $f_custom_fields_data = array();
  823. if( is_array( $t_custom_fields ) && ( count( $t_custom_fields ) > 0 ) ) {
  824. foreach( $t_custom_fields as $t_cfid ) {
  825. $f_custom_fields_data[$t_cfid] = array( (string)META_FILTER_ANY );
  826. }
  827. }
  828. $t_filter['custom_fields'] = $f_custom_fields_data;
  829. $t_cache_default_array[$p_view_type] = $t_filter;
  830. return $t_filter;
  831. }
  832. /**
  833. * Returns the default view type for filters
  834. * @return string Default view type
  835. */
  836. function filter_get_default_view_type() {
  837. if( ADVANCED_DEFAULT == config_get( 'view_filters' ) ) {
  838. return FILTER_VIEW_TYPE_ADVANCED;
  839. } else {
  840. return FILTER_VIEW_TYPE_SIMPLE;
  841. }
  842. }
  843. /**
  844. * Returns the default value for a filter property.
  845. * Relies on filter_get_default_array() to get a defaulted filter.
  846. * @param string $p_filter_property The requested filter property name
  847. * @param string $p_view_type Optional, view type for the defaulted filter (simple/advanced)
  848. * @return mixed The property default value, or null if it doesn't exist
  849. */
  850. function filter_get_default_property( $p_filter_property, $p_view_type = null ) {
  851. $t_default_array = filter_get_default_array( $p_view_type );
  852. if( isset( $t_default_array[$p_filter_property] ) ) {
  853. return $t_default_array[$p_filter_property];
  854. } else {
  855. return null;
  856. }
  857. }
  858. /**
  859. * Get the standard filter that is to be used when no filter was previously saved.
  860. * When creating specific filters, this can be used as a basis for the filter, where
  861. * specific entries can be overridden.
  862. * @return mixed
  863. */
  864. function filter_get_default() {
  865. # Create empty array, validation will fill it with defaults
  866. $t_filter = array();
  867. return filter_ensure_valid_filter( $t_filter );
  868. }
  869. /**
  870. * Deserialize filter string
  871. * Expected strings have this format: "<version>#<json string>" where:
  872. * - <version> is the versio number of the filter structure used. See constant FILTER_VERSION
  873. * - # is a separator
  874. * - <json string> is the json encoded filter array.
  875. * @param string $p_serialized_filter Serialized filter string.
  876. * @return mixed $t_filter array
  877. * @see filter_ensure_valid_filter
  878. */
  879. function filter_deserialize( $p_serialized_filter ) {
  880. if( is_blank( $p_serialized_filter ) ) {
  881. return false;
  882. }
  883. #@TODO cproensa, we could accept a pure json array, without version prefix
  884. # in this case, the filter version field inside the array is to be used
  885. # and if not present, set the current filter version
  886. # check filter version mark
  887. $t_setting_arr = explode( '#', $p_serialized_filter, 2 );
  888. $t_version_string = $t_setting_arr[0];
  889. if( in_array( $t_version_string, array( 'v1', 'v2', 'v3', 'v4' ) ) ) {
  890. # these versions can't be salvaged, they are too old to update
  891. return false;
  892. } elseif( in_array( $t_version_string, array( 'v5', 'v6', 'v7', 'v8' ) ) ) {
  893. # filters from v5 onwards should cope with changing filter indices dynamically
  894. $t_filter_array = unserialize( $t_setting_arr[1] );
  895. } else {
  896. # filters from v9 onwards are stored as json
  897. $t_filter_array = json_decode( $t_setting_arr[1], /* assoc array */ true );
  898. }
  899. # If the unserialez data is not an array, the some error happened, eg, invalid format
  900. if( !is_array( $t_filter_array ) ) {
  901. return false;
  902. }
  903. # Set the filter version that was loaded in the array
  904. $t_filter_array['_version'] = $t_setting_arr[0];
  905. # If upgrade in filter content is needed, it will be done in filter_ensure_valid_filter()
  906. return filter_ensure_valid_filter( $t_filter_array );
  907. }
  908. /**
  909. * Creates a serialized filter with the correct format
  910. * @param array $p_filter_array Filter array to be serialized
  911. * @return string Serialized filter string
  912. */
  913. function filter_serialize( $p_filter_array ) {
  914. $t_cookie_version = FILTER_VERSION;
  915. $p_filter_array = filter_clean_runtime_properties( $p_filter_array );
  916. $t_settings_serialized = json_encode( $p_filter_array );
  917. $t_settings_string = $t_cookie_version . '#' . $t_settings_serialized;
  918. return $t_settings_string;
  919. }
  920. /**
  921. * Get the filter db row $p_filter_id
  922. * using the cached row if it's available
  923. * @global array $g_cache_filter_db_rows
  924. * @param integer $p_filter_id A filter identifier to look up in the database.
  925. * @return array|boolean The row of filter data as stored in db table, or false if does not exist
  926. */
  927. function filter_get_row( $p_filter_id ) {
  928. global $g_cache_filter_db_rows;
  929. if( !isset( $g_cache_filter_db_rows[$p_filter_id] ) ) {
  930. filter_cache_rows( array($p_filter_id) );
  931. }
  932. $t_row = $g_cache_filter_db_rows[$p_filter_id];
  933. return $t_row;
  934. }
  935. /**
  936. * Get the value of the filter field specified by filter id and field name
  937. * @param integer $p_filter_id A filter identifier to look up in the database.
  938. * @param string $p_field_name Name of the filter field to retrieve.
  939. * @return string
  940. */
  941. function filter_get_field( $p_filter_id, $p_field_name ) {
  942. $t_row = filter_get_row( $p_filter_id );
  943. if( isset( $t_row[$p_field_name] ) ) {
  944. return $t_row[$p_field_name];
  945. } else {
  946. error_parameters( $p_field_name );
  947. trigger_error( ERROR_DB_FIELD_NOT_FOUND, WARNING );
  948. return '';
  949. }
  950. }
  951. /**
  952. * Add sort parameters to the query clauses
  953. * @param array &$p_filter Filter to sort.
  954. * @param boolean $p_show_sticky Whether to show sticky items.
  955. * @param array $p_query_clauses Array of query clauses.
  956. * @return array $p_query_clauses
  957. *
  958. * @deprecated Use BugFilterQuery class
  959. */
  960. function filter_get_query_sort_data( array &$p_filter, $p_show_sticky, array $p_query_clauses ) {
  961. error_parameters( __FUNCTION__ . '()', 'BugFilterQuery class' );
  962. trigger_error( ERROR_DEPRECATED_SUPERSEDED, DEPRECATED );
  963. $p_query_clauses['order'] = array();
  964. # Get only the visible, and sortable, column properties
  965. # @TODO cproensa: this defaults to COLUMNS_TARGET_VIEW_PAGE
  966. # are we sure that filters are only used with the column set for view page?
  967. $p_sort_properties = filter_get_visible_sort_properties_array( $p_filter );
  968. $t_sort_fields = $p_sort_properties[FILTER_PROPERTY_SORT_FIELD_NAME];
  969. $t_dir_fields = $p_sort_properties[FILTER_PROPERTY_SORT_DIRECTION];
  970. if( gpc_string_to_bool( $p_filter[FILTER_PROPERTY_STICKY] ) && ( null !== $p_show_sticky ) ) {
  971. $p_query_clauses['order'][] = '{bug}.sticky DESC';
  972. }
  973. $t_included_project_ids = $p_query_clauses['metadata']['included_projects'];
  974. $t_user_id = $p_query_clauses['metadata']['user_id'];
  975. $t_count = count( $t_sort_fields );
  976. for( $i = 0; $i < $t_count; $i++ ) {
  977. $c_sort = $t_sort_fields[$i];
  978. $c_dir = 'DESC' == $t_dir_fields[$i] ? 'DESC' : 'ASC';
  979. # if sorting by a custom field
  980. if( column_is_custom_field( $c_sort ) ) {
  981. $t_custom_field = column_get_custom_field_name( $c_sort );
  982. $t_custom_field_id = custom_field_get_id_from_name( $t_custom_field );
  983. $t_def = custom_field_get_definition( $t_custom_field_id );
  984. $t_value_field = ( $t_def['type'] == CUSTOM_FIELD_TYPE_TEXTAREA ? 'text' : 'value' );
  985. $t_table_name = '';
  986. # if the custom field was filtered, there is already a calculated join, so reuse that table alias
  987. # otherwise, a new join must be calculated
  988. if( isset( $p_query_clauses['metadata']['cf_alias'][$t_custom_field_id] ) ) {
  989. $t_table_name = $p_query_clauses['metadata']['cf_alias'][$t_custom_field_id];
  990. } else {
  991. # @TODO This code for CF visibility is the same as filter_get_bug_rows_query_clauses()
  992. # It should be encapsulated and reused
  993. $t_searchable_projects = array_intersect( $t_included_project_ids, custom_field_get_project_ids( $t_custom_field_id ) );
  994. $t_projects_can_view_field = access_project_array_filter( (int)$t_def['access_level_r'], $t_searchable_projects, $t_user_id );
  995. if( empty( $t_projects_can_view_field ) ) {
  996. continue;
  997. }
  998. $t_table_name = 'cf_sort_' . $t_custom_field_id;
  999. $t_cf_join_clause = 'LEFT OUTER JOIN {custom_field_string} ' . $t_table_name . ' ON {bug}.id = ' . $t_table_name . '.bug_id AND ' . $t_table_name . '.field_id = ' . $t_custom_field_id;
  1000. # This diff will contain those included projects that can't view this custom field
  1001. $t_diff = array_diff( $t_included_project_ids, $t_projects_can_view_field );
  1002. # If not empty, it means there are some projects that can't view the field values,
  1003. # so a project filter must be used to not include values from those projects
  1004. if( !empty( $t_diff ) ) {
  1005. $t_cf_join_clause .= ' AND {bug}.project_id IN (' . implode( ',', $t_projects_can_view_field ) . ')';
  1006. }
  1007. $p_query_clauses['metadata']['cf_alias'][$t_custom_field_id] = $t_table_name;
  1008. $p_query_clauses['join'][] = $t_cf_join_clause;
  1009. }
  1010. # if no join can be used (eg, no view access), skip this field from the order clause
  1011. if( empty( $t_table_name ) ) {
  1012. continue;
  1013. }
  1014. $t_field_alias = 'cf_sortfield_' . $t_custom_field_id;
  1015. $t_sort_col = $t_table_name . '.' . $t_value_field;
  1016. # which types need special type cast
  1017. switch( $t_def['type'] ) {
  1018. case CUSTOM_FIELD_TYPE_FLOAT:
  1019. # mysql can't cast to float, use alternative syntax
  1020. $t_sort_expr = db_is_mysql() ? $t_sort_col . '+0.0' : 'CAST(NULLIF(' . $t_sort_col . ',\'\') AS FLOAT)';
  1021. break;
  1022. case CUSTOM_FIELD_TYPE_DATE:
  1023. case CUSTOM_FIELD_TYPE_NUMERIC:
  1024. $t_sort_expr = 'CAST(NULLIF(' . $t_sort_col . ',\'\') AS DECIMAL)';
  1025. break;
  1026. default: # no cast needed
  1027. $t_sort_expr = $t_sort_col;
  1028. }
  1029. # which types need special treatment for null sorting
  1030. switch( $t_def['type'] ) {
  1031. case CUSTOM_FIELD_TYPE_DATE:
  1032. case CUSTOM_FIELD_TYPE_NUMERIC:
  1033. case CUSTOM_FIELD_TYPE_FLOAT:
  1034. $t_null_last = true;
  1035. break;
  1036. default:
  1037. $t_null_last = false;
  1038. }
  1039. if( $t_null_last ) {
  1040. $t_null_expr = 'CASE WHEN NULLIF(' . $t_sort_col . ', \'\') IS NULL THEN 1 ELSE 0 END';
  1041. $t_clause_for_select = $t_null_expr . ' AS ' . $t_field_alias . '_null';
  1042. $t_clause_for_select .= ', ' . $t_sort_expr . ' AS ' . $t_field_alias;
  1043. $t_clause_for_order = $t_field_alias . '_null ASC, ' . $t_field_alias . ' ' . $c_dir;
  1044. } else {
  1045. $t_clause_for_select = $t_sort_expr . ' AS ' . $t_field_alias;
  1046. $t_clause_for_order = $t_field_alias . ' ' . $c_dir;
  1047. }
  1048. # Note: pgsql needs the sort expression to appear as member of the "select distinct"
  1049. $p_query_clauses['select'][] = $t_clause_for_select;
  1050. $p_query_clauses['order'][] = $t_clause_for_order;
  1051. # if sorting by plugin columns
  1052. } else if( column_is_plugin_column( $c_sort ) ) {
  1053. $t_plugin_columns = columns_get_plugin_columns();
  1054. $t_column_object = $t_plugin_columns[$c_sort];
  1055. $t_clauses = $t_column_object->sortquery( $c_dir );
  1056. if( is_array( $t_clauses ) ) {
  1057. if( isset( $t_clauses['join'] ) ) {
  1058. $p_query_clauses['join'][] = $t_clauses['join'];
  1059. }
  1060. if( isset( $t_clauses['order'] ) ) {
  1061. $p_query_clauses['order'][] = $t_clauses['order'];
  1062. }
  1063. }
  1064. # standard column
  1065. } else {
  1066. $t_sort_col = '{bug}.' . $c_sort;
  1067. # When sorting by due_date, always display undefined dates last.
  1068. # Undefined date is defaulted as "1" in database, so add a special
  1069. # sort clause to group and sort by this.
  1070. if( 'due_date' == $c_sort && 'ASC' == $c_dir ) {
  1071. $t_null_expr = 'CASE ' . $t_sort_col . ' WHEN 1 THEN 1 ELSE 0 END';
  1072. $p_query_clauses['select'][] = $t_null_expr . ' AS due_date_sort_null';
  1073. $p_query_clauses['order'][] = 'due_date_sort_null ASC';
  1074. }
  1075. # main sort clause for due date
  1076. $p_query_clauses['order'][] = $t_sort_col . ' ' .$c_dir;
  1077. }
  1078. }
  1079. # add basic sorting if necessary
  1080. if( !in_array( 'last_updated', $t_sort_fields ) ) {
  1081. $p_query_clauses['order'][] = '{bug}.last_updated DESC';
  1082. }
  1083. if( !in_array( 'date_submitted', $t_sort_fields ) ) {
  1084. $p_query_clauses['order'][] = '{bug}.date_submitted DESC';
  1085. }
  1086. return $p_query_clauses;
  1087. }
  1088. /**
  1089. * Remove any duplicate values in certain elements of query_clauses
  1090. * Do not loop over query clauses as some keys may contain valid duplicate values.
  1091. * We basically want unique values for just the base query elements select, from, and join
  1092. * 'where' and 'where_values' key should not have duplicates as that is handled earlier and applying
  1093. * array_unique here could cause problems with the query.
  1094. * @param array $p_query_clauses Array of query clauses.
  1095. * @return array
  1096. *
  1097. * @deprecated Use BugFilterQuery class
  1098. */
  1099. function filter_unique_query_clauses( array $p_query_clauses ) {
  1100. error_parameters( __FUNCTION__ . '()', 'BugFilterQuery class' );
  1101. trigger_error( ERROR_DEPRECATED_SUPERSEDED, DEPRECATED );
  1102. $p_query_clauses['select'] = array_unique( $p_query_clauses['select'] );
  1103. $p_query_clauses['from'] = array_unique( $p_query_clauses['from'] );
  1104. $p_query_clauses['join'] = array_unique( $p_query_clauses['join'] );
  1105. return $p_query_clauses;
  1106. }
  1107. /**
  1108. * Build a query with the query clauses array, query for bug count and return the result
  1109. *
  1110. * Note: The parameter $p_pop_param can be used as 'false' to keep db_params in the stack,
  1111. * if the same query clauses object is reused for several queries. In that case a db_param_pop()
  1112. * should be used manually when required.
  1113. * This is the case when "filter_get_bug_count" is used followed by "filter_get_bug_rows_result"
  1114. * @param array $p_query_clauses Array of query cla…

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