As long as we decide to support both implementations we should add all non-generic * factory code to this class.

*/ class SoapObjectsFactory { static function newSoapFault( $p_fault_code, $p_fault_string) { if ( class_exists('soap_fault') ) return new soap_fault( $p_fault_code, '', $p_fault_string ); else return new SoapFault( $p_fault_code, $p_fault_string ); } static function unwrapObject( $p_object ) { if ( is_object( $p_object ) ) return get_object_vars( $p_object ); return $p_object; } static function newDateTimeVar( $p_value ) { $string_value = self::newDateTimeString( $p_value ); if ( class_exists('soapval') ) return new soapval( 'due_date', 'xsd:dateTime', $string_value ); else return new SoapVar( $string_value, XSD_DATETIME, 'xsd:dateTime'); } static function newDateTimeString ( $p_timestamp ) { if ( $p_timestamp == null || date_is_null( $p_timestamp) ) return null; else if ( function_exists('timestamp_to_iso8601') ) return timestamp_to_iso8601( $p_timestamp, false); else { return date('c', (int) $p_timestamp); } } static function parseDateTimeString ( $p_string ) { if ( function_exists('iso8601_to_timestamp') ) { return iso8601_to_timestamp( $p_string ); } else { return strtotime( $p_string ); } } static function encodeBinary ( $p_binary ) { if ( class_exists('soap_fault') ) return base64_encode( $p_binary ); else return $p_binary; } static function isSoapFault ( $p_maybe_fault ) { if ( ! is_object( $p_maybe_fault ) ) return false; if ( class_exists('soap_fault') ) { return get_class($p_maybe_fault ) == 'soap_fault'; } else { return get_class($p_maybe_fault ) == 'SoapFault'; } } } /** * Abstract the differences in common actions between the php5 soap extension and nusoap * *

As long as we decide to support both implementations we should add all non-generic * action code to this class.

*/ class SoapActions { /** * Sends a fault to the user and immediately terminates processing * * @param string $p_error_type * @param string $p_error_message * @throws SoapFault */ static function sendSoapFault ( $p_error_type, $p_error_message) { global $l_oServer; if ( $l_oServer ) { $l_oServer->fault( $p_error_type, $p_error_message); $l_oServer->send_response(); exit(); } else { throw new SoapFault($p_error_type, $p_error_message); } } } /** * Get the MantisConnect webservice version. */ function mc_version() { return MANTIS_VERSION; } /** * Attempts to login the user. * If logged in successfully, return user information. * If failed to login in, then throw a fault. */ function mc_login( $p_username, $p_password ) { $t_user_id = mci_check_login( $p_username, $p_password ); if ( $t_user_id === false ) { return mci_soap_fault_login_failed(); } return mci_user_get( $p_username, $p_password, $t_user_id ); } /** * Given an id, this method returns the user. * When calling this method make sure that the caller has the right to retrieve * information about the target user. */ function mci_user_get( $p_username, $p_password, $p_user_id ) { $t_user_data = array(); // if user doesn't exist, then mci_account_get_array_by_id() will throw. $t_user_data['account_data'] = mci_account_get_array_by_id( $p_user_id ); $t_user_data['access_level'] = access_get_global_level( $p_user_id ); $t_user_data['timezone'] = user_pref_get_pref( $p_user_id, 'timezone' ); return $t_user_data; } # access_ if MantisBT installation is marked as offline by the administrator. # true: offline, false: online function mci_is_mantis_offline() { $t_offline_file = dirname( dirname( __FILE__ ) ) . DIRECTORY_SEPARATOR . 'mantis_offline.php'; return file_exists( $t_offline_file ); } # return user_id if successful, otherwise false. function mci_check_login( $p_username, $p_password ) { if( mci_is_mantis_offline() ) { return false; } # if no user name supplied, then attempt to login as anonymous user. if( is_blank( $p_username ) ) { $t_anon_allowed = config_get( 'allow_anonymous_login' ); if( OFF == $t_anon_allowed ) { return false; } $p_username = config_get( 'anonymous_account' ); # do not use password validation. $p_password = null; } else { if( is_blank( $p_password ) ) { # require password for authenticated access return false; } } if( false === auth_attempt_script_login( $p_username, $p_password ) ) { return false; } return auth_get_current_user_id(); } function mci_has_readonly_access( $p_user_id, $p_project_id = ALL_PROJECTS ) { $t_access_level = user_get_access_level( $p_user_id, $p_project_id ); return( $t_access_level >= config_get( 'mc_readonly_access_level_threshold' ) ); } function mci_has_readwrite_access( $p_user_id, $p_project_id = ALL_PROJECTS ) { $t_access_level = user_get_access_level( $p_user_id, $p_project_id ); return( $t_access_level >= config_get( 'mc_readwrite_access_level_threshold' ) ); } function mci_has_access( $p_access_level, $p_user_id, $p_project_id = ALL_PROJECTS ) { $t_access_level = user_get_access_level( $p_user_id, $p_project_id ); return( $t_access_level >= (int) $p_access_level ); } function mci_has_administrator_access( $p_user_id, $p_project_id = ALL_PROJECTS ) { $t_access_level = user_get_access_level( $p_user_id, $p_project_id ); return( $t_access_level >= config_get( 'mc_admin_access_level_threshold' ) ); } function mci_get_project_id( $p_project ) { if ( is_object( $p_project ) ) $p_project = get_object_vars( $p_project ); if ( isset( $p_project['id'] ) && (int) $p_project['id'] != 0 ) { $t_project_id = (int) $p_project['id']; } else if ( isset( $p_project['name'] ) && !is_blank( $p_project['name'] ) ) { $t_project_id = project_get_id_by_name( $p_project['name'] ); } else { $t_project_id = ALL_PROJECTS; } return $t_project_id; } function mci_get_project_status_id( $p_status ) { return mci_get_enum_id_from_objectref( 'project_status', $p_status ); } function mci_get_project_view_state_id( $p_view_state ) { return mci_get_enum_id_from_objectref( 'project_view_state', $p_view_state ); } function mci_get_user_id( $p_user ) { $p_user = SoapObjectsFactory::unwrapObject( $p_user ); $t_user_id = 0; if ( isset( $p_user['id'] ) && (int) $p_user['id'] != 0 ) { $t_user_id = (int) $p_user['id']; } elseif ( isset( $p_user['name'] ) ) { $t_user_id = user_get_id_by_name( $p_user['name'] ); } elseif ( isset( $p_user['email'] ) ) { $t_user_id = user_get_id_by_email( $p_user['email'] ); } return $t_user_id; } function mci_get_user_lang( $p_user_id ) { $t_lang = user_pref_get_pref( $p_user_id, 'language' ); if( $t_lang == 'auto' ) { $t_lang = config_get( 'fallback_language' ); } return $t_lang; } function mci_get_status_id( $p_status ) { return mci_get_enum_id_from_objectref( 'status', $p_status ); } function mci_get_severity_id( $p_severity ) { return mci_get_enum_id_from_objectref( 'severity', $p_severity ); } function mci_get_priority_id( $p_priority ) { return mci_get_enum_id_from_objectref( 'priority', $p_priority ); } function mci_get_reproducibility_id( $p_reproducibility ) { return mci_get_enum_id_from_objectref( 'reproducibility', $p_reproducibility ); } function mci_get_resolution_id( $p_resolution ) { return mci_get_enum_id_from_objectref( 'resolution', $p_resolution ); } function mci_get_projection_id( $p_projection ) { return mci_get_enum_id_from_objectref( 'projection', $p_projection ); } function mci_get_eta_id( $p_eta ) { return mci_get_enum_id_from_objectref( 'eta', $p_eta ); } function mci_get_view_state_id( $p_view_state ) { return mci_get_enum_id_from_objectref( 'view_state', $p_view_state ); } # Get null on empty value. # # @param Object $p_value The value # @return Object The value if not empty; null otherwise. # function mci_null_if_empty( $p_value ) { if( !is_blank( $p_value ) ) { return $p_value; } return null; } /** * Removes any invalid character from the string per XML 1.0 specification * * @param string $p_input * @return string the sanitized XML */ function mci_sanitize_xml_string ( $p_input ) { return preg_replace( '/[^\x9\xA\xD\x20-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]+/u', '', $p_input); } /** * Gets the url for MantisBT. * * @return MantisBT URL terminated by a /. */ function mci_get_mantis_path() { return config_get( 'path' ); } # Given a enum string and num, return the appropriate localized string function mci_get_enum_element( $p_enum_name, $p_val, $p_lang ) { $t_enum_string = config_get( $p_enum_name . '_enum_string' ); $t_localized_enum_string = lang_get( $p_enum_name . '_enum_string', $p_lang ); return MantisEnum::getLocalizedLabel( $t_enum_string, $t_localized_enum_string, $p_val ); } # Gets the sub-projects that are accessible to the specified user / project. function mci_user_get_accessible_subprojects( $p_user_id, $p_parent_project_id, $p_lang = null ) { if( $p_lang === null ) { $t_lang = mci_get_user_lang( $p_user_id ); } else { $t_lang = $p_lang; } $t_result = array(); foreach( user_get_accessible_subprojects( $p_user_id, $p_parent_project_id ) as $t_subproject_id ) { $t_subproject_row = project_cache_row( $t_subproject_id ); $t_subproject = array(); $t_subproject['id'] = $t_subproject_id; $t_subproject['name'] = $t_subproject_row['name']; $t_subproject['status'] = mci_enum_get_array_by_id( $t_subproject_row['status'], 'project_status', $t_lang ); $t_subproject['enabled'] = $t_subproject_row['enabled']; $t_subproject['view_state'] = mci_enum_get_array_by_id( $t_subproject_row['view_state'], 'project_view_state', $t_lang ); $t_subproject['access_min'] = mci_enum_get_array_by_id( $t_subproject_row['access_min'], 'access_levels', $t_lang ); $t_subproject['file_path'] = array_key_exists( 'file_path', $t_subproject_row ) ? $t_subproject_row['file_path'] : ""; $t_subproject['description'] = array_key_exists( 'description', $t_subproject_row ) ? $t_subproject_row['description'] : ""; $t_subproject['subprojects'] = mci_user_get_accessible_subprojects( $p_user_id, $t_subproject_id, $t_lang ); $t_result[] = $t_subproject; } return $t_result; } function translate_category_name_to_id( $p_category_name, $p_project_id ) { if ( !isset( $p_category_name ) ) { return 0; } $t_cat_array = category_get_all_rows( $p_project_id ); foreach( $t_cat_array as $t_category_row ) { if( $t_category_row['name'] == $p_category_name ) { return $t_category_row['id']; } } return 0; } /** * Basically this is a copy of core/filter_api.php#filter_db_get_available_queries(). * The only difference is that the result of this function is not an array of filter * names but an array of filter structures. */ function mci_filter_db_get_available_queries( $p_project_id = null, $p_user_id = null ) { $t_filters_table = db_get_table( 'mantis_filters_table' ); $t_overall_query_arr = array(); if( null === $p_project_id ) { $t_project_id = helper_get_current_project(); } else { $t_project_id = db_prepare_int( $p_project_id ); } if( null === $p_user_id ) { $t_user_id = auth_get_current_user_id(); } else { $t_user_id = db_prepare_int( $p_user_id ); } # If the user doesn't have access rights to stored queries, just return if( !access_has_project_level( config_get( 'stored_query_use_threshold' ) ) ) { return $t_overall_query_arr; } # Get the list of available queries. By sorting such that public queries are # first, we can override any query that has the same name as a private query # with that private one $query = "SELECT * FROM $t_filters_table WHERE (project_id=" . db_param() . " OR project_id=0) AND name!='' AND (is_public = " . db_prepare_bool(true) . " OR user_id = " . db_param() . ") ORDER BY is_public DESC, name ASC"; $result = db_query_bound( $query, Array( $t_project_id, $t_user_id ) ); $query_count = db_num_rows( $result ); for( $i = 0;$i < $query_count;$i++ ) { $row = db_fetch_array( $result ); $t_filter_detail = explode( '#', $row['filter_string'], 2 ); if ( !isset($t_filter_detail[1]) ) { continue; } $t_filter = unserialize( $t_filter_detail[1] ); $t_filter = filter_ensure_valid_filter( $t_filter ); $row['url'] = filter_get_url( $t_filter ); $t_overall_query_arr[$row['name']] = $row; } return array_values( $t_overall_query_arr ); } /** * Get a category definition. * * @param integer $p_category_id The id of the category to retrieve. * @return Array an Array containing the id and the name of the category. */ function mci_category_as_array_by_id( $p_category_id ) { $t_result = array(); $t_result['id'] = $p_category_id; $t_result['name'] = category_get_name( $p_category_id ); return $t_result; } /** * Transforms a version array into an array suitable for marshalling into ProjectVersionData * * @param array $p_version */ function mci_project_version_as_array( $p_version ) { return array( 'id' => $p_version['id'], 'name' => $p_version['version'], 'project_id' => $p_version['project_id'], 'date_order' => SoapObjectsFactory::newDateTimeVar( $p_version['date_order'] ), 'description' => mci_null_if_empty( $p_version['description'] ), 'released' => $p_version['released'], 'obsolete' => $p_version['obsolete'] ); } /** * Returns time tracking information from a bug note. * * @param int $p_issue_id The id of the issue * @param Array $p_note A note as passed to the soap api methods * * @return String the string time entry to be added to the bugnote, in 'HH:mm' format */ function mci_get_time_tracking_from_note( $p_issue_id, $p_note) { if ( !access_has_bug_level( config_get( 'time_tracking_view_threshold' ), $p_issue_id ) ) return '00:00'; if ( !isset( $p_note['time_tracking'] )) return '00:00'; return db_minutes_to_hhmm($p_note['time_tracking']); } # Default error handler # # This handler will not receive E_ERROR, E_PARSE, E_CORE_*, or E_COMPILE_* # errors. # # E_USER_* are triggered by us and will contain an error constant in $p_error # The others, being system errors, will come with a string in $p_error # function mc_error_handler( $p_type, $p_error, $p_file, $p_line, $p_context ) { # check if errors were disabled with @ somewhere in this call chain # also suppress php 5 strict warnings if( 0 == error_reporting() || 2048 == $p_type ) { return; } # flush any language overrides to return to user's natural default if( function_exists( 'db_is_connected' ) ) { if( db_is_connected() ) { lang_push( lang_get_default() ); } } # build an appropriate error string switch( $p_type ) { case E_WARNING: $t_error_type = 'SYSTEM WARNING'; $t_error_description = $p_error; break; case E_NOTICE: $t_error_type = 'SYSTEM NOTICE'; $t_error_description = $p_error; break; case E_USER_ERROR: $t_error_type = "APPLICATION ERROR #$p_error"; $t_error_description = error_string( $p_error ); break; case E_USER_WARNING: $t_error_type = "APPLICATION WARNING #$p_error"; $t_error_description = error_string( $p_error ); break; case E_USER_NOTICE: # used for debugging $t_error_type = 'DEBUG'; $t_error_description = $p_error; break; default: #shouldn't happen, just display the error just in case $t_error_type = ''; $t_error_description = $p_error; } $t_error_stack = error_get_stack_trace(); error_log("[mantisconnect.php] Error Type: $t_error_type,\nError Description: $t_error_description\nStack Trace:\n$t_error_stack"); SoapActions::sendSoapFault('Server', "Error Type: $t_error_type,\nError Description: $t_error_description"); } # Get a stack trace if PHP provides the facility or xdebug is present function error_get_stack_trace() { $t_trace = ''; if ( extension_loaded( 'xdebug' ) ) { #check for xdebug presence $t_stack = xdebug_get_function_stack(); # reverse the array in a separate line of code so the # array_reverse() call doesn't appear in the stack $t_stack = array_reverse( $t_stack ); array_shift( $t_stack ); #remove the call to this function from the stack trace foreach( $t_stack as $t_frame ) { $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' ); $t_args = array(); if ( isset( $t_frame['params'] ) && ( count( $t_frame['params'] ) > 0 ) ) { $t_trace .= ' Params: '; foreach( $t_frame['params'] as $t_value ) { $t_args[] = error_build_parameter_string( $t_value ); } $t_trace .= '(' . implode( $t_args, ', ' ) . ')'; } else { $t_trace .= '()'; } $t_trace .= "\n"; } } else { $t_stack = debug_backtrace(); array_shift( $t_stack ); #remove the call to this function from the stack trace array_shift( $t_stack ); #remove the call to the error handler from the stack trace foreach( $t_stack as $t_frame ) { $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' ); $t_args = array(); if( isset( $t_frame['args'] ) ) { foreach( $t_frame['args'] as $t_value ) { $t_args[] = error_build_parameter_string( $t_value ); } $t_trace .= '(' . implode( $t_args, ', ' ) . ')'; } else { $t_trace .= '()'; } $t_trace .= "\n"; } } return $t_trace; } /** * Returns a soap_fault signalling corresponding to a failed login * situation * * @return soap_fault */ function mci_soap_fault_login_failed() { return SoapObjectsFactory::newSoapFault('Client', 'Access denied'); } /** * Returns a soap_fault signalling that the user does not have * access rights for the specific action. * * @param int $p_user_id a user id, optional * @param string $p_detail The optional details to append to the error message * @return soap_fault */ function mci_soap_fault_access_denied( $p_user_id = 0, $p_detail = '' ) { if ( $p_user_id ) { $t_user_name = user_get_name( $p_user_id ); $t_reason = 'Access denied for user '. $t_user_name . '.'; } else { $t_reason = 'Access denied'; } if ( !is_blank( $p_detail )) $t_reason .= ' Reason: ' . $p_detail . '.'; return SoapObjectsFactory::newSoapFault('Client', $t_reason); }