<?php
// $Id: soapclient.module,v 1.1.4.2 2009/10/12 23:50:02 ilo Exp $

/**
 * @file
 * Provide basic SOAP Client API for other modules.
 */

/* ---------------------------------
 * GLOBAL
 * --------------------------------- */
global $_soapclient_MCRYPT, $_soapclient_AES128, $_soapclient_LIBRARY;

/* ---------------------------------
 * DRUPAL HOOK
 * --------------------------------- */

/**
 * Implementation of hook_init();
 */
function soapclient_init() {
  global $_soapclient_MCRYPT;
  global $_soapclient_AES128;

  // check, which encryption library to be used.
  $_soapclient_MCRYPT = NULL;
  $_soapclient_AES128 = NULL;
  
  if ( ! extension_loaded('mcrypt') ) {
    // try loading the mcrypt
    $ext = ( drupal_strtoupper( drupal_substr(PHP_OS, 0, 3) ) === 'WIN' ) ? 'php_mcrypt.dll' : 'mcrypt.so';
    if ( ! @dl($ext) ) {
      // cannot load mcrypt, use AES128 for password encryption.
      require_once("lib/AES128.php");
      $_soapclient_AES128 = new AES128(FALSE, FALSE);
      return;
    }
  }
  
  $_soapclient_MCRYPT = mcrypt_module_open(MCRYPT_3DES, '', 'cbc', '');
}

/**
 * Implementation of hook_exit();
 */
function soapclient_exit($destination = NULL) {
  global $_soapclient_MCRYPT;
  
  if ( $_soapclient_MCRYPT != NULL ) {
    mcrypt_module_close($_soapclient_MCRYPT);
    $_soapclient_MCRYPT = NULL;
  }
}

/**
 * Implementation of hook_help();
 */
function soapclient_help($section = '') {
  switch ($section) {
    case 'admin/settings/soapclient':
      return t('Configure SOAPClient configuration');
  }
}

/**
 * Implementation of hook_perm();
 */
function soapclient_perm() {
  return array('configure SOAPClient', 'access SOAPClient Test Page');
}

/**
 * Implementation of hook_menu();
 */
function soapclient_menu() {
  $items = array();

  $items['admin/settings/soapclient'] = array(
    'title'            => 'SOAP Client',
    'description'      => 'Configure and test the SOAP Client',
    'type'             => MENU_NORMAL_ITEM,
    'page callback'    => 'drupal_get_form',
    'page arguments'   => array('soapclient_admin'),
    'access callback'  => 'user_access',
    'access arguments' => array('configure SOAPClient'),
  );

  $items['admin/settings/soapclient/config'] = array(
    'title'       => 'Configure',
    'description' => 'Change the proxy and timeout configuration for SOAPClient.',
    'type'        => MENU_DEFAULT_LOCAL_TASK,
    'access callback'  => 'user_access',
    'access arguments' => array('configure SOAPClient'),
  );

  $items['admin/settings/soapclient/test'] = array(
    'title'       => 'Test/Demo',
    'description' => 'The test page for SOAP Client',
    'type'        => MENU_LOCAL_TASK,
    'access callback'  => 'user_access',
    'access arguments' => array('configure SOAPClient'),
  );

  return $items;
}

/* ---------------------------------
 * FORMS and UI
 * --------------------------------- */
function soapclient_admin() {
  switch (arg(3)) {
    case 'test':
      $title = t('Test SOAP Client');
      $output = soapclient_test();
      break;

    case 'config':
    default:
      $title = t('Configure SOAP Client');
      $output = soapclient_config();
  }

  drupal_set_title($title);
  return $output;
}

/**
 * Traverse the sites/all and sites/%current_site directories looking for any
 * installed version of the nuSOAP library.
 *
 * @return
 * Array of nusoap libraries found in the system
 */
function soapclient_find_nusoap_library() {
  //find installed nuSOAP libraries in sites/%current_site and sites/all. See
  //includes/boostrap.inc for conf_path()
  $searchdir[] = './'. conf_path();
  $searchdir[] = './sites/all';

  //Get current list of nusoap libraries installed. Just looks for nusoap.php file
  $nusoap_libs = array();
  foreach ($searchdir as $dir) {
    $nusoap_libs = array_merge($nusoap_libs, file_scan_directory($dir, "nusoap\.php$"));
  }
  return $nusoap_libs;
}
    
