You are here

clients.module in Web Service Clients 7

Clients module - handles keys and service connections and provides an API for clients

File

clients.module
View source
<?php

/**
 * @file
 * Clients module - handles keys and service connections 
 * and provides an API for  clients
 */

/**
 * Implementation of hook_help()
 *
 * @param $path
 *   Which path of the site we're displaying help
 * @param $arg
 *   Holds the current path as would be returned from arg() function
 * @return
 *   Help text for the path
 */
function clients_help($path, $arg) {
  $output = '';
  switch ($path) {
    case "admin/help#clients":
      $output = '<p>' . t("Clients API .") . '</p>';
      break;
    case 'admin/settings/clients':
      $output = '<p>' . t("The clients module allows you to define connections to remote sites that provide services. ") . t("Use the list below to configure and review the connections defined on your site.") . '</p>';
      break;
    case 'admin/settings/clients/connections/test/%':
      $output = '<p>' . t('Use this page to test your connection is set up correctly.') . '</p>';
      break;
  }
  return $output;
}

/**
 * Implementation of hook_perm()
 * @return
 *   An array of valid permissions for the clients module
 */
function clients_perm() {
  return array(
    'admin clients connections',
    'admin clients resources',
  );
}

/**
 * Implementation of hook_menu()
 */
function clients_menu() {
  $items = array();
  $items['admin/settings/clients'] = array(
    'title' => 'Clients',
    'description' => 'Clients',
    'page callback' => 'clients_connections_list',
    'file' => 'clients.connection.admin.inc',
    'access callback' => 'clients_access_callback',
    // TODO: this is doomed.
    'type' => MENU_NORMAL_ITEM,
  );
  $items['admin/settings/clients/connections'] = array(
    'title' => 'Connections',
    'weight' => 1,
    'description' => 'List connections',
    'page callback' => 'clients_connections_list',
    'file' => 'clients.connection.admin.inc',
    'access arguments' => array(
      'admin clients connections',
    ),
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  $items['admin/settings/clients/connections/add/%'] = array(
    'title' => 'Add connection',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'clients_connection_add',
      5,
    ),
    'file' => 'clients.connection.admin.inc',
    'access arguments' => array(
      'admin clients connections',
    ),
  );
  $items['admin/settings/clients/connections/view/%'] = array(
    'title' => 'Show connection',
    'description' => 'Show connection',
    'page callback' => 'clients_connection_view',
    'page arguments' => array(
      5,
    ),
    'file' => 'clients.connection.admin.inc',
    'access arguments' => array(
      'admin clients connections',
    ),
    'type' => MENU_CALLBACK,
  );
  $items['admin/settings/clients/connections/edit/%'] = array(
    'title' => 'Edit connection',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'clients_connection_edit',
      5,
    ),
    'file' => 'clients.connection.admin.inc',
    'access arguments' => array(
      'admin clients connections',
    ),
    'type' => MENU_CALLBACK,
  );
  $items['admin/settings/clients/connections/delete/%'] = array(
    'title' => 'Delete connection',
    'description' => 'Delete a connection',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'client_delete_confirm',
      'connection',
      5,
    ),
    'access arguments' => array(
      'admin clients connections',
    ),
    'type' => MENU_CALLBACK,
  );
  $items['admin/settings/clients/connections/test/%'] = array(
    'title' => 'Test connection',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'clients_connection_test_form',
      5,
    ),
    'file' => 'clients.connection.admin.inc',
    'access arguments' => array(
      'admin clients connections',
    ),
    'type' => MENU_CALLBACK,
  );
  $items['admin/settings/clients/resources'] = array(
    'title' => 'Resources',
    'weight' => 2,
    'description' => 'List resources',
    'page callback' => 'clients_resources',
    'access arguments' => array(
      'admin clients resources',
    ),
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/settings/clients/resources/add'] = array(
    'title' => 'Add resource',
    'weight' => 1,
    'description' => 'Add resource',
    'page callback' => 'clients_resources_add',
    'access arguments' => array(
      'admin clients resources',
    ),
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/settings/clients/resources/%'] = array(
    'title' => 'Show resource',
    'description' => 'Show resource',
    'page callback' => 'clients_resource_view',
    'page arguments' => array(
      4,
    ),
    'access arguments' => array(
      'admin clients resources',
    ),
    'type' => MENU_CALLBACK,
  );
  $items['admin/settings/clients/resources/%/edit'] = array(
    'title' => 'Edit resource',
    'weight' => 1,
    'description' => 'Edit data source',
    'page callback' => 'clients_resources_edit',
    'page arguments' => array(
      4,
    ),
    'access arguments' => array(
      'admin clients resources',
    ),
    'type' => MENU_CALLBACK,
  );
  $items['admin/settings/clients/resources/%/delete'] = array(
    'title' => 'Delete resource',
    'description' => 'Delete a resource',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'client_delete_confirm',
      'resource',
      5,
    ),
    'access arguments' => array(
      'admin clients connections',
    ),
    'type' => MENU_CALLBACK,
  );
  $items['admin/settings/clients/settings'] = array(
    'title' => 'Settings',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'clients_admin_cache',
    ),
    'access arguments' => array(
      'admin clients resources',
    ),
    'weight' => 10,
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/settings/clients/settings/cache'] = array(
    'title' => 'Cache',
    'weight' => -1,
    'description' => 'cccc connections',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'clients_admin_cache',
    ),
    'access arguments' => array(
      'admin clients resources',
    ),
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  return $items;
}

