From af13cf9c20d91a4d21273dac1fe7786bd6ff0417 Mon Sep 17 00:00:00 2001
From: Kyle Browning <kylebrowning@me.com>
Date: Fri, 18 Mar 2011 13:07:41 -0700
Subject: [PATCH] Commit new ctools comments

---
 .../export_ui/services_ctools_export_ui.class.php  |  381 ++++++++++++++++++++
 plugins/export_ui/services_ctools_export_ui.inc    |  210 +++++++++++
 services.install                                   |    1 +
 services.module                                    |  108 ++----
 4 files changed, 620 insertions(+), 80 deletions(-)
 create mode 100644 plugins/export_ui/services_ctools_export_ui.class.php
 create mode 100644 plugins/export_ui/services_ctools_export_ui.inc

diff --git a/plugins/export_ui/services_ctools_export_ui.class.php b/plugins/export_ui/services_ctools_export_ui.class.php
new file mode 100644
index 0000000..eb5eb6c
--- /dev/null
+++ b/plugins/export_ui/services_ctools_export_ui.class.php
@@ -0,0 +1,381 @@
+<?php
+
+/**
+ * @file
+ * Export-ui handler for the Services module.
+ */
+
+class services_ctools_export_ui extends ctools_export_ui {
+
+  /**
+   * Page callback for the resources page.
+   */
+  function resources_page($js, $input, $item) {
+    drupal_set_title($this->get_page_title('resources', $item));
+    return services_edit_endpoint_resources($item);
+  }
+
+  /**
+   * Page callback for the authentication page.
+   */
+  function authentication_page($js, $input, $item) {
+    drupal_set_title($this->get_page_title('authentication', $item));
+    return drupal_get_form('services_edit_form_endpoint_authentication', $item);
+  }
+}
+
+/**
+ * Endpoint authentication configuration form.
+ */
+function services_edit_form_endpoint_authentication($form, &$form_state) {
+  list($endpoint) = $form_state['build_info']['args'];
+  // Loading runtime include as needed by services_authentication_info().
+  module_load_include('runtime.inc', 'services');
+
+  $auth_modules = module_implements('services_authentication_info');
+
+  $form['endpoint_object'] = array(
+    '#type'  => 'value',
+    '#value' => $endpoint,
+  );
+
+  if (empty($auth_modules)) {
+    $form['message'] = array(
+      '#type'          => 'item',
+      '#title'         => t('No installed authentication modules'),
+      '#description'   => t('No authentication modules are installed, standard ' .
+        'Drupal session based security will be used.'),
+    );
+  }
+  elseif (empty($endpoint->authentication)) {
+    $form['message'] = array(
+      '#type'          => 'item',
+      '#title'         => t('No enabled authentication modules'),
+      '#value'   => t('No authentication modules are enabled, standard ' .
+        'Drupal session based security will be used.'),
+    );
+  }
+  else {
+    // Add configuration fieldsets for the authentication modules
+    foreach ($endpoint->authentication as $module => $settings) {
+      $info = services_authentication_info($module);
+      if ($info) {
+        $form[$module] = array(
+          '#type' => 'fieldset',
+          '#title' => $info['title'],
+          '#tree' => TRUE,
+        ) + services_auth_invoke($module, 'security_settings', $settings);
+      }
+    }
+  }
+
+  $form['submit'] = array(
+    '#type'  => 'submit',
+    '#value' => 'Save',
+  );
+
+  return $form;
+}
+
+function services_edit_form_endpoint_authentication_submit($form, $form_state) {
+  $endpoint = $form_state['values']['endpoint_object'];
+
+  foreach (array_keys($endpoint->authentication) as $module) {
+    $endpoint->authentication[$module] = $form_state['values'][$module];
+  }
+
+  drupal_set_message(t('Your authentication options have been saved.'));
+  services_endpoint_save($endpoint);
+}
+
+
+/**
+ * services_edit_endpoint_resources function.
+ * 
+ * Edit Resources endpoint form
+ * @param object $endpoint
+ * @return string  The form to be displayed
+ */
+function services_edit_endpoint_resources($endpoint) {
+  if (!is_object($endpoint)) {
+    $endpoint = services_endpoint_load($endpoint);
+  }
+  if ($endpoint && !empty($endpoint->title)) {
+    drupal_set_title(check_plain($endpoint->title));
+  }
+  return drupal_get_form('services_edit_form_endpoint_resources', $endpoint);
+}
+
+/**
+ * services_edit_form_endpoint_resources function.
+ * 
+ * @param array &$form_state 
+ * @param object $endpoint
+ * @return Form
+ */
+function services_edit_form_endpoint_resources(&$form_state, $endpoint) {
+  module_load_include('resource_build.inc', 'services');
+
+  $form = array();
+
+  $form['endpoint_object'] = array(
+    '#type'  => 'value',
+    '#value' => $endpoint,
+  );
+
+  $ops = array(
+    'create'   => t('Create'),
+    'retrieve' => t('Retrieve'),
+    'update'   => t('Update'),
+    'delete'   => t('Delete'),
+    'index'    => t('Index'),
+  );
+
+  // Call _services_build_resources() directly instead of
+  // services_get_resources to bypass caching.
+  $resources = _services_build_resources();
+  // Apply the endpoint in a non-strict mode, so that the non-active resources
+  // are preserved.
+  _services_apply_endpoint($resources, $endpoint, FALSE);
+
+  $res = array(
+    '#tree' => TRUE,
+  );
+
+  foreach ($resources as $name => $resource) {
+    $rc = $resource['endpoint'];
+    $res_set = array(
+      '#type'        => 'fieldset',
+      '#title'       => t('!name resource', array(
+        '!name' => preg_replace('/[_-]+/', ' ', $name),
+      )),
+      '#collapsible' => TRUE,
+      '#collapsed'   => TRUE,
+      '#tree'        => TRUE,
+      '#attributes'  => array(
+        'class' => array('resource'),
+      ),
+    );
+
+    $res_set['alias'] = array(
+      '#type'          => 'textfield',
+      '#title'         => t('Alias'),
+      '#description'   => t('The alias you enter here will be used instead of the resource name.'),
+      '#size'          => 40,
+      '#maxlength'     => 255,
+      '#default_value' => isset($rc['alias']) ? $rc['alias'] : '',
+    );
+
+    $res_set['operations'] = array(
+      '#tree' => TRUE,
+    );
+    foreach ($ops as $op => $title) {
+      if (isset($resource[$op])) {
+        $res_set['operations'][$op] = array(
+          '#type'        => 'fieldset',
+          '#title'       => $title,
+          '#collapsible' => TRUE,
+          '#collapsed'   => FALSE,
+        );
+        _services_resource_operation_settings($res_set['operations'][$op], $endpoint, $resource, $op);
+      }
+    }
+
+    $classes = array(
+      'actions'          => 'actions',
+      'targeted_actions' => 'targeted actions',
+      'relationships'    => 'relationships',
+    );
+    foreach ($classes as $element => $class) {
+      if (!empty($resource[$class])) {
+        $res_set[$element] = array(
+          '#type'  => 'fieldset',
+          '#title' => t($class),
+          '#tree'  => TRUE,
+        );
+        foreach ($resource[$class] as $action => $definition) {
+          $res_set[$element][$action] = array(
+            '#type'        => 'fieldset',
+            '#title'       => $action,
+            '#collapsible' => TRUE,
+            '#collapsed'   => FALSE,
+          );
+          _services_resource_operation_settings($res_set[$element][$action], $endpoint, $resource, $class, $action);
+        }
+      }
+    }
+
+    drupal_alter('services_resource_settings', $res_set, $resource);
+
+    $res[$name] = $res_set;
+  }
+
+  $form['resources'] = $res;
+
+  $form['save'] = array(
+    '#type'  => 'submit',
+    '#value' => t('Save'),
+  );
+  return $form;
+}
+
+/**
+ * services_edit_form_endpoint_resources_validate function.
+ * 
+ * @param array $form
+ * @param array $form_state
+ * @return void
+ */
+function services_edit_form_endpoint_resources_validate($form, $form_state) {
+  $res = $form_state['values']['resources'];
+
+  // Validate aliases
+  foreach ($res as $name => $resource) {
+    if (!empty($resource['alias'])) {
+      if (!preg_match('/^[a-z-]+$/', $resource['alias'])) {
+        form_set_error("resources][{$name}][alias", t("The alias for the !name may only contain lower case a-z and dashes.", array(
+          '!name' => $form['resources'][$name]['#title'],
+        )));
+      }
+    }
+  }
+}
+
+/**
+ * services_edit_form_endpoint_resources_submit function.
+ * 
+ * @param array $form
+ * @param array $form_state
+ * @return void
+ */
+function services_edit_form_endpoint_resources_submit($form, $form_state) {
+  $resources = $form_state['values']['resources'];
+  $endpoint  = $form_state['values']['endpoint_object'];
+
+  foreach ($resources as $name => $resource) {
+    $used = FALSE;
+    $c = isset($endpoint->resources[$name]) ? $endpoint->resources[$name] : array();
+
+    $c['alias'] = $resource['alias'];
+    if (isset($resource['operations'])) {
+      foreach ($resource['operations'] as $op => $def) {
+        $cop = isset($c['operations'][$op]) ? $c['operations'][$op] : array();
+        $cop = array_merge($cop, $def);
+        if ($cop['enabled']) {
+          $c['operations'][$op] = $cop;
+          $used = $used || TRUE;
+        }
+        else {
+          unset($c['operations'][$op]);
+        }
+      }
+    }
+
+    $classes = array(
+      'actions' => 'actions',
+      'targeted_actions' => 'targeted actions',
+      'relationships' => 'relationships',
+    );
+    foreach ($classes as $element => $class) {
+      $class_used = FALSE;
+      if (!empty($resource[$element])) {
+        foreach ($resource[$element] as $act => $def) {
+          $cop = isset($c[$class][$act]) ? $c[$class][$act] : array();
+          $cop = array_merge($cop, $def);
+          if ($cop['enabled']) {
+            $c[$class][$act] = $cop;
+            $class_used = $class_used || TRUE;
+          }
+          else {
+            unset($c[$class][$act]);
+          }
+        }
+        if (!$class_used) {
+          unset($c[$class]);
+        }
+        $used = $class_used || $used;
+      }
+    }
+
+    if ($used) {
+      $endpoint->resources[$name] = $c;
+    }
+    else {
+      unset($endpoint->resources[$name]);
+    }
+  }
+
+  drupal_set_message(t('Your resources have been saved.'));
+  services_endpoint_save($endpoint);
+}
+
+/**
+ * Returns information about a resource operation given it's class and name.
+ *
+ * @return array
+ *  Information about the operation, or NULL if no matching
+ *  operation was found.
+ */
+function services_get_resource_operation_info($resource, $class, $name = NULL) {
+  $op = NULL;
+
+  if (isset($resource[$class])) {
+    $op = $resource[$class];
+    if (!empty($name)) {
+      $op = isset($op[$name]) ? $op[$name] : NULL;
+    }
+  }
+
+  return $op;
+}
+/**
+ * Constructs the settings form for resource operation.
+ *
+ * @param array $settings
+ *  The root element for the settings form.
+ * @param string $resource
+ *  The resource information array.
+ * @param string $class
+ *  The class of the operation. Can be 'create', 'retrieve', 'update',
+ *  'delete', 'index', 'actions' or 'targeted actions' or 'relationships'.
+ * @param string $name
+ *  Optional. The name parameter is only used for actions, targeted actions
+ *  and relationship.
+ */
+function _services_resource_operation_settings(&$settings, $endpoint, $resource, $class, $name = NULL) {
+  module_load_include('runtime.inc', 'services');
+  if ($rop = services_get_resource_operation_info($resource, $class, $name)) {
+    $settings['enabled'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Enabled'),
+      '#default_value' => !empty($rop['endpoint']) && $rop['endpoint']['enabled'],
+    );
+
+    if (!empty($rop['endpoint']['preprocess'])) {
+      $settings['preprocess'] = array(
+        '#type' => 'item',
+        '#title' => t('Preprocess function'),
+        '#value' => $rop['endpoint']['preprocess'],
+      );
+    }
+
+    if (!empty($rop['endpoint']['postprocess'])) {
+      $settings['preprocess'] = array(
+        '#type' => 'item',
+        '#title' => t('Postprocess function'),
+        '#value' => $rop['endpoint']['Postprocess'],
+      );
+    }
+
+    // Let authentication modules add their configuration options
+    if(isset($endpoint->authentication))
+    foreach ($endpoint->authentication as $auth_module => $auth_settings) {
+      $settings_form = services_auth_invoke($auth_module, 'controller_settings', $auth_settings, $rop, $endpoint, $class, $name);
+      if (!empty($settings_form)) {
+        $settings[$auth_module] = $settings_form;
+      }
+    }
+
+    drupal_alter('services_resource_operation_settings', $settings, $endpoint, $resource, $class, $name);
+  }
+}
diff --git a/plugins/export_ui/services_ctools_export_ui.inc b/plugins/export_ui/services_ctools_export_ui.inc
new file mode 100644
index 0000000..276b499
--- /dev/null
+++ b/plugins/export_ui/services_ctools_export_ui.inc
@@ -0,0 +1,210 @@
+<?php
+/*
+ * Plugin definition for Ctools Export UI
+ */
+$plugin = array(
+  'schema' => 'services_endpoint',
+  'menu' => array(
+    'menu item' => 'services',
+    'menu description' => 'Manage Services',
+    // Add services specific own menu callbacks.
+    'items' => array(
+      'resources' => array(
+        'path' => 'list/%ctools_export_ui/resources',
+        'title' => 'Resources',
+        'page callback' => 'ctools_export_ui_switcher_page',
+        'page arguments' => array('services_ctools_export_ui', 'resources', 4),
+        'load arguments' => array('services_ctools_export_ui'),
+        'access arguments' => array('administer services'),
+        'type' => MENU_LOCAL_TASK,
+      ),
+      'authentication' => array(
+        'path' => 'list/%ctools_export_ui/authentication',
+        'title' => 'Authentication',
+        'page callback' => 'ctools_export_ui_switcher_page',
+        'page arguments' => array('services_ctools_export_ui', 'authentication', 4),
+        'load arguments' => array('services_ctools_export_ui'),
+        'access arguments' => array('administer services'),
+        'type' => MENU_LOCAL_TASK,
+      ),
+    ),
+  ),
+  // Add our custom operations.
+  'allowed operations' => array(
+    'resources'  => array('title' => t('Resources')),
+    'authentication' => array('title' => t('Authentication')),
+  ),
+
+  'handler' => array(
+    'class' => 'services_ctools_export_ui',
+    'parent' => 'ctools_export_ui',
+  ),
+
+  'title' => t('Services'),
+
+  'title singular' => t('service'),
+  'title plural' => t('services'),
+  'title singular proper' => t('Service'),
+  'title plural proper' => t('Services'),
+);
+
+/**
+ * Form to edit the settings of an endpoint.
+ */
+function services_ctools_export_ui_form(&$form, &$form_state) {
+  // Loading runtime include as needed by services_auth_info().
+  module_load_include('runtime.inc', 'services');
+  $endpoint = $form_state['item'];
+  $form['eid'] = array(
+    '#type'  => 'value',
+    '#value' => isset($endpoint->eid) ? $endpoint->eid : '',
+  );
+
+  $form['endpoint_object'] = array(
+    '#type'  => 'value',
+    '#value' => $endpoint,
+  );
+
+  $form['name'] = array(
+    '#type'          => 'textfield',
+    '#size'          => 24,
+    '#maxlength'     => 255,
+    '#default_value' => $endpoint->name,
+    '#title'         => t('Endpoint name'),
+    '#description'   => t('A unique name used to identify this preset internally. It must be only be alpha characters and underscores. No spaces, numbers or uppercase characters.'),
+    '#required'      => TRUE,
+  );
+
+  $form['title'] = array(
+    '#type'          => 'textfield',
+    '#size'          => 24,
+    '#maxlength'     => 255,
+    '#default_value' => $endpoint->title,
+    '#title'         => t('Endpoint title'),
+    '#required'      => TRUE,
+  );
+
+  $servers = services_get_servers();
+  $server_opts = array(
+    '' => t('-- Select a server'),
+  );
+  foreach ($servers as $server => $info) {
+    $server_opts[$server] = $info['name'];
+  }
+  $form['server'] = array(
+    '#type'          => 'select',
+    '#options'       => $server_opts,
+    '#default_value' => $endpoint->server,
+    '#title'         => t('Server'),
+    '#description'   => t('Select a the server that should be used to handle requests to this endpoint.'),
+    '#required'      => TRUE,
+  );
+
+  $form['path'] = array(
+    '#type'          => 'textfield',
+    '#size'          => 24,
+    '#maxlength'     => 255,
+    '#default_value' => $endpoint->path,
+    '#title'         => t('Path to endpoint'),
+    '#required'      => TRUE,
+  );
+
+  $auth_modules = module_implements('services_authentication_info');
+
+  if (!empty($auth_modules)) {
+    $auth_options = array();
+    foreach ($auth_modules as $module) {
+      $info = services_authentication_info($module);
+      $auth_options[$module] = $info['title'];
+    }
+
+    $form['authentication'] = array(
+      '#type'          => 'checkboxes',
+      '#options'       => $auth_options,
+      '#default_value' => array_keys($endpoint->authentication),
+      '#title'         => t('Authentication'),
+      '#description'   => t('Choose which authentication schemes that should ' .
+        'be used with your endpoint. If no authentication method is selected ' .
+        'the standard Drupal session security is used.'),
+    );
+  }
+  else {
+    $form['authentication'] = array(
+      '#type'          => 'item',
+      '#title'         => t('Authentication'),
+      '#description'   => t('No authentication modules are installed, standard ' .
+        'Drupal session based security will be used.'),
+    );
+  }
+
+  $label = (empty($endpoint->eid) && $endpoint->export_type != EXPORT_IN_CODE) ? t('Save and proceed') : t('Save');
+  $form['submit'] = array(
+    '#type'  => 'submit',
+    '#value' => $label,
+  );
+
+  return $form;
+
+}
+
+/**
+ * Validate submission of the preset edit form.
+ */
+function services_ctools_export_ui_form_validate(&$form, &$form_state) {
+  // Test uniqueness of name:
+  if (preg_match("/[^A-Za-z0-9_]/", $form_state['values']['name'])) {
+    form_error($form['name'], t('Endpoint name must be alphanumeric or underscores only.'));
+  }
+  else {
+    $query = db_select('services_endpoint', 'e');
+    $query->addField('e', 'eid');
+    $query->condition('name', $form_state['values']['name']);
+
+    if (!empty($form_state['values']['eid']) && is_numeric($form_state['values']['eid'])) {
+      $query->condition('eid', $form_state['values']['eid'], '!=');
+    }
+
+    $res = $query->execute()->fetchField();
+    if (!empty($res)) {
+      form_error($form['name'], t('Endpoint name must be unique.'));
+    }
+  }
+  //TODO: More validation? Eg. validate path? Transliteration etc?
+}
+
+/**
+ * Submit handler for endpoint.
+ */
+function services_ctools_export_ui_form_submit(&$form, &$form_state) {
+  $endpoint = $form_state['values']['endpoint_object'];
+
+  $endpoint->name           = $form_state['values']['name'];
+  $endpoint->title          = $form_state['values']['title'];
+  $endpoint->server         = $form_state['values']['server'];
+  $endpoint->path           = $form_state['values']['path'];
+
+  // Set the authentication modules, and preserve the settings for modules
+  // that already exist.
+  $auth = array();
+  if (isset($form_state['values']['authentication'])) {
+    foreach (array_keys($form_state['values']['authentication']) as $module) {
+      if (isset($endpoint->authentication[$module])) {
+        $auth[$module] = $endpoint->authentication[$module];
+      }
+      else {
+        $auth[$module] = array();
+      }
+    }
+  }
+  $endpoint->authentication = $auth;
+
+  if (empty($endpoint->eid)) {
+    drupal_set_message(t('Your new endpoint %title has been saved.', array('%title' => $endpoint->title)));
+    services_endpoint_save($endpoint);
+    $form_state['values']['eid'] = $endpoint->eid;
+  }
+  else {
+    drupal_set_message(t('Your changes have been saved.'));
+    services_endpoint_save($endpoint);
+  }
+}
\ No newline at end of file
diff --git a/services.install b/services.install
index 9798802..c1372b6 100644
--- a/services.install
+++ b/services.install
@@ -75,6 +75,7 @@ function services_schema() {
     'export' => array(
       'key' => 'name',
       'identifier' => 'endpoint',
+      'primary key' => 'name',
       'api' => array(
         'owner' => 'services',
         'api' => 'services',
diff --git a/services.module b/services.module
index f0355e1..ede584b 100644
--- a/services.module
+++ b/services.module
@@ -6,6 +6,11 @@
  */
 
 /**
+ * Minimum CTools version needed.
+ */
+define('SERVICES_REQUIRED_CTOOLS_API', '1.7');
+
+/**
  * Implements hook_help().
  */
 function services_help($path, $arg) {
@@ -71,88 +76,10 @@ function services_hook_info() {
 
 /**
  * Implements hook_menu().
+ *
+ * Services UI is defined in the export-ui plugin.
  */
 function services_menu() {
-  $base = array(
-    'access arguments' => array('administer services'),
-    'file'             => 'services.admin.inc',
-  );
-
-  $items['admin/config/services/services'] = array(
-    'title'          => 'Services',
-    'description'    => 'Manage how external applications communicates with Drupal.',
-    'page callback'  => 'services_list_endpoint',
-  ) + $base;
-
-  $items['admin/config/services/services/list'] = array(
-    'title'          => 'List',
-    'page callback'  => 'services_list_endpoint',
-    'type'           => MENU_DEFAULT_LOCAL_TASK,
-    'weight'         => -10,
-  ) + $base;
-  $items['admin/config/services/services/add'] = array(
-    'title'          => 'Add endpoint',
-    'page callback'  => 'services_add_endpoint',
-    'type'           => MENU_LOCAL_TASK,
-  ) + $base;
-  $items['admin/config/services/services/%services_endpoint/edit'] = array(
-    'title'          => 'Edit endpoint',
-    'page callback'  => 'services_edit_endpoint',
-    'page arguments' => array(4),
-    'type'           => MENU_LOCAL_TASK,
-  ) + $base;
-  $items['admin/config/services/services/%services_endpoint/authentication'] = array(
-    'title'          => 'Authentication',
-    'page callback'  => 'services_edit_endpoint_authentication',
-    'page arguments' => array(4),
-    'type'           => MENU_LOCAL_TASK,
-    'weight'         => 5,
-  ) + $base;
-  $items['admin/config/services/services/%services_endpoint/resources'] = array(
-    'title'          => 'Resources',
-    'page callback'  => 'services_edit_endpoint_resources',
-    'page arguments' => array(4),
-    'type'           => MENU_LOCAL_TASK,
-    'weight'         => 10,
-  ) + $base;
-  $items['admin/config/services/services/%services_endpoint/export'] = array(
-    'title'          => 'Export endpoint',
-    'page callback'  => 'drupal_get_form',
-    'page arguments' => array('services_export_endpoint', 4),
-    'type'           => MENU_LOCAL_TASK,
-    'weight'         => 20,
-  ) + $base;
-  $items['admin/config/services/services/%services_endpoint/delete'] = array(
-    'title'          => 'Delete endpoint',
-    'page callback'  => 'drupal_get_form',
-    'page arguments' => array('services_delete_confirm_endpoint', 4),
-    'type'           => MENU_CALLBACK,
-  ) + $base;
-  $items['admin/config/services/services/%services_endpoint/disabledebug'] = array(
-     'page callback'  => 'services_disable_debug_mode',
-     'page arguments' => array(4),
-     'type'           => MENU_CALLBACK,
-  ) + $base;
-  $items['admin/config/services/services/%services_endpoint/enabledebug'] = array(
-     'page callback'  => 'services_enable_debug_mode',
-     'page arguments' => array(4),
-     'type'           => MENU_CALLBACK,
-  ) + $base;
-  $items['admin/config/services/services/%services_endpoint/disable'] = array(
-    'page callback'  => 'services_disable_endpoint',
-    'page arguments' => array(4),
-    'type'           => MENU_CALLBACK,
-  ) + $base;
-  $items['admin/config/services/services/%services_endpoint/enable'] = array(
-    'page callback'  => 'services_enable_endpoint',
-    'page arguments' => array(4),
-    'type'           => MENU_CALLBACK,
-  ) + $base;
-  $items['admin/config/services/services/ahah/security-options'] = array(
-    'page callback' => '_services_ahah_security_options',
-    'type'          => MENU_CALLBACK,
-  ) + $base;
-
   $endpoints = services_endpoint_load_all();
   foreach ($endpoints as $endpoint) {
     if (empty($endpoint->disabled)) {
@@ -167,6 +94,27 @@ function services_menu() {
   }
   return $items;
 }
+/**
+ * Implements of hook_ctools_plugin_api().
+ */
+function services_ctools_plugin_api($module, $api) {
+  if ($module == 'services' && $api == 'plugins') {
+    return array('version' => 3);
+  }
+}
+
+/**
+ * Implement of hook_ctools_plugin_directory().
+ */
+function services_ctools_plugin_directory($module, $type) {
+  // Safety: go away if CTools is not at an appropriate version.
+  if (!module_invoke('ctools', 'api_version', SERVICES_REQUIRED_CTOOLS_API)) {
+    return;
+  }
+  if ($type =='export_ui') {
+    return 'plugins/export_ui';
+  }
+}
 
 /**
  * Access callback that always returns TRUE.
-- 
1.7.3.4