// configuration form
function soapclient_config() {

  $_soapclient_LIBRARY = variable_get('soapclient_lib', 'auto');

  $form    = array();
  $libinfo = soapclient_get_libname();

  $form['library'] = array(
    '#value' => t('<ul>'.
                  '<li>Current SOAP Client Library is <b>!soaplib</b>!soapmode</li>' .
                  '<li>Current Encryption Library is <b>!cryptlib</b></li>'.
                  '</ul>',
                  array(
                    '!soaplib'  => $libinfo['desc'],
                    '!soapmode' => $_soapclient_LIBRARY == 'auto' ? ' (auto)': '',
                    '!cryptlib' => $_soapclient_MCRYPT != NULL ? 'mcrypt extension' : 'AES128 bundled library',
                  )
                ),  
  );

  $form['soapclient_lib'] = array(
    '#type'    => 'radios',
    '#title'   => t('Active SOAP Library'),
    '#options' => array(
                    'PHP5SOAP' => t('SOAP Extension on PHP5'),
                    'nuSOAP'   => t('nuSOAP'),
                    'auto'     => t('Auto detect (PHP5 SOAP first then nuSOAP)'),
                  ),
    '#default_value' => variable_get('soapclient_lib', 'auto'),
    '#description'   => t('Which SOAP library do you want to use? On PHP5, you may use the native SOAP extension, '.
                          'nuSOAP or PHP4 and later. Currently the <strong>!lib</strong> is activated.',
                          array('!lib' => t($libinfo['desc']) )),
  );

  //Find all the possible installed version of nuSOAP library
  $nusoap_libs = soapclient_find_nusoap_library();

  //@TODO: If phpsoap is not enabled, and auto or nusoap have been selected, and no nusoap is detected, drop a message
  //Addecuate a message for the admin to configure nuSOAP library.
  if (count($nusoap_libs)) {
    foreach ($nusoap_libs as $lib) {
      $locations .= $lib->filename ."\n";
    }
  }

  //get the first occurrence of nusoap.php from the scan results
  $default_library = (is_array($nusoap_libs) && count($nusoap_libs)) ? reset($nusoap_libs)->filename : NULL;

  $form['soapclient_nusoap_path'] = array(
    '#type'    => 'textfield',
    '#title'   => t('nuSOAP location'),
    '#size'  => 120,
    '#maxlength' => 255,
    '#default_value' => variable_get('soapclient_nusoap_path', $default_library),
    '#description'   => t('The location of the nusoap library. '.
                          'This is the result of the nuSOAP library SCAN:<strong><br><pre>!lib</pre></strong>',
                          array('!lib' => !empty($locations) ? $locations : "nuSOAP library Not found."  )),
  );

  $form['soapclient_proxyhost'] = array(
    '#type'  => 'textfield',
    '#title' => t('Proxy Host'),
    '#size'  => 32,
    '#maxlength'     => 256,
    '#default_value' => variable_get('soapclient_proxyhost', ''),
    '#description'   => t('Enter the IP address or host name of the proxy server between '.
                          'your Drupal server and the SOAP server, or leave blank if not applicable'),
  );

  $form['soapclient_proxyport'] = array(
    '#type'  => 'textfield',
    '#title' => t('Proxy Port'),
    '#size'  => 5,
    '#maxlength'     => 5,
    '#default_value' => variable_get('soapclient_proxyport', ''),
    '#description'   => t('Enter the IP port of the proxy server between your Drupal server '.
                          'and the SOAP server, or leave blank if not applicable'),
  );

  $form['soapclient_proxyuser'] = array(
    '#type'  => 'textfield',
    '#title' => t('Proxy User'),
    '#size'  => 32,
    '#maxlength'     => 64,
    '#default_value' => variable_get('soapclient_proxyuser', ''),
    '#description'   => t('Enter the user name for the proxy server between your Drupal server '.
                          'and the SOAP server, or leave blank if not applicable'),
  );

  $form['soapclient_proxypass'] = array(
    '#type'  => 'textfield',
    '#title' => t('Proxy Password'),
    '#size'  => 32,
    '#maxlength'     => 64,
    '#default_value' => variable_get('soapclient_proxypass', ''),
    '#description'   => t('Enter the password for the proxy server between your Drupal server '.
                          'and the SOAP server, or leave blank if not applicable'),
  );

  if ( $_soapclient_LIBRARY == 'nuSOAP' ) {
    $form['soapclient_reqtimeout'] = array(
      '#type'  => 'textfield',
      '#title' => t('SOAP Server Connection Timeout'),
      '#size'  => 5,
      '#maxlength'     => 5,
      '#default_value' => variable_get('soapclient_reqtimeout', 10),
      '#description'   => t('Enter the period of time (in seconds) to wait until the connection is '.
                            'established when your Drupal server connect to the SOAP server. (The '.
                            'default value is 10 seconds)'),
    );

    $form['soapclient_reptimeout'] = array(
      '#type'  => 'textfield',
      '#title' => t('SOAP Server Response Timeout'),
      '#size'  => 5,
      '#maxlength'     => 5,
      '#default_value' => variable_get('soapclient_reptimeout', 30),
      '#description'   => t('Enter the period of time (in seconds) to wait until the result is '.
                            'returned when your Drupal server request for a service from the SOAP '.
                            'server. (The default value is 30 seconds)'),
    );

    $form['soapclient_wsdlcache_path'] = array(
      '#type'  => 'textfield',
      '#title' => t('WSDL Cache Directory'),
      '#size'  => 64,
      '#maxlength'     => 255,
      '#default_value' => variable_get('soapclient_wsdlcache_path', variable_get('file_directory_temp', NULL)),
      '#description'   => t('Enter the directory to allow soap client keep it\'s WSDL cache. Note that the '.
                            'read/write permission is required.'),
    );

    $form['soapclient_wsdlcache_lifetime'] = array(
      '#type'  => 'textfield',
      '#title' => t('WSDL Cache Lifetime'),
      '#size'  => 5,
      '#maxlength'     => 5,
      '#default_value' => variable_get('soapclient_wsdlcache_lifetime', 60),
      '#description'   => t('Enter the period of time (in seconds) to keep the WSDL cache. (The default value is 60 seconds)'),
    );
  }

  return system_settings_form($form);
}

