From 01b333c2acd3970117f42bfdff39d884988fc714 Mon Sep 17 00:00:00 2001
From: Jimmy Berry <jimmy@boombatower.com>
Date: Wed, 6 Apr 2011 04:03:15 -0500
Subject: Initial historical services API.

---
 historical/services_historical.info   |    6 ++
 historical/services_historical.module |  142 +++++++++++++++++++++++++++++++++
 2 files changed, 148 insertions(+), 0 deletions(-)
 create mode 100644 historical/services_historical.info
 create mode 100644 historical/services_historical.module

diff --git a/historical/services_historical.info b/historical/services_historical.info
new file mode 100644
index 0000000..5ef01f4
--- /dev/null
+++ b/historical/services_historical.info
@@ -0,0 +1,6 @@
+name = Services historical
+description = An API for providing historical versions of an API.
+package = Services
+version = 1.0
+core = 7.x
+dependencies[] = services
diff --git a/historical/services_historical.module b/historical/services_historical.module
new file mode 100644
index 0000000..d7ed7f0
--- /dev/null
+++ b/historical/services_historical.module
@@ -0,0 +1,142 @@
+<?php
+/**
+ * @file
+ * Provides primary Drupal hook implementations.
+ *
+ * @author Jimmy Berry ("boombatower", http://drupal.org/user/214218)
+ */
+
+/**
+ * Implements hook_services_resources().
+ */
+function services_historical_services_resources() {
+  $services = conduit_historical_services_generate();
+  return $services['resources'];
+}
+
+/**
+ * Implements hook_default_services_endpoint().
+ */
+function services_historical_default_services_endpoint() {
+  $services = conduit_historical_services_generate();
+  return $services['endpoints'];
+}
+
+/**
+ * Implements hook_default_services_endpoint_alter().
+ */
+function services_historical_default_services_endpoint_alter(&$endpoints) {
+  // Alter the current versions of the endpoints to reflect the current version
+  // provided by the historical info hook.
+  foreach (services_historical_info() as $info) {
+    $endpoint = &$endpoints[$info['endpoint']];
+    $endpoint->title .= ' ' . $info['version'];
+    $endpoint->path .= '/' . $info['version'];
+  }
+}
+
+/**
+ * Implements hook_ctools_plugin_api().
+ */
+function services_historical_ctools_plugin_api($owner, $api) {
+  if ($owner == 'services' && $api == 'services') {
+    return array('version' => 3);
+  }
+}
+
+/**
+ * Get historical services information.
+ *
+ * @return
+ *   An associative array of historical services information.
+ * @see hook_services_historical_info()
+ */
+function services_historical_info() {
+  $historical_info = &drupal_static(__FUNCTION__, array());
+  if (!$historical_info) {
+    foreach (module_implements('services_historical_info') as $module) {
+      foreach (module_invoke($module, 'services_historical_info') as $key => $info) {
+        $info += array(
+          'module' => $module,
+          'levels' => 2,
+          'suffix' => 1,
+        );
+        if (!isset($info['format'])) $info['format'] = implode('.', array_fill(0, $info['levels'], '%d'));
+        $historical_info[$key] = $info;
+      }
+    }
+    drupal_alter('services_historical_info', $historical_info);
+  }
+  return $historical_info;
+}
+
+/**
+ * Generate historical services endpoints and resources.
+ *
+ * @return
+ *   Associative array with key 'endpoints' and 'resources' each containing an
+ *   array of their respective type to be given to services.
+ */
+function conduit_historical_services_generate() {
+  // Cache and statically cache since it is expensive to generate.
+  if (!($services = &drupal_static(__FUNCTION__))) {
+    $services = ($cache = cache_get(__FUNCTION__)) ? $cache->data : array('endpoints' => array(), 'resources' => array());
+  }
+
+  // Generate the historical endpoints and resources.
+  if (!$services['endpoints']) {
+    // Get a list of functions sorted to ensure that the number functions run
+    // from smallest to largest so that the $previous parameter is correct.
+    $functions = get_defined_functions();
+    $functions = $functions['user'];
+    sort($functions);
+
+    // Cycle through each historical set.
+    foreach (services_historical_info() as $info) {
+      // Generate the base function name which does not include the suffix so
+      // that it can be used when determine the version number and generate the
+      // suffixed function name to match against.
+      $suffix = ($info['suffix'] ? '_' . $info['suffix'] : '');
+      $base = $info['module'] . '_services_historical_update';
+      $match = $base . $suffix;
+
+      // Retreive the base endpoint from which the versioned endpoints will be
+      // generated using the resources as a "diff".
+      $endpoint_base = module_invoke($info['module'], 'services_historical_endpoint' . $suffix);
+      $resources = array();
+      foreach ($functions as $function) {
+        // Check if the function matches.
+        if (strpos($function, $match) === 0) {
+          // Grab the numbers on the end of the function and format them into a
+          // version string. Generate a machine version string for keys.
+          $version = vsprintf($info['format'], explode('_', str_replace($base . '_', '', $function)));
+          $version_machine = str_replace('.', '_', $version);
+
+          // Call the update function to receive the resources for the version.
+          $function($resources, $version);
+
+          // Clone the endpoint and add the version information.
+          $endpoint = clone $endpoint_base;
+          $endpoint->name .= '_' . $version_machine;
+          $endpoint->title .= ' ' . $version;
+          $endpoint->path .= "/$version";
+
+          // Cycle through the resources enabling them in the endpoint and
+          // adding the version suffix to each key.
+          foreach ($resources as $key => $resource) {
+            $key .=  '_' . $version_machine;
+            foreach ($resource as $operation => $methods) {
+              foreach (array_keys($methods) as $method) {
+                $endpoint->resources[$key][$operation][$method]['enabled'] = (int) TRUE;
+              }
+            }
+            $services['resources'][$key] = $resource;
+          }
+          $services['endpoints'][$endpoint->name] = $endpoint;
+        }
+      }
+    }
+    cache_set(__FUNCTION__, $services);
+  }
+  return $services;
+}
-- 
1.7.4.2


