You are here

wsclient_soap.module in Web service client 7

Web service client SOAP support.

File

wsclient_soap/wsclient_soap.module
View source
<?php

/**
 * @file
 * Web service client SOAP support.
 */

/**
 * Implements hook_wsclient_endpoint_types().
 */
function wsclient_soap_wsclient_endpoint_types() {
  return array(
    'soap' => array(
      'label' => t('SOAP'),
      'class' => 'WSClientSOAPEndpoint',
    ),
    'soap 1.2' => array(
      'label' => t('SOAP 1.2'),
      'class' => 'WSClientSOAPEndpoint',
    ),
  );
}

/**
 * A remote endpoint type for invoking SOAP services.
 */
class WSClientSOAPEndpoint extends WSClientEndpoint {
  public function client() {
    if (!isset($this->client)) {
      $options['exceptions'] = TRUE;
      if ($this->service->type == 'soap 1.2') {
        $options['soap_version'] = SOAP_1_2;
      }

      // Handle Basic HTTP authentication.
      if (!empty($this->service->settings['authentication']['basic'])) {
        $this->service->settings['options']['login'] = $this->service->settings['authentication']['basic']['username'];
        $this->service->settings['options']['password'] = $this->service->settings['authentication']['basic']['password'];
      }
      if (!empty($this->service->settings['options'])) {
        $options += $this->service->settings['options'];
      }
      try {
        $this->client = new SOAPClient($this->url, $options);
      } catch (SoapFault $e) {
        throw new WSClientException('Error initializing SOAP client for service %name', array(
          '%name' => $this->service->name,
        ));
      }

      // Handle WSS style secured webservice.
      // https://www.drupal.org/node/2420779
      if (!empty($this->service->settings['authentication']['wss'])) {
        $this->client
          ->__setSoapHeaders(new WSSESecurityHeader($this->service->settings['authentication']['wss']['username'], $this->service->settings['authentication']['wss']['password']));
      }
      elseif (!empty($this->service->global_header_parameters)) {
        $header_parameters = $this->service->global_header_parameters;
        $data_types = $this->service->datatypes;
        $headers = array();
        foreach ($header_parameters as $type => $parameter) {
          $name_space = $parameter['name space url'];
          $data_type = $data_types[$type];
          $soap_vars = array();
          foreach ($data_type['property info'] as $name => $property) {
            $soap_vars[] = new SoapVar($property['default value'], XSD_STRING, NULL, NULL, $name, $name_space);
          }
          $header_data = new SoapVar($soap_vars, SOAP_ENC_OBJECT, NULL, NULL, $type, $name_space);
          $headers[] = new SoapHeader($name_space, $type, $header_data, FALSE);
        }
        $this->client
          ->__setSoapHeaders($headers);
      }
    }
    return $this->client;
  }

  /**
   * Retrieve metadata from the WSDL about available data types and operations.
   *
   * @param boolean $reset
   *   If TRUE, existing data types and operations will be overwritten.
   */
  public function initializeMetadata($reset = TRUE) {
    $client = $this
      ->client();
    $data_types = wsclient_soap_parse_types($client
      ->__getTypes());
    $operations = wsclient_soap_parse_operations($client
      ->__getFunctions());
    if ($reset) {
      $this->service->datatypes = $data_types;
      $this->service->operations = $operations;
    }
    else {
      $this->service->datatypes += $data_types;
      $this->service->operations += $operations;
    }
    $this->service
      ->clearCache();
  }