// SOAP testing form
function soapclient_test() {
  $_soapclient_LIBRARY = variable_get('soapclient_lib', 'auto');
  
  $libinfo = soapclient_get_libname();

  $form['library'] = array(
    '#value' => t('<p>Current SOAP Client Library is <b>!lib</b>!mode</p>',
                array(
                  '!lib' => t($libinfo['desc']),
                  '!mode' => ($_soapclient_LIBRARY == 'auto' ? ' (auto)': '')
                )
              ),
  );


  $form['endpoint'] = array(
    '#type'  => 'textfield',
    '#title' => t('SOAP server endpoint URL'),
    '#size'  => 128,
    '#maxlength'   => 256,
    '#description' => t('Enter the absolute endpoint URL of the SOAP Server service. If WSDL is being used, this will be the URL to retrieve the WSDL.'),
    '#required'    => TRUE
  );

  $form['wsdl'] = array(
    '#type'  => 'checkbox',
    '#title' => t('Use WSDL?'),
    '#default_value' => 1,
  );

  $form['namespace'] = array(
    '#type'  => 'textfield',
    '#title' => t('Target Namespace'),
    '#size'  => 128,
    '#maxlength'   => 256,
    '#description' => t('If WSDL is <strong>not</strong> used, enter the target namespace URI here. Otherwise, leave it blank.'),
  );
  
  $form['use'] = array(
    '#type'  => 'radios',
    '#title' => 'Use',
    '#default_value' => 0,
    '#options'       => array('encoded', 'literal'),
    '#description'   => t('Specify how the SOAP client serialise the message.'),
  );

  $form['style'] = array(
    '#type'  => 'radios',
    '#title' => 'Style',
    '#default_value' => 0,
    '#options'       => array('rpc', 'document'),
    '#description'   => t('Specify the style of SOAP call.'),
  );

  $form['function'] = array(
    '#type'  => 'textfield',
    '#title' => t('SOAP Function'),
    '#size'  => 128,
    '#maxlength'   => 256,
    '#description' => t('Enter the function name to be called.'),
    '#required'    => TRUE
  );

  $form['arguments'] = array(
    '#type'  => 'textarea',
    '#title' => t('Agruments'),
    '#cols'  => 128,
    '#rows'  => 10,
    '#description' => t('Enter the arguments of the function. One argument per line, '.
                        'for named arguments, the format of <em>name=value<em> may be used.'),
  );

  $form['submit'] = array(
    '#type'  => 'submit',
    '#value' => t('Go!'),
  );

  $form['#submit'][] ='soapclient_test_submit';
  
  return $form;
}

