From 3430c635507dff84c06387138ac02399d0f801e2 Mon Sep 17 00:00:00 2001
From: Joachim Noreiko <joachim@107701.no-reply.drupal.org>
Date: Wed, 15 Jan 2014 16:53:28 +0000
Subject: [PATCH 1/7] woden patch 5

---
 README.txt                            |   50 +++++--
 plugins/services_entity_abstract.inc  |  224 ++++++++++++++++++++++++++++++++
 plugins/services_entity_interface.inc |   14 +-
 plugins/services_entity_resource.inc  |    2 +-
 services_entity.info                  |    1 +
 services_entity.module                |   83 ++++++++++++
 services_entity.resources.inc         |   55 --------
 services_entity.services.inc          |  229 ---------------------------------
 8 files changed, 362 insertions(+), 296 deletions(-)
 create mode 100644 plugins/services_entity_abstract.inc
 delete mode 100644 services_entity.resources.inc
 delete mode 100644 services_entity.services.inc

diff --git a/README.txt b/README.txt
index a1e3bb9..4903890 100644
--- a/README.txt
+++ b/README.txt
@@ -1,11 +1,14 @@
 Services Entity
 ===============
 
-Services Entity allows you to use any entity type with Services, going beyond the core entity types that Services supports.
+Services Entity allows you to use any entity type with Services, going beyond
+the core entity types that Services supports.
 
-Each entity type is defined as a new resource, with the usual services: create, retrieve, update, delete, and index.
+Each entity type is defined as a new resource, with the usual services: create,
+retrieve, update, delete, and index.
 
