From 15b11c5349e77dedb631e63b71f3eb435ce77fea Mon Sep 17 00:00:00 2001
From: Kyle Browning <kylebrowning@me.com>
Date: Thu, 17 Mar 2011 09:51:27 -0700
Subject: [PATCH] Updates Resources UI to a way better interface!

---
 css/services.admin.css                             |   93 ++--
 js/services.admin.js                               |  103 ++++
 .../export_ui/services_ctools_export_ui.class.php  |  358 +++++--------
 plugins/export_ui/services_ctools_export_ui.inc    |   90 ++--
 services.admin.inc                                 |  548 ++++++--------------
 services.module                                    |    4 +
 6 files changed, 508 insertions(+), 688 deletions(-)
 create mode 100644 js/services.admin.js

diff --git a/css/services.admin.css b/css/services.admin.css
index fe32067..98175b6 100644
--- a/css/services.admin.css
+++ b/css/services.admin.css
@@ -1,37 +1,58 @@
-#services-list-endpoint tr.services-endpoint-disabled {
-  background-color: #fcc;
-  border-color: #ebb;
-}
-#services-list-endpoint tr.services-endpoint-debug {
-  background-color: #ffd;
-  border-color: #eeb;
-}
-#services-list-endpoint tr.services-endpoint-enabled {
-  background-color: #dfd;
-  border-color: #beb;
-}
-#services-help-legend .services-endpoint-disabled {
-  background-color: #fcc;
-  border-color: #ebb;
-  display:inline;
-  padding: 5px;
-}
-#services-help-legend .services-endpoint-debug {
-  background-color: #ffd;
-  border-color: #eeb;
-  display:inline;
-  padding: 5px;
-}
-#services-help-legend .services-endpoint-enabled {
-  background-color: #dfd;
-  border-color: #beb;
-  display:inline;
-  padding: 5px;
-}
-#services-help-legend {
-  position:  relative;
-  display:inline;
-}
-#services-help-legend div{
-  border: 1px solid;
+/* Test Table */
+#resource-form-table th.select-all {
+  width: 1em;
+}
+th.resource_test {
+  width: 16em;
+}
+ 
+.resource-image {
+  display: inline-block;
+  cursor: pointer;
+  width: 1em;
+}
+.resource-group-label label {
+  display: inline;
+  font-weight: bold;
+}
+.resource-test-label label {
+  margin-left: 1em; /* LTR */
+}
+.resource-test-description .description {
+  margin: 0;
+}
+#resource-form-table tr td {
+  background-color: white;
+  color: #494949;
+}
+#resource-form-table tr.resource-group td {
+  background-color: #EDF5FA;
+  color: #494949;
+}
+ 
+table#resource-form-table tr.resource-group label {
+  display: inline;
+}
+ 
+div.message > div.item-list {
+  font-weight: normal;
+}
+a.resource-collapse {
+  height: 0;
+  width: 0;
+  top: -99em;
+  position: absolute;
+}
+a.resource-collapse:focus,
+a.resource-collapse:hover {
+  font-size: 80%;
+  top: 0px;
+  height: auto;
+  width: auto;
+  overflow: visible;
+  position: relative;
+  z-index: 1000;
+}
+td.resource-group-alias {
+  width:130px;
 }
