clients.module in Web Service Clients 7
Same filename and directory in other branches
Clients module - handles keys and service connections and provides an API for clients
File
clients.moduleView 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
Name | 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 |