From c061cf600097b6ff01b3e900ad01ea3e7840a36e Mon Sep 17 00:00:00 2001
From: PatchRanger <Staratel@1209848.no-reply.drupal.org>
Date: Fri, 23 Aug 2013 16:06:43 +0700
Subject: [PATCH] Issue #1280332 by Belette, klausi, krlucas, tito.brasolin,
 PatchRanger: REST service formatter UI setting + Add
 support for $request_alter

---
 wsclient_rest/wsclient_rest.api.php |   57 +++++++
 wsclient_rest/wsclient_rest.inc     |  282 +++++++++++++++++++++++++++++++++++
 wsclient_rest/wsclient_rest.info    |    2 +-
 wsclient_rest/wsclient_rest.install |   20 +++
 wsclient_rest/wsclient_rest.module  |  207 +++++++++++++++----------
 wsclient_ui/wsclient_ui.inc         |   10 +-
 6 files changed, 498 insertions(+), 80 deletions(-)
 create mode 100644 wsclient_rest/wsclient_rest.api.php
 create mode 100644 wsclient_rest/wsclient_rest.inc
 create mode 100644 wsclient_rest/wsclient_rest.install

diff --git a/wsclient_rest/wsclient_rest.api.php b/wsclient_rest/wsclient_rest.api.php
new file mode 100644
index 0000000..530e383
--- /dev/null
+++ b/wsclient_rest/wsclient_rest.api.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @file
+ * This file contains no working PHP code; it exists to provide additional
+ * documentation for doxygen as well as to document hooks in the standard
+ * Drupal manner.
+ */
+
+/**
+ * @defgroup wsclient_rest_hooks Web service client REST's hooks
+ * @{
+ * Hooks that can be implemented by other modules in order to extend web service
+ * client REST.
+ */
+
+/**
+ * Alter the http request.
+ *
+ * @param HttpClientRequest $request
+ *   The http request to be manipulated before it's handed over to curl.
+ * @param WSClientServiceDescription $service
+ *   The web service description.
+ *
+ * @see WSClientRESTEndpoint::alterRequest()
+ */
+function hook_wsclient_rest_request_alter(&$request, &$service) {
+  if ($service->name == 'myservice') {
+    // remove empty parameters
+    $request->parameters = array_filter($request->parameters, 'strlen');
+  }
+}
+
+/**
+ * Provide the formatter for handling requests and parsing responses.
+ *
+ * @return array
+ *   An array whose keys are classes implementing HttpClientFormatter interface
+ *       and whose values are arrays containing the keys:
+ *   - label: A human readable, translated label for the formatter.
+ *   - format: Format of the formatter. Is used to group the formatters.
+ *   - settings: An array, containing the settings of the formatter. The key is
+ *       the setting name, the value is the default value of the setting.
+ */
+function hook_wsclient_rest_formatter_info() {
+  return array(
+    'WslientRestJSONFormatter' => array(
+      'label' => t('Basic JSON formatter'),
+      'format' => t('JSON'),
+      'settings' => array(),
+    ),
+  );
+}
+
+/**
+ * @}
+ */
diff --git a/wsclient_rest/wsclient_rest.inc b/wsclient_rest/wsclient_rest.inc
new file mode 100644
index 0000000..8eacefd
--- /dev/null
+++ b/wsclient_rest/wsclient_rest.inc
@@ -0,0 +1,282 @@
+<?php
+
+/**
+ * @file
+ * Web service client REST - include file.
+ */
+
+/**
+ * A remote endpoint type for invoking REST services.
+ */
+class WSClientRESTEndpoint extends WSClientEndpoint {
+
+  /**
+   * @var HttpClient
+   */
+  protected $client;
+
+  public function __construct(WSClientServiceDescription $service) {
+    $this->service = $service;
+    $this->url = $service->url;
+  }
+
+  /**
+   * @return HttpClient
+   */
+  public function client() {
+    if (!isset($this->client)) {
+      $settings = $this->service->settings;
+      if (wsclient_rest_has_old_formatter($settings)) {
+        $formatter = new $settings['formatter']();
+        $this->client = new HttpClient(NULL, $formatter, $this);
+      }
+      else {
+        $formatters = array();
+        foreach (array('serializer', 'parser') as $formatter_type) {
+          // Settings could be missing.
+          $formatter_settings = !empty($settings[$formatter_type]['settings'])
+            ? $settings[$formatter_type]['settings']
+            : array();
+          $formatters[$formatter_type] = new $settings[$formatter_type]['class']($formatter_settings);
+        }
+        $formatter = new HttpClientCompositeFormatter($formatters['serializer'], $formatters['parser']);
+        $this->client = new HttpClient(NULL, $formatter, $this);
+      }
+      // Pass through additional curl options.
+      if (!empty($settings['curl options'])) {
+        $this->client->options['curlopts'] = $settings['curl options'];
+      }
+    }
+    return $this->client;
+  }
+
+  /**
+   * Calls the REST service.
+   *
+   * @param string $operation_name
+   *   The name of the operation to execute.
+   * @param array $arguments
+   *   Arguments to pass to the service with this operation.
+   */
+  public function call($operation_name, $arguments) {
+    $operation = $this->service->operations[$operation_name];
+    $operation_url = isset($operation['url']) ? $operation['url'] : '';
+    // Replace argument patterns in the operation URL.
+    foreach ($arguments as $key => $value) {
+      if (strpos($operation_url, '@' . $key) !== FALSE) {
+        $operation_url = str_replace('@' . $key, $value, $operation_url);
+        unset($arguments[$key]);
+      }
+    }
+    $client = $this->client();
+    $type = isset($operation['type']) ? $operation['type'] : 'GET';
+    $data = NULL;
+    if (isset($operation['data'])) {
+      $data = $arguments[$operation['data']];
+      unset($arguments[$operation['data']]);
+    }
+    // Services module compliance: post fields should be in the $data array
+    // instead of $arguments.
+    if ($type == 'POST') {
+      $data = array_merge((array) $data, $arguments);
+    }
+    try {
+      $response = $client->execute(new HttpClientRequest($this->service->url . $operation_url, array(
+        'method' => $type,
+        'parameters' => $arguments,
+        'data' => $data,
+      )));
+      return $response;
+    }
+    catch (HttpClientException $e) {
+      throw new WSClientException('Error invoking the REST service %name, operation %operation: %error', array('%name' => $this->service->label, '%operation' => $operation_name, '%error' => $e->getMessage()));
+    }
+  }
+
+  public function formAlter(&$form, &$form_state) {
+    $service = $form_state['service'];
+    $options = wsclient_rest_formatters_as_options();
+    switch ($form_state['form']) {
+      case 'main':
+        $default_formatter = NULL;
+        foreach (array('serializer', 'parser') as $formatter_type) {
+          if (wsclient_rest_has_custom_formatter($service->settings, $formatter_type)) {
+            $default_formatter = 'custom';
+          }
+          else {
+            $default_formatter = isset($service->settings[$formatter_type]['class'])
+              ? $service->settings[$formatter_type]['class']
+              : 'WsclientRestJSONFormatter';
+          }
+          switch ($formatter_type) {
+            case 'serializer':
+              $title = t('Request formatter (serializer)');
+              $description = t('Choose how to serialize the request.');
+              $weight = 45;
+              break;
+            case 'parser':
+              $title = t('Response formatter (parser)');
+              $description = t('Choose how to parse the response.');
+              $weight = 50;
+              break;
+          }
+          $form[$formatter_type] = array(
+            '#type' => 'fieldset',
+            '#title' => $title,
+            '#tree' => TRUE,
+            '#weight' => $weight,
+          );
+          $form[$formatter_type]['formatter'] = array(
+            '#type' => 'select',
+            '#title' => t('Formatter'),
+            '#default_value' => $default_formatter,
+            '#description' => $description,
+            '#options' => $options,
+          );
+        }
+        break;
+      case 'operation':
+        $operation = $form_state['operation'];
+        $form['type'] = array(
+          '#type' => 'select',
+          '#title' => t('HTTP Method'),
+          '#default_value' => isset($operation['type']) ? $operation['type'] : 'GET',
+          '#description' => t('Specify the HTTP request method used for this operation.'),
+          '#options' => array(
+            'GET' => 'GET',
+            'POST' => 'POST',
+            'PUT' => 'PUT',
+            'DELETE' => 'DELETE',
+          ),
+          '#weight' => -5,
+        );
+        $form['url'] = array(
+          '#type' => 'textfield',
+          '#title' => t('Path'),
+          '#default_value' => isset($operation['url']) ? $operation['url'] : '',
+          '#description' => t('The path to append to the services base URL. In order to construct the path using parameter values make use of replacements in the form "@parameter-name". E.g. the path "node/@nid" together with a "nid" parameter could be used to construct the path to a Drupal node.'),
+          '#weight' => -5,
+        );
+        break;
+    }
+    // Allow formatters to alter the form.
+    foreach ($options as $options_groupped) {
+      foreach (array_keys($options_groupped) as $formatter_class) {
+        if ($formatter_class == 'custom') {
+          // Skip custom.
+          continue;
+        }
+        $formatter = new $formatter_class();
+        if (method_exists($formatter, 'formAlter')) {
+          $formatter->formAlter($form, $form_state);
+        }
+      }
+    }
+    $form['#submit'][] = 'wsclient_rest_form_submit';
+  }
+
+  /**
+   * Alters the given request.
+   *
+   * The public alterRequest method supported by Hugo Wetterberg's HttpClient.
+   * The request to be altered is passed to drupal_alter() with the service
+   * definition as a context.
+   *
+   * @param HttpClientRequest $request
+   *   The request to be altered.
+   *
+   * @see HttpClient
+   * @see hook_wsclient_rest_request_alter()
+   */
+  public function alterRequest($request) {
+    drupal_alter('wsclient_rest_request', $request, $this->service);
+  }
+}
+
+class WsclientRestFormFormatter extends HttpClientBaseFormatter {
+  public function __construct() {
+    parent::__construct(self::FORMAT_FORM);
+  }
+}
+
+class WsclientRestJSONFormatter extends HttpClientBaseFormatter {
+  public function __construct() {
+    parent::__construct(self::FORMAT_JSON);
+  }
+}
+
+class WsclientRestPHPFormatter extends HttpClientBaseFormatter {
+  public function __construct() {
+    parent::__construct(self::FORMAT_PHP);
+  }
+}
+
+class WsclientRestXMLFormatter extends HttpClientXMLFormatter {
+  public function __construct($settings = array()) {
+    parent::__construct(
+      !empty($settings['default_root']) ? $settings['default_root'] : NULL,
+      !empty($settings['adaptive_root']) ? $settings['adaptive_root'] : NULL
+    );
+    $this->settings = $settings;
+  }
+
+  public function formAlter(&$form, &$form_state) {
+    switch ($form_state['form']) {
+      case 'main':
+        $service = $form_state['service'];
+        $info = module_invoke_all('wsclient_rest_formatter_info');
+        $default_formatter_settings = $info['WsclientRestXMLFormatter']['settings'];
+        foreach (array('serializer', 'parser') as $formatter_type) {
+          // Alias for such long nested path.
+          $formatter_settings = !empty($service->settings[$formatter_type]['settings'])
+            ? $service->settings[$formatter_type]['settings']
+            : array();
+          // UI for settings.
+          $form[$formatter_type]['settings']['default_root'] = array(
+            '#type' => 'textfield',
+            '#title' => t('Default root'),
+            '#default_value' => isset($formatter_settings['default_root']) ? $formatter_settings['default_root'] : $default_formatter_settings['default_root'],
+            '#description' => t('Default root for created XML documents.'),
+            '#states' => array(
+              'visible' => array(
+                'select[name="' . $formatter_type . '[formatter]"]' => array('value' => 'WsclientRestXMLFormatter'),
+              ),
+            ),
+          );
+          $form[$formatter_type]['settings']['adaptive_root'] = array(
+            '#type' => 'checkbox',
+            '#title' => t('Adaptive root'),
+            '#default_value' => isset($formatter_settings['adaptive_root']) ? $formatter_settings['adaptive_root'] : $default_formatter_settings['adaptive_root'],
+            '#description' => t('If it is set to TRUE and the source data has a single root attribute the formatter will use that attribute as root. The object %foo_object would be serialized to %foo_wrapped instead of %foo_wrapped_result.',
+              array(
+                '%foo_object' => '{"foo":"bar"}',
+                '%foo_wrapped' => '<foo>bar</foo>',
+                '%foo_wrapped_result' => '<result><foo>bar</foo></result>',
+              )
+            ),
+            '#states' => array(
+              'visible' => array(
+                'select[name="' . $formatter_type . '[formatter]"]' => array('value' => 'WsclientRestXMLFormatter'),
+              ),
+            ),
+          );
+        }
+        break;
+    }
+  }
+
+  public function formSubmit($form, &$form_state) {
+    switch ($form_state['form']) {
+      case 'main':
+        // Remove XML settings from the formatters that are not XML.
+        foreach (array('serializer', 'parser') as $formatter_type) {
+          if ($form_state['values'][$formatter_type]['formatter'] != 'WsclientRestXMLFormatter') {
+            foreach (array('default_root', 'adaptive_root') as $setting) {
+              unset($form_state['values'][$formatter_type]['settings'][$setting]);
+            }
+          }
+        }
+        break;
+    }
+  }
+}
diff --git a/wsclient_rest/wsclient_rest.info b/wsclient_rest/wsclient_rest.info
index fdb7db4..dd5dff9 100644
--- a/wsclient_rest/wsclient_rest.info
+++ b/wsclient_rest/wsclient_rest.info
@@ -2,6 +2,6 @@ name = Web service client REST
 description = Implements a REST endpoint to connect to REST services.
 package = Services
 core = 7.x
