PDA

View Full Version : TYPO3 Implementation of ExtDirect



steffenk
15 Mar 2010, 10:01 AM
Hi,

i want to show you how we implemented ExtDirect in TYPO3. We only have 2 files (api + router), if you use it, you have to register your class in the system. Here is a description how to use it:
http://wiki.typo3.org/index.php/ExtDirect

And here are the core classes:

API

<?php
/***************************************************************
* Copyright notice
*
* (c) 2010 Sebastian Kurfuerst <sebastian@typo3.org>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project 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.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script 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.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/

/**
* Ext Direct API Generator
*
* @author Sebastian Kurfuerst <sebastian@typo3.org>
* @author Stefan Galinski <stefan.galinski@gmail.com>
* @package TYPO3
*/
class t3lib_extjs_ExtDirectApi {
/**
* @var array
*/
protected $settings = array();

/**
* Constructs this object.
*/
public function __construct() {
if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ExtDirect']) && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ExtDirect'])) {
$this->settings = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ExtDirect'];
}
}

/**
* Parses the ExtDirect configuration array "$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ExtDirect']"
* and feeds the given typo3ajax instance with the resulting information. The get parameter
* "namespace" will be used to filter the configuration.
*
* This method makes usage of the reflection mechanism to fetch the methods inside the
* defined classes together with their amount of parameters. This information are building
* the API and are required by ExtDirect. The result is cached to improve the overall
* performance.
*
* @param array $ajaxParams ajax parameters
* @param TYPO3AJAX $ajaxObj typo3ajax instance
* @return void
*/
public function getAPI($ajaxParams, TYPO3AJAX $ajaxObj) {
$filterNamespace = t3lib_div::_GET('namespace');

// look up into the cache
$cacheIdentifier = 'ExtDirectApi';
$cacheHash = md5($cacheIdentifier . $filterNamespace . serialize($this->settings));
$cacheContent = t3lib_pageSelect::getHash($cacheHash);

// generate the javascript content if it wasn't found inside the cache and cache it!
if (!$cacheContent) {
$javascriptNamespaces = $this->generateAPI($filterNamespace);
t3lib_pageSelect::storeHash(
$cacheHash,
serialize($javascriptNamespaces),
$cacheIdentifier
);
} else {
$javascriptNamespaces = unserialize($cacheContent);
}

// return the generated javascript API configuration
if (count($javascriptNamespaces)) {
$setup = '
if (typeof Ext.app.ExtDirectAPI !== "object") {
Ext.app.ExtDirectAPI = {};
}

if (typeof Object.extend !== "function") {
Object.extend = function(destination, source) {
for (var property in source) {
destination[property] = source[property];
}
return destination;
};
}
';

$ajaxObj->setContent($javascriptNamespaces);
$ajaxObj->setContentFormat('javascript');
$ajaxObj->setJavascriptCallbackWrap(
$setup . 'Ext.app.ExtDirectAPI = Object.extend(Ext.app.ExtDirectAPI, |);'
);
}
}

/**
* Generates the API that is configured inside the ExtDirect configuration
* array "$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ExtDirect']".
*
* @param string $filerNamespace namespace that should be loaded like TYPO3.Backend
* @return array javascript API configuration
*/
protected function generateAPI($filterNamespace) {
$javascriptNamespaces = array();
if (is_array($this->settings)) {
foreach ($this->settings as $javascriptName => $className) {
$splittedJavascriptName = explode('.', $javascriptName);
$javascriptObjectName = array_pop($splittedJavascriptName);
$javascriptNamespace = implode('.', $splittedJavascriptName);

// only items inside the wanted namespace
if (strpos($javascriptNamespace, $filterNamespace) !== 0) {
continue;
}

if (!isset($javascriptNamespaces[$javascriptNamespace])) {
$javascriptNamespaces[$javascriptNamespace] = array(
'url' => t3lib_div::locationHeaderUrl('ajax.php?ajaxID=ExtDirect::route&namespace=') . rawurlencode($javascriptNamespace),
'type' => 'remoting',
'actions' => array(),
'namespace' => $javascriptNamespace
);
}

$serverObject = t3lib_div::getUserObj($className, FALSE);
$javascriptNamespaces[$javascriptNamespace]['actions'][$javascriptObjectName] = array();
foreach (get_class_methods($serverObject) as $methodName) {
$reflectionMethod = new ReflectionMethod($serverObject, $methodName);
$numberOfParameters = $reflectionMethod->getNumberOfParameters();
$docHeader = $reflectionMethod->getDocComment();
$formHandler = (strpos($docHeader, '@formHandler') !== FALSE);

$javascriptNamespaces[$javascriptNamespace]['actions'][$javascriptObjectName][] = array(
'name' => $methodName,
'len' => $numberOfParameters,
'formHandler' => $formHandler
);
}
}
}

return $javascriptNamespaces;
}
}

