SOAP API: do not try to unserialize an invalid filter
[mantis.git] / api / soap / mc_api.php
blob3d15b5697b34f6354bdfa24820ad3872eed5faad
1 <?php
2 # MantisConnect - A webservice interface to Mantis Bug Tracker
3 # Copyright (C) 2004-2011 Victor Boctor - vboctor@users.sourceforge.net
4 # This program is distributed under dual licensing. These include
5 # GPL and a commercial licenses. Victor Boctor reserves the right to
6 # change the license of future releases.
7 # See docs/ folder for more details
9 # set up error_handler() as the new default error handling function
10 set_error_handler( 'mc_error_handler' );
12 # override some MantisBT configurations
13 $g_show_detailed_errors = OFF;
14 $g_stop_on_errors = ON;
15 $g_display_errors = array(
16 E_WARNING => 'halt',
17 E_NOTICE => 'halt',
18 E_USER_ERROR => 'halt',
19 E_USER_WARNING => 'halt',
20 E_USER_NOTICE => 'halt',
23 /**
24 * Get the MantisConnect webservice version.
26 function mc_version() {
27 return MANTIS_VERSION;
30 # Checks if MantisBT installation is marked as offline by the administrator.
31 # true: offline, false: online
32 function mci_is_mantis_offline() {
33 $t_offline_file = dirname( dirname( __FILE__ ) ) . DIRECTORY_SEPARATOR . 'mantis_offline.php';
34 return file_exists( $t_offline_file );
37 # return user_id if successful, otherwise false.
38 function mci_check_login( $p_username, $p_password ) {
39 if( mci_is_mantis_offline() ) {
40 return false;
43 # if no user name supplied, then attempt to login as anonymous user.
44 if( is_blank( $p_username ) ) {
45 $t_anon_allowed = config_get( 'allow_anonymous_login' );
46 if( OFF == $t_anon_allowed ) {
47 return false;
50 $p_username = config_get( 'anonymous_account' );
52 # do not use password validation.
53 $p_password = null;
56 if( false === auth_attempt_script_login( $p_username, $p_password ) ) {
57 return false;
60 return auth_get_current_user_id();
63 function mci_has_readonly_access( $p_user_id, $p_project_id = ALL_PROJECTS ) {
64 $t_access_level = user_get_access_level( $p_user_id, $p_project_id );
65 return( $t_access_level >= config_get( 'mc_readonly_access_level_threshold' ) );
68 function mci_has_readwrite_access( $p_user_id, $p_project_id = ALL_PROJECTS ) {
69 $t_access_level = user_get_access_level( $p_user_id, $p_project_id );
70 return( $t_access_level >= config_get( 'mc_readwrite_access_level_threshold' ) );
73 function mci_has_access( $p_access_level, $p_user_id, $p_project_id = ALL_PROJECTS ) {
74 $t_access_level = user_get_access_level( $p_user_id, $p_project_id );
75 return( $t_access_level >= (int) $p_access_level );
78 function mci_has_administrator_access( $p_user_id, $p_project_id = ALL_PROJECTS ) {
79 $t_access_level = user_get_access_level( $p_user_id, $p_project_id );
80 return( $t_access_level >= config_get( 'mc_admin_access_level_threshold' ) );
83 function mci_get_project_id( $p_project ) {
84 if( (int) $p_project['id'] != 0 ) {
85 $t_project_id = (int) $p_project['id'];
86 } else {
87 $t_project_id = project_get_id_by_name( $p_project['name'] );
90 return $t_project_id;
93 function mci_get_project_status_id( $p_status ) {
94 return mci_get_enum_id_from_objectref( 'project_status', $p_status );
97 function mci_get_project_view_state_id( $p_view_state ) {
98 return mci_get_enum_id_from_objectref( 'project_view_state', $p_view_state );
101 function mci_get_user_id( $p_user ) {
102 $t_user_id = 0;
104 if ( isset( $p_user['id'] ) && (int) $p_user['id'] != 0 ) {
105 $t_user_id = (int) $p_user['id'];
106 } elseif ( isset( $p_user['name'] ) ) {
107 $t_user_id = user_get_id_by_name( $p_user['name'] );
110 return $t_user_id;
113 function mci_get_user_lang( $p_user_id ) {
114 $t_lang = user_pref_get_pref( $p_user_id, 'language' );
115 if( $t_lang == 'auto' ) {
116 $t_lang = config_get( 'fallback_language' );
118 return $t_lang;
121 function mci_get_status_id( $p_status ) {
122 return mci_get_enum_id_from_objectref( 'status', $p_status );
125 function mci_get_severity_id( $p_severity ) {
126 return mci_get_enum_id_from_objectref( 'severity', $p_severity );
129 function mci_get_priority_id( $p_priority ) {
131 return mci_get_enum_id_from_objectref( 'priority', $p_priority );
134 function mci_get_reproducibility_id( $p_reproducibility ) {
135 return mci_get_enum_id_from_objectref( 'reproducibility', $p_reproducibility );
138 function mci_get_resolution_id( $p_resolution ) {
139 return mci_get_enum_id_from_objectref( 'resolution', $p_resolution );
142 function mci_get_projection_id( $p_projection ) {
143 return mci_get_enum_id_from_objectref( 'projection', $p_projection );
146 function mci_get_eta_id( $p_eta ) {
147 return mci_get_enum_id_from_objectref( 'eta', $p_eta );
150 function mci_get_view_state_id( $p_view_state ) {
151 return mci_get_enum_id_from_objectref( 'view_state', $p_view_state );
154 # Get null on empty value.
156 # @param Object $p_value The value
157 # @return Object The value if not empty; null otherwise.
159 function mci_null_if_empty( $p_value ) {
160 if( !is_blank( $p_value ) ) {
161 return $p_value;
164 return null;
168 * Gets the url for MantisBT.
170 * @return MantisBT URL terminated by a /.
172 function mci_get_mantis_path() {
174 return config_get( 'path' );
177 # Given a enum string and num, return the appropriate localized string
178 function mci_get_enum_element( $p_enum_name, $p_val, $p_lang ) {
179 $t_enum_string = config_get( $p_enum_name . '_enum_string' );
180 $t_localized_enum_string = lang_get( $p_enum_name . '_enum_string', $p_lang );
182 return MantisEnum::getLocalizedLabel( $t_enum_string, $t_localized_enum_string, $p_val );
185 # Gets the sub-projects that are accessible to the specified user / project.
186 function mci_user_get_accessible_subprojects( $p_user_id, $p_parent_project_id, $p_lang = null ) {
187 if( $p_lang === null ) {
188 $t_lang = mci_get_user_lang( $p_user_id );
189 } else {
190 $t_lang = $p_lang;
193 $t_result = array();
194 foreach( user_get_accessible_subprojects( $p_user_id, $p_parent_project_id ) as $t_subproject_id ) {
195 $t_subproject_row = project_cache_row( $t_subproject_id );
196 $t_subproject = array();
197 $t_subproject['id'] = $t_subproject_id;
198 $t_subproject['name'] = $t_subproject_row['name'];
199 $t_subproject['status'] = mci_enum_get_array_by_id( $t_subproject_row['status'], 'project_status', $t_lang );
200 $t_subproject['enabled'] = $t_subproject_row['enabled'];
201 $t_subproject['view_state'] = mci_enum_get_array_by_id( $t_subproject_row['view_state'], 'project_view_state', $t_lang );
202 $t_subproject['access_min'] = mci_enum_get_array_by_id( $t_subproject_row['access_min'], 'access_levels', $t_lang );
203 $t_subproject['file_path'] = array_key_exists( 'file_path', $t_subproject_row ) ? $t_subproject_row['file_path'] : "";
204 $t_subproject['description'] = array_key_exists( 'description', $t_subproject_row ) ? $t_subproject_row['description'] : "";
205 $t_subproject['subprojects'] = mci_user_get_accessible_subprojects( $p_user_id, $t_subproject_id, $t_lang );
206 $t_result[] = $t_subproject;
209 return $t_result;
212 function translate_category_name_to_id( $p_category_name, $p_project_id ) {
213 if ( !isset( $p_category_name ) ) {
214 return 0;
217 $t_cat_array = category_get_all_rows( $p_project_id );
218 foreach( $t_cat_array as $t_category_row ) {
219 if( $t_category_row['name'] == $p_category_name ) {
220 return $t_category_row['id'];
223 return 0;
227 * Basically this is a copy of core/filter_api.php#filter_db_get_available_queries().
228 * The only difference is that the result of this function is not an array of filter
229 * names but an array of filter structures.
231 function mci_filter_db_get_available_queries( $p_project_id = null, $p_user_id = null ) {
232 $t_filters_table = db_get_table( 'filters' );
233 $t_overall_query_arr = array();
235 if( null === $p_project_id ) {
236 $t_project_id = helper_get_current_project();
237 } else {
238 $t_project_id = db_prepare_int( $p_project_id );
241 if( null === $p_user_id ) {
242 $t_user_id = auth_get_current_user_id();
243 } else {
244 $t_user_id = db_prepare_int( $p_user_id );
247 # If the user doesn't have access rights to stored queries, just return
248 if( !access_has_project_level( config_get( 'stored_query_use_threshold' ) ) ) {
249 return $t_overall_query_arr;
252 # Get the list of available queries. By sorting such that public queries are
253 # first, we can override any query that has the same name as a private query
254 # with that private one
255 $query = "SELECT * FROM $t_filters_table
256 WHERE (project_id='$t_project_id'
257 OR project_id='0')
258 AND name!=''
259 ORDER BY is_public DESC, name ASC";
260 $result = db_query( $query );
261 $query_count = db_num_rows( $result );
263 for( $i = 0;$i < $query_count;$i++ ) {
264 $row = db_fetch_array( $result );
265 if(( $row['user_id'] == $t_user_id ) || db_prepare_bool( $row['is_public'] ) ) {
267 $t_filter_detail = explode( '#', $row['filter_string'], 2 );
268 if ( !isset($t_filter_detail[1]) ) {
269 continue;
271 $t_filter = unserialize( $t_filter_detail[1] );
272 $t_filter = filter_ensure_valid_filter( $t_filter );
273 $row['url'] = filter_get_url( $t_filter );
274 $t_overall_query_arr[$row['name']] = $row;
278 return array_values( $t_overall_query_arr );
282 * Get a category definition.
284 * @param integer $p_category_id The id of the category to retrieve.
285 * @return Array an Array containing the id and the name of the category.
287 function mci_category_as_array_by_id( $p_category_id ) {
288 $t_result = array();
289 $t_result['id'] = $p_category_id;
290 $t_result['name'] = category_get_name( $p_category_id );
291 return $t_result;
295 * Transforms a version array into an array suitable for marshalling into ProjectVersionData
297 * @param array $p_version
299 function mci_project_version_as_array( $p_version ) {
301 return array(
302 'id' => $p_version['id'],
303 'name' => $p_version['version'],
304 'project_id' => $p_version['project_id'],
305 'date_order' => timestamp_to_iso8601( $p_version['date_order'] ),
306 'description' => mci_null_if_empty( $p_version['description'] ),
307 'released' => $p_version['released'],
308 'obsolete' => $p_version['obsolete']
313 * Returns time tracking information from a bug note.
315 * @param int $p_issue_id The id of the issue
316 * @param Array $p_note A note as passed to the soap api methods
318 * @return String the string time entry to be added to the bugnote, in 'HH:mm' format
320 function mci_get_time_tracking_from_note( $p_issue_id, $p_note) {
322 if ( !access_has_bug_level( config_get( 'time_tracking_view_threshold' ), $p_issue_id ) )
323 return '00:00';
325 if ( !isset( $p_note['time_tracking'] ))
326 return '00:00';
328 return db_minutes_to_hhmm($p_note['time_tracking']);
332 * SECURITY NOTE: these globals are initialized here to prevent them
333 * being spoofed if register_globals is turned on
335 $g_error_parameters = array();
336 $g_error_handled = false;
337 $g_error_proceed_url = null;
339 # Default error handler
341 # This handler will not receive E_ERROR, E_PARSE, E_CORE_*, or E_COMPILE_*
342 # errors.
344 # E_USER_* are triggered by us and will contain an error constant in $p_error
345 # The others, being system errors, will come with a string in $p_error
347 function mc_error_handler( $p_type, $p_error, $p_file, $p_line, $p_context ) {
348 global $g_error_parameters, $g_error_handled, $g_error_proceed_url;
349 global $g_lang_overrides;
350 global $g_error_send_page_header;
351 global $l_oServer;
353 # check if errors were disabled with @ somewhere in this call chain
354 # also suppress php 5 strict warnings
355 if( 0 == error_reporting() || 2048 == $p_type ) {
356 return;
359 $t_lang_pushed = false;
361 # flush any language overrides to return to user's natural default
362 if( function_exists( 'db_is_connected' ) ) {
363 if( db_is_connected() ) {
364 lang_push( lang_get_default() );
365 $t_lang_pushed = true;
369 $t_short_file = basename( $p_file );
370 $t_method_array = config_get( 'display_errors' );
371 if( isset( $t_method_array[$p_type] ) ) {
372 $t_method = $t_method_array[$p_type];
373 } else {
374 $t_method = 'none';
377 # build an appropriate error string
378 switch( $p_type ) {
379 case E_WARNING:
380 $t_error_type = 'SYSTEM WARNING';
381 $t_error_description = $p_error;
382 break;
383 case E_NOTICE:
384 $t_error_type = 'SYSTEM NOTICE';
385 $t_error_description = $p_error;
386 break;
387 case E_USER_ERROR:
388 $t_error_type = "APPLICATION ERROR #$p_error";
389 $t_error_description = error_string( $p_error );
390 break;
391 case E_USER_WARNING:
392 $t_error_type = "APPLICATION WARNING #$p_error";
393 $t_error_description = error_string( $p_error );
394 break;
395 case E_USER_NOTICE:
397 # used for debugging
398 $t_error_type = 'DEBUG';
399 $t_error_description = $p_error;
400 break;
401 default:
403 #shouldn't happen, just display the error just in case
404 $t_error_type = '';
405 $t_error_description = $p_error;
408 $t_error_description = $t_error_description;
409 $t_error_stack = error_get_stack_trace();
411 $l_oServer->fault( 'Server', "Error Type: $t_error_type,\nError Description:\n$t_error_description,\nStack Trace:\n$t_error_stack" );
412 $l_oServer->send_response();
413 exit();
416 # Get a stack trace if PHP provides the facility or xdebug is present
417 function error_get_stack_trace() {
418 $t_trace = '';
420 if ( extension_loaded( 'xdebug' ) ) {
422 #check for xdebug presence
423 $t_stack = xdebug_get_function_stack();
425 # reverse the array in a separate line of code so the
426 # array_reverse() call doesn't appear in the stack
427 $t_stack = array_reverse( $t_stack );
428 array_shift( $t_stack );
430 #remove the call to this function from the stack trace
431 foreach( $t_stack as $t_frame ) {
432 $t_trace .= ( isset( $t_frame['file'] ) ? basename( $t_frame['file'] ) : 'UnknownFile' ) . ' L' . ( isset( $t_frame['line'] ) ? $t_frame['line'] : '?' ) . ' ' . ( isset( $t_frame['function'] ) ? $t_frame['function'] : 'UnknownFunction' );
434 $t_args = array();
435 if ( isset( $t_frame['params'] ) && ( count( $t_frame['params'] ) > 0 ) ) {
436 $t_trace .= ' Params: ';
437 foreach( $t_frame['params'] as $t_value ) {
438 $t_args[] = error_build_parameter_string( $t_value );
441 $t_trace .= '(' . implode( $t_args, ', ' ) . ')';
442 } else {
443 $t_trace .= '()';
446 $t_trace .= "\n";
448 } else {
449 $t_stack = debug_backtrace();
451 array_shift( $t_stack ); #remove the call to this function from the stack trace
452 array_shift( $t_stack ); #remove the call to the error handler from the stack trace
454 foreach( $t_stack as $t_frame ) {
455 $t_trace .= ( isset( $t_frame['file'] ) ? basename( $t_frame['file'] ) : 'UnknownFile' ) . ' L' . ( isset( $t_frame['line'] ) ? $t_frame['line'] : '?' ) . ' ' . ( isset( $t_frame['function'] ) ? $t_frame['function'] : 'UnknownFunction' );
457 $t_args = array();
458 if( isset( $t_frame['args'] ) ) {
459 foreach( $t_frame['args'] as $t_value ) {
460 $t_args[] = error_build_parameter_string( $t_value );
463 $t_trace .= '(' . implode( $t_args, ', ' ) . ')';
464 } else {
465 $t_trace .= '()';
468 $t_trace .= "\n";
472 return $t_trace;
476 * Returns a soap_fault signalling corresponding to a failed login
477 * situation
479 * @return soap_fault
481 function mci_soap_fault_login_failed() {
482 return new soap_fault('Client', '', 'Access denied.');
486 * Returns a soap_fault signalling that the user does not have
487 * access rights for the specific action.
489 * @param int $p_user_id a valid user id
490 * @param string $p_detail The optional details to append to the error message
491 * @return soap_fault
493 function mci_soap_fault_access_denied( $p_user_id, $p_detail = '' ) {
494 $t_user_name = user_get_name( $p_user_id );
495 $t_reason = 'Access denied for user '. $t_user_name . '.';
496 if ( !is_blank( $p_detail ))
497 $t_reason .= ' Reason: ' . $p_detail . '.';
499 return new soap_fault( 'Client', '', $t_reason );