// execute the testing form
function soapclient_test_submit($form, $form_state) {
  $param_str = preg_replace('/[\s]*=[\s]*/', '=', $form['arguments']['#value']);
  $params    = split("\n", $param_str);
  
  $args = array();

  foreach ($params as $param) {
    list($name, $value) = split('=', $param);
    
    if ( isset($value) ) {
      $args[$name] = trim($value);
    }
    else {
      $args[] = trim($name);
    }
  }
  
  $options = array();
  
  $options['namespace'] = $form['namespace']['#value'];
  $options['use']       = ( $form['use']['#value']   == 0 ? 'encoded' : 'literal' );
  $options['style']     = ( $form['style']['#value'] == 0 ? 'rpc'     : 'document');  
  
  $result = soapclient_init_client($form['endpoint']['#value'], $form['wsdl']['#value'], $options);
  
  if ( $result['#error'] !== FALSE ) {
    drupal_set_message(t('<h2>Error!</h2><pre>!msg</pre>', array('!msg' => $result['#error'])), 'error');
    return;
  }
  
  $result = $result['#return']->call($form['function']['#value'], $args);
  
  if ( $result['#error'] !== FALSE ) {
    drupal_set_message(t('<h2>Error!</h2><pre>!msg</pre>', array('!msg' => $result['#error'])), 'error');
  }
  else {
    drupal_set_message(t('Return value is: <pre>@return</pre>', array('@return' => print_r($result['#return'], TRUE))));
  }
}

/* ---------------------------------
 * API
 * --------------------------------- */

/**
 * Determine, which library we are using?
 *
 * @return
 *   An associative array of the SOAP library
 *   information with the following keys:
 *   - 'lib'  : Short name of the library/extension.
 *              Possible values are PHP5SOAP, nuSOAP, N/A
 *   - 'desc' : Full name of the library/extension.
 */
function soapclient_get_libname() {
  $_soapclient_LIBRARY = variable_get('soapclient_lib', 'auto');

  //Convert 'auto' detection library into a real library
  if (variable_get('soapclient_lib', 'auto') == 'auto') {
    //check for PHP5SOAP first, use nuSOAP if PHP5SOAP not available.
    if ( extension_loaded('soap') ) {
      $_soapclient_LIBRARY = 'PHP5SOAP';
    }
    else {
      if (file_exists(variable_get('soapclient_nusoap_path', ''))){
        $_soapclient_LIBRARY = 'nuSOAP';
      }
    }
  }
  $info = array();
  
  switch ($_soapclient_LIBRARY) {
    case 'PHP5SOAP':
      $info['lib']  = 'PHP5SOAP';
      $info['desc'] = 'Native SOAP extension on PHP5';
      break;

    case 'nuSOAP':
      $info['lib']  = 'nuSOAP';
      $info['desc'] = 'nuSOAP Library';
      break;

    default:
      $info['lib']  = 'N/A';
      $info['desc'] = 'None or not supported SOAP Client Library';
      break;
  }

  return $info;
}

/**
 * Determine, which encryption library are we using?
 * Note: When using with a SugarCRM server with LDAP prrovisioning, the mcrypt
 * library is required.
 *
 * @return
 * string with the encryption library name.
 * - 'MCRYPT' - We are using mcrypt module.
 * - 'AES128' - We are using bundled AES128 encryption.
 * - 'Error'  - Cannot determine the encryption library.
 */
function soapclient_get_encryption() {
  global $_soapclient_MCRYPT;
  global $_soapclient_AES128;
  
  if ( $_soapclient_MCRYPT != NULL ) {
    return 'MCRYPT';
  }
  
  if ( $_soapclient_AES128 != NULL ) {
    return 'AES128';
  }
  
  return 'Error';
}

/**
 * Check if a variable is empty (including white space strings), or
 * it contains the value "0".
 *
 * @param  $var
 *   A string to check if for empty.
 * 
 * @return 
 *   Returns BOOLEN FALSE if $var is empty or contains only a white space string
 */
