]> git.sur5r.net Git - bacula/bacula/blob - gui/baculum/framework/Web/UI/ActiveControls/TActivePageAdapter.php
baculum: New Baculum API and Baculum Web
[bacula/bacula] / gui / baculum / framework / Web / UI / ActiveControls / TActivePageAdapter.php
1 <?php
2 /**
3  * TActivePageAdapter, TCallbackErrorHandler and TInvalidCallbackException class file.
4  *
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 &copy; 2005-2016 The PRADO Group
9  * @license https://github.com/pradosoft/prado/blob/master/COPYRIGHT
10  * @package System.Web.UI.ActiveControls
11  */
12
13 /**
14  * Load callback response adapter class.
15  */
16 Prado::using('System.Web.UI.ActiveControls.TCallbackResponseAdapter');
17 Prado::using('System.Web.UI.ActiveControls.TCallbackClientScript');
18 Prado::using('System.Web.UI.ActiveControls.TCallbackEventParameter');
19
20 /**
21  * TActivePageAdapter class.
22  *
23  * Callback request handler.
24  *
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
28  * @since 3.1
29  */
30 class TActivePageAdapter extends TControlAdapter
31 {
32         /**
33          * Callback response data header name.
34          */
35         const CALLBACK_DATA_HEADER = 'X-PRADO-DATA';
36         /**
37          * Callback response client-side action header name.
38          */
39         const CALLBACK_ACTION_HEADER = 'X-PRADO-ACTIONS';
40         /**
41          * Callback error header name.
42          */
43         const CALLBACK_ERROR_HEADER = 'X-PRADO-ERROR';
44         /**
45          * Callback page state header name.
46          */
47         const CALLBACK_PAGESTATE_HEADER = 'X-PRADO-PAGESTATE';
48         /**
49          * Script list header name.
50          */
51         const CALLBACK_SCRIPTLIST_HEADER = 'X-PRADO-SCRIPTLIST';
52         /**
53          * Stylesheet list header name.
54          */
55         const CALLBACK_STYLESHEETLIST_HEADER = 'X-PRADO-STYLESHEETLIST';
56         /**
57          * Stylesheet header name.
58          */
59         const CALLBACK_STYLESHEET_HEADER = 'X-PRADO-STYLESHEET';
60         /**
61          * Hidden field list header name.
62          */
63         const CALLBACK_HIDDENFIELDLIST_HEADER = 'X-PRADO-HIDDENFIELDLIST';
64
65         /**
66          * Callback redirect url header name.
67          */
68         const CALLBACK_REDIRECT = 'X-PRADO-REDIRECT';
69
70         /**
71          * @var ICallbackEventHandler callback event handler.
72          */
73         private $_callbackEventTarget;
74         /**
75          * @var mixed callback event parameter.
76          */
77         private $_callbackEventParameter;
78         /**
79          * @var TCallbackClientScript callback client script handler
80          */
81         private $_callbackClient;
82
83         private $_controlsToRender=array();
84
85         /**
86          * Constructor, trap errors and exception to let the callback response
87          * handle them.
88          */
89         public function __construct(TPage $control)
90         {
91                 parent::__construct($control);
92
93                 //TODO: can this be done later?
94                 $response = $this->getApplication()->getResponse();
95                 $response->setAdapter(new TCallbackResponseAdapter($response));
96
97                 $this->trapCallbackErrorsExceptions();
98         }
99
100         /**
101          * Process the callback request.
102          * @param THtmlWriter html content writer.
103          */
104         public function processCallbackEvent($writer)
105         {
106                 Prado::trace("ActivePage raiseCallbackEvent()",'System.Web.UI.ActiveControls.TActivePageAdapter');
107                 $this->raiseCallbackEvent();
108         }
109
110         /**
111          * Register a control for defered render() call.
112          * @param TControl control for defered rendering
113          * @param THtmlWriter the renderer
114          */
115         public function registerControlToRender($control,$writer)
116         {
117                 $id = $control->getUniqueID();
118                 if(!isset($this->_controlsToRender[$id]))
119                         $this->_controlsToRender[$id] = array($control,$writer);
120         }
121
122         /**
123          * Trap errors and exceptions to be handled by TCallbackErrorHandler.
124          */
125         protected function trapCallbackErrorsExceptions()
126         {
127                 $this->getApplication()->setErrorHandler(new TCallbackErrorHandler);
128         }
129
130         /**
131          * Render the callback response.
132          * @param THtmlWriter html content writer.
133          */
134         public function renderCallbackResponse($writer)
135         {
136                 Prado::trace("ActivePage renderCallbackResponse()",'System.Web.UI.ActiveControls.TActivePageAdapter');
137                 if(($url = $this->getResponse()->getAdapter()->getRedirectedUrl())===null)
138                         $this->renderResponse($writer);
139                 else
140                         $this->redirect($url);
141         }
142
143         /**
144          * Redirect url on the client-side using javascript.
145          * @param string new url to load.
146          */
147         protected function redirect($url)
148         {
149                 Prado::trace("ActivePage redirect()",'System.Web.UI.ActiveControls.TActivePageAdapter');
150                 $this->appendContentPart($this->getResponse(), self::CALLBACK_REDIRECT, $url);
151         }
152
153         /**
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.
157          */
158         protected function renderResponse($writer)
159         {
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]);
164
165                 $response = $this->getResponse();
166
167                 //send response data in header
168                 if($response->getHasAdapter())
169                 {
170                         $responseData = $response->getAdapter()->getResponseData();
171                         if($responseData!==null)
172                         {
173                                 $data = TJavaScript::jsonEncode($responseData);
174
175                                 $this->appendContentPart($response, self::CALLBACK_DATA_HEADER, $data);
176                         }
177                 }
178
179                 //sends page state in header
180                 if(($handler = $this->getCallbackEventTarget()) !== null)
181                 {
182                         if($handler->getActiveControl()->getClientSide()->getEnablePageStateUpdate())
183                         {
184                                 $pagestate = $this->getPage()->getClientState();
185                                 $this->appendContentPart($response, self::CALLBACK_PAGESTATE_HEADER, $pagestate);
186                         }
187                 }
188
189                 //safari must receive at least 1 byte of data.
190                 $writer->write(" ");
191
192                 //output the end javascript
193                 if($this->getPage()->getClientScript()->hasEndScripts())
194                 {
195                         $writer = $response->createHtmlWriter();
196                         $this->getPage()->getClientScript()->renderEndScriptsCallback($writer);
197                         $this->getPage()->getCallbackClient()->evaluateScript($writer);
198                 }
199
200                 //output the actions
201                 $executeJavascript = $this->getCallbackClientHandler()->getClientFunctionsToExecute();
202                 $actions = TJavaScript::jsonEncode($executeJavascript);
203                 $this->appendContentPart($response, self::CALLBACK_ACTION_HEADER, $actions);
204
205
206                 $cs = $this->Page->getClientScript();
207
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));
212
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));
217
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));
222
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));
227         }
228
229         /**
230          * Appends data or javascript code to the body content surrounded with delimiters
231          */
232         private function appendContentPart($response, $delimiter, $data)
233         {
234                 $content = $response->createHtmlWriter();
235                 $content->getWriter()->setBoundary($delimiter);
236                 $content->write($data);
237         }
238
239         /**
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.
244          */
245         private function raiseCallbackEvent()
246         {
247                  if(($callbackHandler=$this->getCallbackEventTarget())!==null)
248                  {
249                         if($callbackHandler instanceof ICallbackEventHandler)
250                         {
251                                 $param = $this->getCallbackEventParameter();
252                                 $result = new TCallbackEventParameter($this->getResponse(), $param);
253                                 $callbackHandler->raiseCallbackEvent($result);
254                         }
255                         else
256                         {
257                                 throw new TInvalidCallbackException(
258                                         'callback_invalid_handler', $callbackHandler->getUniqueID());
259                         }
260                  }
261                  else
262                  {
263                         $target = $this->getRequest()->itemAt(TPage::FIELD_CALLBACK_TARGET);
264                         throw new TInvalidCallbackException('callback_invalid_target', $target);
265                  }
266         }
267
268         /**
269          * @return TControl the control responsible for the current callback event,
270          * null if nonexistent
271          */
272         public function getCallbackEventTarget()
273         {
274                 if($this->_callbackEventTarget===null)
275                 {
276                         $eventTarget=$this->getRequest()->itemAt(TPage::FIELD_CALLBACK_TARGET);
277                         if(!empty($eventTarget))
278                                 $this->_callbackEventTarget=$this->getPage()->findControl($eventTarget);
279                 }
280                 return $this->_callbackEventTarget;
281         }
282
283         /**
284          * Registers a control to raise callback event in the current request.
285          * @param TControl control registered to raise callback event.
286          */
287         public function setCallbackEventTarget(TControl $control)
288         {
289                 $this->_callbackEventTarget=$control;
290         }
291
292         /**
293          * Gets callback parameter.
294          * @return string postback event parameter
295          */
296         public function getCallbackEventParameter()
297         {
298                 if($this->_callbackEventParameter===null)
299                 {
300                         $param = $this->getRequest()->itemAt(TPage::FIELD_CALLBACK_PARAMETER);
301                         $this->_callbackEventParameter=$param;
302                 }
303                 return $this->_callbackEventParameter;
304         }
305
306         /**
307          * @param mixed postback event parameter
308          */
309         public function setCallbackEventParameter($value)
310         {
311                 $this->_callbackEventParameter=$value;
312         }
313
314         /**
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.
318          */
319         public function getCallbackClientHandler()
320         {
321                 if($this->_callbackClient===null)
322                         $this->_callbackClient = new TCallbackClientScript;
323                 return $this->_callbackClient;
324         }
325 }
326
327 /**
328  * TCallbackErrorHandler class.
329  *
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.
334  *
335  * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
336  * @package System.Web.UI.ActiveControls
337  * @since 3.1
338  */
339 class TCallbackErrorHandler extends TErrorHandler
340 {
341         /**
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.
345          */
346         protected function displayException($exception)
347         {
348                 if($this->getApplication()->getMode()===TApplication::STATE_DEBUG)
349                 {
350                         $response = $this->getApplication()->getResponse();
351                         $trace = $this->getExceptionStackTrace($exception);
352                         // avoid error on non-utf8 strings
353                         try {
354                                 $trace = TJavaScript::jsonEncode($trace);
355                         } catch (Exception $e) {
356                                 // strip everythin not 7bit ascii
357                                 $trace = preg_replace('/[^(\x20-\x7F)]*/','', serialize($trace));
358                         }
359
360                         // avoid exception loop if headers have already been sent
361                         try {
362                                 $response->setStatusCode(500, 'Internal Server Error');
363                         } catch (Exception $e) { }
364
365                         $content = $response->createHtmlWriter();
366                         $content->getWriter()->setBoundary(TActivePageAdapter::CALLBACK_ERROR_HEADER);
367                         $content->write($trace);
368                 }
369                 else
370                 {
371                         error_log("Error happened while processing an existing error:\n".$exception->__toString());
372                         header('HTTP/1.0 500 Internal Server Error', true, 500);
373                 }
374                 $this->getApplication()->getResponse()->flush();
375         }
376
377         /**
378          * @param Exception exception details.
379          * @return array exception stack trace details.
380          */
381         private function getExceptionStackTrace($exception)
382         {
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)
388                 {
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']))
392                         {
393                                 $data['file']=$trace[0]['file'];
394                                 $data['line']=$trace[0]['line'];
395                         }
396                 }
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());
401                 return $data;
402         }
403 }
404
405 /**
406  * TInvalidCallbackException class.
407  *
408  * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
409  * @package System.Web.UI.ActiveControls
410  * @since 3.1
411  */
412 class TInvalidCallbackException extends TException
413 {
414 }
415