summaryrefslogtreecommitdiff
path: root/framework/Web/UI/ActiveControls/TActivePageAdapter.php
blob: 67720afdc24548e11cd081336703da73eba8f199 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
<?php
/**
 * TActivePageAdapter class file
 *
 * @author Wei Zhuo <weizhuo[at]gamil[dot]com>
 * @link http://www.pradosoft.com/
 * @copyright Copyright &copy; 2005 PradoSoft
 * @license http://www.pradosoft.com/license/
 * @version $Revision: $  $Date: $
 * @package System.Web.UI.ActiveControls
 */
 
/**
 * TActivePageAdapter class.
 * 
 * Callback request page handler.
 * 
 * @author Wei Zhuo <weizhuo[at]gamil[dot]com>
 * @version $Revision: $  $Date: $
 * @package System.Web.UI.ActiveControls
 * @since 3.0
 */
class TActivePageAdapter extends TControlAdapter
{	
	const CALLBACK_DATA_HEADER = 'X-PRADO-DATA';
	const CALLBACK_ACTION_HEADER = 'X-PRADO-ACTIONS';
	const CALLBACK_ERROR_HEADER = 'X-PRADO-ERROR';
	const CALLBACK_PAGESTATE_HEADER = 'X-PRADO-PAGESTATE';
		
	/**
	 * @var ICallbackEventHandler callback event handler. 
	 */
	private $_callbackEventTarget;
	/**
	 * @var mixed callback event parameter.
	 */
	private $_callbackEventParameter;
	/**
	 * @var TCallbackClientScript callback client script handler
	 */
	private $_callbackClient;
	/**
	 * @var TCallbackEventParameter callback result.
	 */
	private $_result;
	 
	/**
	 * Constructor, trap errors and exception to let the callback response
	 * handle them.
	 */
	public function __construct(TPage $control)
	{
		parent::__construct($control);
		$this->getApplication()->setResponse($this->createCallbackResponseHandler());
		$this->trapCallbackErrorsExceptions();
	}

	/**
	 * Process the callback request.
	 */
	public function processCallbackEvent($writer)
	{
		Prado::trace("ActivePage raiseCallbackEvent()",'System.Web.UI.ActiveControls.TActivePageAdapter');
		$this->raiseCallbackEvent();
	}
	
	protected function trapCallbackErrorsExceptions()
	{
		$this->getApplication()->setErrorHandler(new TCallbackErrorHandler);
	}
	
	/**
	 * Render the callback response.
	 */
	public function renderCallbackResponse($writer)
	{
		Prado::trace("ActivePage renderCallbackResponse()",'System.Web.UI.ActiveControls.TActivePageAdapter');
		$this->renderResponse($writer);
		//$this->getResponse()->flush();
	}	
	
	/**
	 * Renders the callback response by adding additional callback data and
	 * javascript actions in the header and page state if required.
	 */
	protected function renderResponse($writer)
	{
		$response = $this->getResponse();
		$executeJavascript = $this->getCallbackClientHandler()->getClientFunctionsToExecute()->toArray();
		$actions = TJavascript::jsonEncode($executeJavascript);
		$response->appendHeader(self::CALLBACK_ACTION_HEADER.': '.$actions);
		if($this->_result)
		{
			$data = TJavascript::jsonEncode($this->_result->getData());
			$response->appendHeader(self::CALLBACK_DATA_HEADER.': '.$data);
		}
		if(($handler = $this->getCallbackEventTarget()) !== null)
		{
			if($handler->getClientSide()->getEnablePageStateUpdate())
			{
				$pagestate = $this->getPage()->getClientState();
				$response->appendHeader(self::CALLBACK_PAGESTATE_HEADER.': '.$pagestate);
			}
		}
	}
	
	/**
	 * Trys to find the callback event handler and raise its callback event.
	 * @throws TInvalidCallbackRequestException if call back target is not found.
	 * @throws TInvalidCallbackHandlerException if the requested target does not
	 * implement ICallbackEventHandler.
	 */
	private function raiseCallbackEvent()
	{
		 if(($callbackHandler=$this->getCallbackEventTarget())!==null)
		 {
			if($callbackHandler instanceof ICallbackEventHandler)
			{
				$param = $this->getCallbackEventParameter();
				$this->_result = new TCallbackEventParameter($this->getResponse(), $param);
				$callbackHandler->raiseCallbackEvent($this->_result);
			}
			else
			{
				throw new TInvalidCallbackHandlerException($callbackHandler->getUniqueID());
			}
		 }
		 else
		 {
		 	$target = $this->getRequest()->itemAt(TPage::FIELD_CALLBACK_TARGET);
		 	throw new TInvalidCallbackRequestException($target);
		 }
	}
	
	/**
	 * @return mixed callback event result.
	 */
	public function getCallbackEventResult()
	{
		return $this->_callbackEventResult->getResult();
	}
	
	/**
	 * @return TControl the control responsible for the current callback event,
	 * null if nonexistent
	 */
	public function getCallbackEventTarget()
	{
		if($this->_callbackEventTarget===null)
		{
			$eventTarget=$this->getRequest()->itemAt(TPage::FIELD_CALLBACK_TARGET);
			if(!empty($eventTarget))
				$this->_callbackEventTarget=$this->getPage()->findControl($eventTarget);
		}
		return $this->_callbackEventTarget;
	}