\ No newline at end of file
diff --git a/js/services.admin.js b/js/services.admin.js
new file mode 100644
index 0000000..f06bc8b
--- /dev/null
+++ b/js/services.admin.js
@@ -0,0 +1,103 @@
+(function ($) {
+ 
+/**
+ * Add the cool table collapsing on the methoding overview page.
+ */
+Drupal.behaviors.resourceMenuCollapse = {
+  attach: function (context, settings) {
+    var timeout = null;
+    // Adds expand-collapse functionality.
+    $('div.resource-image').each(function () {
+      direction = settings.resource[$(this).attr('id')].imageDirection;
+      $(this).html(settings.resource.images[direction]);
+    });
+ 
+    // Adds group toggling functionality to arrow images.
+    $('div.resource-image').click(function () {
+      var trs = $(this).parents('tbody').children('.' + settings.resource[this.id].methodClass);
+      var direction = settings.resource[this.id].imageDirection;
+      var row = direction ? trs.size() - 1 : 0;
+ 
+      // If clicked in the middle of expanding a group, stop so we can switch directions.
+      if (timeout) {
+        clearTimeout(timeout);
+      }
+ 
+      // Function to toggle an individual row according to the current direction.
+      // We set a timeout of 20 ms until the next row will be shown/hidden to
+      // create a sliding effect.
+      function rowToggle() {
+        if (direction) {
+          if (row >= 0) {
+            $(trs[row]).hide();
+            row--;
+            timeout = setTimeout(rowToggle, 20);
+          }
+        }
+        else {
+          if (row < trs.size()) {
+            $(trs[row]).removeClass('js-hide').show();
+            row++;
+            timeout = setTimeout(rowToggle, 20);
+          }
+        }
+      }
+ 
+      // Kick-off the toggling upon a new click.
+      rowToggle();
+ 
+      // Toggle the arrow image next to the method group title.
+      $(this).html(settings.resource.images[(direction ? 0 : 1)]);
+      settings.resource[this.id].imageDirection = !direction;
+ 
+    });
+  }
+};
+ 
+/**
+ * Select/deselect all the inner checkboxes when the outer checkboxes are
+ * selected/deselected.
+ */
+Drupal.behaviors.resourceSelectAll = {
+  attach: function (context, settings) {
+    $('td.resource-select-all').each(function () {
+      var methodCheckboxes = settings.resource['resource-method-group-' + $(this).attr('id')].methodNames;
+      var groupCheckbox = $('<input type="checkbox" class="form-checkbox" id="' + $(this).attr('id') + '-select-all" />');
+ 
+      // Each time a single-method checkbox is checked or unchecked, make sure
+      // that the associated group checkbox gets the right state too.
+      var updateGroupCheckbox = function () {
+        var checkedTests = 0;
+        for (var i = 0; i < methodCheckboxes.length; i++) {
+          $('#' + methodCheckboxes[i]).each(function () {
+            if (($(this).attr('checked'))) {
+              checkedTests++;
+            }
+          });
+        }
+        $(groupCheckbox).attr('checked', (checkedTests == methodCheckboxes.length));
+      };
+ 
+      // Have the single-method checkboxes follow the group checkbox.
+      groupCheckbox.change(function () {
+        var checked = !!($(this).attr('checked'));
+        for (var i = 0; i < methodCheckboxes.length; i++) {
+          $('#' + methodCheckboxes[i]).attr('checked', checked);
+        }
+      });
+ 
+      // Have the group checkbox follow the single-method checkboxes.
+      for (var i = 0; i < methodCheckboxes.length; i++) {
+        $('#' + methodCheckboxes[i]).change(function () {
+          updateGroupCheckbox();
+        });
+      }
+ 
+      // Initialize status for the group checkbox correctly.
+      updateGroupCheckbox();
+      $(this).append(groupCheckbox);
+    });
+  }
+};
+ 
+})(jQuery);
\ No newline at end of file
diff --git a/plugins/export_ui/services_ctools_export_ui.class.php b/plugins/export_ui/services_ctools_export_ui.class.php
index 2ebf671..2c71717 100644
--- a/plugins/export_ui/services_ctools_export_ui.class.php
+++ b/plugins/export_ui/services_ctools_export_ui.class.php
@@ -1,20 +1,20 @@
 <?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);
+    return drupal_get_form('services_edit_form_endpoint_resources', $item);
   }
-
+ 
   /**
    * Page callback for the authentication page.
    */
@@ -23,7 +23,7 @@ class services_ctools_export_ui extends ctools_export_ui {
     return drupal_get_form('services_edit_form_endpoint_authentication', $item);
   }
 }
-
+ 
 /**
  * Endpoint authentication configuration form.
  */
@@ -31,14 +31,14 @@ 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',
@@ -68,27 +68,27 @@ function services_edit_form_endpoint_authentication($form, &$form_state) {
       }
     }
   }
-
+ 
   $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);
 }
-
-
+ 
+ 
 /**
  * Add resources to an endpoint.
  */
@@ -101,20 +101,18 @@ function services_edit_endpoint_resources($endpoint) {
   }
   return drupal_get_form('services_edit_form_endpoint_resources', $endpoint);
 }
-
+ 
 /**
  * Form to add resources to an endpoint.
  */
 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'),