  /**
   * Calls the SOAP service.
   *
   * @param string $operation
   *   The name of the operation to execute.
   * @param array $arguments
   *   Arguments to pass to the service with this operation.
   */
  public function call($operation, $arguments) {
    $client = $this
      ->client();

    // Soap endpoints MAY also have 'headers' set on a per-operation basis.
    $operation_settings = $this->service->operations[$operation];
    if (!empty($operation_settings['header'])) {
      $headers = array();
      foreach ($operation_settings['header'] as $header_settings) {
        if (!empty($header_settings['actor'])) {
          $headers[] = new SoapHeader($header_settings['namespace'], $header_settings['name'], $header_settings['data'], $header_settings['mustunderstand'], $header_settings['actor']);
        }
        else {
          $headers[] = new SoapHeader($header_settings['namespace'], $header_settings['name'], $header_settings['data'], $header_settings['mustunderstand']);
        }
      }
      $client
        ->__setSoapHeaders($headers);
    }
    try {
      $response = $client
        ->__soapCall($operation, $arguments);
      return $response;
    } catch (SoapFault $e) {
      throw new WSClientException('Error invoking the SOAP service %name, operation %operation: %error', array(
        '%name' => $this->service->label,
        '%operation' => $operation,
        '%error' => $e
          ->getMessage(),
      ));
    }
  }

}

/**
 * Class WSSESecurityHeader
 *
 * A 'Security' Soap header block to support
 * Web Services Security UsernameToken Profile
 * http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0.pdf
 */
class WSSESecurityHeader extends SoapHeader {

  // Thanks to http://stackoverflow.com/a/20498574/213577

  /**
   * Create the header block.
   *
   * @param string $username
   *   Username.
   * @param string $password
   *   Password.
   */
  public function __construct($username, $password) {
    $wsse_ns = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd';
    $security = new SoapVar(array(
      new SoapVar(array(
        new SoapVar($username, XSD_STRING, NULL, NULL, 'Username', $wsse_ns),
        new SoapVar($password, XSD_STRING, NULL, NULL, 'Password', $wsse_ns),
      ), SOAP_ENC_OBJECT, NULL, NULL, 'UsernameToken', $wsse_ns),
    ), SOAP_ENC_OBJECT);
    parent::__construct($wsse_ns, 'Security', $security, FALSE);
  }

}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function wsclient_soap_form_wsclient_service_form_alter(&$form, &$form_state) {
  $form['#submit'][] = 'wsclient_soap_wsclient_service_form_submit';
  $form['#validate'][] = 'wsclient_soap_wsclient_service_form_validate';
}

/**
 * Validation callback to check if the SOAP service URL points to a valid WSDL
 * file.
 */
function wsclient_soap_wsclient_service_form_validate($form, $form_state) {
  $service = $form_state['wsclient_service'];
  if ($form_state['values']['type'] == 'soap' || $form_state['values']['type'] == 'soap 1.2') {

    // The url has to point to a valid WSDL file.
    try {

      // If initializing the SOAPClient succeeds we're good, otherwise we catch
      // the exception below and suppress any further warnings.
      // WARNING: if you have the xdebug PHP module enabled this can cause a
      // fatal error on invalid WSDL files (instead of a catchable SoapFault
      // exception).
      // xdebug_disable();
      @($endpoint = new SOAPClient($form_state['values']['url']));
    } catch (SoapFault $e) {
      form_set_error('url', t('Error parsing the WSDL file: %message', array(
        '%message' => $e
          ->getMessage(),
      )));
    }
  }
}

/**
 * Submit callback for the web service form to populate operations and data
 * types of the new SOAP service.
 */
function wsclient_soap_wsclient_service_form_submit($form, &$form_state) {
  if (($form_state['values']['type'] == 'soap' || $form_state['values']['type'] == 'soap 1.2') && $form_state['op'] == 'add') {
    $service = $form_state['wsclient_service'];
    $endpoint = $service
      ->endpoint();
    $endpoint
      ->initializeMetadata();
    $service
      ->save();
    rules_clear_cache();
    $service
      ->clearCache();
    drupal_set_message(t('Operations and data types of the SOAP service have been imported automatically. If the service expects data types with properties as lists (multiple values for the property), please check the multiple flag on those properties. This cannot be auto-detected at the moment.'));
  }
}

/**
 * Convert metadata about data types provided by a SOAPClient into a wsclient
 * compatible data type array.
 *
 * @param array $types
 *   The array containing the struct strings.
 * @return
 *   A data type array with property information.
 */