/**
 * any defined perms in this module
 */
function clients_access_callback() {
  return user_access('admin clients connections') || user_access('admin clients resources');
}

/**
 * Set cache time in minutes
 * @return
 *   Form array
 */
function clients_admin_cache() {
  $form = array();
  $form['clients_cache_time'] = array(
    '#type' => 'select',
    '#title' => t('Local cache time'),
    '#default_value' => variable_get('clients_cache_time', '0'),
    // change default in production
    '#options' => array(
      '0' => t('No cache'),
      'cron' => t('Refresh on next cron run'),
      '60' => t('1 hour'),
      '720' => t('12 hours'),
      '2880' => t('2 days'),
    ),
    '#description' => t("Minimum cache lifetime (if set, content will refresh on the next cron run after this time has elapsed)"),
    '#required' => TRUE,
  );
  return system_settings_form($form);
}

/**
 * Form builder for confirmation of deletion of a connection or resource.
 */
function client_delete_confirm(&$form_state, $type, $id) {
  if ($type == 'connection') {
    $service = clients_connection_load($id);
  }
  elseif ($type == 'resource') {
    $service = clients_resource_load($id);
  }
  else {

    /**
     * @todo error
     */
    $service = array();
  }
  $form = array();
  $form['type'] = array(
    '#type' => 'value',
    '#value' => $type,
  );
  $form['cid'] = array(
    '#type' => 'value',
    '#value' => $id,
  );
  $form['name'] = array(
    '#type' => 'value',
    '#value' => $service->name,
  );
  $form['check'] = array(
    '#value' => '<p>' . t('Really delete !type @service?', array(
      '@service' => $service->name,
      '!type' => $type,
    )) . '</p>',
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Delete'),
  );
  return $form;
}

/**
 * @todo validate a connection has no associated resources
 */
function client_delete_confirm_validate($form, &$form_state) {
}

/**
* @todo: refactor?
*/
function client_delete_confirm_submit($form, &$form_state) {
  $cid = (int) $form_state['values']['cid'];
  if ($form_state['values']['type'] == 'connection') {
    if ($result = db_query("DELETE FROM {clients_connections} WHERE cid = %d", $cid)) {
      @db_query("DELETE FROM {clients_resources} WHERE cid = %d", $cid);

      // error suppression is less expensive than a query to check if there are any resources
      drupal_set_message(t('Connection @name deleted.', array(
        '@name' => $form_state['values']['name'],
      )));
      $form_state['redirect'] = 'admin/settings/clients/connections';
    }
    else {
      drupal_set_message(t('Problem deleting connection: ') . $form_state['values']['name']);
    }
  }
  elseif ($form_state['values']['type'] == 'resource') {
    if ($result = db_query_range("DELETE FROM {clients_resources} WHERE rid = %d", $form_state['values']['cid'])) {
      drupal_set_message(t('Resource @name deleted.', array(
        '@name' => $form_state['values']['name'],
      )));
      $form_state['redirect'] = 'admin/settings/clients/resources';
    }
    else {
      drupal_set_message(t('Problem deleting resource: ') . $form_state['values']['name']);
    }
  }
}

/**
 * defines hook_clients_call
 */
function clients_call($resource) {
  $connection = clients_connection_load($resource->cid);
  $result = module_invoke_all('clients_call', $connection, $resource);
  module_invoke_all('clients_call_postprocess', $result, $connection, $resource);
  return $result;
}

/**
 * Create a connection object which can then be used to make method calls.
 *
 * Usage:
 *  $client = clients_get_connection($cid);
 *  $client->callMethod('node.load', array(1));
 *
 * @param $cid
 *  The id of a connection.
 */