-files[] = wsclient_rest.module
+files[] = wsclient_rest.inc
 dependencies[] = wsclient
 dependencies[] = http_client
diff --git a/wsclient_rest/wsclient_rest.install b/wsclient_rest/wsclient_rest.install
new file mode 100644
index 0000000..7d4dd41
--- /dev/null
+++ b/wsclient_rest/wsclient_rest.install
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * @file
+ * Web service client REST - installation file.
+ */
+
+/**
+ * Add default formatters (serializer and parser) if they are not set for each existing service.
+ */
+function wsclient_rest_update_7100() {
+  foreach (entity_load_multiple_by_name('wsclient_service') as $service) {
+    foreach (array('serializer', 'parser') as $formatter) {
+      if (empty($service->settings[$formatter]['class'])) {
+        $service->settings[$formatter]['class'] = 'WsclientRestJSONFormatter';
+      }
+    }
+    $service->save();
+  }
+}
diff --git a/wsclient_rest/wsclient_rest.module b/wsclient_rest/wsclient_rest.module
index caa426d..610fe79 100644
--- a/wsclient_rest/wsclient_rest.module
+++ b/wsclient_rest/wsclient_rest.module
@@ -18,98 +18,149 @@ function wsclient_rest_wsclient_endpoint_types() {
 }
 
 /**
- * A remote endpoint type for invoking REST services.
+ * Form submit callback for the form.
  */