	/**
	 * Registers a control to raise callback event in the current request.
	 * @param TControl control registered to raise callback event.
	 */
	public function setCallbackEventTarget(TControl $control)
	{
		$this->_callbackEventTarget=$control;
	}

	/**
	 * Callback parameter is decoded assuming JSON encoding. 
	 * @return string postback event parameter
	 */
	public function getCallbackEventParameter()
	{
		if($this->_callbackEventParameter===null)
		{
			$param = $this->getRequest()->itemAt(TPage::FIELD_CALLBACK_PARAMETER);
			if(strlen($param) > 0)
				$this->_callbackEventParameter=TJavascript::jsonDecode((string)$param);
		}
		return $this->_callbackEventParameter;
	}
	
	/**
	 * @param mixed postback event parameter
	 */
	public function setCallbackEventParameter($value)
	{
		$this->_callbackEventParameter=$value;
	}
	
	/**
	 * Gets the callback client script handler that allows javascript functions
	 * to be executed during the callback response. 
	 * @return TCallbackClientScript callback client handler.
	 */
	public function getCallbackClientHandler()
	{
		if(is_null($this->_callbackClient))
			$this->_callbackClient = new TCallbackClientScript;
		return $this->_callbackClient;
	}
	
	/**
	 * @param TCallbackClientScript new callback client handler.
	 */
	public function setCallbackClientHandler($handler)
	{
		$this->_callbackClient = $handler;
	}
	
	protected function createCallbackResponseHandler()
	{
		return new TCallbackResponse();
	}
	
}

/**
 * TCallbackEventParameter class.
 * 
 * The TCallbackEventParameter provides the parameter passed during the callback
 * requestion in the {@link getParameter Parameter} property. The
 * callback response response content (e.g. new HTML content) can be written to
 * the {@link getOutput Output} property, which returns an instance of
 * THtmlWriter. The response data (i.e., passing results back to the client-side
 * callback handler function) can be set using {@link setData Data} property. 
 * 
 * @author Wei Zhuo <weizhuo[at]gamil[dot]com>
 * @version $Revision: $  $Date: $
 * @package System.Web.UI.ActiveControls
 * @since 3.0
 */
class TCallbackEventParameter extends TEventParameter
{
	/**
	 * @var TCallbackResponse output content.
	 */
	private $_response;
	/**
	 * @var mixed callback request parameter.
	 */
	private $_parameter;
	/**
	 * @var mixed callback response data.
	 */
	private $_data;

	/**
	 * Creates a new TCallbackEventParameter.
	 */
	public function __construct($response, $parameter)
	{
		$this->_response = $response;
		$this->_parameter = $parameter;
	}

	/**
	 * @return THtmlWriter holds the response content.
	 */
	public function getOutput()
	{
		return $this->_response->createHtmlWriter();
	}
	
	/**
	 * @return mixed callback request parameter.
	 */
	public function getParameter()
	{
		return $this->_parameter;
	}
	
	/**
	 * @param mixed callback response data.
	 */
	public function setData($value)
	{
		$this->_data = $value;
	}
	
	/**
	 * @return mixed callback response data.
	 */
	public function getData()
	{
		return $this->_data;
	}
}

class TCallbackErrorHandler extends TErrorHandler
{
	protected function displayException($exception)
	{
		if($this->getApplication()->getMode()===TApplication::STATE_DEBUG)
		{
			$response = $this->getApplication()->getResponse();
			$data = TJavascript::jsonEncode($this->getExceptionData($exception));			
			$response->appendHeader('HTTP/1.0 500 Internal Error');
			$response->appendHeader(TActivePageAdapter::CALLBACK_ERROR_HEADER.': '.$data);
		}
		else
		{
			error_log("Error happened while processing an existing error:\n".$exception->__toString());
			header('HTTP/1.0 500 Internal Error');
		}
		$this->getApplication()->getResponse()->flush();
	}
	
	private function getExceptionData($exception)
	{
		$data['code']=$exception->getCode() > 0 ? $exception->getCode() : 505;
		$data['file']=$exception->getFile();
		$data['line']=$exception->getLine();
		$data['trace']=$exception->getTrace();
		if($exception instanceof TPhpErrorException)
		{
			// if PHP exception, we want to show the 2nd stack level context
			// because the 1st stack level is of little use (it's in error handler)
			if(isset($trace[0]) && isset($trace[0]['file']) && isset($trace[0]['line']))
			{
				$data['file']=$trace[0]['file'];
				$data['line']=$trace[0]['line'];
			}
		}
		$data['type']=get_class($exception);
		$data['message']=$exception->getMessage();
		$data['version']=$_SERVER['SERVER_SOFTWARE'].' '.Prado::getVersion();
		$data['time']=@strftime('%Y-%m-%d %H:%M',time());
		return $data;
	}
}

class TInvalidCallbackHandlerException extends TException
{
	
} 

class TInvalidCallbackRequestException extends TException
{
}

?>