function clients_get_connection($cid) {

  // TODO: Look at how flag module does this, as it's the same principle --
  // create an object of a particular class based on settings -- and the
  // pattern there may be better.
  $connection_types = clients_get_connection_types();
  $connection_data = clients_connection_load($cid);
  if (isset($connection_types[$connection_data->type])) {
    $class = 'clients_connection_' . $connection_data->type;
    $connection = new $class($connection_data);
  }
  else {

    // error, but we would only get here if the DB held a type that no longer had a hook...
  }
  return $connection;
}

/**
 * nano API for callback to resources
 * @param $arg
 *   Array Takes the form array('name1'=>'value1', 'name2'=>'value2')
 */
function clients_setparams(&$resource, $arg) {
  module_invoke_all('clients_setparams', $resource, $arg);
}

/**
 * defines hook_clients_arguments()
 * return array of rows with following fields: 
 * $foo->name, $foo->title, $foo->help,
 */
function clients_arguments($source) {
  return module_invoke_all('clients_arguments', $source);
}

/**
 * defines hook_clients_fields
 */
function clients_fields($resource = NULL) {
  $fields = module_invoke_all('clients_fields', $resource);

  // module_invoke_all performs an array_merge_recursive. This flattens out multiple fields with same name to use the first value
  $result = array();
  foreach ($fields as $field => $val) {
    if (is_array($val['name'])) {
      $val['name'] = $val['name'][0];
      $val['description'] = $val['description'][0];
    }
    $result[$field] = $val;
  }
  return $result;
}

/**
 * hook_clients_service_options, configure options
 */
function clients_service_options($connection_id, $wrapper, $wrapper_values, $resource) {
  $service = clients_connection_load($connection_id);

  // TODO: this pattern is completely wrong: each resource has only one client,
  // therefore invoking ALL hook implementations is silly -- find out the base
  // module of the current resource and invoke the hook in JUST that one.
  return module_invoke_all('clients_service_options', $service, $wrapper, $wrapper_values, $resource);
}

/**
 *
 */
function clients_resources_add() {
  return drupal_get_form('clients_resources_form');
}

/**
 *
 */
function clients_resources_form_submit($form, &$form_state) {
  $values = array();
  $values['name'] = $form_state['values']['name'];
  $values['cid'] = $form_state['values']['clients-resource']['connection'];

  // was backend in case this breaks
  $values['configuration'] = $form_state['values']['clients-resource'];

  // needs try catch?
  if (isset($form['#rid'])) {

    // edit
    $values['rid'] = $form['#rid'];
    if ($result = drupal_write_record('clients_resources', $values, 'rid')) {
      drupal_set_message('Resource edited');
    }
  }
  elseif ($result = drupal_write_record('clients_resources', $values)) {
    drupal_set_message('Resource added');
  }
  drupal_goto('admin/settings/clients/resources');
}

/**
 * @todo validate uniqueness of name
 */
function clients_resources_form(&$form_state, $rid = FALSE) {
  $form = array();
  $resource = FALSE;
  if ($rid) {

    // edit existing
    $resource = clients_resource_load($rid);
    $form['#rid'] = $rid;
  }

  // Register the form with ahah_helper so we can use it. Also updates
  // $form_state['storage'] to ensure it contains the latest values that have
  // been entered, even when the form item has temporarily been removed from
  // the form. So if a form item *once* had a value, you *always* can retrieve
  // it.

  //ahah_helper_register($form, $form_state);
  $wrapper = 'clients-resource';

  // can be anything
  $wrapper_values = $form_state['storage'][$wrapper];
  $form['name'] = array(
    '#type' => 'textfield',
    '#title' => t('Resource name'),
    '#default_value' => $rid ? $resource->name : '',
    '#size' => 50,
    '#maxlength' => 100,
    '#description' => t('Must be unique'),
    '#required' => TRUE,
  );
  $connections = array();
  foreach (clients_get_connections() as $cid => $connection) {
    $connections[$cid] = $connection['name'];
  }
  $form[$wrapper] = array(
    '#type' => 'fieldset',
    '#title' => t('Resource'),
    '#prefix' => '<div id="' . $wrapper . '">',
    // This is our wrapper div.
    '#suffix' => '</div>',
    '#tree' => TRUE,
  );

  /**
   * @todo fix cid being saved as string
   */
  $connection_id = isset($wrapper_values['connection']) ? (int) $wrapper_values['connection'] : ($rid ? (int) $resource->configuration['connection'] : NULL);
  $form[$wrapper]['connection'] = array(
    '#type' => 'select',
    '#title' => t('Connection'),
    '#default_value' => isset($connection_id) ? $connection_id : key($connections),
    // needs reset?
    '#options' => $connections,
    '#required' => TRUE,
  );
  if (isset($connection_id)) {

    // Invokes hook_clients_service_options in backend modules to inject configuration form fragment here which can use ahah wrapper defined above
    $form[$wrapper]['options'] = clients_service_options($connection_id, $wrapper, $wrapper_values, $resource);
  }
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
  );
  return $form;
}