From c0d9315c4d1eb157b5b05cbfe33121d7521021ff Mon Sep 17 00:00:00 2001
From: Jimmy Berry <jimmy@boombatower.com>
Date: Wed, 6 Apr 2011 04:35:45 -0500
Subject: Change suffix default to ''.

---
 historical/services_historical.module |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/historical/services_historical.module b/historical/services_historical.module
index d7ed7f0..3bf16bb 100644
--- a/historical/services_historical.module
+++ b/historical/services_historical.module
@@ -59,7 +59,7 @@ function services_historical_info() {
         $info += array(
           'module' => $module,
           'levels' => 2,
-          'suffix' => 1,
+          'suffix' => '',
         );
         if (!isset($info['format'])) $info['format'] = implode('.', array_fill(0, $info['levels'], '%d'));
         $historical_info[$key] = $info;
-- 
1.7.4.2


From 86643bcfb3c7401209d6011d58c87b342197bdeb Mon Sep 17 00:00:00 2001
From: Jimmy Berry <jimmy@boombatower.com>
Date: Wed, 6 Apr 2011 04:36:16 -0500
Subject: Add API documentation.

---
 historical/services_historical.api.php |  135 ++++++++++++++++++++++++++++++++
 1 files changed, 135 insertions(+), 0 deletions(-)
 create mode 100644 historical/services_historical.api.php

diff --git a/historical/services_historical.api.php b/historical/services_historical.api.php
new file mode 100644
index 0000000..cbf77ab
--- /dev/null
+++ b/historical/services_historical.api.php
@@ -0,0 +1,135 @@
+<?php
+/**
+ * @file
+ * Document services_historical hooks.
+ *
+ * @author Jimmy Berry ("boombatower", http://drupal.org/user/214218)
+ */
+
+/**
+ * Define historical services provided by the module.
+ *
+ * @return
+ *   An associative array whose keys will be used for reference and whose
+ *   values contain the historical descriptions. Each historical description is
+ *   itself an associative array, with the following key-value pairs:
+ *   - 'endpoint': (required) The name of the endpoint to which the historical
+ *   endpoints are related.
+ *   - 'version': (required) The current version of the API (ex. 1.7).
+ *   - 'suffix': (optional) A numerical suffix to append to
+ *   hook_services_historical_endpoint() and to each
+ *   hook_services_historical_update_N() before the N. The suffix will no
+ *   effect the version number used for the update hooks (ie. N will include
+ *   the suffix). The suffix is useful if an API with two or more 'levels' has
+ *   more then one major version and those are defined separately. For example,
+ *   an API with the following version: 1.0, 1.1, 1.2, 2.0, 2.1 could use a
+ *   suffix of 1 and 2 to define each historical set separately, but the
+ *   'version' in each should be the same. By defining major API version
+ *   separately they can be placed in separate modules and/or have different
+ *   base endpoints. Defaults to ''.
+ *   - 'levels': (optional) The number of levels in the API version, must be
+ *   >= 1. For example, 2 levels would mean version numbers like: 1.0, 1.1.
+ *   Defaults to 2.
+ *   - 'format': (optional) The version number format to use in labels and the
+ *   URL, see sprintf() for documentation. The format is generated if not
+ *   defined based on the number of levels. Defaults to %d.%d.
+ *   - 'module': (optional) The module providing the hooks. Defaults to the
+ *   module for which this hook is written.
+ */
+function hook_services_historical_info() {
+  // Simple example where the entire historical API is defined once.
+  return array(
+    'my_historical_api' => array(
+      'endpoint' => 'my_api',
+      'version' => '1.7',
+    ),
+  );
+
+  // Complete example where the historical API is split for each major API
+  // version.
+  return array(
+    'my_historical_api_1' => array(
+      'endpoint' => 'my_api',
+      'version' => '2.0',
+      'suffix' => 1,
+    ),
+    'my_historical_api_2' => array(
+      'endpoint' => 'my_api',
+      'version' => '2.0',
+      'suffix' => 2,
+    ),
+  );
+}
+
+/**
+ * Define the base endpoint used to generate versioned endpoints.
+ *
+ * @return
+ *   A single endpoint object.
+ * @see hook_default_services_endpoint()
+ */
+function hook_services_historical_endpoint() {
+  $endpoint = new stdClass;
+  $endpoint->disabled = FALSE; /* Edit this to true to make a default endpoint disabled initially */
+  $endpoint->api_version = 3;
+  $endpoint->name = 'my_api';
+  $endpoint->title = 'My API';
+  $endpoint->server = 'rest_server';
+  $endpoint->path = 'api';
+  $endpoint->authentication = array(
+    'services_sessauth' => array(),
+  );
+  $endpoint->resources = array();
+  $endpoint->debug = 0;
+  $endpoint->status = 1;
+  return $endpoint;
+}
+
+/**
+ * Update the previous resources for the current version.
+ *
+ * The N should be replaced by the version number this update hook will
+ * generate. The version number should be formatted with underscores between
+ * each level.For example, a two level version number of 1.1 would be formatted
+ * as 1_1.
+ *
+ * Alternatively you can copy and paste the entire resources array for each
+ * version in the corresponding update hook.
+ *
+ * @param $resources
+ *   The resources from the previous update hook which should be altered to
+ *   suite the current version of the API. If this is the first update hook,
+ *   usually something like 1.0, then $resources will be a blank array and
+ *   should set to the original resources provided in the 1.0 version of the
+ *   API. Each subsequent update hook will be give the resources provided by
+ *   the previous version.
+ * @param $version
+ *   The formated version string.
+ */
+function hook_services_historical_update_N(&$resources, $version) {
+  // The original update hook.
+  $resources = array(
+    'thing' => array(
+      'actions' => array(
+        'talk' => array(
+          // ...
+          'args' => array(
+            array(
+              'name' => 'type',
+              'type' => 'string',
+              'description' => 'The type of thing.',
+              'source' => 'data',
+              'optional' => TRUE,
+            ),
+          ),
+        ),
+      ),
+    ),
+  );
+
+  // The next update hook.
+  $resources['thing']['actions']['talk']['args'][0]['optional'] = FALSE;
+
+  // Another update hook down the road.
+  unset($resources['thing']);
+}
-- 
1.7.4.2


From f6ec7d75dc32c1957fbd640fa8e622d493367845 Mon Sep 17 00:00:00 2001
From: Jimmy Berry <jimmy@boombatower.com>
Date: Wed, 6 Apr 2011 04:38:34 -0500
Subject: Generate the machine version directly to avoid format issues.

---
 historical/services_historical.module |    5 +++--
 1 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/historical/services_historical.module b/historical/services_historical.module
index 3bf16bb..a733105 100644
--- a/historical/services_historical.module
+++ b/historical/services_historical.module
@@ -109,8 +109,9 @@ function conduit_historical_services_generate() {
         if (strpos($function, $match) === 0) {
           // Grab the numbers on the end of the function and format them into a
           // version string. Generate a machine version string for keys.
-          $version = vsprintf($info['format'], explode('_', str_replace($base . '_', '', $function)));
-          $version_machine = str_replace('.', '_', $version);
+          $parts = explode('_', str_replace($base . '_', '', $function));
+          $version = vsprintf($info['format'], $parts);
+          $version_machine = implode('_', $parts);
 
           // Call the update function to receive the resources for the version.
           $function($resources, $version);
-- 
1.7.4.2


From 61594e8fe303433e2fee2525f2936cbb4ce9d642 Mon Sep 17 00:00:00 2001
From: Jimmy Berry <jimmy@boombatower.com>
Date: Wed, 6 Apr 2011 08:24:26 -0500
Subject: Add a note about endpoint resources.

---
 historical/services_historical.api.php |    7 ++++++-
 1 files changed, 6 insertions(+), 1 deletions(-)

diff --git a/historical/services_historical.api.php b/historical/services_historical.api.php
index cbf77ab..eb10fa1 100644
--- a/historical/services_historical.api.php
+++ b/historical/services_historical.api.php
@@ -79,7 +79,12 @@ function hook_services_historical_endpoint() {
   $endpoint->authentication = array(
     'services_sessauth' => array(),
   );
-  $endpoint->resources = array();
+  $endpoint->resources = array(
+    // Can include either core services resources or other modules resources
+    // that used in the endpoint, but not changed along with the API and they
+    // will be left alone. Any resources the are defined in the update hooks
+    // will be overridden.
+  );
   $endpoint->debug = 0;
   $endpoint->status = 1;
   return $endpoint;
-- 
1.7.4.2

