Subversion-Projekte lars-tiefland.prado

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/**
3
 * TActivePageAdapter, TCallbackErrorHandler and TInvalidCallbackException class file.
4
 *
5
 * @author Wei Zhuo <weizhuo[at]gamil[dot]com>
6
 * @link http://www.pradosoft.com/
7
 * @copyright Copyright &copy; 2005-2008 PradoSoft
8
 * @license http://www.pradosoft.com/license/
9
 * @version $Id: TActivePageAdapter.php 2564 2008-11-11 21:56:02Z carlgmathisen $
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
 * @version $Id: TActivePageAdapter.php 2564 2008-11-11 21:56:02Z carlgmathisen $
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
	/**
50
	 * Callback redirect url header name.
51
	 */
52
	const CALLBACK_REDIRECT = 'X-PRADO-REDIRECT';
53
 
54
	/**
55
	 * @var ICallbackEventHandler callback event handler.
56
	 */
57
	private $_callbackEventTarget;
58
	/**
59
	 * @var mixed callback event parameter.
60
	 */
61
	private $_callbackEventParameter;
62
	/**
63
	 * @var TCallbackClientScript callback client script handler
64
	 */
65
	private $_callbackClient;
66
 
67
	private $_controlsToRender=array();
68
 
69
	/**
70
	 * Constructor, trap errors and exception to let the callback response
71
	 * handle them.
72
	 */
73
	public function __construct(TPage $control)
74
	{
75
		parent::__construct($control);
76
 
77
		//TODO: can this be done later?
78
		$response = $this->getApplication()->getResponse();
79
		$response->setAdapter(new TCallbackResponseAdapter($response));
80
 
81
		$this->trapCallbackErrorsExceptions();
82
	}
83
 
84
	/**
85
	 * Process the callback request.
86
	 * @param THtmlWriter html content writer.
87
	 */
88
	public function processCallbackEvent($writer)
89
	{
90
		Prado::trace("ActivePage raiseCallbackEvent()",'System.Web.UI.ActiveControls.TActivePageAdapter');
91
		$this->raiseCallbackEvent();
92
	}
93
 
94
	/**
95
	 * Register a control for defered render() call.
96
	 * @param TControl control for defered rendering
97
	 * @param THtmlWriter the renderer
98
	 */
99
	public function registerControlToRender($control,$writer)
100
	{
101
		$id = $control->getUniqueID();
102
		if(!isset($this->_controlsToRender[$id]))
103
			$this->_controlsToRender[$id] = array($control,$writer);
104
	}
105
 
106
	/**
107
	 * Trap errors and exceptions to be handled by TCallbackErrorHandler.
108
	 */
109
	protected function trapCallbackErrorsExceptions()
110
	{
111
		$this->getApplication()->setErrorHandler(new TCallbackErrorHandler);
112
	}
113
 
114
	/**
115
	 * Render the callback response.
116
	 * @param THtmlWriter html content writer.
117
	 */
118
	public function renderCallbackResponse($writer)
119
	{
120
		Prado::trace("ActivePage renderCallbackResponse()",'System.Web.UI.ActiveControls.TActivePageAdapter');
121
		if(($url = $this->getResponse()->getAdapter()->getRedirectedUrl())===null)
122
			$this->renderResponse($writer);
123
		else
124
			$this->redirect($url);
125
	}
126
 
127
	/**
128
	 * Redirect url on the client-side using javascript.
129
	 * @param string new url to load.
130
	 */
131
	protected function redirect($url)
132
	{
133
		Prado::trace("ActivePage redirect()",'System.Web.UI.ActiveControls.TActivePageAdapter');
134
		$this->appendContentPart($this->getResponse(), self::CALLBACK_REDIRECT, $url);
135
		//$this->getResponse()->appendHeader(self::CALLBACK_REDIRECT.': '.$url);
136
	}
137
 
138
	/**
139
	 * Renders the callback response by adding additional callback data and
140
	 * javascript actions in the header and page state if required.
141
	 * @param THtmlWriter html content writer.
142
	 */
143
	protected function renderResponse($writer)
