mirror of
https://github.com/moodle/moodle.git
synced 2025-01-18 05:58:34 +01:00
MDL-20808 Fixes for amf web services and test client - a web service browser.
Includes : MDL-21552 amf web services need to accept params and return values of proper type MDL-21553 amf web service : In Flash an array has normally a numeri
This commit is contained in:
parent
279e2669eb
commit
94a9b9e770
107
webservice/amf/introspector.php
Normal file
107
webservice/amf/introspector.php
Normal file
@ -0,0 +1,107 @@
|
||||
<?php
|
||||
/**
|
||||
* Moodle - Modular Object-Oriented Dynamic Learning Environment
|
||||
* http://moodle.org
|
||||
* Copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @package moodle
|
||||
* @author Penny Leach <penny@liip.ch>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL
|
||||
* @copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com
|
||||
*
|
||||
* Introspection for amf - figures out where all the services are and
|
||||
* returns a list of their available methods.
|
||||
* Requires $CFG->amf_introspection = true for security.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Provides a function to get details of methods available on another class.
|
||||
* @author HP
|
||||
*
|
||||
*/
|
||||
class MethodDescriptor {
|
||||
|
||||
private $methods;
|
||||
private $classes;
|
||||
|
||||
static public $classnametointrospect;
|
||||
|
||||
|
||||
public function __construct() {
|
||||
$this->setup();
|
||||
}
|
||||
|
||||
private function setup() {
|
||||
global $CFG;
|
||||
if (!empty($this->nothing)) {
|
||||
return; // we've already tried, no classes.
|
||||
}
|
||||
if (!empty($this->classes)) { // we've already done it successfully.
|
||||
return;
|
||||
}
|
||||
/*if (empty($CFG->amf_introspection)) {
|
||||
throw new Exception(get_string('amfintrospectiondisabled', 'local'));
|
||||
}*/
|
||||
|
||||
//just one class here, possibility for expansion in future
|
||||
$classes = array(MethodDescriptor::$classnametointrospect);
|
||||
|
||||
$hugestructure = array();
|
||||
|
||||
foreach ($classes as $c) {
|
||||
$r = new ReflectionClass($c);
|
||||
|
||||
if (!$methods = $r->getMethods()) {
|
||||
continue;
|
||||
}
|
||||
$this->classes[] = $c;
|
||||
$hugestructure[$c] = array('docs' => $r->getDocComment(), 'methods' => array());
|
||||
foreach ($methods as $method) {
|
||||
if (!$method->isPublic()) {
|
||||
continue;
|
||||
}
|
||||
$params = array();
|
||||
foreach ($method->getParameters() as $param) {
|
||||
$params[] = array('name' => $param->getName(), 'required' => !$param->isOptional());
|
||||
}
|
||||
$hugestructure[$c]['methods'][$method->getName()] = array(
|
||||
'docs' => $method->getDocComment(),
|
||||
'params' => $params,
|
||||
);
|
||||
}
|
||||
}
|
||||
$this->methods = $hugestructure;
|
||||
if (empty($this->classes)) {
|
||||
$this->nothing = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function getMethods() {
|
||||
$this->setup();
|
||||
return $this->methods;
|
||||
}
|
||||
|
||||
public function getClasses() {
|
||||
$this->setup();
|
||||
return $this->classes;
|
||||
}
|
||||
|
||||
public function isConnected() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,21 @@
|
||||
*/
|
||||
|
||||
require_once("$CFG->dirroot/webservice/lib.php");
|
||||
require_once( "{$CFG->dirroot}/webservice/amf/introspector.php");
|
||||
|
||||
/**
|
||||
* Exception indicating an invalid return value from a function.
|
||||
* Used when an externallib function does not return values of the expected structure.
|
||||
*/
|
||||
class invalid_return_value_exception extends moodle_exception {
|
||||
/**
|
||||
* Constructor
|
||||
* @param string $debuginfo some detailed information
|
||||
*/
|
||||
function __construct($debuginfo=null) {
|
||||
parent::__construct('invalidreturnvalue', 'debug', '', null, $debuginfo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AMF service server implementation.
|
||||
@ -39,7 +54,108 @@ class webservice_amf_server extends webservice_zend_server {
|
||||
parent::__construct($simple, 'Zend_Amf_Server');
|
||||
$this->wsname = 'amf';
|
||||
}
|
||||
protected function init_service_class(){
|
||||
parent::init_service_class();
|
||||
//allow access to data about methods available.
|
||||
$this->zend_server->setClass( "MethodDescriptor" );
|
||||
MethodDescriptor::$classnametointrospect = $this->service_class;
|
||||
}
|
||||
|
||||
protected function service_class_method_body($function, $params){
|
||||
$params = "webservice_amf_server::cast_objects_to_array($params)";
|
||||
$externallibcall = $function->classname.'::'.$function->methodname.'('.$params.')';
|
||||
$descriptionmethod = $function->methodname.'_returns()';
|
||||
$callforreturnvaluedesc = $function->classname.'::'.$descriptionmethod;
|
||||
return
|
||||
' return webservice_amf_server::validate_and_cast_values('.$callforreturnvaluedesc.', '.$externallibcall.', true)';
|
||||
}
|
||||
/**
|
||||
* Validates submitted value, comparing it to a description. If anything is incorrect
|
||||
* invalid_return_value_exception is thrown. Also casts the values to the type specified in
|
||||
* the description.
|
||||
* @param external_description $description description of parameters
|
||||
* @param mixed $value the actual values
|
||||
* @param boolean $singleasobject specifies whether a external_single_structure should be cast to a stdClass object
|
||||
* should always be false for use in validating parameters in externallib functions.
|
||||
* @return mixed params with added defaults for optional items, invalid_parameters_exception thrown if any problem found
|
||||
*/
|
||||
public static function validate_and_cast_values(external_description $description, $value) {
|
||||
if (is_null($description)){
|
||||
return $value;
|
||||
}
|
||||
if ($description instanceof external_value) {
|
||||
if (is_array($value) or is_object($value)) {
|
||||
throw new invalid_return_value_exception('Scalar type expected, array or object received.');
|
||||
}
|
||||
|
||||
if ($description->type == PARAM_BOOL) {
|
||||
// special case for PARAM_BOOL - we want true/false instead of the usual 1/0 - we can not be too strict here ;-)
|
||||
if (is_bool($value) or $value === 0 or $value === 1 or $value === '0' or $value === '1') {
|
||||
return (bool)$value;
|
||||
}
|
||||
}
|
||||
return validate_param($value, $description->type, $description->allownull, 'Invalid external api parameter');
|
||||
|
||||
} else if ($description instanceof external_single_structure) {
|
||||
if (!is_array($value)) {
|
||||
throw new invalid_return_value_exception('Only arrays accepted.');
|
||||
}
|
||||
$result = array();
|
||||
foreach ($description->keys as $key=>$subdesc) {
|
||||
if (!array_key_exists($key, $value)) {
|
||||
if ($subdesc->required == VALUE_REQUIRED) {
|
||||
throw new invalid_return_value_exception('Missing required key in single structure: '.$key);
|
||||
}
|
||||
if ($subdesc instanceof external_value) {
|
||||
if ($subdesc->required == VALUE_DEFAULT) {
|
||||
$result[$key] = self::validate_and_cast_values($subdesc, $subdesc->default);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$result[$key] = self::validate_and_cast_values($subdesc, $value[$key]);
|
||||
}
|
||||
unset($value[$key]);
|
||||
}
|
||||
if (!empty($value)) {
|
||||
throw new invalid_return_value_exception('Unexpected keys detected in parameter array.');
|
||||
}
|
||||
return (object)$result;
|
||||
|
||||
} else if ($description instanceof external_multiple_structure) {
|
||||
if (!is_array($value)) {
|
||||
throw new invalid_return_value_exception('Only arrays accepted.');
|
||||
}
|
||||
$result = array();
|
||||
foreach ($value as $param) {
|
||||
$result[] = self::validate_and_cast_values($description->content, $param);
|
||||
}
|
||||
return $result;
|
||||
|
||||
} else {
|
||||
throw new invalid_return_value_exception('Invalid external api description.');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Recursive function to recurse down into a complex variable and convert all
|
||||
* objects to arrays. Doesn't recurse down into objects or cast objects other than stdClass
|
||||
* which is represented in Flash / Flex as an object.
|
||||
* @param mixed $params value to cast
|
||||
* @return mixed Cast value
|
||||
*/
|
||||
public static function cast_objects_to_array($params){
|
||||
if ($params instanceof stdClass){
|
||||
$params = (array)$params;
|
||||
}
|
||||
if (is_array($params)){
|
||||
$toreturn = array();
|
||||
foreach ($params as $key=> $param){
|
||||
$toreturn[$key] = self::cast_objects_to_array($param);
|
||||
}
|
||||
return $toreturn;
|
||||
} else {
|
||||
return $params;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Set up zend service class
|
||||
* @return void
|
||||
@ -50,6 +166,8 @@ class webservice_amf_server extends webservice_zend_server {
|
||||
//(complete error message displayed into your AMF client)
|
||||
// TODO: add some exception handling
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// TODO: implement AMF test client somehow, maybe we could use moodle form to feed the data to the flash app somehow
|
||||
|
63
webservice/amf/testclient/AMFConnector.as
Normal file
63
webservice/amf/testclient/AMFConnector.as
Normal file
@ -0,0 +1,63 @@
|
||||
package {
|
||||
|
||||
import flash.events.Event;
|
||||
import flash.net.NetConnection;
|
||||
import flash.net.Responder;
|
||||
|
||||
import nl.demonsters.debugger.MonsterDebugger;
|
||||
|
||||
/**
|
||||
* Wrapper class for the NetConnection/Responder instances
|
||||
*
|
||||
* This program is free software. It comes without any warranty, to
|
||||
* the extent permitted by applicable law. You can redistribute it
|
||||
* and/or modify it under the terms of the Do What The Fuck You Want
|
||||
* To Public License, Version 2, as published by Sam Hocevar. See
|
||||
* http://sam.zoy.org/wtfpl/COPYING for more details.
|
||||
*
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
*/
|
||||
public class AMFConnector extends NetConnection {
|
||||
private var responder:Responder;
|
||||
public var data:Object;
|
||||
public var error:Boolean = false;
|
||||
|
||||
public function AMFConnector(url:String) {
|
||||
responder = new Responder(onSuccess, onError);
|
||||
connect(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* executes a command on the remote server, passing all the given arguments along
|
||||
*/
|
||||
public function exec(command:String, ... args:Array):void
|
||||
{
|
||||
if (!args) args = [];
|
||||
args.unshift(responder);
|
||||
args.unshift(command);
|
||||
(call as Function).apply(this, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* handles success
|
||||
*/
|
||||
protected function onSuccess(result:Object):void {
|
||||
MonsterDebugger.trace(this, {'result':result});
|
||||
data = result;
|
||||
dispatchEvent(new Event(Event.COMPLETE));
|
||||
data = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* handles errors
|
||||
*/
|
||||
protected function onError(result:Object):void {
|
||||
data = result;
|
||||
MonsterDebugger.trace(this, {'result':result});
|
||||
error = true;
|
||||
dispatchEvent(new Event(Event.COMPLETE));
|
||||
error = false;
|
||||
data = null;
|
||||
}
|
||||
}
|
||||
}
|
396
webservice/amf/testclient/AMFTester.mxml
Normal file
396
webservice/amf/testclient/AMFTester.mxml
Normal file
@ -0,0 +1,396 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<mx:Application
|
||||
xmlns:mx="http://www.adobe.com/2006/mxml"
|
||||
backgroundColor="white"
|
||||
layout="absolute"
|
||||
creationPolicy="all"
|
||||
height="100%" width="100%"
|
||||
applicationComplete="init()"
|
||||
xmlns:cv="customValidators.*"
|
||||
defaultButton="{call}">
|
||||
|
||||
<mx:Script>
|
||||
<![CDATA[
|
||||
import mx.events.ValidationResultEvent;
|
||||
import mx.validators.Validator;
|
||||
/**
|
||||
* Main class/dialog
|
||||
*
|
||||
* This program is free software. It comes without any warranty, to
|
||||
* the extent permitted by applicable law. You can redistribute it
|
||||
* and/or modify it under the terms of the Do What The Fuck You Want
|
||||
* To Public License, Version 2, as published by Sam Hocevar. See
|
||||
* http://sam.zoy.org/wtfpl/COPYING for more details.
|
||||
*
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
*/
|
||||
|
||||
import mx.controls.Label;
|
||||
import mx.controls.Alert;
|
||||
import mx.messaging.channels.AMFChannel;
|
||||
import com.adobe.serialization.json.JSON;
|
||||
|
||||
/* // Import the debugger
|
||||
import nl.demonsters.debugger.MonsterDebugger;
|
||||
*/
|
||||
public var api:AMFConnector;
|
||||
protected var methods:Array;
|
||||
protected var introspector:String;
|
||||
|
||||
public var rooturl:String;
|
||||
|
||||
[Bindable]
|
||||
public var argumentToolTip:String = "You can use JSON syntax for method arguments ie. an array is written like this [item1, item2, etc.] objects are written {\"propname\":value, \"propname2\":value2, etc}";
|
||||
|
||||
// Variable to hold the debugger
|
||||
// private var debugger:MonsterDebugger;
|
||||
|
||||
/**
|
||||
* restores the last settings if available
|
||||
*/
|
||||
public function init():void
|
||||
{
|
||||
// Init the debugger
|
||||
// debugger = new MonsterDebugger(this);
|
||||
|
||||
// Send a simple trace
|
||||
// MonsterDebugger.trace(this, "Hello World!");
|
||||
|
||||
var so:SharedObject = SharedObject.getLocal('AMFTester');
|
||||
if (so.data.token) {
|
||||
token.text = so.data.token;
|
||||
}
|
||||
if (so.data.username) {
|
||||
username.text = so.data.username;
|
||||
password.text = so.data.password;
|
||||
}
|
||||
if (so.data.mode == 'username'){
|
||||
loginType.selectedIndex = 1;
|
||||
}
|
||||
this.rememberpassword.selected = so.data.rememberpassword;
|
||||
this.remembertoken.selected = so.data.remembertoken;
|
||||
this.rooturl = Application.application.parameters.rooturl;
|
||||
this.urllabel1.text = 'Root URL :'+this.rooturl;
|
||||
this.urllabel2.text = 'Root URL :'+this.rooturl;
|
||||
|
||||
}
|
||||
public function doConnectToken():void
|
||||
{
|
||||
var url:String = this.rooturl + '/webservice/amf/server.php?'+
|
||||
'wstoken='+this.token.text;
|
||||
this.doConnect(url);
|
||||
// saving settings for next time
|
||||
var so:SharedObject = SharedObject.getLocal('AMFTester');
|
||||
if (this.rememberpassword.selected == true ){
|
||||
so.setProperty('token', this.token.text);
|
||||
} else {
|
||||
so.setProperty('token', null);//delete shared obj prop
|
||||
}
|
||||
so.setProperty('remembertoken', this.remembertoken.selected);
|
||||
so.setProperty('mode', 'token');
|
||||
so.flush();
|
||||
}
|
||||
public function doConnectUsername():void
|
||||
{
|
||||
var url:String = this.rooturl + '/webservice/amf/simpleserver.php?'+
|
||||
'wsusername=' + this.username.text+
|
||||
'&wspassword=' + this.password.text;
|
||||
this.doConnect(url);
|
||||
// saving settings for next time
|
||||
var so:SharedObject = SharedObject.getLocal('AMFTester');
|
||||
if (this.rememberpassword.selected == true ){
|
||||
so.setProperty('username', this.username.text);
|
||||
so.setProperty('password', this.password.text);
|
||||
} else {
|
||||
so.setProperty('username', null);//delete shared obj prop
|
||||
so.setProperty('password', null);
|
||||
}
|
||||
so.setProperty('rememberpassword', this.rememberpassword.selected);
|
||||
so.setProperty('mode', 'username');
|
||||
so.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* initializes the connection
|
||||
*/
|
||||
private function doConnect(url:String):void
|
||||
{
|
||||
api = new AMFConnector(url);
|
||||
api.exec('MethodDescriptor.getMethods');
|
||||
api.addEventListener(Event.COMPLETE, handleConnection);
|
||||
if (!api.hasEventListener(NetStatusEvent.NET_STATUS)) {
|
||||
api.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
|
||||
api.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
|
||||
api.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
|
||||
}
|
||||
this.panelDebug.enabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* initializes the debugger dialog with the method list and everything
|
||||
*/
|
||||
protected function handleConnection(event:Event):void
|
||||
{
|
||||
methods = [];
|
||||
for (var cls:String in api.data) {
|
||||
for (var meth:String in api.data[cls]['methods']) {
|
||||
methods.push({label: cls+'.'+meth, docs: api.data[cls]['methods'][meth]['docs'], args: api.data[cls]['methods'][meth]['params']});
|
||||
}
|
||||
}
|
||||
|
||||
this.panelDebug.enabled = true;
|
||||
this.maintabs.selectedIndex = 1;
|
||||
func.dataProvider = methods;
|
||||
api.removeEventListener(Event.COMPLETE, handleConnection);
|
||||
api.addEventListener(Event.COMPLETE, process);
|
||||
reloadArgs();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* outputs a response from the server
|
||||
*/
|
||||
protected function process(event:Event):void
|
||||
{
|
||||
if (api.error) {
|
||||
push(input, time() + ": Exception (code: "+api.data.code+", description: "+api.data.description+", detail: "+api.data.detail+", line: "+api.data.line+")\n");
|
||||
} else {
|
||||
push(input, time() + ": "+JSON.encode(api.data)+"\n");
|
||||
}
|
||||
// MonsterDebugger.trace(this, api.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* updates the display of arguments when the selected method changes
|
||||
*
|
||||
* it's hardly optimal to do it that way but it was faster to copy paste, I just hope nobody needs more than 7 args
|
||||
*/
|
||||
protected function reloadArgs():void
|
||||
{
|
||||
var i:int;
|
||||
for (i = 1; i <= 7; i++) {
|
||||
this['arg'+i].visible = false;
|
||||
this['arg'+i].includeInLayout = false;
|
||||
this['larg'+i].visible = false;
|
||||
this['larg'+i].includeInLayout = false;
|
||||
this['JSONV'+i].enabled = false;
|
||||
}
|
||||
i = 1;
|
||||
for (var arg:String in func.selectedItem.args) {
|
||||
(this['arg'+i] as TextInput).visible = true;
|
||||
(this['arg'+i] as TextInput).includeInLayout = true;
|
||||
(this['larg'+i] as Label).visible = true;
|
||||
(this['larg'+i] as Label).includeInLayout = true;
|
||||
this['JSONV'+i].enabled = true;
|
||||
this['JSONV'+i].required = func.selectedItem.args[arg]['required'];
|
||||
|
||||
(this['larg'+i++] as Label).text = func.selectedItem.args[arg]['name'] + (func.selectedItem.args[arg]['required'] ? "*":"");
|
||||
}
|
||||
if (func.selectedItem.docs == ""){
|
||||
(this.methodDescription as TextArea).text = "";
|
||||
(this.methodDescription as TextArea).visible = false;
|
||||
(this.methodDescription as TextArea).includeInLayout = false;
|
||||
} else {
|
||||
(this.methodDescription as TextArea).text = func.selectedItem.docs.replace(/[\n\r\f]+/g, "\n");
|
||||
(this.methodDescription as TextArea).visible = true;
|
||||
(this.methodDescription as TextArea).includeInLayout = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* calls a method on the server
|
||||
*/
|
||||
protected function execute():void
|
||||
{
|
||||
var input:TextInput;
|
||||
var argumentArray:Array = [];
|
||||
var argumentErrors:Array = Validator.validateAll(argumentValidators);
|
||||
if (argumentErrors.length != 0){
|
||||
// MonsterDebugger.trace(this, argumentErrors);
|
||||
return;
|
||||
}
|
||||
for(var i:int = 1; i < 8; i++)
|
||||
{
|
||||
input = this['arg' +i] as TextInput;
|
||||
if(input)
|
||||
{
|
||||
if (input.text.indexOf("{") == 0 || input.text.indexOf("[") == 0)
|
||||
try {
|
||||
argumentArray.push(JSON.decode(input.text));
|
||||
} catch (err:Error){
|
||||
return;
|
||||
}
|
||||
else
|
||||
argumentArray.push(input.text as String);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
api.exec(func.selectedLabel, argumentArray[0], argumentArray[1], argumentArray[2], argumentArray[3], argumentArray[4], argumentArray[5], argumentArray[6]);
|
||||
// MonsterDebugger.trace(this, [func.selectedLabel, argumentArray[0], argumentArray[1], argumentArray[2], argumentArray[3], argumentArray[4], argumentArray[5], argumentArray[6]]);
|
||||
push(output, time() + ": Calling "+func.selectedLabel+" with arguments - "+JSON.encode(argumentArray));
|
||||
}
|
||||
|
||||
/**
|
||||
* clears debug consoles
|
||||
*/
|
||||
protected function clear():void
|
||||
{
|
||||
input.text = output.text = "";
|
||||
}
|
||||
|
||||
/**
|
||||
* refreshes the method list
|
||||
*/
|
||||
protected function refresh():void
|
||||
{
|
||||
api.removeEventListener(Event.COMPLETE, process);
|
||||
api.addEventListener(Event.COMPLETE, handleConnection);
|
||||
api.exec(introspector);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns timestamp string
|
||||
*/
|
||||
protected function time():String
|
||||
{
|
||||
var d:Date = new Date();
|
||||
var ret:String = d.hours+":"+d.minutes+":"+d.seconds+"."+d.milliseconds;
|
||||
return ret + "000000000000".substring(ret.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* handler for specific net events
|
||||
*/
|
||||
public function netStatusHandler(event:NetStatusEvent):void
|
||||
{
|
||||
push(input, time() + ": Error("+event.type+"): "+event.info.code+", "+event.info.description+", "+event.info.details);
|
||||
}
|
||||
|
||||
/**
|
||||
* handler for security errors
|
||||
*/
|
||||
public function securityErrorHandler(event:SecurityErrorEvent):void
|
||||
{
|
||||
push(input, time() + ": Error("+event.type+"): "+event.text);
|
||||
}
|
||||
|
||||
/**
|
||||
* handler for io errors
|
||||
*/
|
||||
public function ioErrorHandler(event:IOErrorEvent):void
|
||||
{
|
||||
push(input, time() + ": Error("+event.type+"): "+event.text);
|
||||
}
|
||||
|
||||
/**
|
||||
* pushes text into a console and scrolls it down automatically
|
||||
*/
|
||||
public function push(target:TextArea, text:String):void
|
||||
{
|
||||
target.text += text + "\n";
|
||||
target.verticalScrollPosition = target.maxVerticalScrollPosition;
|
||||
}
|
||||
|
||||
]]>
|
||||
</mx:Script>
|
||||
<mx:Array id="argumentValidators">
|
||||
<cv:JSONValidator id="JSONV1" required="false" source="{arg1}" property="text" />
|
||||
<cv:JSONValidator id="JSONV2" required="false" source="{arg2}" property="text" />
|
||||
<cv:JSONValidator id="JSONV3" required="false" source="{arg3}" property="text" />
|
||||
<cv:JSONValidator id="JSONV4" required="false" source="{arg4}" property="text" />
|
||||
<cv:JSONValidator id="JSONV5" required="false" source="{arg5}" property="text" />
|
||||
<cv:JSONValidator id="JSONV6" required="false" source="{arg6}" property="text" />
|
||||
<cv:JSONValidator id="JSONV7" required="false" source="{arg7}" property="text" />
|
||||
</mx:Array>
|
||||
|
||||
|
||||
|
||||
<mx:TabNavigator id="maintabs" height="100%" width="100%" >
|
||||
|
||||
<mx:TabNavigator label="Connect" id="loginType" borderStyle="solid" height="100%" width="100%">
|
||||
<mx:Panel label="Use Token" id="panelConnectToken">
|
||||
<mx:HBox width="100%">
|
||||
<mx:Label text="Token"/>
|
||||
<mx:TextInput id="token" text="" width="100%"/>
|
||||
</mx:HBox>
|
||||
<mx:HBox width="100%">
|
||||
<mx:Label text="Remember"/>
|
||||
<mx:CheckBox id="remembertoken" width="100%"/>
|
||||
</mx:HBox>
|
||||
<mx:Label id="urllabel1" text="URL :" />
|
||||
<mx:HBox width="100%">
|
||||
<mx:Spacer width="100%" />
|
||||
<mx:Button label="Connect" click="doConnectToken()"/>
|
||||
<mx:Spacer width="100%" />
|
||||
</mx:HBox>
|
||||
</mx:Panel>
|
||||
<mx:Panel label="Use Username and Password" id="panelConnectUsername">
|
||||
<mx:HBox width="100%">
|
||||
<mx:Label text="Username"/>
|
||||
<mx:TextInput id="username" text="" width="100%"/>
|
||||
</mx:HBox>
|
||||
|
||||
<mx:HBox width="100%">
|
||||
<mx:Label text="Password"/>
|
||||
<mx:TextInput id="password" text="" displayAsPassword="true" width="100%"/>
|
||||
</mx:HBox>
|
||||
<mx:HBox width="100%">
|
||||
<mx:Label text="Remember"/>
|
||||
<mx:CheckBox id="rememberpassword" width="100%"/>
|
||||
</mx:HBox>
|
||||
<mx:Label id="urllabel2" text="URL :" />
|
||||
|
||||
<mx:HBox width="100%">
|
||||
<mx:Spacer width="100%" />
|
||||
<mx:Button label="Connect" click="doConnectUsername()"/>
|
||||
<mx:Spacer width="100%" />
|
||||
</mx:HBox>
|
||||
</mx:Panel>
|
||||
</mx:TabNavigator>
|
||||
<mx:Panel label="Service Browser" width="100%" height="100%" layout="vertical" title="Moodle AMF Service Browser" enabled="false" id="panelDebug">
|
||||
<mx:HBox width="100%">
|
||||
<mx:Label text="Func "/>
|
||||
<mx:ComboBox id="func" change="reloadArgs()">
|
||||
</mx:ComboBox>
|
||||
</mx:HBox>
|
||||
<mx:TextArea id="methodDescription" text="" width="100%" height="120"/>
|
||||
<mx:HBox width="100%">
|
||||
<mx:Label id="larg1" text="Arg 1"/>
|
||||
<mx:TextInput id="arg1" toolTip="{argumentToolTip}"/>
|
||||
</mx:HBox>
|
||||
<mx:HBox width="100%">
|
||||
<mx:Label id="larg2" text="Arg 2"/>
|
||||
<mx:TextInput id="arg2" toolTip="{argumentToolTip}"/>
|
||||
</mx:HBox>
|
||||
<mx:HBox width="100%">
|
||||
<mx:Label id="larg3" text="Arg 3"/>
|
||||
<mx:TextInput id="arg3" toolTip="{argumentToolTip}"/>
|
||||
</mx:HBox>
|
||||
<mx:HBox width="100%">
|
||||
<mx:Label id="larg4" text="Arg 4"/>
|
||||
<mx:TextInput id="arg4" toolTip="{argumentToolTip}"/>
|
||||
</mx:HBox>
|
||||
<mx:HBox width="100%">
|
||||
<mx:Label id="larg5" text="Arg 5"/>
|
||||
<mx:TextInput id="arg5" toolTip="{argumentToolTip}"/>
|
||||
</mx:HBox>
|
||||
<mx:HBox width="100%">
|
||||
<mx:Label id="larg6" text="Arg 6"/>
|
||||
<mx:TextInput id="arg6" toolTip="{argumentToolTip}"/>
|
||||
</mx:HBox>
|
||||
<mx:HBox width="100%">
|
||||
<mx:Label id="larg7" text="Arg 7"/>
|
||||
<mx:TextInput id="arg7" toolTip="{argumentToolTip}"/>
|
||||
</mx:HBox>
|
||||
<mx:HBox width="100%">
|
||||
<mx:Button id="call" label="Call" click="execute()"/>
|
||||
<mx:Button label="Clear" click="clear()"/>
|
||||
</mx:HBox>
|
||||
<mx:TextArea id="output" width="100%" height="100"/>
|
||||
<mx:TextArea id="input" width="100%" height="300"/>
|
||||
</mx:Panel>
|
||||
</mx:TabNavigator>
|
||||
|
||||
</mx:Application>
|
BIN
webservice/amf/testclient/AMFTester.swf
Normal file
BIN
webservice/amf/testclient/AMFTester.swf
Normal file
Binary file not shown.
40
webservice/amf/testclient/customValidators/JSONValidator.as
Normal file
40
webservice/amf/testclient/customValidators/JSONValidator.as
Normal file
@ -0,0 +1,40 @@
|
||||
package customValidators
|
||||
{
|
||||
import com.adobe.serialization.json.JSON;
|
||||
import com.adobe.serialization.json.JSONParseError;
|
||||
|
||||
import mx.validators.ValidationResult;
|
||||
import mx.validators.Validator;
|
||||
|
||||
import nl.demonsters.debugger.MonsterDebugger;
|
||||
|
||||
public class JSONValidator extends Validator
|
||||
{
|
||||
// Define Array for the return value of doValidation().
|
||||
private var errors:Array;
|
||||
|
||||
public function JSONValidator()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
override protected function doValidation(value:Object):Array {
|
||||
var JSONstring:String = String(value);
|
||||
errors = [];
|
||||
if (JSONstring != ''){
|
||||
try {
|
||||
JSON.decode(JSONstring);
|
||||
} catch (err:Error){
|
||||
errors.push(new ValidationResult(true, null, "JSON decode failed",
|
||||
"Not able to decode this JSON."));
|
||||
}
|
||||
}
|
||||
if (this.required && JSONstring == ''){
|
||||
errors.push(new ValidationResult(true, null, "Required",
|
||||
"You must enter a value for this argument."));
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
AMFTester.mxml can be compiled as a Flex application in Flex builder or using the Flex SDK.
|
||||
|
||||
Copy the following into a Flex project source folder :
|
||||
|
||||
* customValidators folder and contents
|
||||
* AMFConnector.as
|
||||
* AMFTester.mxml
|
||||
|
||||
Then you need to use either the compiled Flex library or the source for the open source library as3corelib available here : http://code.google.com/p/as3corelib/downloads/list
|
@ -1,20 +1,23 @@
|
||||
<?php
|
||||
require "../../../config.php";
|
||||
|
||||
die('TODO');
|
||||
|
||||
$args['movie'] = $CFG->wwwroot.'/webservice/amf/testclient/moodleclient.swf';
|
||||
$args['width'] = '100%';
|
||||
$args['height'] = 500;
|
||||
$args['majorversion'] = 9;
|
||||
$args['build'] = 0;
|
||||
$args['allowscriptaccess'] = 'never';
|
||||
$args['quality'] = 'high';
|
||||
$args['flashvars'] = 'amfurl='.$CFG->wwwroot.'/webservice/amf/server.php';
|
||||
$args['setcontainercss'] = 'true';
|
||||
$flashvars = new object();
|
||||
$flashvars->rooturl =$CFG->wwwroot;
|
||||
|
||||
$PAGE->requires->js('/lib/ufo.js');
|
||||
$PAGE->requires->js_function_call('M.util.create_UFO_object', array('moodletestclient', $args));
|
||||
|
||||
$PAGE->requires->js('/lib/swfobject/swfobject.js', true);
|
||||
|
||||
$PAGE->requires->js_function_call('swfobject.embedSWF',
|
||||
array($CFG->wwwroot.'/webservice/amf/testclient/AMFTester.swf', //movie
|
||||
'moodletestclient', // div id
|
||||
'100%', // width
|
||||
'1000', // height
|
||||
'9.0', // version
|
||||
false,//no express install swf
|
||||
$flashvars), //flash vars
|
||||
true
|
||||
);
|
||||
|
||||
$PAGE->set_title('Test Client');
|
||||
$PAGE->set_heading('Test Client');
|
||||
|
@ -409,6 +409,8 @@ class '.$classname.' {
|
||||
$params = implode(', ', $params);
|
||||
$params_desc = implode("\n", $params_desc);
|
||||
|
||||
$serviceclassmethodbody = $this->service_class_method_body($function, $params);
|
||||
|
||||
if (is_null($function->returns_desc)) {
|
||||
$return = ' * @return void';
|
||||
} else {
|
||||
@ -441,12 +443,24 @@ class '.$classname.' {
|
||||
'.$return.'
|
||||
*/
|
||||
public function '.$function->name.'('.$params.') {
|
||||
return '.$function->classname.'::'.$function->methodname.'('.$params.');
|
||||
'.$serviceclassmethodbody.';
|
||||
}
|
||||
';
|
||||
return $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* You can override this function in your child class to add extra code into the dynamically
|
||||
* created service class. For example it is used in the amf server to cast types of parameters and to
|
||||
* cast the return value to the types as specified in the return value description.
|
||||
* @param unknown_type $function
|
||||
* @param unknown_type $params
|
||||
* @return string body of the method for $function ie. everything within the {} of the method declaration.
|
||||
*/
|
||||
protected function service_class_method_body($function, $params){
|
||||
return ' return '.$function->classname.'::'.$function->methodname.'('.$params.')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up zend service class
|
||||
* @return void
|
||||
|
Loading…
x
Reference in New Issue
Block a user