-Entity types must declare an access callback (see entity_crud_hook_entity_info() in Entity API's hook documentation, and also entity_access()).
+Entity types must declare an access callback (see entity_crud_hook_entity_info()
+in Entity API's hook documentation, and also entity_access()).
 
 Entity Processors
 -----------------
@@ -13,15 +16,17 @@ Entity Processors
 Services Entity comes with two entity processors. These determine the way in
 which the provided resources handle parameters:
 
-* The Generic Entity Processor returns and expects an entity structure exactly as Drupal uses internally.
+* The Generic Entity Processor returns and expects an entity structure exactly
+  as Drupal uses internally.
 * The Clean Entity Processor uses a simplified version of the entity structure,
-with 'Drupalisms' such as deep field arrays removed. It also uses Entity API
-metadata property names instead of entity schema property names.
+  with 'Drupalisms' such as deep field arrays removed. It also uses Entity API
+  metadata property names instead of entity schema property names.
 
 Resources
 ---------
 
-An entity type 'foo' has a resource 'entity_foo' defined for it. On this the following services are defined:
+An entity type 'foo' has a resource 'entity_foo' defined for it. On this the
+following services are defined:
 
 - create:
   - Send the object data in the body.
@@ -33,7 +38,8 @@ An entity type 'foo' has a resource 'entity_foo' defined for it. On this the fol
 
 - retrieve
   Has the following query string parameters:
-  - 'fields': specify which fields to return. For example: endpoint/entity_node/1?fields=title,nid
+  - 'fields': specify which fields to return.
+    For example: endpoint/entity_node/1?fields=title,nid
 
 - delete
   - Returns 200 if successful.
@@ -41,8 +47,32 @@ An entity type 'foo' has a resource 'entity_foo' defined for it. On this the fol
 - index
   Has the following query string parameters:
   - 'fields': A comma separated list of fields to get.
-  -  'parameters': Filter parameters array of entity properties, such as parameters[title]="test"
+  - 'parameters': Filter parameters array of entity properties, such as
+    parameters[title]="test"
   - 'page': The zero-based index of the page to get, defaults to 0.
-  - 'pagesize': Number of records to get per page. This has a maximum imposed on it to prevent excessive load to the server. By default this is 100, but this may be changed in the admin settings.
+  - 'pagesize': Number of records to get per page. This has a maximum imposed on
+    it to prevent excessive load to the server. By default this is 100, but this
+    may be changed in the admin settings.
   - 'sort': Field or property to sort by.
   - 'direction': Direction of the sort. ASC or DESC.
+
+  Custom Controllers
+  ------------------
+  You may provide your own controllers, either globally or for specific entities.
+  These should implement the ServicesResourceControllerInterface.
+
+  A global controller should be registered by implementing
+  hook_services_entity_resource_info(). It may then be chosen in the UI, or by
+  setting the 'services_entity_resource_class" system variable.
+
+  An entity-specific controller should be registered by implementing
+  hook_entity_info() or hook_entity_info_alter() and adding a
+  'resource controller' key to the info array.  The specified class will then be
+  used for that entity type.
+
+  A custom controller must implement the resourceInfo() method to describe the
+  operations, actions and relationships which it provides. For convenience, the
+  ServicesResourceControllerAbstract class is provided to define basic CRUD/I
+  operations. You may extend this to add additional arguments, actions or
+  relationships. Note: this class provides definitions only; you must extend to
+  provide implementations yourself.
diff --git a/plugins/services_entity_abstract.inc b/plugins/services_entity_abstract.inc
new file mode 100644
index 0000000..321ad55
--- /dev/null
+++ b/plugins/services_entity_abstract.inc
@@ -0,0 +1,224 @@
+<?php
+
+/**
+ * @file
+ * Services Entity module integration for entities.
+ */
+
+/**
+ * An abstract controller providing basic CRUD resource info for entities.
+ *
+ * Note that this controller only provides the resource definition. There
+ * are no implementations of any of the CRUD methods.  You should extend
+ * this class to provide your own implementations.
+ *
+ * @see ServicesEntityResourceController
+ */
+abstract class ServicesResourceControllerAbstract implements ServicesResourceControllerInterface {
+  /**
+   * Helper function to return basic information about a resource operation.
+   *
+   * Invoke this method to build the basic info array, and then override
+   * elements as needed for your specific entity/operation.
+   *
+   * @param string $entity_type
+   *   The entity type.
+   * @param string $method
+   *   The method name to invoke. This method must be defined in the controller class.
+   * @param boolean $id_arg
+   *   If set, add an argument representing the entity_id. This will be taken from
+   *   path index 0.
+   * @param boolean $values_arg
+   *   If set, add an argument representing the entity values. This will be taken from
+   *   the POST data.
+   *
+   * @return
+   *   An info array which can be used as a basis for building a services resource
+   *   definition.
+   *
+   * @see ServicesResourceControllerAbstract::resourceInfo()
+   * @see hook_services_resources()
+   */
+  protected function getInfoElement($entity_type, $method, $id_arg, $values_arg) {
+    $element = array(
+      'callback' => '_services_entity_resource_callback',
+      'help' => "Executes the $method operation on entities of type $entity_type",
+      'args' => array(
+        // This pseudo-argument is how we will pass the specific method name to
+        // our generic callback.
+        array(
+          'name' => 'method',
+          'optional' => TRUE,  // Otherwise throws an error
+          'default value' => $method,
+          'type' => 'string',
+          'description' => 'Internal use only: the method to invoke.',
+        ),
+        // This pseudo-argument is how we will pass the specific entity type to
+        // our generic callback.
+        array(
+          'name' => 'entity_type',
+          'optional' => TRUE,  // Otherwise throws an error
+          'default value' => $entity_type,
+          'type' => 'string',
+          'description' => 'Internal use only: the type of entity.',
+        ),
+      ),
+      'access callback' => '_services_entity_access_callback',
+      // We must specify 'access arguments' (even though not used) in order to
+      // force services to append the rest of the arguments.
+      'access arguments' => array(TRUE),
+      'access arguments append' => TRUE,
+    );
+
+    // If specified, add an argument representing the entity_id.
+    // This will be taken from index 0 of the path.
+    if ($id_arg) {
+      $element['args'][] = array(
+        'name' => $entity_type . '_id',
+        'optional' => FALSE,
+        'source' => array('path' => 0),
+        'type' => 'int',
+        'description' => "The $entity_type id.",
+      );
+    }
+
+    // If specified, add an argument representing the entity data.
+    // This will be taken from the post data.
+    if ($values_arg) {
+      $element['args'][] = array(
+        'name' => 'values',
+        'optional' => FALSE,
+        'source' => 'data',
+        'description' => "A representation of the $entity_type",
+        'type' => 'struct',
+      );
+    }
+
+    return $element;
+  }
+
+  /**
+   * Implemented to define basic CRUD/I operations.
+   *
+   * May be extended to add arguments to these operations, or to
+   * add additional actions or relationships.
+   *
+   * @see ServicesResourceControllerInterface::resourceInfo()
+   */
+  public function resourceInfo($entity_type) {
+    $info = array();
+
+    // Create.
+    if (entity_type_supports($entity_type, 'create')) {
+      $info['create'] = $this->getInfoElement($entity_type, 'create', FALSE, TRUE);
+    }
+    $info['create']['help'] = "Creates a $entity_type entity.";
+
+    // Retrieve
+    $info['retrieve'] = $this->getInfoElement($entity_type, 'retrieve', TRUE, FALSE);
+    $info['retrieve']['help'] = "Retrieves a $entity_type entity.";
+    $info['retrieve']['args'][] = array(
+      'name' => 'fields',
+      'optional' => TRUE,
+      'type' => 'string',
+      'description' => 'A comma separated list of fields to get.',
+      'default value' => '*',
+      'source' => array('param' => 'fields'),
+    );
+
+    // Update
+    if (entity_type_supports($entity_type, 'save')) {
+      $info['update'] = $this->getInfoElement($entity_type, 'update', TRUE, TRUE);
+    }
+    $info['update']['help'] = "Updates a $entity_type entity";
+
+    // Delete
+    if (entity_type_supports($entity_type, 'delete')) {
+      $info['delete'] = $this->getInfoElement($entity_type, 'delete', TRUE, FALSE);
+    }
+    $info['delete']['help'] = "Deletes a $entity_type entity";
+
+    // Index
+    $info['index'] = $this->getInfoElement($entity_type, 'index', FALSE, FALSE);
+    $info['index']['help'] = "Retrieves a list of $entity_type entities.";
+    /**
+     * Fields to return
+     *
+     * these should be specified in a comma separated list like ?fields=title,created,uid
+     */
+    $info['index']['args'][] = array(
+      'name' => 'fields',
+      'optional' => TRUE,
+      'type' => 'string',
+      'description' => 'A comma separated list of fields to get.',
+      'default value' => '*',
+      'source' => array('param' => 'fields'),
+    );
+    /**
+     * Filter parameters
+     *
+     * these should be specified by ?parameters[title]=My Title&param[created]=4403305
+     */
+    $info['index']['args'][] = array(
+      'name' => 'parameters',
+      'optional' => TRUE,
+      'type' => 'array',
+      'description' => 'Filter parameters array such as parameters[title]="test"',
+      'default value' => array(),
+      'source' => array('param' => 'parameters'),
+    );
+    /**
+     * Page number
+     *
+     * A zero based page number like ?page=3 (returns the fourth page)
+     */
+    $info['index']['args'][] = array(
+      'name' => 'page',
+      'optional' => TRUE,
+      'type' => 'int',
+      'description' => 'The zero-based index of the page to get, defaults to 0.',
+      'default value' => 0,
+      'source' => array('param' => 'page'),
+    );
+    /**
+     * Page Size
+     *
+     * How many records per page to return. ?pagesize=20
+     */
+    $info['index']['args'][] = array(
+      'name' => 'pagesize',
+      'optional' => TRUE,
+      'type' => 'int',
+      'description' => 'Number of records to get per page.',
+      'default value' => variable_get('services_entity_' . $entity_type . '_index_page_size', 20),
+      'source' => array('param' => 'pagesize'),
+    );
+    /**
+     * Sort field
+     *
+     * Which field to sort on. ?sort=created
+     */
+    $info['index']['args'][] = array(
+      'name' => 'sort',
+      'optional' => TRUE,
+      'type' => 'string',
+      'description' => 'Field to sort by.',
+      'default value' => '',
+      'source' => array('param' => 'sort'),
+    );
+    /**
+     * Sort Direction
+     *
+     * Which direction to sort. Possible Values = "ASC|DESC" ?direction=DESC
+     */
+    $info['index']['args'][] = array(
+      'name' => 'direction',
+      'optional' => TRUE,
+      'type' => 'string',
+      'description' => 'Direction of the sort. ASC or DESC.',
+      'default value' => 'ASC',
+      'source' => array('param' => 'direction'),
+    );
+    return $info;
+  }
+}
diff --git a/plugins/services_entity_interface.inc b/plugins/services_entity_interface.inc
index 08508f1..0dade2f 100644
--- a/plugins/services_entity_interface.inc
+++ b/plugins/services_entity_interface.inc
@@ -11,6 +11,19 @@
 interface ServicesResourceControllerInterface {
 
   /**
+   * Specify services resource informatino for an entity type.
+   *
+   * @param string $entity_type
+   *   The entity type whose info should be specified.
+   *
+   * @return
+   *   An info array as would be returned by hook_services_resources().
+   *
+   * @see hooK_services_resources().
+   */
+  public function resourceInfo($entity_type);
+
+  /**
    * Determines access for a given operation and resource.
    *
    * @param string $op
@@ -107,6 +120,5 @@ interface ServicesResourceControllerInterface {
    *  Entity data for the field.
    */
   public function field($entity_type, $entity_id, $field_name, $fields = '*', $raw = FALSE);
-
 }
 
diff --git a/plugins/services_entity_resource.inc b/plugins/services_entity_resource.inc
index 3e095ce..0f9f869 100644
--- a/plugins/services_entity_resource.inc
+++ b/plugins/services_entity_resource.inc
@@ -3,7 +3,7 @@
 /**
  * Generic controller for entity-bases resources.
  */
-class ServicesEntityResourceController implements ServicesResourceControllerInterface {
+class ServicesEntityResourceController extends ServicesResourceControllerAbstract {
 
   /**
    * Implements ServicesResourceControllerInterface::access().
diff --git a/services_entity.info b/services_entity.info
index 8425cc0..c449110 100644
--- a/services_entity.info
+++ b/services_entity.info
@@ -7,6 +7,7 @@ dependencies[] = entity
 
 files[] = tests/services_entity.test
 files[] = plugins/services_entity_interface.inc
+files[] = plugins/services_entity_abstract.inc
 files[] = plugins/services_entity_resource.inc
 files[] = plugins/services_entity_resource_clean.inc
 files[] = plugins/services_entity_resource_recurse.inc
diff --git a/services_entity.module b/services_entity.module
index c1f70e3..eada6f4 100644
--- a/services_entity.module
+++ b/services_entity.module
@@ -88,6 +88,20 @@ function services_entity_services_entity_resource_info() {
 }
 
 /**
+ * Implements of hook_services_resources().
+ */
+function services_entity_services_resources() {
+  $resources = array();
+
+  foreach (entity_get_info() as $entity_type => $info) {
+    $controller = _services_entity_get_controller($entity_type);
+    $resources["entity_$entity_type"] = $controller->resourceInfo($entity_type);
+  }
+
+  return $resources;
+}
+
+/**
  * Implements hook_services_resources_alter().
  */
 function services_entity_services_resources_alter(&$resources) {
@@ -141,3 +155,72 @@ function services_entity_entity_query_alter($query) {
     unset($conditions['bundle']);
   }
 }
+
+/**
+ * Gets an instance of the resource controller for an entity type.
+ *
+ * @param string $entity_type
+ *   The entity type.
+ *
+ * @return ServicesEntityResourceControllerInterface
+ *   A resource controller object.
+ */
+function _services_entity_get_controller($entity_type) {
+  $info = entity_get_info($entity_type);
+  if (isset($info['resource controller']) && class_exists($info['resource controller'])) {
+    $class = $info['resource controler'];
+  }
+  else {
+    $class = variable_get('services_entity_resource_class', 'ServicesEntityResourceController');
+  }
+  return new $class();
+}
+
+/**
+ * Services resource callback.
+ *
+ * Invokes the specified method of the appropriate controller.
+ *
+ * @param string $method
+ *   The method to invoke.
+ * @param string $entity_type
+ *   The entity type.
+ * @param ...
+ *   Additional arguments to pass to the method.
+ *
+ * @return mixed
+ *   The response payload for the service, ready for formatting.
+ */
+function _services_entity_resource_callback($method, $entity_type) {
+  $args = func_get_args();
+  $method = array_shift($args);
+  $entity_type = reset($args);
+  $controller = _services_entity_get_controller($entity_type);
+  return call_user_func_array(array($controller, $method), $args);
+}
+
+/**
+ * Services access callback.
+ *
+ * Invokes the 'access' method of the appropriate controller.
+ *
+ * @param boolean $access
+ *   Always TRUE. Present to force service to append the other arguments.
+ * @param array $args
+ *   An array of arguments that will be passed to the resource callback.
+ *
+ * @return boolean
+ *   TRUE to grant access, FALSE otherwise.
+ */
+function _services_entity_access_callback($access, $args) {
+  $args = func_get_args();
+  $args = end($args);
+  $op = array_shift($args);
+  $entity_type = reset($args);
+  $controller = _services_entity_get_controller($entity_type);
+  // We need to translate the "retrieve" method name to "view" in order
+  // to pass it on directly to entity_access(). For all other operations
+  // we can just use the method name.
+  $op = $op === 'retrieve' ? 'view' : $op;
+  return call_user_func_array(array($controller, 'access'), array($op, $args));
+}
diff --git a/services_entity.resources.inc b/services_entity.resources.inc
deleted file mode 100644
index 7efc332..0000000
--- a/services_entity.resources.inc
+++ /dev/null
@@ -1,55 +0,0 @@
-<?php
-
-/**
- * Services resource access callback.
- */
-function _services_entity_resource_access($op, $args) {
-  $resourceclass = variable_get('services_entity_resource_class', 'ServicesEntityResourceController');
-  $resource = new $resourceclass;
-  return $resource->access($op, $args);
-}
-
-/**
- * Services resource callback.
- */
-function _services_entity_resource_create($entity_type, $values) {
-  $resourceclass = variable_get('services_entity_resource_class', 'ServicesEntityResourceController');
-  $resource = new $resourceclass;
-  return $resource->create($entity_type, $values);
-}
-
-/**
- * Services resource callback.
- */
-function _services_entity_resource_retrieve($entity_type, $entity_key, $fields) {
-  $resourceclass = variable_get('services_entity_resource_class', 'ServicesEntityResourceController');
-  $resource = new $resourceclass;
-  return $resource->retrieve($entity_type, $entity_key, $fields);
-}
-
-/**
- * Services resource callback.
- */
-function _services_entity_resource_update($entity_type, $entity_id, $values) {
-  $resourceclass = variable_get('services_entity_resource_class', 'ServicesEntityResourceController');
-  $resource = new $resourceclass;
-  return $resource->update($entity_type, $entity_id, $values);
-}
-
-/**
- * Services resource callback.
- */
-function _services_entity_resource_delete($entity_type, $entity_id) {
-  $resourceclass = variable_get('services_entity_resource_class', 'ServicesEntityResourceController');
-  $resource = new $resourceclass;
-  return $resource->delete($entity_type, $entity_id);
-}
-
-/**
- * Services resource callback.
- */
-function _services_entity_resource_index($entity_type, $fields, $parameters, $page, $pagesize, $sort, $direction) {
-  $resourceclass = variable_get('services_entity_resource_class', 'ServicesEntityResourceController');
-  $resource = new $resourceclass;
-  return $resource->index($entity_type, $fields, $parameters, $page, $pagesize, $sort, $direction);
-}
diff --git a/services_entity.services.inc b/services_entity.services.inc
deleted file mode 100644
index 8060fbe..0000000
--- a/services_entity.services.inc
+++ /dev/null
@@ -1,229 +0,0 @@
-<?php
-
-/**
- * Implementation of hook_services_resources().
- */
-function services_entity_services_resources() {
-  $resources = array();
-
-  foreach (entity_get_info() as $entity_type => $info) {
-    if (entity_type_supports($entity_type, 'create')) {
-      $resources["entity_$entity_type"]['create'] = array(
-        'file' => array('type' => 'inc', 'module' => 'services_entity', 'name' => 'services_entity.resources'),
-        'callback' => '_services_entity_resource_create',
-        'description' => "Creates a $entity_type",
-        'args' => array(
-          array(
-            'name' => 'entity_type',
-            'optional' => TRUE,  // Otherwise throws an error
-            'default value' => $entity_type,
-            'type' => 'string',
-            'description' => 'The type of the entity to create',
-          ),
-          array(
-            'name' => 'values',
-            'optional' => FALSE,
-            'source' => 'data',
-            'description' => 'The values used to create the entity',
-            'type' => 'struct',
-          ),
-        ),
-        'access callback' => '_services_entity_resource_access',
-        'access arguments' => array('create'),
-        'access arguments append' => TRUE,
-      );
-    }
-
-    $resources["entity_$entity_type"]['retrieve'] = array(
-      'file' => array('type' => 'inc', 'module' => 'services_entity', 'name' => 'services_entity.resources'),
-      'callback' => '_services_entity_resource_retrieve',
-      'description' => "Retrieves a $entity_type",
-      'args' => array(
-        array(
-          'name' => 'entity_type',
-          'optional' => TRUE,  // Otherwise throws an error
-          'default value' => $entity_type,
-          'type' => 'string',
-          'description' => 'The type of the entity to get',
-        ),
-        array(
-          'name' => $entity_type . '_id',
-          'optional' => FALSE,
-          'source' => array('path' => 0),
-          'type' => 'int',
-          'description' => 'The ID of the ' . $entity_type . ' to retrieve.',
-        ),
-        array(
-          'name' => 'fields',
-          'optional' => TRUE,
-          'type' => 'string',
-          'description' => 'A comma separated list of fields to get.',
-          'default value' => '*',
-          'source' => array('param' => 'fields'),
-        ),
-      ),
-      'access callback' => '_services_entity_resource_access',
-      'access arguments' => array('view'),
-      'access arguments append' => TRUE,
-    );
-
-    if (entity_type_supports($entity_type, 'save')) {
-      $resources["entity_$entity_type"]['update'] = array(
-        'file' => array('type' => 'inc', 'module' => 'services_entity', 'name' => 'services_entity.resources'),
-        'callback' => '_services_entity_resource_update',
-        'description' => "Updates a $entity_type",
-        'args' => array(
-          array(
-            'name' => 'entity_type',
-            'optional' => TRUE,  // Otherwise throws an error
-            'default value' => $entity_type,
-            'type' => 'string',
-            'description' => 'The type of the entity to update',
-          ),
-          array(
-            'name' => $entity_type . '_id',
-            'optional' => FALSE,
-            'source' => array('path' => 0),
-            'type' => 'int',
-          'description' => 'The ID of the ' . $entity_type . ' to update.',
-          ),
-          array(
-            'name' => 'values',
-            'optional' => FALSE,
-            'source' => 'data',
-            'description' => 'The entity data to update',
-            'type' => 'struct',
-          ),
-        ),
-        'access callback' => '_services_entity_resource_access',
-        'access arguments' => array('update'),
-        'access arguments append' => TRUE,
-      );
-    }
-
-    if (entity_type_supports($entity_type, 'delete')) {
-      $resources["entity_$entity_type"]['delete'] = array(
-        'file' => array('type' => 'inc', 'module' => 'services_entity', 'name' => 'services_entity.resources'),
-        'callback' => '_services_entity_resource_delete',
-        'description' => "Deletes a $entity_type",
-        'args' => array(
-          array(
-            'name' => 'entity_type',
-            'optional' => TRUE,  // Otherwise throws an error
-            'default value' => $entity_type,
-            'type' => 'string',
-            'description' => 'The type of the entity to delete',
-          ),
-          array(
-            'name' => $entity_type . '_id',
-            'optional' => FALSE,
-            'source' => array('path' => 0),
-            'type' => 'int',
-            'description' => 'The ID of the ' . $entity_type . ' to delete.',
-          ),
-        ),
-        'access callback' => '_services_entity_resource_access',
-        'access arguments' => array('delete'),
-        'access arguments append' => TRUE,
-      );
-    }
-
-    $resources["entity_$entity_type"]['index'] = array(
-      'file' => array('type' => 'inc', 'module' => 'services_entity', 'name' => 'services_entity.resources'),
-      'callback' => '_services_entity_resource_index',
-      'description' => "Returns a list of $entity_type",
-      'args' => array(
-        array(
-          'name' => 'entity_type',
-          'optional' => TRUE,  // Otherwise throws an error
-          'default value' => $entity_type,
-          'type' => 'string',
-          'description' => 'The type of the entity to get',
-        ),
-        /**
-         * Fields to return
-         *
-         * these should be specified in a comma separated list like ?fields=title,created,uid
-         */
-        array(
-          'name' => 'fields',
-          'optional' => TRUE,
-          'type' => 'string',
-          'description' => 'A comma separated list of fields to get.',
-          'default value' => '*',
-          'source' => array('param' => 'fields'),
-        ),
-        /**
-         * Filter parameters
-         *
-         * these should be specified by ?parameters[title]=My Title&param[created]=4403305
-         */
-        array(
-          'name' => 'parameters',
-          'optional' => TRUE,
-          'type' => 'array',
-          'description' => 'Filter parameters array such as parameters[title]="test"',
-          'default value' => array(),
-          'source' => array('param' => 'parameters'),
-        ),
-        /**
-         * Page number
-         *
-         * A zero based page number like ?page=3 (returns the fourth page)
-         */
-        array(
-          'name' => 'page',
-          'optional' => TRUE,
-          'type' => 'int',
-          'description' => 'The zero-based index of the page to get, defaults to 0.',
-          'default value' => 0,
-          'source' => array('param' => 'page'),
-        ),
-        /**
-         * Page Size
-         *
-         * How many records per page to return. ?pagesize=20
-         */
-        array(
-          'name' => 'pagesize',
-          'optional' => TRUE,
-          'type' => 'int',
-          'description' => 'Number of records to get per page.',
-          'default value' => variable_get('services_entity_' . $entity_type . '_index_page_size', 20),
-          'source' => array('param' => 'pagesize'),
-        ),
-        /**
-         * Sort field
-         *
-         * Which field to sort on. ?sort=created
-         */
-        array(
-          'name' => 'sort',
-          'optional' => TRUE,
-          'type' => 'string',
-          'description' => 'Field to sort by.',
-          'default value' => '',
-          'source' => array('param' => 'sort'),
-        ),
-        /**
-         * Sort Direction
-         *
-         * Which direction to sort. Possible Values = "ASC|DESC" ?direction=DESC
-         */
-        array(
-          'name' => 'direction',
-          'optional' => TRUE,
-          'type' => 'string',
-          'description' => 'Direction of the sort. ASC or DESC.',
-          'default value' => 'ASC',
-          'source' => array('param' => 'direction'),
-        ),
-      ),
-      'access callback' => '_services_entity_resource_access',
-      'access arguments' => array('index'),
-      'access arguments append' => TRUE,
-    );
-  }
-
-  return $resources;
-}
-- 
1.7.10.3


From 888cf8b7b6f474a885d93e0f2c13e6055575d5c6 Mon Sep 17 00:00:00 2001
From: Joachim Noreiko <joachim@107701.no-reply.drupal.org>
Date: Wed, 15 Jan 2014 16:54:17 +0000
Subject: [PATCH 2/7] Fix from #6

---
 plugins/services_entity_abstract.inc |    6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/plugins/services_entity_abstract.inc b/plugins/services_entity_abstract.inc
index 321ad55..d2ac480 100644
--- a/plugins/services_entity_abstract.inc
+++ b/plugins/services_entity_abstract.inc
@@ -111,8 +111,8 @@ abstract class ServicesResourceControllerAbstract implements ServicesResourceCon
     // Create.
     if (entity_type_supports($entity_type, 'create')) {
       $info['create'] = $this->getInfoElement($entity_type, 'create', FALSE, TRUE);
+      $info['create']['help'] = "Creates a $entity_type entity.";
     }
-    $info['create']['help'] = "Creates a $entity_type entity.";
 
     // Retrieve
     $info['retrieve'] = $this->getInfoElement($entity_type, 'retrieve', TRUE, FALSE);
@@ -129,14 +129,14 @@ abstract class ServicesResourceControllerAbstract implements ServicesResourceCon
     // Update
     if (entity_type_supports($entity_type, 'save')) {
       $info['update'] = $this->getInfoElement($entity_type, 'update', TRUE, TRUE);
+      $info['update']['help'] = "Updates a $entity_type entity";
     }
-    $info['update']['help'] = "Updates a $entity_type entity";
 
     // Delete
     if (entity_type_supports($entity_type, 'delete')) {
       $info['delete'] = $this->getInfoElement($entity_type, 'delete', TRUE, FALSE);
+      $info['delete']['help'] = "Deletes a $entity_type entity";
     }
-    $info['delete']['help'] = "Deletes a $entity_type entity";
 
     // Index
     $info['index'] = $this->getInfoElement($entity_type, 'index', FALSE, FALSE);
-- 
1.7.10.3


From 0363c0d156f46b48caa5c781977cf8b7a3c4efa6 Mon Sep 17 00:00:00 2001
From: Joachim Noreiko <joachim@107701.no-reply.drupal.org>
Date: Wed, 15 Jan 2014 17:03:02 +0000
Subject: [PATCH 3/7] Spacing and comments

---
 plugins/services_entity_abstract.inc |   32 +++++++++++++++++---------------
 1 file changed, 17 insertions(+), 15 deletions(-)

diff --git a/plugins/services_entity_abstract.inc b/plugins/services_entity_abstract.inc
index d2ac480..f19c683 100644
--- a/plugins/services_entity_abstract.inc
+++ b/plugins/services_entity_abstract.inc
@@ -8,13 +8,14 @@
 /**
  * An abstract controller providing basic CRUD resource info for entities.
  *
- * Note that this controller only provides the resource definition. There
- * are no implementations of any of the CRUD methods.  You should extend
- * this class to provide your own implementations.
+ * Note that this controller only provides the resource definition. There are
+ * no implementations of any of the CRUD methods. You should extend this class
+ * to provide your own implementations.
  *
  * @see ServicesEntityResourceController
  */
 abstract class ServicesResourceControllerAbstract implements ServicesResourceControllerInterface {
+
   /**
    * Helper function to return basic information about a resource operation.
    *
@@ -114,7 +115,7 @@ abstract class ServicesResourceControllerAbstract implements ServicesResourceCon
       $info['create']['help'] = "Creates a $entity_type entity.";
     }
 
-    // Retrieve
+    // Retrieve.
     $info['retrieve'] = $this->getInfoElement($entity_type, 'retrieve', TRUE, FALSE);
     $info['retrieve']['help'] = "Retrieves a $entity_type entity.";
     $info['retrieve']['args'][] = array(
@@ -126,25 +127,25 @@ abstract class ServicesResourceControllerAbstract implements ServicesResourceCon
       'source' => array('param' => 'fields'),
     );
 
-    // Update
+    // Update.
     if (entity_type_supports($entity_type, 'save')) {
       $info['update'] = $this->getInfoElement($entity_type, 'update', TRUE, TRUE);
       $info['update']['help'] = "Updates a $entity_type entity";
     }
 
-    // Delete
+    // Delete.
     if (entity_type_supports($entity_type, 'delete')) {
       $info['delete'] = $this->getInfoElement($entity_type, 'delete', TRUE, FALSE);
       $info['delete']['help'] = "Deletes a $entity_type entity";
     }
 
-    // Index
+    // Index.
     $info['index'] = $this->getInfoElement($entity_type, 'index', FALSE, FALSE);
     $info['index']['help'] = "Retrieves a list of $entity_type entities.";
     /**
-     * Fields to return
+     * Fields to return.
      *
-     * these should be specified in a comma separated list like ?fields=title,created,uid
+     * These should be specified in a comma separated list like ?fields=title,created,uid
      */
     $info['index']['args'][] = array(
       'name' => 'fields',
@@ -155,9 +156,9 @@ abstract class ServicesResourceControllerAbstract implements ServicesResourceCon
       'source' => array('param' => 'fields'),
     );
     /**
-     * Filter parameters
+     * Filter parameters.
      *
-     * these should be specified by ?parameters[title]=My Title&param[created]=4403305
+     * These should be specified by ?parameters[title]=My Title&param[created]=4403305
      */
     $info['index']['args'][] = array(
       'name' => 'parameters',
@@ -168,7 +169,7 @@ abstract class ServicesResourceControllerAbstract implements ServicesResourceCon
       'source' => array('param' => 'parameters'),
     );
     /**
-     * Page number
+     * Page number.
      *
      * A zero based page number like ?page=3 (returns the fourth page)
      */
@@ -181,7 +182,7 @@ abstract class ServicesResourceControllerAbstract implements ServicesResourceCon
       'source' => array('param' => 'page'),
     );
     /**
-     * Page Size
+     * Page Size.
      *
      * How many records per page to return. ?pagesize=20
      */
@@ -194,7 +195,7 @@ abstract class ServicesResourceControllerAbstract implements ServicesResourceCon
       'source' => array('param' => 'pagesize'),
     );
     /**
-     * Sort field
+     * Sort field.
      *
      * Which field to sort on. ?sort=created
      */
@@ -207,7 +208,7 @@ abstract class ServicesResourceControllerAbstract implements ServicesResourceCon
       'source' => array('param' => 'sort'),
     );
     /**
-     * Sort Direction
+     * Sort Direction.
      *
      * Which direction to sort. Possible Values = "ASC|DESC" ?direction=DESC
      */
@@ -221,4 +222,5 @@ abstract class ServicesResourceControllerAbstract implements ServicesResourceCon
     );
     return $info;
   }