144
	{
145
		Prado::trace("ActivePage renderResponse()",'System.Web.UI.ActiveControls.TActivePageAdapter');
146
		//renders all the defered render() calls.
147
		foreach($this->_controlsToRender as $rid => $forRender)
148
			$forRender[0]->render($forRender[1]);
149
 
150
		$response = $this->getResponse();
151
 
152
		//send response data in header
153
		if($response->getHasAdapter())
154
		{
155
			$responseData = $response->getAdapter()->getResponseData();
156
			if(!is_null($responseData))
157
			{
158
				$data = TJavaScript::jsonEncode($responseData);
159
 
160
				$this->appendContentPart($response, self::CALLBACK_DATA_HEADER, $data);
161
				//$response->appendHeader(self::CALLBACK_DATA_HEADER.': '.$data);
162
			}
163
		}
164
 
165
		//sends page state in header
166
		if(($handler = $this->getCallbackEventTarget()) !== null)
167
		{
168
			if($handler->getActiveControl()->getClientSide()->getEnablePageStateUpdate())
169
			{
170
				$pagestate = $this->getPage()->getClientState();
171
				$this->appendContentPart($response, self::CALLBACK_PAGESTATE_HEADER, $pagestate);
172
				//$response->appendHeader(self::CALLBACK_PAGESTATE_HEADER.': '.$pagestate);
173
			}
174
		}
175
 
176
		//safari must receive at least 1 byte of data.
177
		$writer->write(" ");
178
 
179
		//output the end javascript
180
		if($this->getPage()->getClientScript()->hasEndScripts())
181
		{
182
			$writer = $response->createHtmlWriter();
183
			$this->getPage()->getClientScript()->renderEndScripts($writer);
184
			$this->getPage()->getCallbackClient()->evaluateScript($writer);
185
		}
186
 
187
		//output the actions
188
		$executeJavascript = $this->getCallbackClientHandler()->getClientFunctionsToExecute();
189
		$actions = TJavaScript::jsonEncode($executeJavascript);
190
		$this->appendContentPart($response, self::CALLBACK_ACTION_HEADER, $actions);
191
		//$response->appendHeader(self::CALLBACK_ACTION_HEADER.': '.$actions);
192
	}
193
 
194
	/**
195
	 * Appends data or javascript code to the body content surrounded with delimiters
196
	 */
197
	private function appendContentPart($response, $delimiter, $data)
198
	{
199
		$content = $response->createHtmlWriter();
200
		$content->getWriter()->setBoundary($delimiter);
201
		$content->write($data);
202
	}
203
 
204
	/**
205
	 * Trys to find the callback event handler and raise its callback event.
206
	 * @throws TInvalidCallbackException if call back target is not found.
207
	 * @throws TInvalidCallbackException if the requested target does not
208
	 * implement ICallbackEventHandler.
209
	 */
210
	private function raiseCallbackEvent()
211
	{
212
		 if(($callbackHandler=$this->getCallbackEventTarget())!==null)
213
		 {
214
			if($callbackHandler instanceof ICallbackEventHandler)
215
			{
216
				$param = $this->getCallbackEventParameter();
217
				$result = new TCallbackEventParameter($this->getResponse(), $param);
218
				$callbackHandler->raiseCallbackEvent($result);
219
			}
220
			else
221
			{
222
				throw new TInvalidCallbackException(
223
					'callback_invalid_handler', $callbackHandler->getUniqueID());
224
			}
225
		 }
226
		 else
227
		 {
228
		 	$target = $this->getRequest()->itemAt(TPage::FIELD_CALLBACK_TARGET);
229
		 	throw new TInvalidCallbackException('callback_invalid_target', $target);
230
		 }
231
	}
232
 
233
	/**
234
	 * @return TControl the control responsible for the current callback event,
235
	 * null if nonexistent
236
	 */
237
	public function getCallbackEventTarget()
238
	{
239
		if($this->_callbackEventTarget===null)
240
		{
241
			$eventTarget=$this->getRequest()->itemAt(TPage::FIELD_CALLBACK_TARGET);
242
			if(!empty($eventTarget))
243
				$this->_callbackEventTarget=$this->getPage()->findControl($eventTarget);
244
		}
245
		return $this->_callbackEventTarget;
246
	}
247
 
248
	/**
249
	 * Registers a control to raise callback event in the current request.
250
	 * @param TControl control registered to raise callback event.
251
	 */
252
	public function setCallbackEventTarget(TControl $control)
253
	{
254
		$this->_callbackEventTarget=$control;
255
	}
256
 