/**
 *
 */
function clients_resources() {
  $sources = array();
  foreach (clients_resources_load() as $resource_id => $resource) {
    $connection = clients_connection_load($resource['connection']);
    $resources[] = array(
      'name' => l($resource['name'], 'admin/settings/clients/resources/' . $resource_id),
      'connection' => $connection->type . ': ' . $connection->name,
      'operations' => l(t('Edit'), 'admin/settings/clients/resources/' . $resource_id . '/edit') . ' | ' . l(t('Delete'), 'admin/settings/clients/resources/' . $resource_id . '/delete'),
    );
  }
  if (!count($resources)) {
    $resources[] = array(
      array(
        'data' => t('No resources defined yet.'),
        'colspan' => '3',
      ),
    );
  }
  return theme_table(array(
    'Name',
    'Connection',
    'Operations',
  ), $resources);
}

/**
 * @todo hide password until clicked to prevent overshoulder attacks 
 * when keys are being checked
 **/
function clients_resource_view($arg) {
  drupal_set_breadcrumb(array(
    l(t('Home'), NULL),
    l(t('Administer'), 'admin'),
    l(t('Site configuration'), 'admin/settings'),
    l(t('Clients'), 'admin/settings/clients'),
    l(t('Resources'), 'admin/settings/clients/resources'),
  ));
  $resource = clients_resource_load($arg);
  $output[] = array(
    'Name',
    $resource->name,
  );
  $connection = clients_connection_load($resource->cid);
  $output[] = array(
    'Connection',
    $connection->name,
  );
  $output[] = array(
    'Type',
    $connection->type,
  );
  foreach ($resource->configuration['options'] as $label => $val) {
    if (is_array($val)) {
      $val = implode(', ', $val);

      // needs to be recursive?
    }
    $output[] = array(
      ucfirst($label),
      $val,
    );
  }
  $operations = l(t('Edit'), 'admin/settings/clients/resources/' . $arg . '/edit') . ' | ' . l(t('Delete'), 'admin/settings/clients/resources/' . $arg . '/delete');
  $output[] = array(
    'Operations',
    $operations,
  );

  // add any service agnostic stuff here
  return theme_table(array(), $output);
}

/**
 * Implementation of hook_flush_caches().
 */
function clients_flush_caches() {
  return array(
    'cache_clients',
  );
}

// TODO: obsolete.
function clients_connection_save($values) {
  $values = module_invoke_all('clients_connection_save', $values);
  if ($resultbase = drupal_write_record('clients_connections', $values)) {

    // @todo add watchdog
    drupal_set_message('Added connection');
  }
  else {
    drupal_set_message('Problem adding connection - drupal_write_record() result: ' . $resultbase);
  }
  drupal_goto('admin/settings/clients/connections');
}

/**
 * Load a client connection.
 *
 * TODO: there's no need for this to be an object -- change to an array.
 *
 * @param $connection_info
 *  Either a connection ID or the name of the connection.
 */
function clients_connection_load($connection_info) {
  if (is_numeric($connection_info)) {
    $result = db_query("SELECT * FROM {clients_connections} WHERE {clients_connections}.cid = %d", $connection_info);
  }
  elseif (is_string($connection_info)) {
    $result = db_query("SELECT * FROM {clients_connections} WHERE {clients_connections}.name = '%s'", $connection_info);
  }
  else {

    // @todo watchdog
    drupal_set_message('error');
    return;

    // or FALSE?
  }
  $connection = db_fetch_object($result);

  //dsm($connection);
  $connection->configuration = unserialize($connection->configuration);

  // invoke hook for contrib modules to process configuration
  // TODO: this has no need to be an invoke all pattern -- instead, call only
  // the function TYPE_clients_connection_load.
  module_invoke_all('clients_connection_load', $connection);

  // TODO: not working!!
  return $connection;
}

/**
 * Loads all connections
 *
 * @param $types
 *  (optional) Specify a single type or a list of types to include. 
 *  If omitted, all are returned.
 * @return
 *   Array of connections.
 */