+
 }
-- 
1.7.10.3


From f8f99f8b4ec3e153d430c4edef990cd66f34a9bb Mon Sep 17 00:00:00 2001
From: Joachim Noreiko <joachim@107701.no-reply.drupal.org>
Date: Wed, 15 Jan 2014 17:08:07 +0000
Subject: [PATCH 4/7] Changed $method param to $controller_method

---
 plugins/services_entity_abstract.inc |   11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/plugins/services_entity_abstract.inc b/plugins/services_entity_abstract.inc
index f19c683..16bffa6 100644
--- a/plugins/services_entity_abstract.inc
+++ b/plugins/services_entity_abstract.inc
@@ -24,8 +24,9 @@ abstract class ServicesResourceControllerAbstract implements ServicesResourceCon
    *
    * @param string $entity_type
    *   The entity type.
-   * @param string $method
-   *   The method name to invoke. This method must be defined in the controller class.
+   * @param string $controller_method
+   *   The name of the method the service should invoke on the controller. This
+   *   must be defined in the controller class.
    * @param boolean $id_arg
    *   If set, add an argument representing the entity_id. This will be taken from
    *   path index 0.
@@ -40,17 +41,17 @@ abstract class ServicesResourceControllerAbstract implements ServicesResourceCon
    * @see ServicesResourceControllerAbstract::resourceInfo()
    * @see hook_services_resources()
    */
