Commit 98ca273e authored by Aaron Wells's avatar Aaron Wells

Bug 1620879: Remove extraneous "/webservice" from end of plugin components

Currently, a plugin has to list its component as e.g.
"module/mobileapi/webservice". This could just be "module/mobileapi".

Since we're using the component string as part of the automated token
request system, it's better to change this now to something more sensible.

behatnotneeded: Tests to come later

Change-Id: Ia7663065b79598d92c3bf8450c5539efb6aa3e2d
parent 2bffb229
......@@ -506,6 +506,47 @@ function xmldb_auth_webservice_upgrade($oldversion=0) {
if (!field_exists($table, $field)) {
add_field($table, $field);
}
// The old code listed the components with "/webservice"
// on the end of them, e.g. "artefact/internal/webservice".
// This is redundant and makes for a poorer user interface.
log_debug('Change in "component" format for plugins');
$oldtail = '/webservice';
$length = strlen($oldtail);
// Functions
execute_sql(
"UPDATE {external_functions}
SET
component = LEFT(
component,
LENGTH(component) - {$length}
)
WHERE
component <> 'webservice'
AND RIGHT(
component,
{$length}
) = '{$oldtail}'
"
);
// Services
execute_sql(
"UPDATE {external_services}
SET
component = LEFT(
component,
LENGTH(component) - {$length}
)
WHERE
component <> 'webservice'
AND RIGHT(
component,
{$length}
) = '{$oldtail}'
"
);
}
// sweep for webservice updates everytime
......
......@@ -1501,8 +1501,11 @@ function check_dir_exists($dir, $create=true, $recursive=true) {
* @param string $filename the name of the file to include within the plugin structure
* @param string $function (optional, defaults to require) the require/include function to use
* @param string $nonfatal (optional, defaults to false) just returns false if the file doesn't exist
* @param array $returnvars (optional, defaults to null) Variables (defined in the file) to return.
* Useful for files like version.php that simply define variables. If null, instead returns the
* value of the include/require operation.
*/
function safe_require($plugintype, $pluginname, $filename='lib.php', $function='require_once', $nonfatal=false) {
function safe_require($plugintype, $pluginname, $filename='lib.php', $function='require_once', $nonfatal=false, $returnvars = null) {
$plugintypes = plugin_types();
if (!in_array($plugintype, $plugintypes)) {
throw new SystemException("\"$plugintype\" is not a valid plugin type");
......@@ -1561,11 +1564,17 @@ function safe_require($plugintype, $pluginname, $filename='lib.php', $function='
throw new SystemException ("File $fullpath was outside document root!");
}
if ($function == 'require') { return require($realpath); }
if ($function == 'include') { return include($realpath); }
if ($function == 'require_once') { return require_once($realpath); }
if ($function == 'include_once') { return include_once($realpath); }
if ($function == 'require') { $isloaded = require($realpath); }
if ($function == 'include') { $isloaded = include($realpath); }
if ($function == 'require_once') { $isloaded = require_once($realpath); }
if ($function == 'include_once') { $isloaded = include_once($realpath); }
if ($isloaded && $returnvars && is_array($returnvars)) {
return compact($returnvars);
}
else {
return $isloaded;
}
}
/**
......@@ -1583,11 +1592,13 @@ function safe_require($plugintype, $pluginname, $filename='lib.php', $function='
* @param string $filename the name of the file to include within the plugin structure
* @param string $function (optional, defaults to require) the require/include function to use
* @param string $nonfatal (optional, defaults to false) just returns false if the file doesn't exist
* @param array $returnvars (optional, defaults to null) Variables (defined in the file) to return.
* Useful for files like version.php that simply define variables. If null, instead returns the
* value of the include/require operation.
*/
function safe_require_plugin($plugintype, $pluginname, $filename='lib.php', $function='require_once', $nonfatal=false) {
function safe_require_plugin($plugintype, $pluginname, $filename='lib.php', $function='require_once', $nonfatal=false, $returnvars = null) {
try {
safe_require($plugintype, $pluginname, $filename, $function, $nonfatal);
return true;
return safe_require($plugintype, $pluginname, $filename, $function, $nonfatal, $returnvars);
}
catch (SystemException $e) {
if (get_field($plugintype . '_installed', 'active', 'name', $pluginname) == 1) {
......@@ -3618,7 +3629,7 @@ function get_my_tags($limit=null, $cloud=true, $sort='freq') {
array($id, $id, $id)
);
if (!$tagrecords) {
return false;
return array();
}
if ($cloud) {
$minfreq = $tagrecords[count($tagrecords) - 1]->count;
......
......@@ -46,14 +46,14 @@ define('EXTERNAL_TOKEN_EMBEDDED', 1);
define('EXTERNAL_TOKEN_OAUTH1', 2);
/**
* OAuth Token type for registered applications oauth v1
* Security token self-generated by a normal user
*/
define('EXTERNAL_TOKEN_USER', 3);
/**
* Personal User Tokens expiry time
* Personal User Tokens expiry time (12 weeks)
*/
define('EXTERNAL_TOKEN_USER_EXPIRES', (30 * 24 * 60 * 60));
define('EXTERNAL_TOKEN_USER_EXPIRES', (12 * 7 * 24 * 60 * 60));
define('WEBSERVICE_AUTHMETHOD_USERNAME', 0);
define('WEBSERVICE_AUTHMETHOD_PERMANENT_TOKEN', 1);
......@@ -62,6 +62,7 @@ define('WEBSERVICE_AUTHMETHOD_OAUTH_TOKEN', 3);
define('WEBSERVICE_AUTHMETHOD_USER_TOKEN', 4);
// strictness check
define('IGNORE_MISSING', 1);
define('MUST_EXIST', 2);
/** Get remote addr constant */
......@@ -169,25 +170,22 @@ function webservice_validate_user($dbuser) {
}
/**
* List all installed component web service directories
* List all potential webservice locations
* (i.e. plugins, local, and the "pseudo-module" /webservice).
*
* @return array of web service plugin directories
*/
function get_ws_subsystems() {
static $plugindirs = null;
static $plugindirs = false;
if (!$plugindirs) {
// add the root webservice first which is empty because it is docroot, and local
$plugindirs = array(WEBSERVICE_DIRECTORY, 'local/' . WEBSERVICE_DIRECTORY);
foreach (plugin_types_installed() as $t) {
foreach (plugins_installed($t) as $name => $plugindata) {
$plugindir = $t . '/' . $name;
if (!empty($plugindata->authplugin)) {
$plugindir = 'auth/' . $plugindata->authplugin . '/' . $plugindir;
}
$plugindirs[] = $plugindir . '/' . WEBSERVICE_DIRECTORY;
}
$plugindirs = [
'webservice',
'local'
];
$activeplugins = plugin_all_installed();
foreach ($activeplugins as $plugindata) {
$plugindirs[] = "{$plugindata->plugintype}/{$plugindata->name}";
}
}
......@@ -263,6 +261,47 @@ function webservice_create_service_token($servicename, $userid, $institution = '
return webservice_generate_token(EXTERNAL_TOKEN_EMBEDDED, $service, $userid, $institution, $validuntil, $iprestriction);
}
/**
* Calculate where the webservices directory should be, for a given value of
* "component".
*
* There are three general types of "component" value we expect to see.
* 1. "webservice": Indicates a the "core" webservices, which are in htdocs/webservice
* 2. "local": Indicates custom webservices under htdocs/local/webservice
* 3. "{plugintype}/{pluginname}": Indicates webservices for a plugin.
*
* @param string $component The component to look for
* @param reference $plugintype If the component represents a plugin, the plugin's type will
* be returned via this variable, passed by reference.
* @param reference $pluginname If the component represents a plugin, the plugin's name will
* be returned via this variable, passed by reference.
* @return string Relative path to the component's webservices directory. If the component
* is a plugin, this path will be relative the plugin's directory. Otherwise, it'll be
* relative to $CFG->docroot.
* @throws WebserviceCodingException
*/
function webservice_component_ws_directory($component, &$plugintype, &$pluginname) {
if ($component == WEBSERVICE_DIRECTORY) {
$plugintype = false;
$pluginname = false;
return WEBSERVICE_DIRECTORY;
}
if ($component == 'local') {
$plugintype = false;
$pluginname = false;
return 'local/' . WEBSERVICE_DIRECTORY;
}
$bits = explode('/', $component);
if (count($bits) == 2) {
list($plugintype, $pluginname) = $bits;
return WEBSERVICE_DIRECTORY;
}
throw new WebserviceCodingException("Invalid component name: '{$component}'");
}
/**
* Returns detailed function information
* @param string|object $function name of external function or record from external_function
......@@ -270,45 +309,99 @@ function webservice_create_service_token($servicename, $userid, $institution = '
* MUST_EXIST means throw exception if no record or multiple records found
* @return object description or false if not found or exception thrown
*/
function webservice_function_info($function, $strictness=MUST_EXIST) {
function webservice_function_info($function, $strictness=MUST_EXIST, $component = null) {
$mustexist = ($strictness === MUST_EXIST);
if (!is_object($function)) {
if (!$function = get_record('external_functions', 'name', $function, NULL, NULL, NULL, NULL, '*')) {
if ($component) {
$function = get_record('external_functions', 'name', $function, 'component', $component);
}
else {
$function = get_record('external_functions', 'name', $function);
}
if (!$function) {
return false;
}
$component = $function->component;
}
//first find and include the ext implementation class
$function->classpath = empty($function->classpath) ? get_config('docroot') . $function->component : get_config('docroot') . $function->classpath;
if (!file_exists($function->classpath . '/functions/' . $function->classname . '.php')) {
throw new WebserviceCodingException(get_string('cannotfindimplfile', 'auth.webservice'));
if (!class_exists($function->classname)) {
$wsdir = webservice_component_ws_directory(
$function->component,
$plugintype,
$pluginname
);
if ($plugintype && $pluginname) {
// Standard plugin; can use safe_require
$foundfile = safe_require_plugin(
$plugintype,
$pluginname,
$wsdir . '/functions/' . $function->classname . '.php',
'require_once',
true
);
if (!$foundfile) {
if ($mustexist) {
throw new WebserviceCodingException(get_string('cannotfindimplfile', 'auth.webservice'));
}
return false;
}
}
else {
// Not a plugin, must handle manually
$filepath = get_config('docroot') . $wsdir . '/functions/' . $function->classname . '.php';
if (!file_exists($filepath)) {
if ($mustexist) {
throw new WebserviceCodingException(get_string('cannotfindimplfile', 'auth.webservice'));
}
return false;
}
require_once($filepath);
}
}
require_once($function->classpath . '/functions/' . $function->classname . '.php');
$function->parameters_method = $function->methodname . '_parameters';
$function->returns_method = $function->methodname . '_returns';
// make sure the implementaion class is ok
if (!method_exists($function->classname, $function->methodname)) {
throw new WebserviceCodingException(get_string('missingimplofmeth', 'auth.webservice', $function->classname . '::' . $function->methodname));
if ($mustexist) {
throw new WebserviceCodingException(get_string('missingimplofmeth', 'auth.webservice', $function->classname . '::' . $function->methodname));
}
return false;
}
if (!method_exists($function->classname, $function->parameters_method)) {
throw new WebserviceCodingException(get_string('missingparamdesc', 'auth.webservice'));
if ($mustexist) {
throw new WebserviceCodingException(get_string('missingparamdesc', 'auth.webservice'));
}
return false;
}
if (!method_exists($function->classname, $function->returns_method)) {
throw new WebserviceCodingException(get_string('missingretvaldesc', 'auth.webservice'));
if ($mustexist) {
throw new WebserviceCodingException(get_string('missingretvaldesc', 'auth.webservice'));
}
return false;
}
// fetch the parameters description
$function->parameters_desc = call_user_func(array($function->classname, $function->parameters_method));
if (!($function->parameters_desc instanceof external_function_parameters)) {
throw new WebserviceCodingException(get_string('invalidparamdesc', 'auth.webservice'));
if ($mustexist) {
throw new WebserviceCodingException(get_string('invalidparamdesc', 'auth.webservice'));
}
return false;
}
// fetch the return values description
$function->returns_desc = call_user_func(array($function->classname, $function->returns_method));
// null means void result or result is ignored
if (!is_null($function->returns_desc) and !($function->returns_desc instanceof external_description)) {
throw new WebserviceCodingException(get_string('invalidretdesc', 'auth.webservice'));
if ($mustexist) {
throw new WebserviceCodingException(get_string('invalidretdesc', 'auth.webservice'));
}
return false;
}
//now get the function description
......@@ -317,15 +410,10 @@ function webservice_function_info($function, $strictness=MUST_EXIST) {
// on the other hand this is still a bit in a flux and we need to find some new naming
// conventions for these descriptions in lang packs
$function->description = null;
$servicesfile = $function->classpath . '/services.php';
if (file_exists($servicesfile)) {
$functions = null;
include($servicesfile);
if (isset($functions[$function->name]['description'])) {
$function->description = $functions[$function->name]['description'];
}
$result = webservice_load_services_file($function->component);
$functionlist = $result['functions'];
if (isset($functionlist[$function->name]['description'])) {
$function->description = $functionlist[$function->name]['description'];
}
return $function;
......@@ -488,11 +576,7 @@ class external_api {
throw new WebserviceInvalidParameterException(get_string('errormissingkey', 'auth.webservice', $key));
}
if ($subdesc->required == VALUE_DEFAULT) {
try {
$result[$key] = self::validate_parameters($subdesc, $subdesc->default);
} catch (WebserviceInvalidParameterException $e) {
throw new WebserviceParameterException('invalidextparam', $key);
}
$result[$key] = $subdesc->default;
}
}
else {
......@@ -1835,14 +1919,10 @@ abstract class webservice_base_server extends webservice_server {
/**
* Delete all service and external functions information defined in the specified component.
* @param string $component name of component (mahara, local, etc.)
* @param bool $dir does this component name have the directory on it
* @return void
*/
function external_delete_descriptions($component, $dir=true) {
function external_delete_descriptions($component) {
if ($dir) {
$component .= ($component ? '/' : '') . WEBSERVICE_DIRECTORY;
}
$params = array($component);
delete_records_select('external_services_users', "externalserviceid IN (SELECT id FROM {external_services} WHERE component = ?)", $params);
......@@ -1872,48 +1952,97 @@ function webservice_clean_webservice_logs() {
function external_reload_webservices() {
// first - prune all components that are nolonger available/installed
$dead_components = get_records_sql_array('SELECT DISTINCT component AS component FROM {external_functions} WHERE component NOT IN ('.
implode(', ', array_fill(1, count(get_ws_subsystems()), '?')).')', get_ws_subsystems());
$dead_components = get_records_sql_array(
'SELECT DISTINCT component AS component
FROM {external_functions}
WHERE
component != \'\'
AND component NOT IN ('.
implode(', ', array_fill(1, count(get_ws_subsystems()), '?'))
.')',
get_ws_subsystems()
);
if ($dead_components) {
foreach ($dead_components as $component) {
external_delete_descriptions($component->component, false);
external_delete_descriptions($component->component);
}
}
foreach (get_ws_subsystems() as $component) {
external_reload_component($component, false);
external_reload_component($component);
}
return true;
}
/**
* Reload the webservice descriptions for a single plugins
* Utility function to load up the $services and $functions arrays
* from the services.php file for the specified component. (Calling it
* from its own function in order to avoid polluting the namespace.)
*
* @param string $component
* @param bool $dir does this component name have the directory on it
* @return bool true = success
* @return array [$services, $functions]
*/
function webservice_load_services_file($component) {
function external_reload_component($component, $dir=true) {
$wsdir = webservice_component_ws_directory(
$component,
$plugintype,
$pluginname
);
if ($plugintype && $pluginname) {
// Standard plugin; can use safe_require
$file = safe_require(
$plugintype,
$pluginname,
WEBSERVICE_DIRECTORY . '/services.php',
'include',
true,
array('services', 'functions')
);
$services = $file['services'];
$functions = $file['functions'];
}
else {
// Not a plugin, must handle manually
$filepath = get_config('docroot') . $wsdir . '/services.php';
if (file_exists($filepath . '/services.php')) {
include($filepath . '/services.php');
}
}
// are there web service plugins
if ($dir) {
$component .= ($component ? '/' : '') . WEBSERVICE_DIRECTORY;
if (empty($services)) {
$services = array();
}
$basepath = get_config('docroot') . $component;
if (empty($functions)) {
$functions = array();
}
return array(
'services' => $services,
'functions' => $functions
);
}
/**
* Reload the webservice descriptions for a single plugins
*
* @param string $component ("webservice", "local", or a plugin e.g.
* "artefact/internal".
* @return bool Whether or not we found webservices for this component
*/
function external_reload_component($component) {
// is there a webservice directory with the right files
if (!file_exists($basepath) || !file_exists($basepath.'/services.php')) {
// Load arrays $services and $functions from the plugin or component's
// {path_to_plugin}/webservice/services.php file.
$result = webservice_load_services_file($component);
$services = $result['services'];
$functions = $result['functions'];
// Does the component have a valid services.php file?
if (!$services && !$functions) {
external_delete_descriptions($component);
return false;
}
$defpath = $basepath . '/services.php';
// load new info
$functions = array();
$services = array();
include($defpath);
// update all function first
$dbfunctions = get_records_array('external_functions', 'component', $component);
......@@ -1927,7 +2056,15 @@ function external_reload_component($component, $dir=true) {
}
$function = $functions[$dbfunction->name];
unset($functions[$dbfunction->name]);
$function['classpath'] = empty($function['classpath']) ? $component : $function['classpath'];
// Fill in default classpath
if (empty($function['classpath'])) {
if ($component === WEBSERVICE_DIRECTORY) {
$function['classpath'] = WEBSERVICE_DIRECTORY;
}
else {
$function['classpath'] = $component . '/' . WEBSERVICE_DIRECTORY;
}
}
$update = false;
if ($dbfunction->classname != $function['classname']) {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment