'. t('Visit the Services Handbook for help and information.', array('@handbook_url' => 'http://drupal.org/node/109782')) .'

'; case 'admin/build/services': case 'admin/build/services/browse': $output = '

'. t('Services are collections of methods available to remote applications. They are defined in modules, and may be accessed in a number of ways through server modules. Visit the Services Handbook for help and information.', array('@handbook_url' => 'http://drupal.org/node/109782')) .'

'; $output .= '

'. t('All enabled services and methods are shown. Click on any method to view information or test.') .'

'; return $output; case 'admin/build/services/keys': return t('An API key is required to allow an application to access Drupal remotely.'); } } /** * Implementation of hook_perm(). */ function services_perm() { return array('administer services'); } /** * Implementation of hook_menu(). */ function services_menu() { $items['admin/build/services'] = array( 'title' => 'Services', 'description' => 'Allows external applications to communicate with Drupal.', 'access arguments' => array('administer services'), 'page callback' => 'services_admin_browse_index', 'file' => 'services_admin_browse.inc', ); $items['admin/build/services/browse'] = array( 'title' => 'Browse', 'description' => 'Browse and test available remote services.', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10, ); $items['admin/build/services/browse/%services_method'] = array( 'title' => 'Services', 'description' => 'Calls a Services method.', 'page callback' => 'services_admin_browse_method', 'page arguments' => array(4), 'access arguments' => array('administer services'), 'type' => MENU_LOCAL_TASK, 'file' => 'services_admin_browse.inc', ); $items['admin/build/services/settings'] = array( 'title' => 'Settings', 'description' => 'Configure service settings.', 'page callback' => 'drupal_get_form', 'page arguments' => array('services_admin_settings'), 'access arguments' => array('administer services'), 'type' => MENU_LOCAL_TASK, 'file' => 'services_admin_browse.inc', ); $items['admin/build/services/settings/general'] = array( 'title' => 'General', 'description' => 'Configure service settings.', 'page callback' => 'drupal_get_form', 'page arguments' => array('services_admin_settings'), 'access arguments' => array('administer services'), 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10, 'file' => 'services_admin_browse.inc', ); $items['admin/services/ahah/security-options'] = array( 'file' => 'services_admin_browse.inc', 'page callback' => '_services_ahah_security_options', 'access arguments' => array('administer services'), 'type' => MENU_CALLBACK, ); $items['crossdomain.xml'] = array( 'access callback' => 'services_access_menu', 'page callback' => 'services_crossdomain_xml', 'type' => MENU_CALLBACK, ); $items['services/%'] = array( 'title' => 'Services', 'access callback' => 'services_access_menu', 'page callback' => 'services_server', 'page arguments' => array(1), 'type' => MENU_CALLBACK, ); return $items; } /** * Implementation of hook_theme(). */ function services_theme() { return array( 'services_admin_browse_test' => array( 'arguments' => array('form' => NULL), ), ); } /** * Callback for server endpoint */ function services_server($server_path = NULL) { // Find which module the server is part of foreach (module_implements('server_info') as $module) { $info = module_invoke($module, 'server_info'); services_strip_hashes($info); if ($info['path'] == $server_path) { // call the server services_set_server_info($module); print module_invoke($module, 'server'); // Do not let this output module_invoke_all('exit'); exit; } } // return 404 if the server doesn't exist drupal_not_found(); } /** * Callback for crossdomain.xml */ function services_crossdomain_xml() { global $base_url; $output = ''."\n"; $output .= ''."\n"; $output .= ' '."\n"; $output .= ' '."\n"; $keys = (function_exists('services_keyauth_get_keys')) ? services_keyauth_get_keys() : array(); foreach ($keys as $key) { if (!empty($key->domain)) { $output .= ' '."\n"; $output .= ' '."\n"; } } $output .= ''; services_xml_output($output); } /** * Helper function for sending xml output. This method outputs a xml * processing instruction and the necessary headers and then exits. * * @param string $xml * @return void */ function services_xml_output($xml) { $xml = ''."\n". $xml; header('Connection: close'); header('Content-Length: '. drupal_strlen($xml)); header('Content-Type: text/xml'); header('Date: '. date('r')); echo $xml; exit; } /** * Sets information about which server implementation that is used for the * call. * * @param string $module * The server module that's handling the call. * @return object * Server info object. */ function services_set_server_info($module) { $server_info = new stdClass(); $server_info->module = $module; $server_info->drupal_path = getcwd(); return services_get_server_info($server_info); } /** * Returns or sets the server info object. * * @param object $server_info * Optional. Pass a object to set the server info object. Omit to just * retrieve the server info. * @return object * Server info object. */ function services_get_server_info($server_info = NULL) { static $info; if (!$info && $server_info) { $info = $server_info; } return $info; } /** * Prepare an error message for returning to the server. * * @param string $message * The error message. * @param int $code * Optional. A error code, these should map as closely to the applicable * http error codes as closely as possible. * @param Exception $exception * Optional. The exception was thrown (if any) when the error occured. * @return mixed */ function services_error($message, $code = 0, $exception = NULL) { $server_info = services_get_server_info(); // Allow external modules to log this error module_invoke_all('services_error', $message, $code, $exception); // Look for custom error handling function. // Should be defined in each server module. if ($server_info && module_hook($server_info->module, 'server_error')) { return module_invoke($server_info->module, 'server_error', $message, $code, $exception); } // No custom error handling function found. return $message; } /** * Gets information about a authentication module. * If a property name is passed the value of the property will be returned, * otherwise the whole information array will be returned. * * @param string $property * Optional. The name of a single property to get. Defaults to null. * @param string $module * Optional. The module to get info forDefaults to the current authentication module. * @return mixed * The information array or property value, or FALSE if the information or property wasn't found */ function services_auth_info($property = NULL, $module = NULL) { static $info = array(); // Default the module param to the current auth module $module = $module ? $module : variable_get('services_auth_module', ''); if (!isset($info[$module])) { if (!empty($module) && module_exists($module) && is_callable($module .'_authentication_info')) { $info[$module] = call_user_func($module .'_authentication_info'); } else { $info[$module] = FALSE; } } // If a property was requested it should be returned if ($property) { return isset($info[$module][$property]) ? $info[$module][$property] : FALSE; } // Return the info array return $info[$module]; } /** * Invokes a method for the configured authentication module. * * @param string $method * The method to call. * @param mixed $arg1 * Optional. First argument. * @param mixed $arg2 * Optional. Second argument. * @param mixed $arg3 * Optional. Third argument. * @return mixed */ function services_auth_invoke($method, &$arg1 = NULL, &$arg2 = NULL, &$arg3 = NULL) { $module = variable_get('services_auth_module', ''); // Get information about the current auth module $func = services_auth_info($method, $module); if ($func) { if ($file = services_auth_info('file')) { require_once(drupal_get_path('module', $module) .'/'. $file); } if (is_callable($func)) { $args = func_get_args(); // Replace method name and arg1 with reference to $arg1 and $arg2. array_splice($args, 0, 3, array(&$arg1, &$arg2, &$arg3)); return call_user_func_array($func, $args); } } else{ return TRUE; } } /** * Invokes a method for the given authentication module. * * @param string $module * The authentication module to call the method for. * @param string $method * The method to call. * @param mixed $arg1 * Optional. First argument. * @param mixed $arg2 * Optional. Second argument. * @param mixed $arg3 * Optional. Third argument. * @return mixed */ function services_auth_invoke_custom($module, $method, &$arg1 = NULL, &$arg2 = NULL, &$arg3 = NULL) { // Get information about the auth module $func = services_auth_info($method, $module); if ($func) { if ($file = services_auth_info('file', $module)) { require_once(drupal_get_path('module', $module) .'/'. $file); } if (is_callable($func)) { $args = func_get_args(); // Replace module and method name and arg1 with reference to $arg1 and $arg2. array_splice($args, 0, 4, array(&$arg1, &$arg2, &$arg3)); return call_user_func_array($func, $args); } } else{ return TRUE; } } /** * Invokes a services method. * * @param mixed $method_name * The method name or a method definition array. * @param array $args * Optional. An array containing arguments for the method. These arguments are not * matched by name but by position. * @param bool $browsing * Optional. Whether the call was made by the services browser or not. * @return mixed */ function services_method_call($method_name, $args = array(), $browsing = FALSE) { // Allow external modules to log the results of this service call module_invoke_all('services_method_call', $method_name, $args, $browsing); if (is_array($method_name) && isset($method_name['callback'])) { $method = $method_name; } else { $method = services_method_get($method_name); } // Check that method exists. if (empty($method)) { return services_error(t('Method %name does not exist', array('%name' => $method_name)), 406); } // Check for missing args $hash_parameters = array(); foreach ($method['args'] as $key => $arg) { if (!$arg['optional']) { if (!isset($args[$key]) && !is_array($args[$key]) && !is_bool($args[$key])) { return services_error(t('Missing required arguments.'), 406); } } } // Check authentication if ($method['auth'] && $auth_error = services_auth_invoke('authenticate_call', $method, $method_name, $args)) { if ($browsing) { drupal_set_message(t('Authentication failed: !message', array('!message' => $auth_error)), 'error'); } else { return services_error($auth_error, 401); } } // Load the proper file. if ($file = $method['file']) { // Initialize file name if not given. $file += array('file name' => ''); module_load_include($file['file'], $file['module'], $file['file name']); } // Construct access arguments array if (isset($method['access arguments'])) { $access_arguments = $method['access arguments']; if (isset($method['access arguments append']) && $method['access arguments append']) { $access_arguments[] = $args; } } else { // Just use the arguments array if no access arguments have been specified $access_arguments = $args; } // Call default or custom access callback if (call_user_func_array($method['access callback'], $access_arguments) != TRUE) { return services_error(t('Access denied'), 401); } // Change working directory to drupal root to call drupal function, // then change it back to server module root to handle return. $server_root = getcwd(); $server_info = services_get_server_info(); if ($server_info) { chdir($server_info->drupal_path); } $result = call_user_func_array($method['callback'], $args); if ($server_info) { chdir($server_root); } // Allow external modules to log the results of this service call module_invoke_all('services_method_call_results', $method_name, $args, $browsing, $result); return $result; } /** * Registers a callback for formatting resource uri's. * Use parameterless call to get the current formatter callback. * * @param mixed $callback * Optional. The callaback to register for uri formatting. No changes are made * if this parameter is omitted or NULL. * @return mixed * Returns the registered callback for resource uri formatting */ function services_resource_uri_formatter($callback = NULL) { static $formatter; if ($callback !== NULL) { $formatter = $callback; } return $formatter; } /** * Formats a resource uri using the formatter registered through * services_resource_uri_formatter(). * * @param array $path * An array of strings containing the component parts of the path to the resource. * @return string * Returns the formatted resource uri, or NULL if no formatter has been registered. */ function services_resource_uri($path) { $formatter = services_resource_uri_formatter(); if ($formatter) { return call_user_func($formatter, $path); } return NULL; } /** * Gets all resource definitions. * * @return array * An array containing all resources. */ function services_get_all_resources($include_services = TRUE, $reset = FALSE) { $cache_key = 'services:resources'. ($include_services?'_with_services':''); if (!$reset && ($cache = cache_get($cache_key)) && isset($cache->data)) { return $cache->data; } else { $resources = module_invoke_all('service_resource'); drupal_alter('service_resources', $resources); services_strip_hashes($resources); $controllers = array(); services_process_resources($resources, $controllers); foreach ($controllers as &$controller) { if (!isset($controller['access callback'])) { $controller['access callback'] = 'services_access_menu'; } if (!isset($controller['auth'])) { $controller['auth'] = TRUE; } if (!isset($controller['key'])) { $controller['key'] = TRUE; } } drupal_alter('service_resources_post_processing', $resources); services_auth_invoke('alter_methods', $controllers); if ($include_services) { $services = services_get_all(FALSE); // Include the file that has the necessary functions for translating // methods to resources. if (!empty($services)) { module_load_include('inc', 'services', 'services.resource-translation'); $resources = array_merge(_services_services_as_resources($services), $resources); } } cache_set($cache_key, $resources); return $resources; } } /** * Implementation of hook_form_alter(). */ function services_form_alter(&$form, $form_state, $form_id) { if ($form_id == 'system_modules') { // Add our own submit hook to clear cache $form['#submit'][] = 'services_system_modules_submit'; } } /** * Submit handler for the system_modules form that clears the services cache. */ function services_system_modules_submit($form, &$form_state) { // Reset services cache services_get_all(TRUE, TRUE); } /** * Processes the passed resources and adds all the controllers to the * controller array. * * @param array $resources * The resources that should be processed. * @param string $controllers * An array (passed by reference) that will be populated with the controllers * of the passed resources. * @param string $path * Optional. Deprecated. */ function services_process_resources(&$resources, &$controllers, $path=array()) { foreach ($resources as $name => &$resource) { if (drupal_substr($name, 0, 1) != '#') { _services_process_resource(array_merge($path, array($name)), $resource, $controllers); } } } /** * Processes a single resource and adds it's controllers to the controllers * array. * * @param string $name * The name of the resource. * @param array $resource * The resource definition. * @param array $controllers * An array (passed by reference) that will be populated with the controllers * of the passed resources. * @return void */ function _services_process_resource($name, &$resource, &$controllers) { $path = join($name, '/'); $resource['name'] = $path; $keys = array('retrieve', 'create', 'update', 'delete'); foreach ($keys as $key) { if (isset($resource[$key])) { $controllers[$path .'/'. $key] = &$resource[$key]; } } if (isset($resource['index'])) { $controllers[$path .'/index'] = &$resource['index']; } if (isset($resource['relationships'])) { foreach ($resource['relationships'] as $relname => $rel) { // Run some inheritance logic if (isset($resource['retrieve'])) { if (empty($rel['args']) || $rel['args'][0]['name'] !== $resource['retrieve']['args'][0]['name']) { array_unshift($rel['args'], $resource['retrieve']['args'][0]); } $resource['relationships'][$relname] = array_merge($resource['retrieve'], $rel); } $controllers[$path .'/relationship/'. $relname] = &$resource['relationships'][$relname]; } } if (isset($resource['actions'])) { foreach ($resource['actions'] as $actname => $act) { // Run some inheritance logic if (isset($resource['update'])) { $up = $resource['update']; unset($up['args']); $resource['actions'][$actname] = array_merge($up, $act); } $controllers[$path .'/action/'. $actname] = &$resource['actions'][$actname]; } } if (isset($resource['targeted actions'])) { foreach ($resource['targeted actions'] as $actname => $act) { // Run some inheritance logic if (isset($resource['update'])) { if (empty($act['args']) || $act['args'][0]['name'] !== $resource['update']['args'][0]['name']) { array_unshift($act['args'], $resource['update']['args'][0]); } $resource['targeted actions'][$actname] = array_merge($resource['update'], $act); } $controllers[$path .'/targeted_action/'. $actname] = &$resource['actions'][$actname]; } } } /** * Should be removed. This code doesn't seem to be used anywhere. * * @param string $perm * The permission to check for. * @return bool */ function services_delegate_access($perm) { return services_auth_invoke('delegate_access', $perm); } /** * Gets all service definitions * * @param bool $include_resources * Optional. When TRUE resource-based service definitions will be translated to * the appropriat method callas and included in the service listing. * Defaults to TRUE. * @return array * An array containing all services and thir methods */ function services_get_all($include_resources = TRUE, $reset = FALSE) { $cache_key = 'services:methods'. ($include_resources?'_with_resources':''); if (!$reset && ($cache = cache_get($cache_key)) && isset($cache->data)) { return $cache->data; } else { $methods = module_invoke_all('service'); services_strip_hashes($methods); foreach ($methods as $key => $method) { if (!isset($methods[$key]['access callback'])) { $methods[$key]['access callback'] = 'services_access_menu'; } if (!isset($methods[$key]['args'])) { $methods[$key]['args'] = array(); } // set defaults for args foreach ($methods[$key]['args'] as $arg_key => $arg) { if (is_array($arg)) { if (!isset($arg['optional'])) { $methods[$key]['args'][$arg_key]['optional'] = FALSE; } } else { $arr_arg = array(); $arr_arg['name'] = t('unnamed'); $arr_arg['type'] = $arg; $arr_arg['description'] = t('No description given.'); $arr_arg['optional'] = FALSE; $methods[$key]['args'][$arg_key] = $arr_arg; } } reset($methods[$key]['args']); } // Allow auth module to alter the methods services_auth_invoke('alter_methods', $methods); // Add resources if wanted if ($include_resources) { $resources = services_get_all_resources(FALSE, $reset); // Include the file that has the necessary functions for translating // resources to method calls. if (!empty($resources)) { module_load_include('inc', 'services', 'services.resource-translation'); // Translate all resources foreach ($resources as $name => $def) { foreach (_services_resource_as_services($def) as $method) { $methods[] = $method; } } } } cache_set($cache_key, $methods); return $methods; } } /** * Menu wildcard loader for browsing Service methods. */ function services_method_load($method) { $method = services_method_get($method); return isset($method) ? $method : FALSE; } /** * Get's the definition of a method. * * @param string $method_name * The name of the method to get the definition for. * @return array * The method definition. */ function services_method_get($method_name) { static $method_cache; if (!isset($method_cache[$method_name])) { foreach (services_get_all() as $method) { if ($method_name == $method['method']) { $method_cache[$method_name] = $method; break; } } } return $method_cache[$method_name]; } /** * Cleanup function to remove unnecessary hashes from service definitions. * * @param array $array * A service definition or an array of service definitions. * @return void */ function services_strip_hashes(&$array) { foreach ($array as $key => $value) { if (is_array($value)) { services_strip_hashes($array[$key]); } if (strpos($key, '#') === 0) { $array[substr($key, 1)] = $array[$key]; unset($array[$key]); } } } /** * Creates an object that only contains the specified attributes from the node * object. * * @param object $node * The node to get the attributes. * @param array $fields * An array containing the names of the attributes to get. * @return object * An object with the specified attributes. */ function services_node_load($node, $fields = array()) { if (!isset($node->nid)) { return NULL; } // Loop through and get only requested fields if (count($fields) > 0) { foreach ($fields as $field) { $val->{$field} = $node->{$field}; } } else { $val = $node; } return $val; } /** * Backup current session data and import user session. */ function services_session_load($sessid) { global $user; // If user's session is already loaded, just return current user's data if ($user->sid == $sessid) { return $user; } // Make backup of current user and session data $backup = $user; $backup->session = session_encode(); // Empty current session data $_SESSION = array(); // Some client/servers, like XMLRPC, do not handle cookies, so imitate it to make sess_read() function try to look for user, // instead of just loading anonymous user :). if (!isset($_COOKIE[session_name()])) $_COOKIE[session_name()] = $sessid; // Load session data session_id($sessid); sess_read($sessid); // Check if it really loaded user and, for additional security, if user was logged from the same IP. If not, then revert automatically. if ($user->sid != $sessid) { services_session_unload($backup); return NULL; } // Prevent saving of this impersonation in case of unexpected failure. session_save_session(FALSE); return $backup; } /** * Revert to previously backuped session. */ function services_session_unload($backup) { global $user; // No point in reverting if it's the same user's data if ($user->sid == $backup->sid) { return; } // Some client/servers, like XMLRPC, do not handle cookies, so imitate it to make sess_read() function try to look for user, // instead of just loading anonymous user :). if (!isset($_COOKIE[session_name()])) $_COOKIE[session_name()] = $backup->sessid; // Save current session data sess_write($user->sid, session_encode()); // Empty current session data $_SESSION = array(); // Revert to previous user and session data $user = $backup; session_id($backup->sessid); session_decode($user->session); session_save_session(TRUE); } /** * Return true so as services menu callbacks work */ function services_access_menu() { return TRUE; }