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 '&#160;&#160;' . 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 '&#160;&#160;' . 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 '&#160;&#160;' . 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>&#160;&#160;%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