function soapclient_empty_string($var) {
  $result = trim($var);
  return (!is_numeric($result)) ? empty($result) : FALSE;
}

/**
 * Initialising the client.
 *
 * @param $endpoint
 *   A string represent the service endpoint URL, or the URL for
 *   the WSDL if WSDL mode is enabled.
 *
 * @param $use_wsdl
 *   Boolean value to configure soapclient to use (TRUE) or
 *   not use (FALSE) a WSDL file.
 *
 * @param $options
 *   An associative array to supply as the options for SOAP library.
 *   Following options will be mapped to an appropriate parameters
 *   for each library automatically.
 *
 *   - namespace : the target namespace URI
 *   - use       : 'encoded' or 'literal'
 *   - style     : 'rpc' or 'document'
 *
 *   Following options will be supplied as 'call()' arguments for
 *   nuSOAP.
 *
 *   - action  : optional SOAPAction value (WSDL can override)
 *   - headers : optional string of XML with SOAP header content, or
 *               array of soapval objects for SOAP headers
 *
 *   For additional options, please consult the PHP5 manual or the
 *   NuSOAP manual for details.
 *
 *   Note that the proxy information will automatically append to
 *   the $options array. Is can be configured via the soapclient's
 *   configuration page.
 *
 * @return
 *   An array of the result with following keys:
 *   - #error  : false, if no error. Otherwise, it is the error message
 *   - #return : DrupalSoapClient instance - see drupal_soap_client.inc
 *               for more details.
 */
