diff --git a/api/src/Etemplate.php b/api/src/Etemplate.php index 7db2a093cd..cf0dab30b6 100644 --- a/api/src/Etemplate.php +++ b/api/src/Etemplate.php @@ -16,7 +16,6 @@ namespace EGroupware\Api; // explicitly import old not yet ported classes use egw; use egw_framework; -use egw_json_response; use categories; // css /** @@ -147,7 +146,7 @@ class Etemplate extends Etemplate\Widget\Template if ($output_mode == 4) { $output_mode = 0; - self::$response = egw_json_response::get(); + self::$response = Json\Response::get(); } self::$request->output_mode = $output_mode; // let extensions "know" they are run eg. in a popup self::$request->content = self::$cont = $content; @@ -274,13 +273,13 @@ class Etemplate extends Etemplate\Widget\Template echo '
'."\n"; } // Send any accumulated json responses - after flush to avoid sending the buffer as a response - if(egw_json_response::isJSONResponse()) + if(Json\Response::isJSONResponse()) { - $load_array['response'] = egw_json_response::get()->returnResult(); + $load_array['response'] = Json\Response::get()->returnResult(); } // '; if ($output_mode == 2) @@ -331,7 +330,7 @@ class Etemplate extends Etemplate\Widget\Template self::$request = Etemplate\Request::read($etemplate_exec_id); //error_log('request='.array2string(self::$request)); - self::$response = egw_json_response::get(); + self::$response = Json\Response::get(); if (!($template = self::instance(self::$request->template['name'], self::$request->template['template_set'], self::$request->template['version'], self::$request->template['load_via']))) @@ -418,7 +417,7 @@ class Etemplate extends Etemplate\Widget\Template // Strip out any script tags $GLOBALS['egw_info']['flags']['java_script'] = preg_replace(array('/(]*>)([^<]*)/is','/<\/script>/'),array('$2',''),$GLOBALS['egw_info']['flags']['java_script']); self::$response->script($GLOBALS['egw_info']['flags']['java_script']); - //error_log($app .' added javascript to $GLOBALS[egw_info][flags][java_script] - use egw_json_response->script() instead.'); + //error_log($app .' added javascript to $GLOBALS[egw_info][flags][java_script] - use Json\Response->script() instead.'); } return $content; diff --git a/api/src/Etemplate/Request.php b/api/src/Etemplate/Request.php index 0eb41babb9..553e560672 100644 --- a/api/src/Etemplate/Request.php +++ b/api/src/Etemplate/Request.php @@ -13,10 +13,10 @@ namespace EGroupware\Api\Etemplate; +use EGroupware\Api; + // explicitly import old not yet ported classes use egw_framework; -use egw_json_response; -use egw_json_request; use common; // egw_exit /** @@ -59,7 +59,7 @@ use common; // egw_exit * * if (($new_id = $request->id()) != $exec_id) * { - * egw_json_response::get()->generic('assign', array( + * Api\Json\Response::get()->generic('assign', array( * 'etemplate_exec_id' => $id, * 'id' => '', * 'key' => 'etemplate_exec_id', @@ -71,7 +71,7 @@ use common; // egw_exit * * if (($new_id = $request->id()) != $exec_id) * { - * egw_json_response::get()->assign('etemplate_exec_id','value',$new_id); + * Api\Json\Response::get()->assign('etemplate_exec_id','value',$new_id); * } * * For an example look in link_widget::ajax_search() @@ -200,13 +200,13 @@ class Request '/index.php?menuaction='.$GLOBALS['egw_info']['apps'][$app]['index'] : '/'.$app.'/index.php'; // add a unique token to redirect to avoid client-side framework tries refreshing via nextmatch $index_url .= (strpos($index_url, '?') ? '&' : '?').'redirect='.microtime(true); - error_log(__METHOD__."('$id', ...) eT2 request not found / expired --> redirecting app $app to $index_url (_GET[menuaction]=$_GET[menuaction], isJSONRequest()=".array2string(egw_json_request::isJSONRequest()).')'); - if (egw_json_request::isJSONRequest()) + error_log(__METHOD__."('$id', ...) eT2 request not found / expired --> redirecting app $app to $index_url (_GET[menuaction]=$_GET[menuaction], isJSONRequest()=".array2string(Api\Json\Request::isJSONRequest()).')'); + if (Api\Json\Request::isJSONRequest()) { // we must not redirect ajax_destroy_session calls, as they might originate from our own redirect! if (strpos($_GET['menuaction'], '.ajax_destroy_session.etemplate') === false) { - $response = egw_json_response::get(); + $response = Api\Json\Response::get(); $response->redirect($index_url, false, $app); common::egw_exit(); } diff --git a/api/src/Etemplate/Widget.php b/api/src/Etemplate/Widget.php index 206e816cd9..0df277aa91 100644 --- a/api/src/Etemplate/Widget.php +++ b/api/src/Etemplate/Widget.php @@ -17,10 +17,6 @@ use EGroupware\Api; use XMLReader; use ReflectionMethod; -// explicitly import old not yet ported classes -use egw_json_response; - - /** * eTemplate widget baseclass */ @@ -73,7 +69,7 @@ class Widget /** * JSON response object, if we run via a JSON request * - * @var egw_json_response + * @var Api\Json\Response */ static protected $response; diff --git a/api/src/Etemplate/Widget/File.php b/api/src/Etemplate/Widget/File.php index 1c9df80cdb..f4c6ddd77c 100644 --- a/api/src/Etemplate/Widget/File.php +++ b/api/src/Etemplate/Widget/File.php @@ -16,9 +16,6 @@ namespace EGroupware\Api\Etemplate\Widget; use EGroupware\Api\Etemplate; use EGroupware\Api; -// explicitly import old not yet ported classes -use egw_json_response; - /** * eTemplate file upload widget * Uses AJAX to send file(s) to server, and stores for submit @@ -52,7 +49,7 @@ class File extends Etemplate\Widget * @note Currently, no attempt is made to clean up files automatically. */ public static function ajax_upload() { - $response = egw_json_response::get(); + $response = Api\Json\Response::get(); $request_id = str_replace(' ', '+', rawurldecode($_REQUEST['request_id'])); $widget_id = $_REQUEST['widget_id']; if(!self::$request = Etemplate\Request::read($request_id)) { diff --git a/api/src/Etemplate/Widget/ItemPicker.php b/api/src/Etemplate/Widget/ItemPicker.php index 1b27fdd6e6..3e61b570b8 100755 --- a/api/src/Etemplate/Widget/ItemPicker.php +++ b/api/src/Etemplate/Widget/ItemPicker.php @@ -18,9 +18,6 @@ namespace EGroupware\Api\Etemplate\Widget; use EGroupware\Api\Etemplate; use EGroupware\Api; -// explicitly import old not yet ported classes -use egw_json_response; - /** * eTemplate itempicker widget */ @@ -48,7 +45,7 @@ class ItemPicker extends Etemplate\Widget $options['type'] = $type ? $type : $options['type']; $items = Api\Link::query($app, $pattern, $options); - $response = egw_json_response::get(); + $response = Api\Json\Response::get(); $response->data($items); } } diff --git a/api/src/Etemplate/Widget/Link.php b/api/src/Etemplate/Widget/Link.php index 4b9e683620..86bf50cac7 100644 --- a/api/src/Etemplate/Widget/Link.php +++ b/api/src/Etemplate/Widget/Link.php @@ -17,7 +17,6 @@ use EGroupware\Api\Etemplate; use EGroupware\Api; // explicitly import old not yet ported classes -use egw_json_response; use common; // egw_exit /** @@ -109,7 +108,7 @@ class Link extends Etemplate\Widget if(!$options['num_rows']) $options['num_rows'] = 1000; $links = Api\Link::query($app, $pattern, $options); $linksc = array_combine(array_map(create_function('$k', 'return (string)" ".$k;'), array_keys($links)), $links); - $response = egw_json_response::get(); + $response = Api\Json\Response::get(); $response->data($linksc); } @@ -124,7 +123,7 @@ class Link extends Etemplate\Widget { $title = Api\Link::title($app, $id); //error_log(__METHOD__."('$app', '$id') = ".array2string($title)); - egw_json_response::get()->data($title); + Api\Json\Response::get()->data($title); } /** @@ -147,7 +146,7 @@ class Link extends Etemplate\Widget error_log(__METHOD__."(".array2string($app_ids).") got invalid title request: app=$app, ids=" . array2string($ids)); } } - egw_json_response::get()->data($response); + Api\Json\Response::get()->data($response); } /** @@ -172,7 +171,7 @@ class Link extends Etemplate\Widget } $result = Api\Link::link($app, $id, $links); - $response = egw_json_response::get(); + $response = Api\Json\Response::get(); $response->data(is_array($id) ? $id : $result !== false); } @@ -207,7 +206,7 @@ class Link extends Etemplate\Widget } } - $response = egw_json_response::get(); + $response = Api\Json\Response::get(); // Strip keys, unneeded and cause index problems on the client side $response->data(array_values($links)); } @@ -234,7 +233,7 @@ class Link extends Etemplate\Widget $result = Api\Vfs::proppatch($path, array(array('name' => 'comment', 'val' => $comment))); } } - $response = egw_json_response::get(); + $response = Api\Json\Response::get(); $response->data($result !== false); } @@ -258,7 +257,7 @@ class Link extends Etemplate\Widget public static function ajax_delete($value) { - $response = egw_json_response::get(); + $response = Api\Json\Response::get(); $response->data(Api\Link::unlink($value)); } diff --git a/api/src/Etemplate/Widget/Nextmatch.php b/api/src/Etemplate/Widget/Nextmatch.php index e411ce01e9..f3eb058dfa 100644 --- a/api/src/Etemplate/Widget/Nextmatch.php +++ b/api/src/Etemplate/Widget/Nextmatch.php @@ -17,7 +17,6 @@ use EGroupware\Api\Etemplate; use EGroupware\Api; // explicitly import old not yet ported classes -use egw_json_response; use egw; use egw_framework; // includeCSS use categories; @@ -291,7 +290,7 @@ class Nextmatch extends Etemplate\Widget { self::$request->content = array($form_name => array()); } - self::$response = egw_json_response::get(); + self::$response = Api\Json\Response::get(); $value = self::get_array(self::$request->content, $form_name, true); if(!is_array($value)) @@ -362,7 +361,7 @@ class Nextmatch extends Etemplate\Widget if (isset($GLOBALS['egw_info']['flags']['app_header']) && self::$request->app_header != $GLOBALS['egw_info']['flags']['app_header']) { self::$request->app_header = $GLOBALS['egw_info']['flags']['app_header']; - egw_json_response::get()->apply('egw_app_header', array($GLOBALS['egw_info']['flags']['app_header'])); + Api\Json\Response::get()->apply('egw_app_header', array($GLOBALS['egw_info']['flags']['app_header'])); } $row_id = isset($value['row_id']) ? $value['row_id'] : 'id'; @@ -502,7 +501,7 @@ class Nextmatch extends Etemplate\Widget $changes = true; $request_value[$key] = $value[$key]; - egw_json_response::get()->generic('assign', array( + Api\Json\Response::get()->generic('assign', array( 'etemplate_exec_id' => $exec_id, 'id' => $form_name, 'key' => $key, @@ -519,12 +518,12 @@ class Nextmatch extends Etemplate\Widget // Send back data //foreach($result as $name => $value) if ($name != 'readonlys') error_log(__METHOD__."() result['$name']=".array2string($name == 'data' ? array_keys($value) : $value)); - egw_json_response::get()->data($result); + Api\Json\Response::get()->data($result); // If etemplate_exec_id has changed, update the client side if (($new_id = self::$request->id()) != $exec_id) { - egw_json_response::get()->generic('assign', array( + Api\Json\Response::get()->generic('assign', array( 'etemplate_exec_id' => $exec_id, 'id' => '', 'key' => 'etemplate_exec_id', diff --git a/api/src/Etemplate/Widget/Select.php b/api/src/Etemplate/Widget/Select.php index 3b2b65e9fd..0f815fcc9e 100644 --- a/api/src/Etemplate/Widget/Select.php +++ b/api/src/Etemplate/Widget/Select.php @@ -19,7 +19,6 @@ use EGroupware\Api; // explicitly import old not yet ported classes use categories; use calendar_timezones; -use egw_json_response; /** * eTemplate select widget @@ -861,7 +860,7 @@ class Select extends Etemplate\Widget $no_lang = false; $options = self::typeOptions($type, $attributes,$no_lang,false,$value); self::fix_encoded_options($options,true); - $response = egw_json_response::get(); + $response = Api\Json\Response::get(); $response->data($options); } } diff --git a/api/src/Etemplate/Widget/Taglist.php b/api/src/Etemplate/Widget/Taglist.php index 56e5f8bc30..d531892c17 100644 --- a/api/src/Etemplate/Widget/Taglist.php +++ b/api/src/Etemplate/Widget/Taglist.php @@ -17,7 +17,6 @@ use EGroupware\Api\Etemplate; use EGroupware\Api; // explicitly import old not yet ported classes -use egw_json_request; use common; use mail_compose; @@ -74,7 +73,7 @@ class Taglist extends Etemplate\Widget $results[] = array('id' => $id, 'label' => $name); } // switch regular JSON response handling off - egw_json_request::isJSONRequest(false); + Api\Json\Request::isJSONRequest(false); header('Content-Type: application/json; charset=utf-8'); echo json_encode($results); diff --git a/api/src/Etemplate/Widget/Tree.php b/api/src/Etemplate/Widget/Tree.php index 7ba35e4ead..64fb4038f2 100644 --- a/api/src/Etemplate/Widget/Tree.php +++ b/api/src/Etemplate/Widget/Tree.php @@ -17,7 +17,6 @@ use EGroupware\Api\Etemplate; use EGroupware\Api; // explicitly import old not yet ported classes -use egw_json_request; use common; // egw_exit use categories; use egw_framework; @@ -152,7 +151,7 @@ class Tree extends Etemplate\Widget public static function send_quote_json(array $data) { // switch regular JSON response handling off - egw_json_request::isJSONRequest(false); + Api\Json\Request::isJSONRequest(false); header('Content-Type: application/json; charset=utf-8'); echo json_encode(self::htmlencode_node($data)); diff --git a/api/src/Etemplate/Widget/Vfs.php b/api/src/Etemplate/Widget/Vfs.php index bea0c5aa58..b984fe9bb3 100644 --- a/api/src/Etemplate/Widget/Vfs.php +++ b/api/src/Etemplate/Widget/Vfs.php @@ -17,7 +17,6 @@ use EGroupware\Api\Etemplate; use EGroupware\Api; // explicitly import old not yet ported classes -use egw_json_request; use common; // egw_exit use egw; // link @@ -162,7 +161,7 @@ class Vfs extends File } } // switch regular JSON response handling off - egw_json_request::isJSONRequest(false); + Api\Json\Request::isJSONRequest(false); $file = array( "uploaded" => (int)empty($error), diff --git a/api/src/Json/Exception.php b/api/src/Json/Exception.php new file mode 100644 index 0000000000..6c2681a6c7 --- /dev/null +++ b/api/src/Json/Exception.php @@ -0,0 +1,23 @@ + + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package api + * @subpackage json + * @access public + * @version $Id$ + */ + +namespace EGroupware\Api\Json; + +use EGroupware\Api; + +/** + * A method or function was called with a wrong or missing parameter + * + * As you get this only by an error in the code or during development, the message does not need to be translated + */ +class Exception extends Api\Exception\WrongParameter { } diff --git a/api/src/Json/Exception/InvalidName.php b/api/src/Json/Exception/InvalidName.php new file mode 100644 index 0000000000..7f415102d4 --- /dev/null +++ b/api/src/Json/Exception/InvalidName.php @@ -0,0 +1,23 @@ + + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package api + * @subpackage json + * @access public + * @version $Id$ + */ + +namespace EGroupware\Api\Json\Exception; + +use EGroupware\Api\Exception; + +/** + * A method or function was called with a wrong or missing parameter + * + * As you get this only by an error in the code or during development, the message does not need to be translated + */ +class InvalidName extends Exception\NoPermission { } diff --git a/api/src/Json/Exception/NotOnline.php b/api/src/Json/Exception/NotOnline.php new file mode 100644 index 0000000000..386d6eb2d1 --- /dev/null +++ b/api/src/Json/Exception/NotOnline.php @@ -0,0 +1,23 @@ + + * @version $Id$ + */ + +namespace EGroupware\Api\Json\Exception; + +use EGroupware\Api\Json; + +/** + * Exception thrown, if message can not be pushed + */ +class NotOnline extends Json\Exception +{ + +} \ No newline at end of file diff --git a/api/src/Json/Exception/ScriptTags.php b/api/src/Json/Exception/ScriptTags.php new file mode 100644 index 0000000000..e5dc13ea1d --- /dev/null +++ b/api/src/Json/Exception/ScriptTags.php @@ -0,0 +1,23 @@ + + * @version $Id$ + */ + +namespace EGroupware\Api\Json\Exception; + +use EGroupware\Api\Json; + +/** + * Exception thrown, if request contains script tags + */ +class ScriptTags extends Json\Exception +{ + +} \ No newline at end of file diff --git a/api/src/Json/Msg.php b/api/src/Json/Msg.php new file mode 100644 index 0000000000..bd7810501e --- /dev/null +++ b/api/src/Json/Msg.php @@ -0,0 +1,229 @@ + + * @author Ralf Becker + * @version $Id$ + */ + +namespace EGroupware\Api\Json; + +/** + * Abstract class implementing different type of JSON messages understood by client-side + */ +abstract class Msg +{ + /** + * Adds an "alert" to the response which can be handeled on the client side. + * + * The default implementation simply displays the text supplied here with the JavaScript function "alert". + * + * @param string $message contains the actual message being sent to the client. + * @param string $details (optional) can be used to inform the user on the client side about additional details about the error. This might be information how the error can be resolved/why it was raised or simply some debug data. + */ + public function alert($message, $details = '') + { + if (is_string($message) && is_string($details)) + { + $this->addGeneric('alert', array( + "message" => $message, + "details" => $details)); + } + else + { + throw new Exception("Invalid parameters supplied."); + } + } + + /** + * Allows to add a generic java script to the response which will be executed upon the request gets received. + * + * @deprecated + * @param string $script the script code which should be executed upon receiving + */ + public function script($script) + { + if (is_string($script)) + { + $this->addGeneric('script', $script); + } + else + { + throw new Exception("Invalid parameters supplied."); + } + } + + /** + * Allows to call a global javascript function with given parameters: window[$func].apply(window, $parameters) + * + * @param string $function name of the global (window) javascript function to call + * @param array $parameters =array() + */ + public function apply($function,array $parameters=array()) + { + if (is_string($function)) + { + $this->addGeneric('apply', array( + 'func' => $function, + 'parms' => $parameters, + )); + } + else + { + throw new Exception("Invalid parameters supplied."); + } + } + + /** + * Allows to call a global javascript function with given parameters: window[$func].call(window[, $param1[, ...]]) + * + * @param string $func name of the global (window) javascript function to call + * @param mixed $parameters variable number of parameters + */ + public function call($function) + { + $parameters = func_get_args(); + array_shift($parameters); // shift off $function + + if (is_string($function)) + { + $this->addGeneric('apply', array( + 'func' => $function, + 'parms' => $parameters, + )); + } + else + { + throw new Exception("Invalid parameters supplied."); + } + } + + /** + * Allows to call a jquery function on a selector with given parameters: $j($selector).$func($parmeters) + * + * @param string $selector jquery selector + * @param string $method name of the jquery to call + * @param array $parameters =array() + */ + public function jquery($selector,$method,array $parameters=array()) + { + if (is_string($selector) && is_string($method)) + { + $this->addGeneric('jquery', array( + 'select' => $selector, + 'func' => $method, + 'parms' => $parameters, + )); + } + else + { + throw new Exception("Invalid parameters supplied."); + } + } + + public function generic($type, array $parameters = array()) + { + if (is_string($type)) + { + $this->addGeneric($type, $parameters); + } + else + { + throw new Exception("Invalid parameters supplied."); + } + } + + /** + * Adds an html assign to the response, which is excecuted upon the request is received. + * + * @param string $id id of dom element to modify + * @param string $key attribute name of dom element which should be modified + * @param string $value the value which should be assigned to the given attribute + */ + public function assign($id, $key, $value) + { + if (is_string($id) && is_string($key) && (is_string($value) || is_numeric($value) || is_null($value))) + { + $this->addGeneric('assign', array( + 'id' => $id, + 'key' => $key, + 'value' => $value, + )); + } + else + { + throw new Exception("Invalid parameters supplied"); + } + } + + /** + * Redirect to given url + * + * @param string $url + * @param boolean $global specifies whether to redirect the whole framework + * @param string $app =null default current app from flags + * or only the current application + */ + public function redirect($url, $global = false, $app=null) + { + if (is_string($url) && is_bool($global)) + { + //self::script("location.href = '$url';"); + $this->addGeneric('redirect', array( + 'url' => $url, + 'global' => $global, + 'app' => $app ? $app : $GLOBALS['egw_info']['flags']['currentapp'], + )); + } + } + + /** + * Displays an error message on the client + */ + public function error($msg) + { + if (is_string($msg)) + { + $this->addGeneric('error', $msg); + } + } + + /** + * Includes the given CSS file. Every url can only be included once. + * + * @param string $url specifies the url to the css file to include + */ + public function includeCSS($url) + { + if (is_string($url)) + { + $this->addGeneric('css', $url); + } + } + + /** + * Includes the given JS file. Every url can only be included once. + * + * @param string $url specifies the url to the css file to include + */ + public function includeScript($url) + { + if (is_string($url)) + { + $this->addGeneric('js', $url); + } + } + + /** + * Adds any type of data to the message + * + * @param string $key + * @param mixed $data + */ + abstract protected function addGeneric($key, $data); +} diff --git a/phpgwapi/inc/class.egw_json_push.inc.php b/api/src/Json/Push.php similarity index 62% rename from phpgwapi/inc/class.egw_json_push.inc.php rename to api/src/Json/Push.php index cf4db64113..27f8c84d0b 100644 --- a/phpgwapi/inc/class.egw_json_push.inc.php +++ b/api/src/Json/Push.php @@ -5,15 +5,17 @@ * @link http://www.egroupware.org * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @package api - * @subpackage ajax + * @subpackage json * @author Ralf Becker * @version $Id$ */ +namespace EGroupware\Api\Json; + /** * Class to push JSON commands to client */ -class egw_json_push extends egw_json_msg +class Push extends Msg { /** * Available backends to try @@ -51,7 +53,7 @@ class egw_json_push extends egw_json_msg * * @param string $key * @param mixed $data - * @throws egw_json_push_exception_not_online if $account_id is not online + * @throws Exception\NotOnline if $account_id is not online */ protected function addGeneric($key, $data) { @@ -65,41 +67,17 @@ class egw_json_push extends egw_json_msg self::$backend = new $class; break; } - catch (Exception $e) { - // ignore exception + catch (\Exception $e) { + // ignore all exceptions unset($e, self::$backend); } } } if (!isset(self::$backend)) { - throw new egw_json_push_exception_not_online('No valid push-backend found!'); + throw new Exception\NotOnline('No valid push-backend found!'); } } self::$backend->addGeneric($this->account_id, $key, $data); } } - -/** - * Interface for push backends - */ -interface egw_json_push_backend -{ - /** - * Adds any type of data to the message - * - * @param int $account_id account_id to push message too - * @param string $key - * @param mixed $data - * @throws egw_json_push_exception_not_online if $account_id is not online - */ - public function addGeneric($account_id, $key, $data); -} - -/** - * Exception thrown, if message can not be pushed - */ -class egw_json_push_exception_not_online extends egw_exception_not_found -{ - -} \ No newline at end of file diff --git a/api/src/Json/PushBackend.php b/api/src/Json/PushBackend.php new file mode 100644 index 0000000000..9c47923942 --- /dev/null +++ b/api/src/Json/PushBackend.php @@ -0,0 +1,29 @@ + + * @version $Id$ + */ + +namespace EGroupware\Api\Json; + +/** + * Interface for push backends + */ +interface PushBackend +{ + /** + * Adds any type of data to the message + * + * @param int $account_id account_id to push message too + * @param string $key + * @param mixed $data + * @throws Exception\NotOnline if $account_id is not online + */ + public function addGeneric($account_id, $key, $data); +} diff --git a/api/src/Json/Request.php b/api/src/Json/Request.php new file mode 100644 index 0000000000..93245bac72 --- /dev/null +++ b/api/src/Json/Request.php @@ -0,0 +1,185 @@ + + * @author Ralf Becker + * @version $Id$ + */ + +namespace EGroupware\Api\Json; + +use ReflectionMethod; +use EGroupware\Api; + +// explicitly import old, not yet ported api classes +use notifications_push; +use egw_framework; + +/** + * Class handling JSON requests to the server + */ +class Request +{ + private static $_hadJSONRequest = false; + + /** + * Check if JSON request running or (re)set JSON request flag + * + * Can be used to: + * - detect regular JSON request: + * Api\Json\Request::isJSONRequest() + * - switch regular JSON response handling off, which would send arbitrary output via response method "html". + * Neccessary if json.php is used to send arbitrary JSON data eg. nodes for foldertree! + * Api\Json\Request::isJSONRequest(false) + * + * @param boolean $set =null + * @return boolean + */ + public static function isJSONRequest($set=null) + { + $ret = self::$_hadJSONRequest; + if (isset($set)) self::$_hadJSONRequest = $set; + return $ret; + } + + /** + * Parses the raw input data supplied with the input_data parameter and calls the menuaction + * passing all parameters supplied in the request to it. + * + * Also handle queued requests (menuaction == 'home.queue') containing multiple requests + * + * @param string menuaction to call + * @param string $input_data is the RAW input data as it was received from the client + */ + public function parseRequest($menuaction, $input_data) + { + // Remember that we currently are in a JSON request - e.g. used in the redirect code + self::$_hadJSONRequest = true; + + if (get_magic_quotes_gpc()) $input_data = stripslashes($input_data); + + $json_data = json_decode($input_data,true); + if (is_array($json_data) && isset($json_data['request']) && isset($json_data['request']['parameters']) && is_array($json_data['request']['parameters'])) + { + //error_log(__METHOD__.__LINE__.array2string($json_data['request']).function_backtrace()); + $parameters =& $json_data['request']['parameters']; + } + else + { + $parameters = array(); + } + // do we have a single request or an array of queued requests + if ($menuaction == 'home.queue') + { + $responses = array(); + $response = Response::get(); + foreach($parameters[0] as $uid => $data) + { + //error_log("$uid: menuaction=$data[menuaction], parameters=".array2string($data['parameters'])); + $this->handleRequest($data['menuaction'], (array)$data['parameters']); + $responses[$uid] = $response->initResponseArray(); + //error_log("responses[$uid]=".array2string($responses[$uid])); + } + $response->data($responses); // send all responses as data + } + else + { + $this->handleRequest($menuaction, $parameters); + } + } + + /** + * Request handler + * + * @param string $menuaction + * @param array $parameters + */ + public function handleRequest($menuaction, array $parameters) + { + if (strpos($menuaction,'::') !== false && strpos($menuaction,'.') === false) // static method name app_something::method + { + @list($className,$functionName,$handler) = explode('::',$menuaction); + if (substr($className, 0, 11) == 'EGroupware\\') + { + list(,$appName) = explode('\\', strtolower($className)); + } + else + { + list($appName) = explode('_',$className); + } + + // Check for a real static method, avoid instanciation if it is + $m = new ReflectionMethod($menuaction); + if($m->isStatic()) + { + $ajaxClass = $className; + } + } + else + { + @list($appName, $className, $functionName, $handler) = explode('.',$menuaction); + } + //error_log("json.php: appName=$appName, className=$className, functionName=$functionName, handler=$handler"); + + switch($handler) + { + case '/etemplate/process_exec': + $_GET['menuaction'] = $appName.'.'.$className.'.'.$functionName; + $appName = $className = 'etemplate'; + $functionName = 'process_exec'; + $menuaction = 'etemplate.etemplate.process_exec'; + + $parameters = array( + $parameters[0]['etemplate_exec_id'], + $parameters[0]['submit_button'], + $parameters[0], + 'xajaxResponse', + ); + //error_log("xajax_doXMLHTTP() /etemplate/process_exec handler: arg0='$menuaction', menuaction='$_GET[menuaction]'"); + break; + case 'etemplate': // eg. ajax code in an eTemplate widget + $menuaction = ($appName = 'etemplate').'.'.$className.'.'.$functionName; + break; + case 'template': // calling current template / framework object + $menuaction = $appName.'.'.$className.'.'.$functionName; + $className = get_class($GLOBALS['egw']->framework); + list($template) = explode('_', $className); + break; + } + + if(substr($className,0,4) != 'ajax' && substr($className,-4) != 'ajax' && + $menuaction != 'etemplate.etemplate.process_exec' && substr($functionName,0,4) != 'ajax' || + !preg_match('/^[A-Za-z0-9_\\\\-]+(\.[A-Za-z0-9_\\\\]+\.|::)[A-Za-z0-9_]+$/',$menuaction)) + { + // stopped for security reasons + error_log("className='$className', functionName='$functionName', menuaction='$menuaction'"); + error_log($_SERVER['PHP_SELF']. ' stopped for security reason. '.$menuaction.' is not valid. class- or function-name must start with ajax!!!'); + // send message also to the user + throw new Exception\InvalidName($_SERVER['PHP_SELF']. ' stopped for security reason. '.$menuaction.' is not valid. class- or function-name must start with ajax!!!'); + } + + if (isset($template)) + { + $ajaxClass = $GLOBALS['egw']->framework; + } + else if (!$ajaxClass) + { + $ajaxClass = class_exists($className) ? new $className() : CreateObject($appName.'.'.$className); + } + + // for Ajax: no need to load the "standard" javascript files, + // they are already loaded, in fact jquery has a problem if loaded twice + egw_framework::js_files(array()); + + call_user_func_array(array($ajaxClass, $functionName), + Api\Translation::convert($parameters, 'utf-8')); + + // check if we have push notifications, if notifications app available + if (class_exists('notifications_push')) notifications_push::get(); + } +} diff --git a/api/src/Json/Response.php b/api/src/Json/Response.php new file mode 100644 index 0000000000..94055dc5b1 --- /dev/null +++ b/api/src/Json/Response.php @@ -0,0 +1,309 @@ + + * @author Ralf Becker + * @version $Id$ + */ + +namespace EGroupware\Api\Json; + +use EGroupware\Api; + +/** + * Class used to send ajax responses + */ +class Response extends Msg +{ + /** + * A response can only contain one generic data part. + * This variable is used to store, whether a data part had already been added to the response. + * + * @var boolean + */ + private $hasData = false; + + /** + * Array containing all beforeSendData callbacks + */ + protected $beforeSendDataProcs = array(); + + /** + * Holds the actual response data which is then encoded to JSON + * once the "getJSON" function is called + * + * @var array + */ + protected $responseArray = array(); + + /** + * Holding instance of class for singelton Response::get() + * + * @var Response + */ + private static $response = null; + + /** + * Force use of singleton: $response = Response::get(); + */ + protected function __construct() + { + + } + + /** + * Singelton for class + * + * @return Response + */ + public static function get() + { + if (!isset(self::$response)) + { + self::$response = new Response(); + self::sendHeader(); + } + return self::$response; + } + + public static function isJSONResponse() + { + return isset(self::$response); + } + + /** + * Do we have a JSON response to send back + * + * @return boolean + */ + public function haveJSONResponse() + { + return $this->responseArray || $this->beforeSendDataProcs; + } + + /** + * Private function used to send the HTTP header of the JSON response + */ + private static function sendHeader() + { + $file = $line = null; + if (headers_sent($file, $line)) + { + error_log(__METHOD__."() header already sent by $file line $line: ".function_backtrace()); + } + else + { + //Send the character encoding header + header('content-type: application/json; charset='.Api\Translation::charset()); + } + } + + /** + * Private function which is used to send the result via HTTP + */ + public static function sendResult() + { + $inst = self::get(); + + //Call each attached before send data proc + foreach ($inst->beforeSendDataProcs as $proc) + { + call_user_func_array($proc['proc'], $proc['params']); + } + + // check if application made some direct output + if (($output = ob_get_clean())) + { + if (!$inst->haveJSONResponse()) + { + error_log(__METHOD__."() adding output with inst->addGeneric('html', '$output')"); + $inst->addGeneric('html', $output); + } + else + { + $inst->alert('Application echoed something', $output); + } + } + + echo $inst->getJSON(); + $inst->initResponseArray(); + } + + /** + * Return json response data, after running beforeSendDataProcs + * + * Used to send json response with etemplate data in GET request + * + * @return array responseArray + */ + public static function returnResult() + { + $inst = self::get(); + + //Call each attached before send data proc + foreach ($inst->beforeSendDataProcs as $proc) + { + call_user_func_array($proc['proc'], $proc['params']); + } + return $inst->initResponseArray(); + } + + /** + * xAjax compatibility function + * + * @deprecated output is send by egw::__destruct() + */ + public function printOutput() + { + // do nothing, as output is triggered by egw::__destruct() + } + + /** + * Adds any type of data to the message + * + * @param string $key + * @param mixed $data + */ + protected function addGeneric($key, $data) + { + self::get()->responseArray[] = array( + 'type' => $key, + 'data' => $data, + ); + } + + /** + * Init responseArray + * + * @param array $arr + * @return array previous content + */ + public function initResponseArray() + { + $return = $this->responseArray; + $this->responseArray = $this->beforeSendDataProcs = array(); + $this->hasData = false; + return $return; + } + + + /** + * Adds a "data" response to the json response. + * + * This function may only be called once for a single JSON response object. + * + * @param object|array|string $data can be of any data type and will be added JSON Encoded to your response. + */ + public function data($data) + { + /* Only allow adding the data response once */ + $inst = self::get(); + if (!$inst->hasData) + { + $inst->addGeneric('data', $data); + $inst->hasData = true; + } + else + { + throw new Exception("Adding more than one data response to a JSON response is not allowed."); + } + } + + /** + * Returns the actual JSON code generated by calling the above "add" function. + * + * @return string + */ + public function getJSON() + { + $inst = self::get(); + + /* Wrap the result array into a parent "response" Object */ + $res = array('response' => $inst->responseArray); + + return self::json_encode($res); //PHP5.3+, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP); + } + + /** + * More fault-tollerant version of json_encode removing everything that does not json_encode eg. because not utf-8 + * + * @param mixed $var + * @return string + */ + public static function json_encode($var) + { + $ret = json_encode($var); + + if ($ret === false && ($err = json_last_error())) + { + static $json_err2str = array( + JSON_ERROR_NONE => 'No errors', + JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', + JSON_ERROR_STATE_MISMATCH => 'Underflow or the modes mismatch', + JSON_ERROR_CTRL_CHAR => 'Unexpected control character found', + JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON', + JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded', + ); + error_log(__METHOD__.'('.array2string($var).') json_last_error()='.$err.'='.$json_err2str[$err]); + + if (($var = self::fix_content($var))) + { + return self::json_encode($var); + } + } + return $ret; + } + + /** + * Set everything in $var to null, that does not json_encode, eg. because no valid utf-8 + * + * @param midex $var + * @param string $prefix ='' + * @return mixed + */ + public static function fix_content($var, $prefix='') + { + if (json_encode($var) !== false) return $var; + + if (is_scalar($var)) + { + error_log(__METHOD__."() json_encode($prefix='$var') === false --> setting it to null"); + $var = null; + } + else + { + foreach($var as $name => &$value) + { + $value = self::fix_content($value, $prefix ? $prefix.'['.$name.']' : $name); + } + } + return $var; + } + + /** + * Function which can be used to add an event listener callback function to + * the "beforeSendData" callback. This callback might be used to add a response + * which always has to be added after all other responses. + * @param callback Callback function or method which should be called before the response gets sent + * @param mixed n Optional parameters which get passed to the callback function. + */ + public function addBeforeSendDataCallback($proc) + { + //Get the current instance + $inst = self::get(); + + //Get all parameters passed to the function and delete the first one + $params = func_get_args(); + array_shift($params); + + $inst->beforeSendDataProcs[] = array( + 'proc' => $proc, + 'params' => $params + ); + } +} diff --git a/api/src/Link.php b/api/src/Link.php index 81537b9d69..0ae1d937d1 100644 --- a/api/src/Link.php +++ b/api/src/Link.php @@ -19,7 +19,6 @@ namespace EGroupware\Api; // explicitly reference classes still in phpgwapi use egw; // on_shutdown -use egw_json_response; /** * Generalized linking between entries of EGroupware apps @@ -1474,10 +1473,10 @@ class Link extends Link\Storage { self::notify('update',$link['app'],$link['id'],$app,$id,$link_id,$data); } - if($data[Link::OLD_LINK_TITLE] && egw_json_response::isJSONResponse()) + if($data[Link::OLD_LINK_TITLE] && Api\Json\Response::isJSONResponse()) { // Update client side with new title - egw_json_response::get()->apply('egw.link_title_callback',array(array($app => array($id => self::title($app, $id))))); + Api\Json\Response::get()->apply('egw.link_title_callback',array(array($app => array($id => self::title($app, $id))))); } } diff --git a/json.php b/json.php index a3ce6283ef..2160226d7d 100644 --- a/json.php +++ b/json.php @@ -10,6 +10,8 @@ * @version $Id$ */ +use EGroupware\Api\Json; + /** * callback if the session-check fails, redirects to login.php * @@ -19,8 +21,8 @@ function login_redirect(&$anon_account) { unset($anon_account); - egw_json_request::isJSONRequest(true); // because egw_json_request::parseRequest() is not (yet) called - $response = egw_json_response::get(); + Json\Request::isJSONRequest(true); // because egw_json_request::parseRequest() is not (yet) called + $response = Json\Response::get(); $response->redirect($GLOBALS['egw_info']['server']['webserver_url'].'/login.php?cd=10', true); common::egw_exit(); @@ -46,7 +48,7 @@ function ajax_exception_handler($e) { _egw_log_exception($e,$message); } - $response = egw_json_response::get(); + $response = Json\Response::get(); $message .= ($message ? "\n\n" : '').$e->getMessage(); // only show trace (incl. function arguments) if explicitly enabled, eg. on a development system @@ -103,17 +105,17 @@ if (isset($_GET['menuaction'])) //Create a new json handler - $json = new egw_json_request(); + $json = new Json\Request(); //Check whether the request data is set if (isset($GLOBALS['egw_unset_vars']['_POST[json_data]'])) { $json->isJSONRequest(true); // otherwise exception is not send back to client, as we have not yet called parseRequest() - throw new egw_exception_assertion_failed("JSON Data contains script tags. Aborting..."); + throw new Json\Exception\ScriptTags("JSON Data contains script tags. Aborting..."); } $json->parseRequest($_GET['menuaction'], $_REQUEST['json_data']); - egw_json_response::get(); + Json\Response::get(); common::egw_exit(); } -throw new Exception($_SERVER['PHP_SELF'] . ' Invalid AJAX JSON Request'); +throw new Json\Exception($_SERVER['PHP_SELF'] . ' Invalid AJAX JSON Request'); diff --git a/phpgwapi/inc/class.egw_json.inc.php b/phpgwapi/inc/class.egw_json.inc.php index bf8fdff5ef..5e3517c02f 100644 --- a/phpgwapi/inc/class.egw_json.inc.php +++ b/phpgwapi/inc/class.egw_json.inc.php @@ -10,523 +10,20 @@ * @version $Id$ */ +use EGroupware\Api\Json; + /** * Class handling JSON requests to the server + * + * @deprecated use Api\Json\Request */ -class egw_json_request -{ - private static $_hadJSONRequest = false; - - /** - * Check if JSON request running or (re)set JSON request flag - * - * Can be used to: - * - detect regular JSON request: - * egw_json_request::isJSONRequest() - * - switch regular JSON response handling off, which would send arbitrary output via response method "html". - * Neccessary if json.php is used to send arbitrary JSON data eg. nodes for foldertree! - * egw_json_request::isJSONRequest(false) - * - * @param boolean $set =null - * @return boolean - */ - public static function isJSONRequest($set=null) - { - $ret = self::$_hadJSONRequest; - if (isset($set)) self::$_hadJSONRequest = $set; - return $ret; - } - - /** - * Parses the raw input data supplied with the input_data parameter and calls the menuaction - * passing all parameters supplied in the request to it. - * - * Also handle queued requests (menuaction == 'home.queue') containing multiple requests - * - * @param string menuaction to call - * @param string $input_data is the RAW input data as it was received from the client - */ - public function parseRequest($menuaction, $input_data) - { - // Remember that we currently are in a JSON request - e.g. used in the redirect code - self::$_hadJSONRequest = true; - - if (get_magic_quotes_gpc()) $input_data = stripslashes($input_data); - - $json_data = json_decode($input_data,true); - if (is_array($json_data) && isset($json_data['request']) && isset($json_data['request']['parameters']) && is_array($json_data['request']['parameters'])) - { - //error_log(__METHOD__.__LINE__.array2string($json_data['request']).function_backtrace()); - $parameters =& $json_data['request']['parameters']; - } - else - { - $parameters = array(); - } - // do we have a single request or an array of queued requests - if ($menuaction == 'home.queue') - { - $responses = array(); - $response = egw_json_response::get(); - foreach($parameters[0] as $uid => $data) - { - //error_log("$uid: menuaction=$data[menuaction], parameters=".array2string($data['parameters'])); - $this->handleRequest($data['menuaction'], (array)$data['parameters']); - $responses[$uid] = $response->initResponseArray(); - //error_log("responses[$uid]=".array2string($responses[$uid])); - } - $response->data($responses); // send all responses as data - } - else - { - $this->handleRequest($menuaction, $parameters); - } - } - - /** - * Request handler - * - * @param string $menuaction - * @param array $parameters - */ - public function handleRequest($menuaction, array $parameters) - { - if (strpos($menuaction,'::') !== false && strpos($menuaction,'.') === false) // static method name app_something::method - { - @list($className,$functionName,$handler) = explode('::',$menuaction); - if (substr($className, 0, 11) == 'EGroupware\\') - { - list(,$appName) = explode('\\', strtolower($className)); - } - else - { - list($appName) = explode('_',$className); - } - - // Check for a real static method, avoid instanciation if it is - $m = new ReflectionMethod($menuaction); - if($m->isStatic()) - { - $ajaxClass = $className; - } - } - else - { - @list($appName, $className, $functionName, $handler) = explode('.',$menuaction); - } - //error_log("json.php: appName=$appName, className=$className, functionName=$functionName, handler=$handler"); - - switch($handler) - { - case '/etemplate/process_exec': - $_GET['menuaction'] = $appName.'.'.$className.'.'.$functionName; - $appName = $className = 'etemplate'; - $functionName = 'process_exec'; - $menuaction = 'etemplate.etemplate.process_exec'; - - $parameters = array( - $parameters[0]['etemplate_exec_id'], - $parameters[0]['submit_button'], - $parameters[0], - 'xajaxResponse', - ); - //error_log("xajax_doXMLHTTP() /etemplate/process_exec handler: arg0='$menuaction', menuaction='$_GET[menuaction]'"); - break; - case 'etemplate': // eg. ajax code in an eTemplate widget - $menuaction = ($appName = 'etemplate').'.'.$className.'.'.$functionName; - break; - case 'template': // calling current template / framework object - $menuaction = $appName.'.'.$className.'.'.$functionName; - $className = get_class($GLOBALS['egw']->framework); - list($template) = explode('_', $className); - break; - } - - if(substr($className,0,4) != 'ajax' && substr($className,-4) != 'ajax' && - $menuaction != 'etemplate.etemplate.process_exec' && substr($functionName,0,4) != 'ajax' || - !preg_match('/^[A-Za-z0-9_\\\\-]+(\.[A-Za-z0-9_\\\\]+\.|::)[A-Za-z0-9_]+$/',$menuaction)) - { - // stopped for security reasons - error_log("className='$className', functionName='$functionName', menuaction='$menuaction'"); - error_log($_SERVER['PHP_SELF']. ' stopped for security reason. '.$menuaction.' is not valid. class- or function-name must start with ajax!!!'); - // send message also to the user - throw new Exception($_SERVER['PHP_SELF']. ' stopped for security reason. '.$menuaction.' is not valid. class- or function-name must start with ajax!!!'); - } - - if (isset($template)) - { - $ajaxClass = $GLOBALS['egw']->framework; - } - else if (!$ajaxClass) - { - $ajaxClass = CreateObject($appName.'.'.$className); - } - - // for Ajax: no need to load the "standard" javascript files, - // they are already loaded, in fact jquery has a problem if loaded twice - egw_framework::js_files(array()); - - call_user_func_array(array($ajaxClass, $functionName), - translation::convert($parameters, 'utf-8')); - - // check if we have push notifications, if notifications app available - if (class_exists('notifications_push')) notifications_push::get(); - } -} - -/** - * Abstract class implementing different type of JSON messages understood by client-side - */ -abstract class egw_json_msg -{ - /** - * Adds an "alert" to the response which can be handeled on the client side. - * - * The default implementation simply displays the text supplied here with the JavaScript function "alert". - * - * @param string $message contains the actual message being sent to the client. - * @param string $details (optional) can be used to inform the user on the client side about additional details about the error. This might be information how the error can be resolved/why it was raised or simply some debug data. - */ - public function alert($message, $details = '') - { - if (is_string($message) && is_string($details)) - { - $this->addGeneric('alert', array( - "message" => $message, - "details" => $details)); - } - else - { - throw new Exception("Invalid parameters supplied."); - } - } - - /** - * Allows to add a generic java script to the response which will be executed upon the request gets received. - * - * @deprecated - * @param string $script the script code which should be executed upon receiving - */ - public function script($script) - { - if (is_string($script)) - { - $this->addGeneric('script', $script); - } - else - { - throw new Exception("Invalid parameters supplied."); - } - } - - /** - * Allows to call a global javascript function with given parameters: window[$func].apply(window, $parameters) - * - * @param string $function name of the global (window) javascript function to call - * @param array $parameters =array() - */ - public function apply($function,array $parameters=array()) - { - if (is_string($function)) - { - $this->addGeneric('apply', array( - 'func' => $function, - 'parms' => $parameters, - )); - } - else - { - throw new Exception("Invalid parameters supplied."); - } - } - - /** - * Allows to call a global javascript function with given parameters: window[$func].call(window[, $param1[, ...]]) - * - * @param string $func name of the global (window) javascript function to call - * @param mixed $parameters variable number of parameters - */ - public function call($function) - { - $parameters = func_get_args(); - array_shift($parameters); // shift off $function - - if (is_string($function)) - { - $this->addGeneric('apply', array( - 'func' => $function, - 'parms' => $parameters, - )); - } - else - { - throw new Exception("Invalid parameters supplied."); - } - } - - /** - * Allows to call a jquery function on a selector with given parameters: $j($selector).$func($parmeters) - * - * @param string $selector jquery selector - * @param string $method name of the jquery to call - * @param array $parameters =array() - */ - public function jquery($selector,$method,array $parameters=array()) - { - if (is_string($selector) && is_string($method)) - { - $this->addGeneric('jquery', array( - 'select' => $selector, - 'func' => $method, - 'parms' => $parameters, - )); - } - else - { - throw new Exception("Invalid parameters supplied."); - } - } - - public function generic($type, array $parameters = array()) - { - if (is_string($type)) - { - $this->addGeneric($type, $parameters); - } - else - { - throw new Exception("Invalid parameters supplied."); - } - } - - /** - * Adds an html assign to the response, which is excecuted upon the request is received. - * - * @param string $id id of dom element to modify - * @param string $key attribute name of dom element which should be modified - * @param string $value the value which should be assigned to the given attribute - */ - public function assign($id, $key, $value) - { - if (is_string($id) && is_string($key) && (is_string($value) || is_numeric($value) || is_null($value))) - { - $this->addGeneric('assign', array( - 'id' => $id, - 'key' => $key, - 'value' => $value, - )); - } - else - { - throw new Exception("Invalid parameters supplied"); - } - } - - /** - * Redirect to given url - * - * @param string $url - * @param boolean $global specifies whether to redirect the whole framework - * @param string $app =null default current app from flags - * or only the current application - */ - public function redirect($url, $global = false, $app=null) - { - if (is_string($url) && is_bool($global)) - { - //self::script("location.href = '$url';"); - $this->addGeneric('redirect', array( - 'url' => $url, - 'global' => $global, - 'app' => $app ? $app : $GLOBALS['egw_info']['flags']['currentapp'], - )); - } - } - - /** - * Displays an error message on the client - */ - public function error($msg) - { - if (is_string($msg)) - { - $this->addGeneric('error', $msg); - } - } - - /** - * Includes the given CSS file. Every url can only be included once. - * - * @param string $url specifies the url to the css file to include - */ - public function includeCSS($url) - { - if (is_string($url)) - { - $this->addGeneric('css', $url); - } - } - - /** - * Includes the given JS file. Every url can only be included once. - * - * @param string $url specifies the url to the css file to include - */ - public function includeScript($url) - { - if (is_string($url)) - { - $this->addGeneric('js', $url); - } - } - - /** - * Adds any type of data to the message - * - * @param string $key - * @param mixed $data - */ - abstract protected function addGeneric($key, $data); -} +class egw_json_request extends Json\Request {} /** * Class used to send ajax responses */ -class egw_json_response extends egw_json_msg +class egw_json_response extends Json\Response { - /** - * A response can only contain one generic data part. - * This variable is used to store, whether a data part had already been added to the response. - * - * @var boolean - */ - private $hasData = false; - - /** - * Array containing all beforeSendData callbacks - */ - protected $beforeSendDataProcs = array(); - - /** - * Holds the actual response data which is then encoded to JSON - * once the "getJSON" function is called - * - * @var array - */ - protected $responseArray = array(); - - /** - * Holding instance of class for singelton egw_json_response::get() - * - * @var egw_json_response - */ - private static $response = null; - - /** - * Force use of singleton: $response = egw_json_response::get(); - */ - protected function __construct() - { - - } - - /** - * Singelton for class - * - * @return egw_json_response - */ - public static function get() - { - if (!isset(self::$response)) - { - self::$response = new egw_json_response(); - self::sendHeader(); - } - return self::$response; - } - - public static function isJSONResponse() - { - return isset(self::$response); - } - - /** - * Do we have a JSON response to send back - * - * @return boolean - */ - public function haveJSONResponse() - { - return $this->responseArray || $this->beforeSendDataProcs; - } - - /** - * Private function used to send the HTTP header of the JSON response - */ - private static function sendHeader() - { - $file = $line = null; - if (headers_sent($file, $line)) - { - error_log(__METHOD__."() header already sent by $file line $line: ".function_backtrace()); - } - else - { - //Send the character encoding header - header('content-type: application/json; charset='.translation::charset()); - } - } - - /** - * Private function which is used to send the result via HTTP - */ - public static function sendResult() - { - $inst = self::get(); - - //Call each attached before send data proc - foreach ($inst->beforeSendDataProcs as $proc) - { - call_user_func_array($proc['proc'], $proc['params']); - } - - // check if application made some direct output - if (($output = ob_get_clean())) - { - if (!$inst->haveJSONResponse()) - { - error_log(__METHOD__."() adding output with inst->addGeneric('html', '$output')"); - $inst->addGeneric('html', $output); - } - else - { - $inst->alert('Application echoed something', $output); - } - } - - echo $inst->getJSON(); - $inst->initResponseArray(); - } - - /** - * Return json response data, after running beforeSendDataProcs - * - * Used to send json response with etemplate data in GET request - * - * @return array responseArray - */ - public static function returnResult() - { - $inst = self::get(); - - //Call each attached before send data proc - foreach ($inst->beforeSendDataProcs as $proc) - { - call_user_func_array($proc['proc'], $proc['params']); - } - return $inst->initResponseArray(); - } - /** * xAjax compatibility function */ @@ -534,154 +31,12 @@ class egw_json_response extends egw_json_msg { // do nothing, as output is triggered by egw::__destruct() } - - /** - * Adds any type of data to the message - * - * @param string $key - * @param mixed $data - */ - protected function addGeneric($key, $data) - { - self::get()->responseArray[] = array( - 'type' => $key, - 'data' => $data, - ); - } - - /** - * Init responseArray - * - * @param array $arr - * @return array previous content - */ - public function initResponseArray() - { - $return = $this->responseArray; - $this->responseArray = $this->beforeSendDataProcs = array(); - $this->hasData = false; - return $return; - } - - - /** - * Adds a "data" response to the json response. - * - * This function may only be called once for a single JSON response object. - * - * @param object|array|string $data can be of any data type and will be added JSON Encoded to your response. - */ - public function data($data) - { - /* Only allow adding the data response once */ - $inst = self::get(); - if (!$inst->hasData) - { - $inst->addGeneric('data', $data); - $inst->hasData = true; - } - else - { - throw new Exception("Adding more than one data response to a JSON response is not allowed."); - } - } - - /** - * Returns the actual JSON code generated by calling the above "add" function. - * - * @return string - */ - public function getJSON() - { - $inst = self::get(); - - /* Wrap the result array into a parent "response" Object */ - $res = array('response' => $inst->responseArray); - - return self::json_encode($res); //PHP5.3+, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP); - } - - /** - * More fault-tollerant version of json_encode removing everything that does not json_encode eg. because not utf-8 - * - * @param mixed $var - * @return string - */ - public static function json_encode($var) - { - $ret = json_encode($var); - - if ($ret === false && ($err = json_last_error())) - { - static $json_err2str = array( - JSON_ERROR_NONE => 'No errors', - JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', - JSON_ERROR_STATE_MISMATCH => 'Underflow or the modes mismatch', - JSON_ERROR_CTRL_CHAR => 'Unexpected control character found', - JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON', - JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded', - ); - error_log(__METHOD__.'('.array2string($var).') json_last_error()='.$err.'='.$json_err2str[$err]); - - if (($var = self::fix_content($var))) - { - return self::json_encode($var); - } - } - return $ret; - } - - /** - * Set everything in $var to null, that does not json_encode, eg. because no valid utf-8 - * - * @param midex $var - * @param string $prefix ='' - * @return mixed - */ - public static function fix_content($var, $prefix='') - { - if (json_encode($var) !== false) return $var; - - if (is_scalar($var)) - { - error_log(__METHOD__."() json_encode($prefix='$var') === false --> setting it to null"); - $var = null; - } - else - { - foreach($var as $name => &$value) - { - $value = self::fix_content($value, $prefix ? $prefix.'['.$name.']' : $name); - } - } - return $var; - } - - /** - * Function which can be used to add an event listener callback function to - * the "beforeSendData" callback. This callback might be used to add a response - * which always has to be added after all other responses. - * @param callback Callback function or method which should be called before the response gets sent - * @param mixed n Optional parameters which get passed to the callback function. - */ - public function addBeforeSendDataCallback($proc) - { - //Get the current instance - $inst = self::get(); - - //Get all parameters passed to the function and delete the first one - $params = func_get_args(); - array_shift($params); - - $inst->beforeSendDataProcs[] = array( - 'proc' => $proc, - 'params' => $params - ); - } } /** * Deprecated legacy xajax wrapper functions for the new egw_json interface + * + * @deprecated use Api\Json\Response methods */ class xajaxResponse {