257
	/**
258
	 * Gets callback parameter. JSON encoding is assumed.
259
	 * @return string postback event parameter
260
	 */
261
	public function getCallbackEventParameter()
262
	{
263
		if($this->_callbackEventParameter===null)
264
		{
265
			$param = $this->getRequest()->itemAt(TPage::FIELD_CALLBACK_PARAMETER);
266
			if(strlen($param) > 0)
267
				$this->_callbackEventParameter=TJavaScript::jsonDecode((string)$param);
268
		}
269
		return $this->_callbackEventParameter;
270
	}
271
 
272
	/**
273
	 * @param mixed postback event parameter
274
	 */
275
	public function setCallbackEventParameter($value)
276
	{
277
		$this->_callbackEventParameter=$value;
278
	}
279
 
280
	/**
281
	 * Gets the callback client script handler. It handlers the javascript functions
282
	 * to be executed during the callback response.
283
	 * @return TCallbackClientScript callback client handler.
284
	 */
285
	public function getCallbackClientHandler()
286
	{
287
		if(is_null($this->_callbackClient))
288
			$this->_callbackClient = new TCallbackClientScript;
289
		return $this->_callbackClient;
290
	}
291
}
292
 
293
/**
294
 * TCallbackErrorHandler class.
295
 *
296
 * Captures errors and exceptions and send them back during callback response.
297
 * When the application is in debug mode, the error and exception stack trace
298
 * are shown. A TJavascriptLogger must be present on the client-side to view
299
 * the error stack trace.
300
 *
301
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
302
 * @version $Id: TActivePageAdapter.php 2564 2008-11-11 21:56:02Z carlgmathisen $
303
 * @package System.Web.UI.ActiveControls
304
 * @since 3.1
305
 */
306
class TCallbackErrorHandler extends TErrorHandler
307
{
308
	/**
309
	 * Displays the exceptions to the client-side TJavascriptLogger.
310
	 * A HTTP 500 status code is sent and the stack trace is sent as JSON encoded.
311
	 * @param Exception exception details.
312
	 */
313
	protected function displayException($exception)
314
	{
315
		if($this->getApplication()->getMode()===TApplication::STATE_DEBUG)
316
		{
317
			$response = $this->getApplication()->getResponse();
318
			$trace = TJavaScript::jsonEncode($this->getExceptionStackTrace($exception));
319
			$response->appendHeader('HTTP/1.0 500 Internal Error');
320
			$response->appendHeader(TActivePageAdapter::CALLBACK_ERROR_HEADER.': '.$trace);
321
		}
322
		else
323
		{
324
			error_log("Error happened while processing an existing error:\n".$exception->__toString());
325
			header('HTTP/1.0 500 Internal Error');
326
		}
327
		$this->getApplication()->getResponse()->flush();
328
	}
329
 
330
	/**
331
	 * @param Exception exception details.
332
	 * @return array exception stack trace details.
333
	 */
334
	private function getExceptionStackTrace($exception)
335
	{
336
		$data['code']=$exception->getCode() > 0 ? $exception->getCode() : 500;
337
		$data['file']=$exception->getFile();
338
		$data['line']=$exception->getLine();
339
		$data['trace']=$exception->getTrace();
340
		if($exception instanceof TPhpErrorException)
341
		{
342
			// if PHP exception, we want to show the 2nd stack level context
343
			// because the 1st stack level is of little use (it's in error handler)
344
			if(isset($trace[0]) && isset($trace[0]['file']) && isset($trace[0]['line']))
345
			{
346
				$data['file']=$trace[0]['file'];
347
				$data['line']=$trace[0]['line'];
348
			}
349
		}
350
		$data['type']=get_class($exception);
351
		$data['message']=$exception->getMessage();
352
		$data['version']=$_SERVER['SERVER_SOFTWARE'].' '.Prado::getVersion();
353
		$data['time']=@strftime('%Y-%m-%d %H:%M',time());
354
		return $data;
355
	}
356
}
357
 
358
/**
359
 * TInvalidCallbackException class.
360
 *
361
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
362
 * @version $Id: TActivePageAdapter.php 2564 2008-11-11 21:56:02Z carlgmathisen $
363
 * @package System.Web.UI.ActiveControls
364
 * @since 3.1
365
 */
366
class TInvalidCallbackException extends TException
367
{
368
}
369