@@ -122,98 +120,92 @@ function services_edit_form_endpoint_resources(&$form_state, $endpoint) {
     '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,
+  $form['resources'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Resources'),
+    '#description' => t('Select the resource(s) or resource group(s) you would like to enable, and click <em>Save</em>.'),
+   );
+ 
+  $form['resources']['table'] = array(
+    '#theme' => 'services_resource_table',
   );
-
-  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'),
-      ),
+  $ignoreArray = array('actions', 'relationships', 'endpoint', 'name', 'file');
+  // Generate the list of methods arranged by resource.
+  foreach ($resources as $resource => $methods) {
+    $form['resources']['table'][$resource] = array(
+      '#collapsed' => TRUE,
     );
-
-    $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);
-      }
+    if(isset($endpoint['build_info']['args'][0]->resources[$resource])) {
+      $alias = $endpoint['build_info']['args'][0]->resources[$resource]['alias'];
+    } else {
+      $alias = '';
     }
-
-    $classes = array(
-      'actions'          => 'actions',
-      'targeted_actions' => 'targeted actions',
-      'relationships'    => 'relationships',
+    if(isset($form['resources']['table'][$resource]))
+    $form['resources']['table'][$resource]['alias'] = array(
+      '#type' => 'textfield',
+      '#default_value' => $alias,
+      '#name' => $resource .'/alias',
+      '#size' => 20,
     );
-    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);
+    foreach ($methods as $class => $info) {
+      if(!in_array($class, $ignoreArray)) {
+        if(!isset($info['help'])) {
+          $description = t('No description is available');
+        } else {
+          $description = $info['help'];
         }
-      }
-    }
-
-    drupal_alter('services_resource_settings', $res_set, $resource);
-
-    $res[$name] = $res_set;
-  }
-
-  $form['resources'] = $res;
-
-  $form['save'] = array(
-    '#type'  => 'submit',
-    '#value' => t('Save'),
-  );
+        if (isset($endpoint['build_info']['args'][0]->resources[$resource]['operations'][$class])) {
+          $default_value = $endpoint['build_info']['args'][0]->resources[$resource]['operations'][$class]['enabled'];
+        } else {
+          $default_value = 0;
+        }
+        $form['resources']['table'][$resource][$resource .'/'. $class] = array(
+          '#type' => 'checkbox',
+          '#title' => $class,
+          '#description' => $description,
+          '#default_value' => $default_value,
+         );
+      } else if($class == 'actions' || $class == 'relationships' || $class == 'targeted_actions') {
+        foreach($info as $key=>$action) {
+          if(!isset($action['help'])) {
+            $description = t('No description is available');
+          } else {
+            $description = $action['help'];
+          }
+          if (isset($endpoint['build_info']['args'][0]->resources[$resource][$class][$key])) {
+            $default_value = $endpoint['build_info']['args'][0]->resources[$resource][$class][$key]['enabled'];
+          } else {
+            $default_value = 0;
+          }
+          $form['resources']['table'][$resource][$resource .'/'. $key .'/'. $class] = array(
+            '#type' => 'checkbox',
+            '#title' => $key,
+            '#description' => $description,
+            '#default_value' => $default_value,
+          );
+         }
+       }
+     }
+   }
+ 
+   $form['save'] = array(
+     '#type'  => 'submit',
+     '#value' => t('Save'),
+   );
   return $form;
 }
-
+ 
 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'])) {
@@ -224,136 +216,60 @@ function services_edit_form_endpoint_resources_validate($form, $form_state) {
       }
     }
   }
+*/
 }
