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