function soapclient_init_client($endpoint, $use_wsdl, $options = array()) {

  $_soapclient_LIBRARY = variable_get('soapclient_lib', 'auto');
  //Convert 'auto' detection library into a real library
  if (variable_get('soapclient_lib', 'auto') == 'auto') {
    //check for PHP5SOAP first, use nuSOAP if PHP5SOAP not available.
    if ( extension_loaded('soap') ) {
      $_soapclient_LIBRARY = 'PHP5SOAP';
    }
    else {
      if (file_exists(variable_get('soapclient_nusoap_path', ''))) {
        $_soapclient_LIBRARY = 'nuSOAP';
      }
    }
  }

  $client = NULL;
  $result = array();
  $result['#error']  = FALSE;  
  $result['#return'] = $client;
    
  if ( empty($endpoint) ) {
    $result['#error'] = t('Service endpoint is empty!');
    return $result;
  }

  // validate options
  if ( isset($options['use']) && ! in_array($options['use'], array('literal', 'encoded')) ) {
    $result['#error'] = t("Invalid 'use' value - {$options['use']}. Valid values are 'literal' or 'encoded')");
    return $result;
  }
  
  if ( isset($options['style']) && ! in_array($options['style'], array('rpc', 'document')) ) {
    $result['#error'] = t("Invalid 'style' value - {$options['style']}. Valid values are 'rpc' or 'document')");
    return $result;
  }

  $options = (array) $options;
  
  $proxyhost = variable_get('soapclient_proxyhost', '');
  if (soapclient_empty_string($proxyhost)) $proxyhost = NULL;
  $proxyport = variable_get('soapclient_proxyport', '');
  if (soapclient_empty_string($proxyport)) $proxyport = NULL;
  $proxyuser = variable_get('soapclient_proxyuser', '');
  if (soapclient_empty_string($proxyuser)) $proxyuser = NULL;
  $proxypass = variable_get('soapclient_proxypass', '');
  if (soapclient_empty_string($proxypass)) $proxypass = NULL;

  if ( $_soapclient_LIBRARY == 'nuSOAP' ) {

    //Checks for correct nuSOAP configuration.
    //Check if library is configured.
    $nusoap_location = variable_get('soapclient_nusoap_path', '');
    if ( soapclient_empty_string($nusoap_location) ) {
      $result['#error'] = t('nuSOAP library is not configured.');
      return $result;
    }

    //Check if library is really there
    if ( !file_exists($nusoap_location) ) {
      $result['#error'] = t('nuSOAP library is not available at !lib!', array('!lib' => $_soapclient_LIBRARY) );
      return $result;
    }

    //Load required library and cache classes.
    require_once($nusoap_location);
    require_once(str_replace('nusoap.php', 'class.wsdlcache.php', $nusoap_location));

    //prepare parameters for initialisation
    $reqtimeout = variable_get('soapclient_reqtimeout', 10);
    $reptimeout = variable_get('soapclient_reptimeout', 30);

    if ( $use_wsdl ) {        
      // load from cache first
      $cache = new wsdlcache(variable_get('soapclient_wsdlcache_path', variable_get('file_directory_temp', NULL)), variable_get('soapclient_wsdlcache_lifetime', 60));
      $wsdl = $cache->get($url);

      if ( empty($wsdl) ) {
        // not available, create new one
        $wsdl = new wsdl($endpoint, $proxyhost, $proxyport, $proxyuser, $proxypass);
        $cache->put($wsdl);
      }
      
      $endpoint = $wsdl;
    }

    //Old style, using modified nusoap client as up to
    if (class_exists('nusoapClient')) {
      $client = new nusoapClient($endpoint, $use_wsdl, $proxyhost, $proxyport, $proxyuser, $proxypass, $reqtimeout, $reptimeout);
    }else {
      //This should be the new nusoap client as downloaded from sf.
      $client = new nusoap_client($endpoint, $use_wsdl, $proxyhost, $proxyport, $proxyuser, $proxypass, $reqtimeout, $reptimeout);
    }

    $err_msg = $client->getError();

    if ( ! empty($err_msg) ) {
      $result['#error'] = t('Cannot create client - !msg', array('!msg' => $err_msg));
      return $result;
    }
  }
  elseif ( $_soapclient_LIBRARY == 'PHP5SOAP' ) {

    //Check PHP5SOAP module availability
    if ( !extension_loaded('soap') ) {
      $result['#error'] = t('PHP5 SOAP was the selected library to use by Soap Client module, but the extension is not enabled. Please follow the documentation of how to enable PHP5 SOAP extension.');
      return $result;
    }

    if ( ! $use_wsdl ) {
      if ( empty($options['namespace']) ) {
        $result['#error'] = t('Target namespace uri is required when using non-WSDL mode of PHP5 SOAP');
        return $result;
      }

      $options['location'] = $endpoint;
      $options['uri']      = $options['namespace'];      
    }

    $options['proxy_host']     = $proxyhost == '' ? NULL : $proxyhost;
    $options['proxy_port']     = $proxyport;
    $options['proxy_login']    = $proxyuser;
    $options['proxy_password'] = $proxypass;
    
    if ( isset($options['use']) ) {
      $options['use'] = ( $options['use'] == 'literal' ? SOAP_LITERAL : SOAP_ENCODED );
    }
    
    if ( isset($options['style']) ) {
      $options['style'] = ( $options['style'] == 'document' ? SOAP_DOCUMENT : SOAP_RPC );
    }

    try {
      $client = new SoapClient( ($use_wsdl ? $endpoint : NULL), $options );
    }
    catch (Exception $e) {
      $result['#error'] = t($e->getMessage());
      return $result;
    }
  }
  else {
    // not supported library
    $result['#error'] = t("Un-supported SOAP library - $_soapclient_LIBRARY");
    return $result;
  }

  //Load the SoapClient class definition
  module_load_include('inc', 'soapclient', 'lib/drupal_soap_client');
  $result['#return'] = new DrupalSoapClient($_soapclient_LIBRARY, $client, $options);
  
  return $result;
}

/**
 * Helper function to convert XML string to multi-dimension array.
 *
 * @param $xml
 *   an XML string.
 *
 * @return
 *   An array of the result with following keys:
 *   - #error  : false, if no error. Otherwise, it is the error message
 *   - #return : a multi-dimension associative array represent the value
 *               of the XML input string.
 */
function soapclient_parse_xml_result($xml) {
  $input  = array();
  $result = array();
  
  $result['#error']  = FALSE;
  $result['#return'] = NULL;

  $xmlparser = xml_parser_create();
  $ret = xml_parse_into_struct($xmlparser, $xml, $input);
  xml_parser_free($xmlparser);

  if ( empty($input) ) {
    $result['#return'] = $xml;
  }
  else {
    if ( $ret > 0 ) {
      $result['#return'] = _soapclient_parse($input);
    }
    else {
      $result['#error'] = t('Error parsing XML result - error code = '. xml_get_error_code($xmlparser) .' at '.
                            'line '. xml_get_current_line_number($xmlparser) .' '.
                            'char '. xml_get_current_column_number($xmlparser));
    }
  }

  return $result;
}