-
+ 
 function services_edit_form_endpoint_resources_submit($form, $form_state) {
-  $resources = $form_state['values']['resources'];
+  $resources = $form_state['values'];
+  //remove unneeded form inputs so they do not interfere with the foreach, these still exist on $form_state, dont worry :)
+  unset($resources['endpoint_object']);
+  unset($resources['save']);
+  unset($resources['form_build_id']);
+  unset($resources['form_token']);
+  unset($resources['form_id']);
+  unset($resources['op']);
   $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;
+  $existing_resources = _services_build_resources();
+  // Apply the endpoint in a non-strict mode, so that the non-active resources
+  // are preserved.
+  _services_apply_endpoint($existing_resources, $endpoint, FALSE);
+  $resources = $endpoint['input'];
+  $endpoint = $endpoint['build_info']['args'][0];
+   
+  $ops = array(
+    'create'   => t('Create'),
+    'retrieve' => t('Retrieve'),
+    'update'   => t('Update'),
+    'delete'   => t('Delete'),
+    'index'    => t('Index'),
+  );
+ 
+  foreach ($resources as $path => $state) {
+    if (strpos($path, '/')) {
+      $split_path = split('/', $path);
+      $resource = $split_path[0];
+      $method = $split_path[1];
+      if($method != 'alias') {
+        //Not sure what to call this var, mainly because it could be actions, relationships, or targeted actions
+        if(isset($split_path[2]))
+        $non_op_name = $split_path[2];
+        if (array_key_exists($method, $ops)) {
+          //Were an operation, treat it as such
+          if ($state) {
+            $final_resource[$resource]['operations'][$method]['enabled'] = 1;
           }
-          else {
-            unset($c[$class][$act]);
+        } else {
+          if ($state) {
+            $final_resource[$resource][$non_op_name][$method]['enabled'] = 1;
           }
         }
-        if (!$class_used) {
-          unset($c[$class]);
-        }
-        $used = $class_used || $used;
+      } //Else were an alias
+      else {
+        $final_resource[$resource]['alias'] = $state;
       }
-    }
-
-    if ($used) {
-      $endpoint->resources[$name] = $c;
-    }
-    else {
-      unset($endpoint->resources[$name]);
-    }
+    } 
   }
-
-  drupal_set_message(t('Your resources have been saved.'));
+  $endpoint->resources = $final_resource;
+   
   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);
-  }
-}
+}
\ No newline at end of file
diff --git a/plugins/export_ui/services_ctools_export_ui.inc b/plugins/export_ui/services_ctools_export_ui.inc
index e591d33..d24ce0a 100644
--- a/plugins/export_ui/services_ctools_export_ui.inc
+++ b/plugins/export_ui/services_ctools_export_ui.inc
@@ -1,5 +1,5 @@
 <?php
-
+ 
 $plugin = array(
   'schema' => 'services_endpoint',
   'menu' => array(
@@ -29,23 +29,27 @@ $plugin = array(
   ),
   // Add our custom operations.
   'allowed operations' => array(
-    'resources'  => array('title' => t('Resources')),
-    'authentication' => array('title' => t('Authentication')),
+    'resources'  => array('title' => t('Edit Resources')),
+    'authentication' => array('title' => t('Edit Authentication')),
+  ),
+  'form' => array(
+    'settings' => 'services_ctools_export_ui_form',
+    'validate' => 'services_ctools_export_ui_form_validate',
+    'submit' => 'services_ctools_export_ui_form_submit',
   ),
-
   '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.
  */
@@ -57,22 +61,12 @@ function services_ctools_export_ui_form(&$form, &$form_state) {
     '#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,
@@ -81,7 +75,7 @@ function services_ctools_export_ui_form(&$form, &$form_state) {
     '#title'         => t('Endpoint title'),
     '#required'      => TRUE,
   );
-
+ 
   $servers = services_get_servers();
   $server_opts = array(
     '' => t('-- Select a server'),
@@ -97,7 +91,7 @@ function services_ctools_export_ui_form(&$form, &$form_state) {
     '#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,
@@ -106,16 +100,16 @@ function services_ctools_export_ui_form(&$form, &$form_state) {
     '#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,
@@ -134,53 +128,50 @@ function services_ctools_export_ui_form(&$form, &$form_state) {
         '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.'));
+    form_error($form['info']['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.'));
+    if (is_string($res)) {
+      form_error($form['info']['name'], t('Endpoint name must be unique.'));
+      return;
     }
   }
   //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'];
-
+ 
+  $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();
@@ -195,14 +186,5 @@ function services_ctools_export_ui_form_submit(&$form, &$form_state) {
     }
   }
   $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);
-  }
+  $form_state['values']['endpoint_object'] = $endpoint;
 }
\ No newline at end of file
diff --git a/services.admin.inc b/services.admin.inc
index 6c1fca6..2bc5d00 100644
--- a/services.admin.inc
+++ b/services.admin.inc
@@ -1,19 +1,146 @@
 <?php
-
+ 
 /**
  * @file
  * Administrative functions for the Services module.
  *
  * This provides the UI to list, create, edit and delete endpoints.
  */