function wsclient_soap_parse_types(array $types) {
  $wsclient_types = array();
  foreach ($types as $type_string) {
    if (strpos($type_string, 'struct') === 0) {
      $parts = explode('{', $type_string);

      // Cut off struct and whitespaces from type name.
      $type_name = trim(substr($parts[0], 6));
      $wsclient_types[$type_name] = array(
        'label' => $type_name,
      );
      $property_string = $parts[1];

      // Cut off trailing '}'
      $property_string = substr($property_string, 0, -1);
      $properties = explode(';', $property_string);

      // Remove last empty element
      array_pop($properties);

      // Initialize empty property information.
      $wsclient_types[$type_name]['property info'] = array();
      foreach ($properties as $property_string) {

        // Cut off white spaces.
        $property_string = trim($property_string);
        $parts = explode(' ', $property_string);
        $property_type = $parts[0];
        $property_name = $parts[1];
        $wsclient_types[$type_name]['property info'][$property_name] = array(
          'type' => wsclient_soap_type_mapper($property_type),
        );
      }
    }
  }
  return $wsclient_types;
}

/**
 * Convert metadata about operations provided by a SOAPClient into a wsclient
 * compatible operations array.
 *
 * @param array $operations
 *   The array containing the operation signature strings.
 * @return
 *   An operations array with parameter information.
 */
function wsclient_soap_parse_operations(array $operations) {
  $wsclient_operations = array();
  foreach ($operations as $operation) {
    $parts = explode(' ', $operation);
    $return_type = wsclient_soap_type_mapper($parts[0]);
    $name_parts = explode('(', $parts[1]);
    $op_name = $name_parts[0];
    $wsclient_operations[$op_name] = array(
      'label' => $op_name,
      'result' => array(
        'type' => $return_type,
        'label' => $return_type,
      ),
    );
    $parts = explode('(', $operation);

    // Cut off trailing ')'.
    $param_string = substr($parts[1], 0, -1);
    if ($param_string) {
      $parameters = explode(',', $param_string);
      foreach ($parameters as $parameter) {
        $parameter = trim($parameter);
        $parts = explode(' ', $parameter);
        $param_type = $parts[0];

        // Remove leading '$' from parameter name.
        $param_name = substr($parts[1], 1);
        $wsclient_operations[$op_name]['parameter'][$param_name] = array(
          'type' => wsclient_soap_type_mapper($param_type),
        );
      }
    }
  }
  return $wsclient_operations;
}

/**
 * Maps data type names from SOAPClient to wsclient/rules internals.
 */
function wsclient_soap_type_mapper($type) {
  $primitive_types = array(
    'string',
    'int',
    'long',
    'float',
    'boolean',
    'double',
    'short',
    'decimal',
  );
  if (in_array($type, $primitive_types)) {
    switch ($type) {
      case 'double':
      case 'float':
        return 'decimal';
      case 'int':
      case 'long':
      case 'short':
        return 'integer';
      case 'string':
        return 'text';
    }
  }

  // Check for list types.
  if (strpos($type, 'ArrayOf') === 0) {
    $type = substr($type, 7);
    $primitive = strtolower($type);
    if (in_array($primitive, $primitive_types)) {
      return 'list<' . $primitive . '>';
    }
    return 'list<' . $type . '>';
  }

  // Otherwise return the type as is.
  return $type;
}

Functions

Namesort descending Description
wsclient_soap_form_wsclient_service_form_alter Implements hook_form_FORM_ID_alter().
wsclient_soap_parse_operations Convert metadata about operations provided by a SOAPClient into a wsclient compatible operations array.
wsclient_soap_parse_types Convert metadata about data types provided by a SOAPClient into a wsclient compatible data type array.
wsclient_soap_type_mapper Maps data type names from SOAPClient to wsclient/rules internals.
wsclient_soap_wsclient_endpoint_types Implements hook_wsclient_endpoint_types().
wsclient_soap_wsclient_service_form_submit Submit callback for the web service form to populate operations and data types of the new SOAP service.
wsclient_soap_wsclient_service_form_validate Validation callback to check if the SOAP service URL points to a valid WSDL file.

Classes

Namesort descending Description
WSClientSOAPEndpoint A remote endpoint type for invoking SOAP services.
WSSESecurityHeader Class WSSESecurityHeader