if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_extjs_extdirectapi.php']) {
include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_extjs_extdirectapi.php']);
}

?>

ROUTER


<?php
/***************************************************************
* Copyright notice
*
* (c) 2010 Sebastian Kurfuerst <sebastian@typo3.org>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project 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.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script 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.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/

/**
* Ext Direct Router
*
* @author Sebastian Kurfuerst <sebastian@typo3.org>
* @author Stefan Galinski <stefan.galinski@gmail.com>
* @package TYPO3
*/
class t3lib_extjs_ExtDirectRouter {
/**
* Dispatches the incoming calls to methods about the ExtDirect API.
*
* @param aray $ajaxParams ajax parameters
* @param TYPO3AJAX $ajaxObj typo3ajax instance
* @return void
*/
public function route($ajaxParams, TYPO3AJAX $ajaxObj) {
try {
$isForm = FALSE;
$isUpload = FALSE;
$rawPostData = file_get_contents('php://input');
$postParameters = t3lib_div::_POST();
$namespace = t3lib_div::_GET('namespace');

if (!empty($postParameters['extAction'])) {
$isForm = TRUE;
$isUpload = $postParameters['extUpload'] === 'true';

$request->action = $postParameters['extAction'];
$request->method = $postParameters['extMethod'];
$request->tid = $postParameters['extTID'];
$request->data = array($_POST + $_FILES);
} elseif (!empty($rawPostData)) {
$request = json_decode($rawPostData);
} else {
throw new t3lib_error_Exception('ExtDirect: Missing Parameters!');
}

$response = NULL;
if (is_array($request)) {
$response = array();
foreach ($request as $singleRequest) {
$response[] = $this->processRpc($singleRequest, $namespace);
}
} else {
$response = $this->processRpc($request, $namespace);
}

if ($isForm && $isUpload) {
$ajaxObj->setContentFormat('plain');
$response = json_encode($response);
$response = preg_replace('/&quot;/', '\\&quot;', $response);

$response = array(
'<html><body><textarea>' .
$response .
'</textarea></body></html>'
);
} else {
$ajaxObj->setContentFormat('jsonbody');
}
} catch (t3lib_error_Exception $exception) {
$response = array(
'type' => 'exception',
'message' => $exception->getMessage(),
'where' => $exception->getTraceAsString()
);
}

$ajaxObj->setContent($response);
}


/**
* Processes an incoming extDirect call by executing the defined method. The configuration
* array "$GLOBALS['TYPO3_CONF_VARS']['BE']['ExtDirect']" is taken to find the class/method
* information.
*
* @param object $singleRequest request object from extJS
* @param string $namespace namespace like TYPO3.Backend
* @return mixed return value of the called method
*/
protected function processRpc($singleRequest, $namespace) {
try {
$endpointName = $namespace . '.' . $singleRequest->action;

// theoretically this can never happen, because of an javascript error on
// the client side due the missing namespace/endpoint
if (!isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ExtDirect'][$endpointName])) {
throw new t3lib_error_Exception('ExtDirect: Call to undefined endpoint: ' . $endpointName);
}

$response = array(
'type' => 'rpc',
'tid' => $singleRequest->tid,
'action' => $singleRequest->action,
'method' => $singleRequest->method
);

$endpointObject = t3lib_div::getUserObj(
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ExtDirect'][$endpointName],
FALSE
);

$response['result'] = call_user_func_array(
array($endpointObject, $singleRequest->method),
is_array($singleRequest->data) ? $singleRequest->data : array()
);

} catch (t3lib_error_Exception $exception) {
throw $exception;
}

return $response;
}
}

if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_extjs_extdirectrouter.php']) {
include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_extjs_extdirectrouter.php']);
}

?>


For Forms you simply use @formHandler in the phpDoc of the function. All works fine, even file uploads (which seems not working proper in the other php implementations)