-
+/**
+ * Returns HTML for an endpoint generated by services_edit_form_endpoint_resources() into a table.
+ *
+ * @param $variables
+ *   An associative array containing:
+ *   - table: A render element representing the table.
+ *
+ * @ingroup themeable
+ */
+function theme_services_resource_table($variables) {
+  $table = $variables['table'];
+ 
+  drupal_add_css(drupal_get_path('module', 'services') . '/css/services.admin.css');
+  drupal_add_js(drupal_get_path('module', 'services') . '/js/services.admin.js');
+  drupal_add_js('misc/tableselect.js');
+ 
+  // Create header for resource selection table.
+  $header = array(
+    array('class' => array('select-all')),
+    array('data' => t('Resource'), 'class' => array('resource_method')),
+    array('data' => t('Description'), 'class' => array('resource_description')),
+    array('data' => t('Alias'), 'class' => array('resource_alias')),
+  );
+ 
+  // Define the images used to expand/collapse the method groups.
+  $js = array(
+    'images' => array(
+      theme('image', array('path' => 'misc/menu-collapsed.png', 'alt' => t('Expand'), 'title' => t('Expand'))) . ' <a href="#" class="resource-collapse">(' . t('Expand') . ')</a>',
+      theme('image', array('path' => 'misc/menu-expanded.png', 'alt' => t('Collapse'), 'title' => t('Collapse'))) . ' <a href="#" class="resource-collapse">(' . t('Collapse') . ')</a>',
+    ),
+  );
+ 
+  // Cycle through each method group and create a row.
+  $rows = array();
+  foreach (element_children($table) as $key) {
+    $element = &$table[$key];
+    $row = array();
+ 
+    // Make the class name safe for output on the page by replacing all
+    // non-word/decimal characters with a dash (-).
+    $method_class = strtolower(trim(preg_replace("/[^\w\d]/", "-", $key)));
+ 
+    // Select the right "expand"/"collapse" image, depending on whether the
+    // category is expanded (at least one method selected) or not.
+    $collapsed = !empty($element['#collapsed']);
+    $image_index = $collapsed ? 0 : 1;
+ 
+    // Place-holder for checkboxes to select group of methods.
+    $row[] = array('id' => $method_class, 'class' => array('resource-select-all'));
+ 
+    // Expand/collapse image and group title.
+    $row[] = array(
+      'data' => '<div class="resource-image" id="resource-method-group-' . $method_class . '"></div>' .
+        '<label for="' . $method_class . '-select-all" class="resource-group-label">' . $key . '</label>',
+      'class' => array('resource-group-label'),
+    );
+     
+    $row[] = array(
+      'data' => '&nbsp;',
+      'class' => array('resource-group-description'),
+    );
+    $row[] = array(
+      'data' => drupal_render($element['alias']),
+      'class' => array('resource-group-alias'),
+    );
+    $rows[] = array('data' => $row, 'class' => array('resource-group'));
+ 
+    // Add individual methods to group.
+    $current_js = array(
+      'methodClass' => $method_class . '-method',
+      'methodNames' => array(),
+      'imageDirection' => $image_index,
+      'clickActive' => FALSE,
+    );
+ 
+    // Cycle through each method within the current group.
+    foreach (element_children($element) as $method_name) {
+      if($method_name != 'alias') {
+        $method = $element[$method_name];
+        $row = array();
+   
+        $current_js['methodNames'][] = $method['#id'];
+   
+        // Store method title and description so that checkbox won't render them.
+        $title = $method['#title'];
+        $description = $method['#description'];
+   
+        $method['#title_display'] = 'invisible';
+        unset($method['#description']);
+   
+        // Test name is used to determine what methods to run.
+        $method['#name'] = $method_name;
+   
+        $row[] = array(
+          'data' => drupal_render($method),
+          'class' => array('resource-method-select'),
+        );
+        $row[] = array(
+          'data' => '<label for="' . $method['#id'] . '">' . $title . '</label>',
+          'class' => array('resource-method-label'),
+        );
+        $row[] = array(
+          'data' => '<div class="description">' . $description . '</div>',
+          'class' => array('resource-method-description'),
+        );
+        $row[] = array(
+          'data' => '<div class="alias">&nbsp;</div>',
+          'class' => array('resource-method-alias'),
+        );
+        $rows[] = array('data' => $row, 'class' => array($method_class . '-method', ($collapsed ? 'js-hide' : '')));
+      } 
+ 
+    }
+    $js['resource-method-group-' . $method_class] = $current_js;
+    unset($table[$key]);
+  }
+ 
+  // Add js array of settings.
+  drupal_add_js(array('resource' => $js), 'setting');
+ 
+  if (empty($rows)) {
+    return '<strong>' . t('No resourcess to display.') . '</strong>';
+  }
+  else {
+    return theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'resource-form-table')));
+  }
+}
+ 
 /**
  * Output a list of endpoints.
  */
 function services_list_endpoint($js = NULL) {
   // Loading runtime include as needed by services_auth_info().
   module_load_include('runtime.inc', 'services');
-
+ 
   $header = array(
     array('data' => t('Title'),          'class' => 'services-endpoint-title'),
     array('data' => t('Path'),           'class' => 'services-endpoint-path'),
@@ -23,10 +150,10 @@ function services_list_endpoint($js = NULL) {
     array('data' => t('Debug'),        'class' => 'services-endpoint-debug'),
     array('data' => t('Operations'),     'class' => 'services-endpoint-operations'),
   );
-
+ 
   $endpoints = services_endpoint_load_all();
   $rows = array();
-
+ 
   foreach ($endpoints as $endpoint) {
     $debug = array();
     if ($endpoint->debug) {
@@ -45,7 +172,7 @@ function services_list_endpoint($js = NULL) {
       );
     }
     $operations = array();
-
+ 
     if (empty($endpoint->disabled)) {
       $operations[] = array(
         'title' => t('Edit'),
@@ -56,7 +183,7 @@ function services_list_endpoint($js = NULL) {
         'href'  => 'admin/config/services/services/' . $endpoint->name . '/export',
       );
     }
-
+ 
     if ($endpoint->export_type == (EXPORT_IN_CODE | EXPORT_IN_DATABASE)) {
       $operations[] = array(
         'title' => t('Revert'),
@@ -76,7 +203,7 @@ function services_list_endpoint($js = NULL) {
         'query' => drupal_get_destination(),
       );
     }
-
+ 
     $auth_names = array();
     foreach (array_keys($endpoint->authentication) as $module) {
       $info = services_authentication_info($module);
@@ -99,7 +226,7 @@ function services_list_endpoint($js = NULL) {
       $info = services_authentication_info($module);
       $auth_names[] = $info['title'];
     }
-
+ 
     $row_class = array($row_class);
     $rows[$endpoint->name] = array(
       'data' => array(
@@ -142,7 +269,7 @@ function services_list_endpoint($js = NULL) {
     );
   }
   drupal_add_css(drupal_get_path('module', 'services') . '/css/services.admin.css');
-
+ 
   $build['table'] = array(
     '#theme' => 'table',
     '#header' => $header,
@@ -150,10 +277,10 @@ function services_list_endpoint($js = NULL) {
     '#attributes' => array('id' => array('services-list-endpoint')),
     '#empty' => t('No endpoints defined'),
   );
-
+ 
   return $build;
 }
-
+ 
 /**
  * Handle the add endpoint page..
  */
@@ -162,7 +289,7 @@ function services_add_endpoint() {
   drupal_set_title(t('Add endpoint'));
   return services_edit_endpoint($endpoint);
 }
-
+ 
 /**
  * Edit an endpoint.
  *
@@ -177,7 +304,7 @@ function services_edit_endpoint($endpoint) {
   }
   return drupal_get_form('services_edit_form_endpoint', $endpoint);
 }
-
+ 
 /**
  * Form to edit the settings of an endpoint.
  */
@@ -185,17 +312,17 @@ function services_edit_form_endpoint($form, &$form_state) {
   list($endpoint) = $form_state['build_info']['args'];
   // Loading runtime include as needed by services_auth_info().
   module_load_include('runtime.inc', 'services');
-
+ 
   $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,
@@ -205,7 +332,7 @@ function services_edit_form_endpoint($form, &$form_state) {
     '#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,
@@ -214,7 +341,7 @@ function services_edit_form_endpoint($form, &$form_state) {
     '#title'         => t('Endpoint title'),
     '#required'      => TRUE,
   );
-
+ 
   $servers = services_get_servers();
   $server_opts = array(
     '' => t('-- Select a server'),
@@ -230,7 +357,7 @@ function services_edit_form_endpoint($form, &$form_state) {
     '#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,
@@ -239,16 +366,16 @@ function services_edit_form_endpoint($form, &$form_state) {
     '#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,
@@ -267,16 +394,16 @@ function services_edit_form_endpoint($form, &$form_state) {
         '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.
  */
@@ -289,11 +416,11 @@ function services_edit_form_endpoint_validate($form, &$form_state) {
     $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.'));
@@ -301,18 +428,18 @@ function services_edit_form_endpoint_validate($form, &$form_state) {
   }
   //TODO: More validation? Eg. validate path? Transliteration etc?
 }
-
+ 
 /**
  * Process submission of the mini panel edit form.
  */
 function services_edit_form_endpoint_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();
@@ -327,7 +454,7 @@ function services_edit_form_endpoint_submit($form, &$form_state) {
     }
   }
   $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);
@@ -340,7 +467,7 @@ function services_edit_form_endpoint_submit($form, &$form_state) {
     $form_state['redirect'] = 'admin/config/services/services';
   }
 }
-
+ 
 /**
  * Page callback to export an endpoint to PHP code.
  */
@@ -351,7 +478,7 @@ function services_export_endpoint($form, &$form_state) {
   }
   drupal_set_title(check_plain($endpoint->title));
   $code = services_endpoint_export($endpoint);
-
+ 
   $lines = substr_count($code, "\n");
   $form['code'] = array(
     '#type'          => 'textarea',
@@ -359,10 +486,10 @@ function services_export_endpoint($form, &$form_state) {
     '#default_value' => $code,
     '#rows'          => $lines,
   );
-
+ 
   return $form;
 }
-
+ 
 /**
  * Provide a form to confirm deletion of an endpoint.
  */
@@ -392,7 +519,7 @@ function services_delete_confirm_endpoint($form, &$form_state) {
     $submit, t('Cancel')
   );
 }
-
+ 
 /**
  * Handle the submit button to delete an endpoint.
  */
@@ -403,7 +530,7 @@ function services_delete_confirm_endpoint_submit($form, &$form_state) {
     $form_state['redirect'] = 'admin/config/services/services';
   }
 }
-
+ 
 /**
  * Enable debug mode on an endpoint.
  */
@@ -415,7 +542,7 @@ function services_enable_debug_mode($endpoint) {
   services_endpoint_save($endpoint);
   drupal_goto();
 }
-
+ 
 /**
  * Disable debug mode on an endpoint.
  */
@@ -427,7 +554,7 @@ function services_disable_debug_mode($endpoint) {
   services_endpoint_save($endpoint);
   drupal_goto();
 }
-
+ 
 /**
  * Enable a default endpoint.
  */
@@ -441,7 +568,7 @@ function services_enable_endpoint($endpoint) {
   menu_rebuild();
   drupal_goto();
 }
-
+ 
 /**
  * Disable a default endpoint.
  */
@@ -455,8 +582,8 @@ function services_disable_endpoint($endpoint) {
   menu_rebuild();
   drupal_goto();
 }
-
-
+ 
+ 
 /**
  * Configure authentication for a endpoint.
  */
@@ -468,337 +595,4 @@ function services_edit_endpoint_authentication($endpoint) {
     drupal_set_title(check_plain($endpoint->title));
   }
   return drupal_get_form('services_edit_form_endpoint_authentication', $endpoint);
-}
-
-/**
- * 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);
-}
-
-/**
- * Add resources to an endpoint.
- */
-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);
-}
-
-/**
- * Form to add resources to an endpoint.
- */
-function services_edit_form_endpoint_resources($form, &$form_state) {
-  list($endpoint) = $form_state['build_info']['args'];
-  module_load_include('resource_build.inc', 'services');
-
-  $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;
-}
-
-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'],
-        )));
-      }
-    }
-  }
-}
-
-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
-    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);
-  }
-}
+}
\ No newline at end of file
diff --git a/services.module b/services.module
index ede584b..cdc1c27 100644
--- a/services.module
+++ b/services.module
@@ -139,6 +139,10 @@ function services_theme() {
       'template'  => 'services_endpoint_index',
       'arguments' => array('endpoints' => NULL),
     ),
+    'services_resource_table' => array(
+      'render element' => 'table',
+      'file' => 'services.admin.inc',
+    ),
   );
 }
 
-- 
1.7.3.4