// private helper for soapclient_parse_xml_result, to recusively parsing the result
function _soapclient_parse($input, $depth = 1) {
  $output = array();
  $children = array();

  foreach ( $input as $data ) {
    if ( $data['level'] == $depth ) {
      switch ($data['type']) {
        case 'complete':
          $output[$data['tag']] = $data['value'];
          break;

        case 'open':
          $children = array();
          break;

        case 'close':
          $output[$data['tag']] = _soapclient_parse($children, $depth + 1);
          break;
      }
    }
    else {
      $children[] = $data;
    }
  }
  return $output;
}

/**
 * encrypt text
 *
 * @param $plain
 * Plain text in a string (any length)
 *
 * @param $key
 * Encryption key in a string (any length)
 *
 * @param $iv
 * Encryption initialisation vector in a string. String longer than
 * 8 character will be truncated to 8 character before use.
 *
 * @return
 * Encrypted text in a set of hexadecimal digits string.
 */
// encrypt text
function soapclient_encrypt($plain, $key, $iv = 'password') {
  global $_soapclient_MCRYPT;
  global $_soapclient_AES128;
  
  if ( empty($plain) ) {
    return $plain;
  }
  
  if ( $_soapclient_MCRYPT != NULL ) {
    $key = drupal_substr(md5($key), 0, mcrypt_enc_get_key_size($_soapclient_MCRYPT));
    mcrypt_generic_init($_soapclient_MCRYPT, $key, drupal_substr($iv, 0, 8));
    $ct = bin2hex(mcrypt_generic($_soapclient_MCRYPT, $plain));
    mcrypt_generic_deinit($_soapclient_MCRYPT);
  }
  else {    
    $key = $_soapclient_AES128->makeKey(md5($key, TRUE)); // to ensure that key is 128-bytes length
    
    $ct = array();

    // block encryption
    for ( $i = 0; $i < drupal_strlen($plain); $i += 16 ) {
      $st = drupal_substr($plain, $i);
      $bl = drupal_strlen($st);
      $bl = ( $bl > 16 ) ? 16 : $bl;

      $ct[] = sprintf("%02d", $bl) . bin2hex($_soapclient_AES128->blockEncrypt(drupal_substr($st, 0, $bl), $key));
    }
    
    $ct = implode("\n", $ct);    
  }
  
  return $ct;
} 

/**
 * decrypt text, encrypted by soapclient_encrypt
 * @TODO Check MultiByte functionality..
 *
 * @param $cipher
 * Hexadecimal digits string of an encrypted text from soapclient_encrypt()
 *
 * @param $key
 * String with encryption key as supplied to soapclient_encrypt()
 *
 * @param $iv
 * Encryption initialisation vector in a string. String longer than
 * 8 character will be truncated to 8 character before use.
 *
 * @return
 * String with decrypted text.
 */
function soapclient_decrypt($cipher, $key, $iv = 'password') {
  global $_soapclient_MCRYPT;
  global $_soapclient_AES128;
  
  if ( empty($cipher) ) {
    return $cipher;
  }
  
  if ( $_soapclient_MCRYPT != NULL ) {
    $key = drupal_substr(md5($key), 0, mcrypt_enc_get_key_size($_soapclient_MCRYPT));
    mcrypt_generic_init($_soapclient_MCRYPT, $key, drupal_substr($iv, 0, 8));
    $pt = mdecrypt_generic($_soapclient_MCRYPT, pack("H*", $cipher));
    
    if ( strpos($pt, "\0") > 0 ) {
      $pt = drupal_substr($pt, 0, strpos($pt, "\0"));
    }
    
    mcrypt_generic_deinit($_soapclient_MCRYPT);
  }
  else {
    $key = $_soapclient_AES128->makeKey(md5($key, TRUE)); // to ensure that key is 128-bytes length

    $pt = '';
    $ct = explode("\n", $cipher);
    
    for ( $i = 0; $i < count($ct); $i++ ) {
      $st = $_soapclient_AES128->blockDecrypt(pack("H*", drupal_substr($ct[$i], 2)), $key);

      if ( $i >= count($ct) - 1 ) {
        $st = drupal_substr($st, 0, (int) drupal_substr($ct[$i], 0, 2));
      }

      $pt .= $st;
    }
  }
  return $pt;
}