-class WSClientRESTEndpoint extends WSClientEndpoint {
-
-  /**
-   * @var HttpClient
-   */
-  protected $client;
-
-  public function __construct(WSClientServiceDescription $service) {
-    $this->service = $service;
-    $this->url = $service->url;
-  }
-
-  /**
-   * @return HttpClient
-   */
-  public function client() {
-    if (!isset($this->client)) {
-      if (isset($this->service->settings['formatter'])) {
-        $formatter = new $this->service->settings['formatter']();
-        $this->client = new HttpClient(NULL, $formatter);
-      }
-      else {
-        $this->client = new HttpClient(NULL, new HttpClientBaseFormatter(HttpClientBaseFormatter::FORMAT_JSON));
+function wsclient_rest_form_submit($form, &$form_state) {
+  // Allow formatters to react on form submit.
+  $options = wsclient_rest_formatters_as_options();
+  foreach ($options as $options_groupped) {
+    foreach (array_keys($options_groupped) as $formatter_class) {
+      if ($formatter_class == 'custom') {
+        // Skip custom.
+        continue;
       }
-      // Pass through additional curl options.
-      if (!empty($this->service->settings['curl options'])) {
-        $this->client->options['curlopts'] = $this->service->settings['curl options'];
+      $formatter = new $formatter_class();
+      if (method_exists($formatter, 'formSubmit')) {
+        $formatter->formSubmit($form, $form_state);
       }
     }
-    return $this->client;
   }
+  switch ($form_state['form']) {
+    case 'main':
+      wsclient_rest_main_form_submit($form, $form_state);
+      break;
+    case 'operation':
+      wsclient_rest_operation_form_submit($form, $form_state);
+      break;
+  }
+}
 
-  /**
-   * Calls the REST service.
-   *
-   * @param string $operation_name
-   *   The name of the operation to execute.
-   * @param array $arguments
-   *   Arguments to pass to the service with this operation.
-   */
-  public function call($operation_name, $arguments) {
-    $operation = $this->service->operations[$operation_name];
-    $operation_url = isset($operation['url']) ? $operation['url'] : '';
-    // Replace argument patterns in the operation URL.
-    foreach ($arguments as $key => $value) {
-      if (strpos($operation_url, '@' . $key) !== FALSE) {
-        $operation_url = str_replace('@' . $key, $value, $operation_url);
-        unset($arguments[$key]);
-      }
-    }
-    $client = $this->client();
-    $type = isset($operation['type']) ? $operation['type'] : 'GET';
-    $data = NULL;
-    if (isset($operation['data'])) {
-      $data = $arguments[$operation['data']];
-      unset($arguments[$operation['data']]);
-    }
-    try {
-      $response = $client->execute(new HttpClientRequest($this->service->url . $operation_url, array(
-        'method' => $type,
-        'parameters' => $arguments,
-        'data' => $data,
-      )));
-      return $response;
+/**
+ * Form submit callback for the operation form.
+ */
+function wsclient_rest_operation_form_submit($form, &$form_state) {
+  $form_state['operation']['type'] = $form_state['values']['type'];
+  $form_state['operation']['url'] = $form_state['values']['url'];
+}
+
+/**
+ * Form submit callback for the main form.
+ */
+function wsclient_rest_main_form_submit($form, &$form_state) {
+  foreach (array('serializer', 'parser') as $formatter_type) {
+    // Make sure to avoid overridding of the custom formatter, defined in the settings.
+    if ($form_state['values'][$formatter_type]['formatter'] === 'custom') {
+      continue;
     }
-    catch (HttpClientException $e) {
-      throw new WSClientException('Error invoking the REST service %name, operation %operation: %error', array('%name' => $this->service->label, '%operation' => $operation_name, '%error' => $e->getMessage()));
+    // Save settings in the service settings.
+    $formatter_settings = &$form_state['service']->settings[$formatter_type];
+    $formatter_settings['class'] = $form_state['values'][$formatter_type]['formatter'];
+    if (!empty($form_state['values'][$formatter_type]['settings'])) {
+      $formatter_settings['settings'] = $form_state['values'][$formatter_type]['settings'];
     }
   }
+}
 
-  public function formAlter(&$form, &$form_state) {
-    if ($form_state['form'] == 'operation') {
-      $operation = $form_state['operation'];
-
-      $form['url'] = array(
-        '#type' => 'textfield',
-        '#title' => t('Path'),
-        '#default_value' => isset($operation['url']) ? $operation['url'] : '',
-        '#description' => t('The path to append to the services base URL. In order to construct the path using parameter values make use of replacements in the form "@parameter-name". E.g. the path "node/@nid" together with a "nid" parameter could be used to construct the path to a Drupal node.'),
-        '#weight' => -5,
-      );
-      $form['#submit'][] = 'wsclient_rest_operation_form_submit';
-    }
+function wsclient_rest_formatters_as_options() {
+  $options = array();
+  // Get a list of all format definitions.
+  $info = module_invoke_all('wsclient_rest_formatter_info');
+  // Turn this list into the options array.
+  foreach ($info as $formatter_class => $implementation) {
+    $options[$implementation['format']][$formatter_class] = $implementation['label'];
   }
+  return $options;
+}
 
+/**
+ * Implements hook_wsclient_rest_formatter_info().
+ */
+function wsclient_rest_wsclient_rest_formatter_info() {
+  return array(
+    'WsclientRestFormFormatter' => array(
+      'label' => t('Basic url-encoded form formatter'),
+      'format' => t('Url-encoded form'),
+      'settings' => array(),
+    ),
+    'WsclientRestJSONFormatter' => array(
+      'label' => t('Basic JSON formatter'),
+      'format' => t('JSON'),
+      'settings' => array(),
+    ),
+    'WsclientRestPHPFormatter' => array(
+      'label' => t('Basic PHP formatter'),
+      'format' => t('PHP'),
+      'settings' => array(),
+    ),
+    'WsclientRestXMLFormatter' => array(
+      'label' => t('Basic XML formatter'),
+      'format' => t('XML'),
+      'settings' => array(
+        'default_root' => 'result',
+        'adaptive_root' => FALSE,
+      ),
+    ),
+    // Is defined to provide backward compatibility.
+    'custom' => array(
+      'label' => t('Custom formatter defined in service settings'),
+      'format' => t('Custom formatters without hook implementation'),
+      'settings' => array(),
+    ),
+  );
 }
 
 /**
- * Form submit callback for the operation form.
+ * Determines whether custom formatter is defined in settings: "custom" means
+ *   that it implements the HttpClientFormatter interface but has no
+ *   corresponding implementation in info hook.
+ * @see hook_wsclient_rest_formatter_info()
+ *
+ * @param array $settings
+ * @param string $formatter_type
+ *   Either 'serializer' or 'parser'.
+ * @return boolean
  */
-function wsclient_rest_operation_form_submit($form, &$form_state) {
-  $form_state['operation']['url'] = $form_state['values']['url'];
+function wsclient_rest_has_custom_formatter($settings, $formatter_type) {
+  $custom = TRUE;
+  // In case if valid formatter found configured old-style - skip returning true.
+  if (wsclient_rest_has_old_formatter($settings)) {
+    return TRUE;
+  }
+  // Empty entry couldn't be custom implementation.
+  if (empty($settings[$formatter_type]['class'])) {
+    return FALSE;
+  }
+  // If doesn't implement the interface - considered as broken and will be overridden.
+  if (!(new $settings[$formatter_type]['class']() instanceof HttpClientFormatter)) {
+    return FALSE;
+  }
+  // Search through the implementations.
+  $info = module_invoke_all('wsclient_rest_formatter_info');
+  if (!empty($info[$settings[$formatter_type]['class']])) {
+    return FALSE;
+  }
+  // If none of the above applies, it means that we've finally checked that
+  // the formatter is really custom.
+  return $custom;
+}
+
+/**
+ * Determines whether the formatter is defined old-style.
+ *   Provided for backward compatibility.
+ *
+ * @param array $settings
+ * @return boolean
+ */
+function wsclient_rest_has_old_formatter($settings) {
+  return !empty($settings['formatter']) && (new $settings['formatter']() instanceof HttpClientFormatter);
 }
diff --git a/wsclient_ui/wsclient_ui.inc b/wsclient_ui/wsclient_ui.inc
index 0df37be..cf7b414 100644
--- a/wsclient_ui/wsclient_ui.inc
+++ b/wsclient_ui/wsclient_ui.inc
@@ -105,6 +105,7 @@ function wsclient_service_form($form, &$form_state, $service, $op = 'edit') {
     '#default_value' => $service->label,
     '#required' => TRUE,
     '#description' => t('The human-readable name.'),
+    '#weight' => 10,
   );
   $form['name'] = array(
     '#type' => 'machine_name',
@@ -117,6 +118,7 @@ function wsclient_service_form($form, &$form_state, $service, $op = 'edit') {
     '#required' => TRUE,
     '#description' => t('The machine-readable name of this service is used internally to identify the service. This name must contain only lowercase letters, numbers, and underscores and must be unique.'),
     '#element_validate' => array('form_validate_machine_name', 'entity_ui_validate_machine_name'),
+    '#weight' => 20,
   );
   $form['url'] = array(
     '#type' => 'textfield',
@@ -125,6 +127,7 @@ function wsclient_service_form($form, &$form_state, $service, $op = 'edit') {
     '#required' => TRUE,
     '#description' => t('The URL of the web service.'),
     '#element_validate' => array('wsclient_ui_element_url_validate'),
+    '#weight' => 30,
   );
   $form['type'] = array(
     '#type' => 'select',
@@ -133,6 +136,7 @@ function wsclient_service_form($form, &$form_state, $service, $op = 'edit') {
     '#options' => $types,
     '#required' => TRUE,
     '#description' => t('The type of the web service.'),
+    '#weight' => 40,
   );
   if ($op == 'edit') {
     // Operations of the web service in a table
@@ -177,6 +181,7 @@ function wsclient_service_form($form, &$form_state, $service, $op = 'edit') {
       '#caption' => t('Operations'),
       '#rows' => $rows,
       '#header' => $header,
+      '#weight' => 50,
     );
     // Add some table styling from Rules.
     $form['operations']['#attributes']['class'][] = 'rules-elements-table';
@@ -223,6 +228,7 @@ function wsclient_service_form($form, &$form_state, $service, $op = 'edit') {
       '#caption' => t('Data types'),
       '#rows' => $rows,
       '#header' => $header,
+      '#weight' => 60,
     );
 
     // Input for global service parameters.
@@ -232,6 +238,7 @@ function wsclient_service_form($form, &$form_state, $service, $op = 'edit') {
       '#theme' => 'wsclient_ui_global_parameter_form',
       '#title' => t('Input for global service parameters'),
       '#description' => t('Specify the global parameters for the service. Global parameters will be used if the value of an operation parameter with the same name is empty.'),
+      '#weight' => 70,
     );
 
     $weight = 0;
@@ -261,8 +268,9 @@ function wsclient_service_form($form, &$form_state, $service, $op = 'edit') {
   $form['submit'] = array(
     '#type' => 'submit',
     '#value' => t('Save'),
+    '#weight' => 80,
   );
-
+  $form_state['service'] = $service;
   // Allow the endpoint to make alterations to the form.
   $form_state['form'] = 'main';
   if ($service->type) {
-- 
1.7.9.5