-  protected function getInfoElement($entity_type, $method, $id_arg, $values_arg) {
+  protected function getInfoElement($entity_type, $controller_method, $id_arg, $values_arg) {
     $element = array(
       'callback' => '_services_entity_resource_callback',
-      'help' => "Executes the $method operation on entities of type $entity_type",
+      'help' => "Executes the $controller_method operation on entities of type $entity_type",
       'args' => array(
         // This pseudo-argument is how we will pass the specific method name to
         // our generic callback.
         array(
           'name' => 'method',
           'optional' => TRUE,  // Otherwise throws an error
-          'default value' => $method,
+          'default value' => $controller_method,
           'type' => 'string',
           'description' => 'Internal use only: the method to invoke.',
         ),
-- 
1.7.10.3


From 9bf13f6db777361c9bea7779a3050ae153a3ec42 Mon Sep 17 00:00:00 2001
From: Joachim Noreiko <joachim@107701.no-reply.drupal.org>
Date: Wed, 15 Jan 2014 17:31:38 +0000
Subject: [PATCH 5/7] added param to getInfoElement()

---
 plugins/services_entity_abstract.inc |   22 +++++++++++++---------
 services_entity.module               |   22 ++++++++++------------
 2 files changed, 23 insertions(+), 21 deletions(-)

diff --git a/plugins/services_entity_abstract.inc b/plugins/services_entity_abstract.inc
index 16bffa6..7bdae9f 100644
--- a/plugins/services_entity_abstract.inc
+++ b/plugins/services_entity_abstract.inc
@@ -24,6 +24,8 @@ abstract class ServicesResourceControllerAbstract implements ServicesResourceCon
    *
    * @param string $entity_type
    *   The entity type.
+   * @param $entity_access_op
+   *   The op value to pass to entity_access() for this service.
    * @param string $controller_method
    *   The name of the method the service should invoke on the controller. This
    *   must be defined in the controller class.
@@ -41,7 +43,7 @@ abstract class ServicesResourceControllerAbstract implements ServicesResourceCon
    * @see ServicesResourceControllerAbstract::resourceInfo()
    * @see hook_services_resources()
    */
-  protected function getInfoElement($entity_type, $controller_method, $id_arg, $values_arg) {
+  protected function getInfoElement($entity_type, $entity_access_op, $controller_method, $id_arg, $values_arg) {
     $element = array(
       'callback' => '_services_entity_resource_callback',
       'help' => "Executes the $controller_method operation on entities of type $entity_type",
@@ -66,9 +68,7 @@ abstract class ServicesResourceControllerAbstract implements ServicesResourceCon
         ),
       ),
       'access callback' => '_services_entity_access_callback',
-      // We must specify 'access arguments' (even though not used) in order to
-      // force services to append the rest of the arguments.
-      'access arguments' => array(TRUE),
+      'access arguments' => array($entity_access_op),
       'access arguments append' => TRUE,
     );
 
@@ -112,12 +112,12 @@ abstract class ServicesResourceControllerAbstract implements ServicesResourceCon
 
     // Create.
     if (entity_type_supports($entity_type, 'create')) {
-      $info['create'] = $this->getInfoElement($entity_type, 'create', FALSE, TRUE);
+      $info['create'] = $this->getInfoElement($entity_type, 'create', 'create', FALSE, TRUE);
       $info['create']['help'] = "Creates a $entity_type entity.";
     }
 
     // Retrieve.
-    $info['retrieve'] = $this->getInfoElement($entity_type, 'retrieve', TRUE, FALSE);
+    $info['retrieve'] = $this->getInfoElement($entity_type, 'view', 'retrieve', TRUE, FALSE);
     $info['retrieve']['help'] = "Retrieves a $entity_type entity.";
     $info['retrieve']['args'][] = array(
       'name' => 'fields',
@@ -130,18 +130,22 @@ abstract class ServicesResourceControllerAbstract implements ServicesResourceCon
 
     // Update.
     if (entity_type_supports($entity_type, 'save')) {
-      $info['update'] = $this->getInfoElement($entity_type, 'update', TRUE, TRUE);
+      $info['update'] = $this->getInfoElement($entity_type, 'update', 'update', TRUE, TRUE);
       $info['update']['help'] = "Updates a $entity_type entity";
     }
 
     // Delete.
     if (entity_type_supports($entity_type, 'delete')) {
-      $info['delete'] = $this->getInfoElement($entity_type, 'delete', TRUE, FALSE);
+      $info['delete'] = $this->getInfoElement($entity_type, 'delete', 'delete', TRUE, FALSE);
       $info['delete']['help'] = "Deletes a $entity_type entity";
     }
 
     // Index.
-    $info['index'] = $this->getInfoElement($entity_type, 'index', FALSE, FALSE);
+    // Technically, there is no 'index' entity_access() operation; the
+    // controller's access() method has special handling for this.
+    // @todo: change this in a follow-up to use TRUE as a value for access()
+    // to request a bypass.
+    $info['index'] = $this->getInfoElement($entity_type, 'index', 'index', FALSE, FALSE);
     $info['index']['help'] = "Retrieves a list of $entity_type entities.";
     /**
      * Fields to return.
diff --git a/services_entity.module b/services_entity.module
index eada6f4..6d33c33 100644
--- a/services_entity.module
+++ b/services_entity.module
@@ -204,23 +204,21 @@ function _services_entity_resource_callback($method, $entity_type) {
  *
  * Invokes the 'access' method of the appropriate controller.
  *
- * @param boolean $access
- *   Always TRUE. Present to force service to append the other arguments.
+ * @param $entity_access_op
+ *   The $op value to pass to entity_access().
  * @param array $args
- *   An array of arguments that will be passed to the resource callback.
+ *   An array of arguments that will be passed to the resource callback. This
+ *   is due to our setting of 'access arguments append' in the resource info.
  *
  * @return boolean
  *   TRUE to grant access, FALSE otherwise.
  */
-function _services_entity_access_callback($access, $args) {
-  $args = func_get_args();
-  $args = end($args);
-  $op = array_shift($args);
+function _services_entity_access_callback($entity_access_op, $args) {
+  // Remove the first of the arguments, which is the resource method name.
+  array_shift($args);
+
   $entity_type = reset($args);
   $controller = _services_entity_get_controller($entity_type);
-  // We need to translate the "retrieve" method name to "view" in order
-  // to pass it on directly to entity_access(). For all other operations
-  // we can just use the method name.
-  $op = $op === 'retrieve' ? 'view' : $op;
-  return call_user_func_array(array($controller, 'access'), array($op, $args));
+
+  return call_user_func_array(array($controller, 'access'), array($entity_access_op, $args));
 }
-- 
1.7.10.3


From 9768861d3624b628085eca9e327c0cfa1e5bdff1 Mon Sep 17 00:00:00 2001
From: Joachim Noreiko <joachim@107701.no-reply.drupal.org>
Date: Thu, 16 Jan 2014 12:08:06 +0000
Subject: [PATCH 6/7] Moved Services resource callbacks back to a .inc file.

---
 plugins/services_entity_abstract.inc |    5 ++++
 services_entity.module               |   47 ------------------------------
 services_entity.resources.inc        |   52 ++++++++++++++++++++++++++++++++++
 3 files changed, 57 insertions(+), 47 deletions(-)
 create mode 100644 services_entity.resources.inc

diff --git a/plugins/services_entity_abstract.inc b/plugins/services_entity_abstract.inc
index 7bdae9f..48749eb 100644
--- a/plugins/services_entity_abstract.inc
+++ b/plugins/services_entity_abstract.inc
@@ -46,6 +46,11 @@ abstract class ServicesResourceControllerAbstract implements ServicesResourceCon
   protected function getInfoElement($entity_type, $entity_access_op, $controller_method, $id_arg, $values_arg) {
     $element = array(
       'callback' => '_services_entity_resource_callback',
+      'file' => array(
+        'type' => 'inc',
+        'module' => 'services_entity',
+        'name' => 'services_entity.resources',
+      ),
       'help' => "Executes the $controller_method operation on entities of type $entity_type",
       'args' => array(
         // This pseudo-argument is how we will pass the specific method name to
diff --git a/services_entity.module b/services_entity.module
index 6d33c33..9440cc5 100644
--- a/services_entity.module
+++ b/services_entity.module
@@ -175,50 +175,3 @@ function _services_entity_get_controller($entity_type) {
   }
   return new $class();
 }
-
-/**
- * Services resource callback.
- *
- * Invokes the specified method of the appropriate controller.
- *
- * @param string $method
- *   The method to invoke.
- * @param string $entity_type
- *   The entity type.
- * @param ...
- *   Additional arguments to pass to the method.
- *
- * @return mixed
- *   The response payload for the service, ready for formatting.
- */
-function _services_entity_resource_callback($method, $entity_type) {
-  $args = func_get_args();
-  $method = array_shift($args);
-  $entity_type = reset($args);
-  $controller = _services_entity_get_controller($entity_type);
-  return call_user_func_array(array($controller, $method), $args);
-}
-
-/**
- * Services access callback.
- *
- * Invokes the 'access' method of the appropriate controller.
- *
- * @param $entity_access_op
- *   The $op value to pass to entity_access().
- * @param array $args
- *   An array of arguments that will be passed to the resource callback. This
- *   is due to our setting of 'access arguments append' in the resource info.
- *
- * @return boolean
- *   TRUE to grant access, FALSE otherwise.
- */
-function _services_entity_access_callback($entity_access_op, $args) {
-  // Remove the first of the arguments, which is the resource method name.
-  array_shift($args);
-
-  $entity_type = reset($args);
-  $controller = _services_entity_get_controller($entity_type);
-
-  return call_user_func_array(array($controller, 'access'), array($entity_access_op, $args));
-}
diff --git a/services_entity.resources.inc b/services_entity.resources.inc
new file mode 100644
index 0000000..e3c3811
--- /dev/null
+++ b/services_entity.resources.inc
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * Contains Services resources callbacks.
+ */
+
+/**
+ * Services access callback.
+ *
+ * Invokes the 'access' method of the appropriate controller.
+ *
+ * @param $entity_access_op
+ *   The $op value to pass to entity_access().
+ * @param array $args
+ *   An array of arguments that will be passed to the resource callback. This
+ *   is due to our setting of 'access arguments append' in the resource info.
+ *
+ * @return boolean
+ *   TRUE to grant access, FALSE otherwise.
+ */
+function _services_entity_access_callback($entity_access_op, $args) {
+  // Remove the first of the arguments, which is the resource method name.
+  array_shift($args);
+
+  $entity_type = reset($args);
+  $controller = _services_entity_get_controller($entity_type);
+
+  return call_user_func_array(array($controller, 'access'), array($entity_access_op, $args));
+}
+
+/**
+ * Services resource callback.
+ *
+ * Invokes the specified method of the appropriate controller.
+ *
+ * @param string $method
+ *   The method to invoke.
+ * @param string $entity_type
+ *   The entity type.
+ * @param ...
+ *   Additional arguments to pass to the method.
+ *
+ * @return mixed
+ *   The response payload for the service, ready for formatting.
+ */
+function _services_entity_resource_callback($method, $entity_type) {
+  $args = func_get_args();
+  $method = array_shift($args);
+  $entity_type = reset($args);
+  $controller = _services_entity_get_controller($entity_type);
+  return call_user_func_array(array($controller, $method), $args);
+}
-- 
1.7.10.3


From 76cfdde5712125efeed9a4fdd777ba570e30f15a Mon Sep 17 00:00:00 2001
From: Joachim Noreiko <joachim@107701.no-reply.drupal.org>
Date: Thu, 16 Jan 2014 12:18:51 +0000
Subject: [PATCH 7/7] moved Services hooks back to an inc file

---
 services_entity.module       |   14 --------------
 services_entity.services.inc |   19 +++++++++++++++++++
 2 files changed, 19 insertions(+), 14 deletions(-)
 create mode 100644 services_entity.services.inc

diff --git a/services_entity.module b/services_entity.module
index 9440cc5..0b2c9a2 100644
--- a/services_entity.module
+++ b/services_entity.module
@@ -88,20 +88,6 @@ function services_entity_services_entity_resource_info() {
 }
 
 /**
- * Implements of hook_services_resources().
- */
-function services_entity_services_resources() {
-  $resources = array();
-
-  foreach (entity_get_info() as $entity_type => $info) {
-    $controller = _services_entity_get_controller($entity_type);
-    $resources["entity_$entity_type"] = $controller->resourceInfo($entity_type);
-  }
-
-  return $resources;
-}
-
-/**
  * Implements hook_services_resources_alter().
  */
 function services_entity_services_resources_alter(&$resources) {
diff --git a/services_entity.services.inc b/services_entity.services.inc
new file mode 100644
index 0000000..c4e5569
--- /dev/null
+++ b/services_entity.services.inc
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * Contains Services hooks.
+ */
+
+/**
+ * Implements of hook_services_resources().
+ */
+function services_entity_services_resources() {
+  $resources = array();
+
+  foreach (entity_get_info() as $entity_type => $info) {
+    $controller = _services_entity_get_controller($entity_type);
+    $resources["entity_$entity_type"] = $controller->resourceInfo($entity_type);
+  }
+
+  return $resources;
+}
-- 
1.7.10.3

