3 * TActivePageAdapter, TCallbackErrorHandler and TInvalidCallbackException class file.
5 * @author Wei Zhuo <weizhuo[at]gamil[dot]com>
6 * @author Gabor Berczi <gabor.berczi@devworx.hu> (lazyload additions & progressive rendering)
7 * @link https://github.com/pradosoft/prado
8 * @copyright Copyright © 2005-2016 The PRADO Group
9 * @license https://github.com/pradosoft/prado/blob/master/COPYRIGHT
10 * @package System.Web.UI.ActiveControls
14 * Load callback response adapter class.
16 Prado::using('System.Web.UI.ActiveControls.TCallbackResponseAdapter');
17 Prado::using('System.Web.UI.ActiveControls.TCallbackClientScript');
18 Prado::using('System.Web.UI.ActiveControls.TCallbackEventParameter');
21 * TActivePageAdapter class.
23 * Callback request handler.
25 * @author Wei Zhuo <weizhuo[at]gamil[dot]com>
26 * @author Gabor Berczi <gabor.berczi@devworx.hu> (lazyload additions & progressive rendering)
27 * @package System.Web.UI.ActiveControls
30 class TActivePageAdapter extends TControlAdapter
33 * Callback response data header name.
35 const CALLBACK_DATA_HEADER = 'X-PRADO-DATA';
37 * Callback response client-side action header name.
39 const CALLBACK_ACTION_HEADER = 'X-PRADO-ACTIONS';
41 * Callback error header name.
43 const CALLBACK_ERROR_HEADER = 'X-PRADO-ERROR';
45 * Callback page state header name.
47 const CALLBACK_PAGESTATE_HEADER = 'X-PRADO-PAGESTATE';
49 * Script list header name.
51 const CALLBACK_SCRIPTLIST_HEADER = 'X-PRADO-SCRIPTLIST';
53 * Stylesheet list header name.
55 const CALLBACK_STYLESHEETLIST_HEADER = 'X-PRADO-STYLESHEETLIST';
57 * Stylesheet header name.
59 const CALLBACK_STYLESHEET_HEADER = 'X-PRADO-STYLESHEET';
61 * Hidden field list header name.
63 const CALLBACK_HIDDENFIELDLIST_HEADER = 'X-PRADO-HIDDENFIELDLIST';
66 * Callback redirect url header name.
68 const CALLBACK_REDIRECT = 'X-PRADO-REDIRECT';
71 * @var ICallbackEventHandler callback event handler.
73 private $_callbackEventTarget;
75 * @var mixed callback event parameter.
77 private $_callbackEventParameter;
79 * @var TCallbackClientScript callback client script handler
81 private $_callbackClient;
83 private $_controlsToRender=array();
86 * Constructor, trap errors and exception to let the callback response
89 public function __construct(TPage $control)
91 parent::__construct($control);
93 //TODO: can this be done later?
94 $response = $this->getApplication()->getResponse();
95 $response->setAdapter(new TCallbackResponseAdapter($response));
97 $this->trapCallbackErrorsExceptions();
101 * Process the callback request.
102 * @param THtmlWriter html content writer.
104 public function processCallbackEvent($writer)
106 Prado::trace("ActivePage raiseCallbackEvent()",'System.Web.UI.ActiveControls.TActivePageAdapter');
107 $this->raiseCallbackEvent();
111 * Register a control for defered render() call.
112 * @param TControl control for defered rendering
113 * @param THtmlWriter the renderer
115 public function registerControlToRender($control,$writer)
117 $id = $control->getUniqueID();
118 if(!isset($this->_controlsToRender[$id]))
119 $this->_controlsToRender[$id] = array($control,$writer);
123 * Trap errors and exceptions to be handled by TCallbackErrorHandler.
125 protected function trapCallbackErrorsExceptions()
127 $this->getApplication()->setErrorHandler(new TCallbackErrorHandler);
131 * Render the callback response.
132 * @param THtmlWriter html content writer.
134 public function renderCallbackResponse($writer)
136 Prado::trace("ActivePage renderCallbackResponse()",'System.Web.UI.ActiveControls.TActivePageAdapter');
137 if(($url = $this->getResponse()->getAdapter()->getRedirectedUrl())===null)
138 $this->renderResponse($writer);
140 $this->redirect($url);
144 * Redirect url on the client-side using javascript.
145 * @param string new url to load.
147 protected function redirect($url)
149 Prado::trace("ActivePage redirect()",'System.Web.UI.ActiveControls.TActivePageAdapter');
150 $this->appendContentPart($this->getResponse(), self::CALLBACK_REDIRECT, $url);
154 * Renders the callback response by adding additional callback data and
155 * javascript actions in the header and page state if required.
156 * @param THtmlWriter html content writer.
158 protected function renderResponse($writer)
160 Prado::trace("ActivePage renderResponse()",'System.Web.UI.ActiveControls.TActivePageAdapter');
161 //renders all the defered render() calls.
162 foreach($this->_controlsToRender as $rid => $forRender)
163 $forRender[0]->render($forRender[1]);
165 $response = $this->getResponse();
167 //send response data in header
168 if($response->getHasAdapter())
170 $responseData = $response->getAdapter()->getResponseData();
171 if($responseData!==null)
173 $data = TJavaScript::jsonEncode($responseData);
175 $this->appendContentPart($response, self::CALLBACK_DATA_HEADER, $data);
179 //sends page state in header
180 if(($handler = $this->getCallbackEventTarget()) !== null)
182 if($handler->getActiveControl()->getClientSide()->getEnablePageStateUpdate())
184 $pagestate = $this->getPage()->getClientState();
185 $this->appendContentPart($response, self::CALLBACK_PAGESTATE_HEADER, $pagestate);
189 //safari must receive at least 1 byte of data.
192 //output the end javascript
193 if($this->getPage()->getClientScript()->hasEndScripts())
195 $writer = $response->createHtmlWriter();
196 $this->getPage()->getClientScript()->renderEndScriptsCallback($writer);
197 $this->getPage()->getCallbackClient()->evaluateScript($writer);
201 $executeJavascript = $this->getCallbackClientHandler()->getClientFunctionsToExecute();
202 $actions = TJavaScript::jsonEncode($executeJavascript);
203 $this->appendContentPart($response, self::CALLBACK_ACTION_HEADER, $actions);
206 $cs = $this->Page->getClientScript();
208 // collect all stylesheet file references
209 $stylesheets = $cs->getStyleSheetUrls();
210 if (count($stylesheets)>0)
211 $this->appendContentPart($response, self::CALLBACK_STYLESHEETLIST_HEADER, TJavaScript::jsonEncode($stylesheets));
213 // collect all stylesheet snippets references
214 $stylesheets = $cs->getStyleSheetCodes();
215 if (count($stylesheets)>0)
216 $this->appendContentPart($response, self::CALLBACK_STYLESHEET_HEADER, TJavaScript::jsonEncode($stylesheets));
218 // collect all script file references
219 $scripts = $cs->getScriptUrls();
220 if (count($scripts)>0)
221 $this->appendContentPart($response, self::CALLBACK_SCRIPTLIST_HEADER, TJavaScript::jsonEncode($scripts));
223 // collect all hidden field references
224 $fields = $cs->getHiddenFields();
225 if (count($fields)>0)
226 $this->appendContentPart($response, self::CALLBACK_HIDDENFIELDLIST_HEADER, TJavaScript::jsonEncode($fields));
230 * Appends data or javascript code to the body content surrounded with delimiters
232 private function appendContentPart($response, $delimiter, $data)
234 $content = $response->createHtmlWriter();
235 $content->getWriter()->setBoundary($delimiter);
236 $content->write($data);
240 * Trys to find the callback event handler and raise its callback event.
241 * @throws TInvalidCallbackException if call back target is not found.
242 * @throws TInvalidCallbackException if the requested target does not
243 * implement ICallbackEventHandler.
245 private function raiseCallbackEvent()
247 if(($callbackHandler=$this->getCallbackEventTarget())!==null)
249 if($callbackHandler instanceof ICallbackEventHandler)
251 $param = $this->getCallbackEventParameter();
252 $result = new TCallbackEventParameter($this->getResponse(), $param);
253 $callbackHandler->raiseCallbackEvent($result);
257 throw new TInvalidCallbackException(
258 'callback_invalid_handler', $callbackHandler->getUniqueID());
263 $target = $this->getRequest()->itemAt(TPage::FIELD_CALLBACK_TARGET);
264 throw new TInvalidCallbackException('callback_invalid_target', $target);
269 * @return TControl the control responsible for the current callback event,
270 * null if nonexistent
272 public function getCallbackEventTarget()
274 if($this->_callbackEventTarget===null)
276 $eventTarget=$this->getRequest()->itemAt(TPage::FIELD_CALLBACK_TARGET);
277 if(!empty($eventTarget))
278 $this->_callbackEventTarget=$this->getPage()->findControl($eventTarget);
280 return $this->_callbackEventTarget;
284 * Registers a control to raise callback event in the current request.
285 * @param TControl control registered to raise callback event.
287 public function setCallbackEventTarget(TControl $control)
289 $this->_callbackEventTarget=$control;
293 * Gets callback parameter.
294 * @return string postback event parameter
296 public function getCallbackEventParameter()
298 if($this->_callbackEventParameter===null)
300 $param = $this->getRequest()->itemAt(TPage::FIELD_CALLBACK_PARAMETER);
301 $this->_callbackEventParameter=$param;
303 return $this->_callbackEventParameter;
307 * @param mixed postback event parameter
309 public function setCallbackEventParameter($value)
311 $this->_callbackEventParameter=$value;
315 * Gets the callback client script handler. It handlers the javascript functions
316 * to be executed during the callback response.
317 * @return TCallbackClientScript callback client handler.
319 public function getCallbackClientHandler()
321 if($this->_callbackClient===null)
322 $this->_callbackClient = new TCallbackClientScript;
323 return $this->_callbackClient;
328 * TCallbackErrorHandler class.
330 * Captures errors and exceptions and send them back during callback response.
331 * When the application is in debug mode, the error and exception stack trace
332 * are shown. A TJavascriptLogger must be present on the client-side to view
333 * the error stack trace.
335 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
336 * @package System.Web.UI.ActiveControls
339 class TCallbackErrorHandler extends TErrorHandler
342 * Displays the exceptions to the client-side TJavascriptLogger.
343 * A HTTP 500 status code is sent and the stack trace is sent as JSON encoded.
344 * @param Exception exception details.
346 protected function displayException($exception)
348 if($this->getApplication()->getMode()===TApplication::STATE_DEBUG)
350 $response = $this->getApplication()->getResponse();
351 $trace = $this->getExceptionStackTrace($exception);
352 // avoid error on non-utf8 strings
354 $trace = TJavaScript::jsonEncode($trace);
355 } catch (Exception $e) {
356 // strip everythin not 7bit ascii
357 $trace = preg_replace('/[^(\x20-\x7F)]*/','', serialize($trace));
360 // avoid exception loop if headers have already been sent
362 $response->setStatusCode(500, 'Internal Server Error');
363 } catch (Exception $e) { }
365 $content = $response->createHtmlWriter();
366 $content->getWriter()->setBoundary(TActivePageAdapter::CALLBACK_ERROR_HEADER);
367 $content->write($trace);
371 error_log("Error happened while processing an existing error:\n".$exception->__toString());
372 header('HTTP/1.0 500 Internal Server Error', true, 500);
374 $this->getApplication()->getResponse()->flush();
378 * @param Exception exception details.
379 * @return array exception stack trace details.
381 private function getExceptionStackTrace($exception)
383 $data['code']=$exception->getCode() > 0 ? $exception->getCode() : 500;
384 $data['file']=$exception->getFile();
385 $data['line']=$exception->getLine();
386 $data['trace']=$exception->getTrace();
387 if($exception instanceof TPhpErrorException)
389 // if PHP exception, we want to show the 2nd stack level context
390 // because the 1st stack level is of little use (it's in error handler)
391 if(isset($trace[0]) && isset($trace[0]['file']) && isset($trace[0]['line']))
393 $data['file']=$trace[0]['file'];
394 $data['line']=$trace[0]['line'];
397 $data['type']=get_class($exception);
398 $data['message']=$exception->getMessage();
399 $data['version']=$_SERVER['SERVER_SOFTWARE'].' '.Prado::getVersion();
400 $data['time']=@strftime('%Y-%m-%d %H:%M',time());
406 * TInvalidCallbackException class.
408 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
409 * @package System.Web.UI.ActiveControls
412 class TInvalidCallbackException extends TException