function clients_get_connections($types = array()) {
  if (!is_array($types)) {
    $types = array(
      $types,
    );
  }
  if (count($types)) {
    $placeholders = implode(',', array_fill(0, count($types), "'%s'"));
    $where = "WHERE type IN ({$placeholders})";
  }
  else {
    $where = '';
  }
  $connections = array();
  $result = db_query("SELECT * FROM {clients_connections}  {$where} ORDER BY name", $types);
  while ($connection = db_fetch_object($result)) {
    $connections[$connection->cid] = array(
      'name' => $connection->name,
      'type' => $connection->type,
      'endpoint' => $connection->endpoint,
    );
  }
  return $connections;
}

/**
 * Loads all resources
 *
 * @return
 *   Array
 */
function clients_resources_load() {
  $resources = array();
  $result = db_query("SELECT * FROM {clients_resources} ORDER BY name");
  while ($data = db_fetch_object($result)) {
    $resources[$data->rid] = array(
      'name' => $data->name,
      'connection' => $data->cid,
    );
  }
  return $resources;
}

/**
 * Loads a resource
 *
 * @return
 *   Array
 */
function clients_resource_load($identifier) {
  if (is_numeric($identifier)) {
    $result = db_query("SELECT * FROM {clients_resources} WHERE {clients_resources}.rid = %d", $identifier);
  }
  elseif (is_string($identifier)) {
    $result = db_query("SELECT * FROM {clients_resources} WHERE {clients_resources}.name = '%s'", $identifier);
  }
  else {

    // @todo watchdog
    drupal_set_message('error');
    return;

    // or FALSE?
  }
  $resource = db_fetch_object($result);

  /**
   * @TODO test resource has loaded successfully, return FALSE if not
   */
  $resource->configuration = unserialize($resource->configuration);
  return $resource;
}

/**
 * Get a list of all connection types.
 */
function clients_get_connection_types() {
  static $connection_types;
  if (!isset($connection_types)) {

    // Invoke hook_clients_connection_type_info().
    $connection_types = module_invoke_all('clients_connection_type_info');
  }
  return $connection_types;
}

/**
 * FormAPI helper to get a list of clients for a select form element.
 *
 * @param $types
 *  (optional) Specify a single type or a list of types to include. 
 *  If omitted, all are returned.
 * @param $required
 *  Whether the select element is required.
 * @return
 *   Array of options for a FormAPI select element; assumed to be single
 *   rather than multiple-valued.
 */
function clients_connections_select_options($types = array(), $required = TRUE) {
  $options = array();
  if ($required) {
    $options[0] = t('- Please choose -');
  }
  else {
    $options[0] = t('- None selected -');
  }
  foreach (clients_get_connections($types) as $cid => $connection) {
    $options[$cid] = $connection['name'];
  }
  return $options;
}

/**
 * FormAPI helper to get a list of clients for a checkboxes form element.
 *
 * @param $types
 *  (optional) Specify a single type or a list of types to include. 
 *  If omitted, all are returned.
 */
function clients_connections_checkbox_options($types = array()) {
  $options = array();
  foreach (clients_get_connections($types) as $cid => $connection) {
    $options[$cid] = check_plain($connection['name']);
  }
  return $options;
}

Functions

Namesort descending Description
clients_access_callback any defined perms in this module
clients_admin_cache Set cache time in minutes
clients_arguments defines hook_clients_arguments() return array of rows with following fields: $foo->name, $foo->title, $foo->help,
clients_call defines hook_clients_call
clients_connections_checkbox_options FormAPI helper to get a list of clients for a checkboxes form element.
clients_connections_select_options FormAPI helper to get a list of clients for a select form element.
clients_connection_load Load a client connection.
clients_connection_save
clients_fields defines hook_clients_fields
clients_flush_caches Implementation of hook_flush_caches().
clients_get_connection Create a connection object which can then be used to make method calls.
clients_get_connections Loads all connections
clients_get_connection_types Get a list of all connection types.
clients_help Implementation of hook_help()
clients_menu Implementation of hook_menu()
clients_perm Implementation of hook_perm()
clients_resources
clients_resources_add
clients_resources_form @todo validate uniqueness of name
clients_resources_form_submit
clients_resources_load Loads all resources
clients_resource_load Loads a resource
clients_resource_view @todo hide password until clicked to prevent overshoulder attacks when keys are being checked
clients_service_options hook_clients_service_options, configure options
clients_setparams nano API for callback to resources
client_delete_confirm Form builder for confirmation of deletion of a connection or resource.
client_delete_confirm_submit @todo: refactor?
client_delete_confirm_validate @todo validate a connection has no associated resources