From: jerome Date: Tue, 13 Jan 2009 07:00:26 +0000 (+0000) Subject: web service MDL-12886 alpha web service api + alpha user core functions api + alpha... X-Git-Url: http://git.mjollnir.org/gw?a=commitdiff_plain;h=dee7b68b35b61bf636019c7ddbb6eb4aa14ed33c;p=moodle.git web service MDL-12886 alpha web service api + alpha user core functions api + alpha Rest server (+ rest test client) --- diff --git a/lib/moodlewsapi.php b/lib/moodlewsapi.php new file mode 100644 index 0000000000..e80cf5a0fa --- /dev/null +++ b/lib/moodlewsapi.php @@ -0,0 +1,43 @@ +descriptions = array(); + + } + + /** + * + * @param $functionname + */ + function get_function_webservice_description($functionname) { + if (key_exists($functionname, $this->descriptions)) { + return $this->descriptions[$functionname]; + } + else { + return false; + } + } + + function get_descriptions() { + return $this->descriptions; + } + +} +?> diff --git a/user/api.php b/user/api.php new file mode 100644 index 0000000000..bf10b2a5f6 --- /dev/null +++ b/user/api.php @@ -0,0 +1,218 @@ +search string A simple string to search for + * ->confirmed bool A switch to allow/disallow unconfirmed users + * ->exceptions array(int) A list of IDs to ignore, eg 2,4,5,8,9,10 + * ->firstinitial string ? + * ->lastinitial string ? + * @return array|false Array of {@link $USER} objects. False is returned if an error is encountered. + */ +static function tmp_namedparams_get_users($sort='firstname ASC', $recordsperpage=999999, $page=0, $fields='*', $selectioncriteria=NULL) { + global $DB; + + ///WS: convert array into an object + if (!empty($selectioncriteria) && is_array($selectioncriteria)) { + $selectioncriteria = (object) $selectioncriteria; + } + + $LIKE = $DB->sql_ilike(); + $fullname = $DB->sql_fullname(); + + $select = " username <> :guest AND deleted = 0"; + $params = array('guest'=>'guest'); + + if (!empty($selectioncriteria->search)){ + $selectioncriteria->search = trim($selectioncriteria->search); + $select .= " AND ($fullname $LIKE :search1 OR email $LIKE :search2 OR username = :search3)"; + $params['search1'] = "%".$selectioncriteria->search."%"; + $params['search2'] = "%".$selectioncriteria->search."%"; + $params['search3'] = $selectioncriteria->search; + } + + if (!empty($selectioncriteria->confirmed)) { + $select .= " AND confirmed = 1"; + } + + if (!empty($selectioncriteria->exceptions)) { + list($selectioncriteria->exceptions, $eparams) = $DB->get_in_or_equal($selectioncriteria->exceptions, SQL_PARAMS_NAMED, 'ex0000', false); + $params = $params + $eparams; + $except = " AND id ".$selectioncriteria->exceptions; + } + + if (!empty($selectioncriteria->firstinitial)) { + $select .= " AND firstname $LIKE :fni"; + $params['fni'] = $selectioncriteria->firstinitial."%"; + } + if (!empty($selectioncriteria->lastinitial)) { + $select .= " AND lastname $LIKE :lni"; + $params['lni'] = $selectioncriteria->lastinitial."%"; + } + + if (!empty($selectioncriteria->extraselect)) { + $select .= " AND ".$selectioncriteria->extraselect; + if (empty($selectioncriteria->extraparams)){ + $params = $params + (array)$selectioncriteria->extraparams; + } + } + + return $DB->get_records_select('user', $select, $params, $sort, $fields, $page, $recordsperpage); +} + +/** + * Returns a subset of users + * + * @uses $CFG + * @param bool $get If false then only a count of the records is returned + * @param string $search A simple string to search for + * @param bool $confirmed A switch to allow/disallow unconfirmed users + * @param array(int) $exceptions A list of IDs to ignore, eg 2,4,5,8,9,10 + * @param string $sort A SQL snippet for the sorting criteria to use + * @param string $firstinitial ? + * @param string $lastinitial ? + * @param string $page ? + * @param string $recordsperpage ? + * @param string $fields A comma separated list of fields to be returned from the chosen table. + * @return object|false|int {@link $USER} records unless get is false in which case the integer count of the records found is returned. False is returned if an error is encountered. + */ +static function tmp_get_users($get=true, $search='', $confirmed=false, array $exceptions=null, $sort='firstname ASC', + $firstinitial='', $lastinitial='', $page='', $recordsperpage='', $fields='*', $extraselect='', array $extraparams=null) { + global $DB; + + if ($get && !$recordsperpage) { + debugging('Call to get_users with $get = true no $recordsperpage limit. ' . + 'On large installations, this will probably cause an out of memory error. ' . + 'Please think again and change your code so that it does not try to ' . + 'load so much data into memory.', DEBUG_DEVELOPER); + } + + $LIKE = $DB->sql_ilike(); + $fullname = $DB->sql_fullname(); + + $select = " username <> :guest AND deleted = 0"; + $params = array('guest'=>'guest'); + + if (!empty($search)){ + $search = trim($search); + $select .= " AND ($fullname $LIKE :search1 OR email $LIKE :search2 OR username = :search3)"; + $params['search1'] = "%$search%"; + $params['search2'] = "%$search%"; + $params['search3'] = "$search"; + } + + if ($confirmed) { + $select .= " AND confirmed = 1"; + } + + if ($exceptions) { + list($exceptions, $eparams) = $DB->get_in_or_equal($exceptions, SQL_PARAMS_NAMED, 'ex0000', false); + $params = $params + $eparams; + $except = " AND id $exceptions"; + } + + if ($firstinitial) { + $select .= " AND firstname $LIKE :fni"; + $params['fni'] = "$firstinitial%"; + } + if ($lastinitial) { + $select .= " AND lastname $LIKE :lni"; + $params['lni'] = "$lastinitial%"; + } + + if ($extraselect) { + $select .= " AND $extraselect"; + $params = $params + (array)$extraparams; + } + + if ($get) { + return $DB->get_records_select('user', $select, $params, $sort, $fields, $page, $recordsperpage); + } else { + return $DB->count_records_select('user', $select, $params); + } +} + +/** + * Creates an User with given information. Required fields are: + * -username + * -idnumber + * -firstname + * -lastname + * -email + * + * And there's some interesting fields: + * -password + * -auth + * -confirmed + * -timezone + * -country + * -emailstop + * -theme + * -lang + * -mailformat + * + * @param assoc array or object $user + * + * @return userid or thrown exceptions + */ +static function tmp_create_user($user) { + global $CFG, $DB; + ///WS: convert user array into an user object + if (is_array($user)) { + $user = (object) $user; + } + + ///check password and auth fields + if (!isset($user->password)) { + $user->password = ''; + } + if (!isset($user->auth)) { + $user->auth = 'manual'; + } + + $required = array('username','firstname','lastname','email'); + foreach ($required as $req) { + if (!isset($user->{$req})) { + throw new moodle_exception('missingerequiredfield'); + } + } + + $record = create_user_record($user->username, $user->password, $user->auth); + if ($record) { + $user->id = $record->id; + if ($DB->update_record('user',$user)) { + return $record->id; + } else { + $DB->delete_record('user',array('id' => $record->id)); + } + } + throw new moodle_exception('couldnotcreateuser'); +} + +} + + + + + +?> diff --git a/user/wsapi.php b/user/wsapi.php new file mode 100644 index 0000000000..8c91fdc177 --- /dev/null +++ b/user/wsapi.php @@ -0,0 +1,63 @@ +descriptions = array(); + ///The desciption of the web service + /// + ///'wsparams' and 'return' are used to described the web services to the end user (can build WSDL file from these information) + ///'paramorder' is used internally by developers implementing a new protocol. It contains the params of the called function in a good order and with default value + /// + ///Note: web services param names have not importance. However 'paramorder' must match the function params order. + ///And all web services param names defined into 'wsparams' should be included into 'paramorder' (otherwise they will not be used) + /// + ///How to define an object/array attribut web service parameter: 'any object/array name' + _ + 'attribut/key name'. 'attribut/key name' must match the real attribut name. + ///e.g: a function has a parameter that is an object with a attribut named 'username'. You will need to declare 'anyobjectname_username' into 'wsparams'. + /// Then 'paramorder'=> array('anyobjectname' => array('username' => ...)); + /// + ///TODO: manage object->object parameter + $this->descriptions['tmp_get_users'] = array( 'wsparams' => array('search'=> PARAM_RAW), + 'return' => array('user', array('id' => PARAM_RAW, 'auth' => PARAM_RAW, 'confirmed' => PARAM_RAW, 'username' => PARAM_RAW, 'idnumber' => PARAM_RAW, + 'firstname' => PARAM_RAW, 'lastname' => PARAM_RAW, 'email' => PARAM_RAW, 'emailstop' => PARAM_RAW, + 'lang' => PARAM_RAW, 'theme' => PARAM_RAW, 'timezone' => PARAM_RAW, 'mailformat' => PARAM_RAW)), + 'paramorder' => array('get' => true, 'search' => '', 'confirmed' => false, 'exceptions' =>null, 'sort' => 'firstname ASC', + 'firstinitial' => '', 'lastinitial' => '', 'page' => '', 'recordsperpage' => '', + 'fields' => 'id, auth, confirmed, username, idnumber, firstname, lastname, email, emailstop, lang, theme, timezone, mailformat', + 'extraselect' => '', 'extraparams' => null)); + + $this->descriptions['tmp_create_user'] = array( 'wsparams' => array('user:username'=> PARAM_RAW, 'user:firstname'=> PARAM_RAW, 'user:lastname'=> PARAM_RAW, 'user:email'=> PARAM_RAW, 'user:password'=> PARAM_RAW), + 'return' => array('userid', PARAM_RAW), + 'paramorder' => array('user' => array('username' => null, 'firstname' => null, 'lastname'=> null, 'email'=> null, 'password'=>''))); + + $this->descriptions['tmp_namedparams_get_users'] = array( 'wsparams' => array('selectioncriteria:search'=> PARAM_RAW), + 'return' => array('user', array('id' => PARAM_RAW, 'auth' => PARAM_RAW, 'confirmed' => PARAM_RAW, 'username' => PARAM_RAW, 'idnumber' => PARAM_RAW, + 'firstname' => PARAM_RAW, 'lastname' => PARAM_RAW, 'email' => PARAM_RAW, 'emailstop' => PARAM_RAW, + 'lang' => PARAM_RAW, 'theme' => PARAM_RAW, 'timezone' => PARAM_RAW, 'mailformat' => PARAM_RAW)), + 'paramorder' => array('sort' => 'firstname ASC', '$recordsperpage' => 999999, 'page' => 0, + 'fields' => 'id, auth, confirmed, username, idnumber, firstname, lastname, email, emailstop, lang, theme, timezone, mailformat', + 'selectioncriteria' => array('search' => ''))); + } + + +} + + + + + +?> diff --git a/webservice/rest/lib.php b/webservice/rest/lib.php new file mode 100644 index 0000000000..7cd42b781c --- /dev/null +++ b/webservice/rest/lib.php @@ -0,0 +1,127 @@ + _mod_forum_) + $classname = substr($classname,1, strlen($classname) - 1); //remove first _ (e.g. _mod_forum => mod_forum) + $coreclassname = $classname."api"; + $classname .= 'ws_api'; + +///these three next lines can be generic => create a function + require_once($CFG->dirroot.$apipath.'wsapi.php'); + $api = new $classname(); + + $description = $api->get_function_webservice_description($functionname); //retrieve the web service description for this function + +///This following line is only REST protocol + $params = retrieve_params ($description); //retrieve the REST params + +///Generic part to any protocols + if ($params === false) { + //return an error message, the REST params doesn't match with the web service description + } + require_once($CFG->dirroot.$apipath.'api.php'); + $res = call_user_func_array ( $coreclassname.'::'.$functionname, $params); + +///Transform result into xml in order to send the REST response + $return = mdl_conn_rest_object_to_xml ($res,$description['return'][0]); + + return "$return"; +} + + +/** + * + * @author Jerome Mouneyrac + * @param $description + * @return + */ +function retrieve_params ($description) { + $params = $description['paramorder']; + //retrieve REST param matching the description + + foreach ($description['wsparams'] as $paramname => $paramtype) { + varlog('--retrieve_params--'); + varlog($paramname); + $value = optional_param($paramname,null,$paramtype); + varlog($value); + if (!empty($value)) { + $fullstopposition = strrpos($paramname,":"); + varlog(substr($paramname,0,$fullstopposition)); + varlog(substr($paramname,$fullstopposition+1, strlen($paramname) - $fullstopposition)); + //case: param is an object/array + if (!empty($fullstopposition)) { + $params[substr($paramname,0,$fullstopposition)][substr($paramname,$fullstopposition+1, strlen($paramname) - $fullstopposition)] = $value; + } else { + $params[$paramname] = $value; + } + } + } + varlog($params); + return $params; +} + +/** + * auxiliar function for simplexml_object_to_xml + * @author Ferran Recio, David Castro Garcia + * @param $obj + * @param $tag + * @param $atts assoc array (key => value) + * @return string + */ +function mdl_conn_rest_object_to_xml ($obj, $tag,$atts=false) { + $res = ''; + $tag_atts = ''; + if ($atts) { + $main_atts = array(); + foreach ($atts as $att=>$val) { + $main_atts[] = "$att=\"".urlencode($val)."\""; + } + if (count($main_atts)) $tag_atts = ' '.implode(' ',$main_atts); + } + + //if is an object + if (is_object($obj)) { + $parts = get_object_vars($obj); + foreach ($parts as $tag2 => $val) { + $res.= mdl_conn_rest_object_to_xml ($val, $tag2); + } + return "<$tag$tag_atts>\n$res\n"; + } + //if it's an array all elements will be inside te same tag but with a new atribute key + if (is_array($obj)){ + if (!$atts) $atts = array(); + //we came from another array + if (isset($atts['keys'])) $atts = array(); + foreach ($obj as $key=>$val) { + $array_atts = $atts; + $array_atts['key'] = $key; + $res.= mdl_conn_rest_object_to_xml ($val, $tag,$array_atts); + } + return $res; + } + //any other type, just encapsule it + $obj = htmlentities($obj); + return "<$tag$tag_atts>$obj\n"; + +} + +?> \ No newline at end of file diff --git a/webservice/rest/server.php b/webservice/rest/server.php new file mode 100644 index 0000000000..5f84d7a7ae --- /dev/null +++ b/webservice/rest/server.php @@ -0,0 +1,24 @@ + \ No newline at end of file diff --git a/webservice/rest/testclient/config_rest.php b/webservice/rest/testclient/config_rest.php new file mode 100644 index 0000000000..e070aad49d --- /dev/null +++ b/webservice/rest/testclient/config_rest.php @@ -0,0 +1,18 @@ +serverurl = $CFG->wwwroot.'/webservice/rest/server.php'; +if (!function_exists('curl_init')) die ('CURL library was not found!'); + +?> \ No newline at end of file diff --git a/webservice/rest/testclient/createuser.php b/webservice/rest/testclient/createuser.php new file mode 100644 index 0000000000..2b9344c811 --- /dev/null +++ b/webservice/rest/testclient/createuser.php @@ -0,0 +1,38 @@ +serverurl.'/user/tmp_create_user'); +curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); +curl_setopt($ch, CURLOPT_POST, 1); +curl_setopt($ch, CURLOPT_POSTFIELDS, format_postdata($data)); + +$out = curl_exec($ch); + +$res = basicxml_xml_to_object($out); + +show_object($res->userid); + +show_xml ($out); + +end_interface(); +?> \ No newline at end of file diff --git a/webservice/rest/testclient/getusers.php b/webservice/rest/testclient/getusers.php new file mode 100644 index 0000000000..5287b94da7 --- /dev/null +++ b/webservice/rest/testclient/getusers.php @@ -0,0 +1,55 @@ + + +
+ + + +
Search:
+
+ +serverurl.'/user/tmp_get_users'); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $CFG->serverurl.'/user/tmp_get_users'); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, format_postdata($data)); + $out = curl_exec($ch); + + $res = basicxml_xml_to_object($out); + + show_object($res->user,2,'auth'); + + show_xml ($out); +} else { + echo "

Fill the form first

"; +} + +end_interface(); + +?> diff --git a/webservice/rest/testclient/index.php b/webservice/rest/testclient/index.php new file mode 100644 index 0000000000..42e22c21eb --- /dev/null +++ b/webservice/rest/testclient/index.php @@ -0,0 +1,27 @@ +'; +foreach ($links as $link) { + echo '
  • '.$link[1].'
  • '; +} +echo ''; + +end_interface(false); +?> \ No newline at end of file diff --git a/webservice/rest/testclient/lib.php b/webservice/rest/testclient/lib.php new file mode 100644 index 0000000000..8d99273285 --- /dev/null +++ b/webservice/rest/testclient/lib.php @@ -0,0 +1,224 @@ +'; + echo "Moodle Webservice Rest Test Client"; + echo ''; + echo ''; + echo '

    Moodle Webservice Rest Test Client

    '; + echo "

    $title2

    "; + echo '
    '; + echo '
    '; + if ($title) echo '

    '; +} + +/** + * end interface + * + * @param bool $ret=true: show return button + */ +function end_interface ($ret = true) { + if ($ret) echo '

    '; + echo '
    '; + echo ''; + echo ''; +} + +/** + * print XML div area + * + * @param string $xml + * + */ +function show_xml ($xml) { + echo '
    '; + echo 'Hide/Show XML'; + echo "
    "; + echo '
    ';echo htmlentities($xml);echo '
    '; + echo "
    "; + echo "
    "; +} + +/** + * format post data + */ +function format_postdata ($data) { + $o=""; + foreach ($data as $k=>$v) { + $o.= "$k=".rawurlencode($v)."&"; + } + $post_data=substr($o,0,-1); + return $post_data; +} + +/** + * shows an object in list format + * + * @param mixed $obj + * @param integer $cols: number of colums + * @ string string $check=false: if this attribute is not present, the $obj is ans error + * + */ +function show_object ($obj,$cols=1,$check=false) { + if (!is_array($obj)) $obj = array($obj); + echo '
      '; + foreach ($obj as $r) { + + if ($check && (!isset($r->$check) || $r->$check==-1)) { + echo '
    • '; + echo "EMPTY ROW!"; + } else { + if (is_object($r)) { + echo '
    • '; + $text = array(); + $parts = get_object_vars($r); + $num = 1; + $currline = ''; + foreach ($parts as $key => $val) { + $currline.= "$key: $val, "; + if ($num >= $cols) { + $currline=substr($currline,0,-2); + $text[] = $currline; + $currline = ''; + $num = 0; + } + $num++; + } + echo implode('
      ',$text); + } else { + if ($r==-1 || !$r) { + echo '
    • '; + echo "EMPTY ROW!"; + } else { + echo '
    • '; + echo "Returned Value: $r"; + } + } + } + echo '
    • '; + } + echo '
    '; +} + + +//---- XML simple parser ---- +//this code was donated by Ferran Recio + +/** + * convert a simple xml into php object + * + * @author ferran recio + * + * @param String $xml + * + * @return mixed + */ +function basicxml_xml_to_object ($xml) { + $xml=utf8_encode($xml); + + //create the parser + $parser = xml_parser_create (); + xml_set_default_handler ($parser,'basicxml_xml_to_object_aux'); + + $values = array(); + $index = array(); + xml_parse_into_struct($parser,$xml,$values,$index); + + //print_object($values); + //print_object($index); + + //just simplexml tag (disabled) + //if (strtolower($values[0]['tag']) != 'basicxml') return false; + //if (strtolower($values[count($values)-1]['tag']) != 'basicxml') return false; + + $res = basicxml_xml_to_object_aux ($values); + //omit the first tag + $parts = array_keys(get_object_vars($res)); + $key = $parts[0]; + return $res->$key; +} + +/** + * auxiliar function to basicxml_xml_to_object + * + * @author ferran recio + * + * @param mixed $values + * + * @return mixed + */ +function basicxml_xml_to_object_aux ($values) { + + if (!is_array($values)) return false; + //print_object ($values); + $currset = array(); + $search = false; + + foreach ($values as $value) { + $tag = strtolower($value['tag']); + //if we are acomulating, just acomulate it + if ($search) { + //if it closes a tag, we just stop searching + if ($tag == $search && $value['type']=='close') { + //recursivity + $obj2 = basicxml_xml_to_object_aux ($currset); + //search cleaning + $search = false; + //add to result + if (isset($res->{$tag})){ + if (is_array($res->{$tag})){ + $res->{$tag}[] = $obj2; + } else { + $res->{$tag} = array($res->{$tag},$obj2); + } + } else { + $res->{$tag} = $obj2; + } + } else { + //we are searching. If it's cdada, pass it throw + if ($value['type']=='cdata') continue; + //if isn't cdata, put it in the set and continue searching + //(because isn't the close we're searching) + $currset[] = $value; + } + } else { + //walking the xml + if ($value['type']=='open'){ + //on open, let's search on it + $currset = array(); + $search = $tag; + } else { + //if it's complete just save it + if ($value['type']=='complete') { + $val = html_entity_decode($value['value']); + if (isset($res->{$tag})){ + if (is_array($res->{$tag})){ + $res->{$tag}[] = $val; + } else { + $res->{$tag} = array($res->{$tag},$val); + } + } else { + $res->{$tag} = $val; + } + } + } + } + } + return $res; +} +?> \ No newline at end of file diff --git a/webservice/rest/testclient/return.gif b/webservice/rest/testclient/return.gif new file mode 100644 index 0000000000..ff918c0087 Binary files /dev/null and b/webservice/rest/testclient/return.gif differ diff --git a/webservice/rest/testclient/style.css b/webservice/rest/testclient/style.css new file mode 100644 index 0000000000..ddd466b9a9 --- /dev/null +++ b/webservice/rest/testclient/style.css @@ -0,0 +1,57 @@ +body { + background-color: #FAFFFF;; +} + +.xmlshow { + background-color: #DDDDFF; + padding:20px; +} + +.xmlshow div { + height: 200px; + overflow: auto; + padding: 20px; + background-color: #EEEEFF; +} + +.head, .footer { + background-color: #AAAAFF; + text-align: center; +} + +.footer { + font-size: small; + padding: 10px; +} + +.content { + padding-left: 20px; + padding-right: 20px; +} + +.results li{ + margin-bottom:20px; + padding: 10px; + list-style-type: square; +} + +.element { + background-color: #CCCCFF; +} + +.element span:hover{ + background-color:#DDDDFF; +} + +.error { + background-color: #FFCCCC; +} + +.return a, .return a:hover, .return a:visited{ + color:#0000DD; +} + +h1, h2 { + margin:0px; + padding:5px; +} \ No newline at end of file