summaryrefslogtreecommitdiff
path: root/framework/PradoBase.php
blob: f6c0312fbb4ab9233c34ed0f18883e5ffcf2f02e (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
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
<?php
/**
 * PradoBase class file.
 *
 * This is the file that establishes the PRADO component model
 * and error handling mechanism.
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @link https://github.com/pradosoft/prado
 * @copyright Copyright &copy; 2005-2016 The PRADO Group
 * @license https://github.com/pradosoft/prado/blob/master/COPYRIGHT
 * @package System
 */

/**
 * Defines the PRADO framework installation path.
 */
if(!defined('PRADO_DIR'))
	define('PRADO_DIR',dirname(__FILE__));
/**
 * Defines the default permission for writable directories and files
 */
if(!defined('PRADO_CHMOD'))
	define('PRADO_CHMOD',0777);

/**
 * PradoBase class.
 *
 * PradoBase implements a few fundamental static methods.
 *
 * To use the static methods, Use Prado as the class name rather than PradoBase.
 * PradoBase is meant to serve as the base class of Prado. The latter might be
 * rewritten for customization.
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @package System
 * @since 3.0
 */
class PradoBase
{
	/**
	 * File extension for Prado class files.
	 */
	const CLASS_FILE_EXT='.php';
	/**
	 * @var array list of path aliases
	 */
	private static $_aliases=array('System'=>PRADO_DIR);
	/**
	 * @var array list of namespaces currently in use
	 */
	private static $_usings=array();
	/**
	 * @var TApplication the application instance
	 */
	private static $_application=null;
	/**
	 * @var TLogger logger instance
	 */
	private static $_logger=null;

	/**
	 * @var array list of class exists checks
	 */
	protected static $classExists = array();

	/**
	 * @return string the version of Prado framework
	 */
	public static function getVersion()
	{
		return '3.3.2';
	}

	/**
	 * Initializes error handlers.
	 * This method set error and exception handlers to be functions
	 * defined in this class.
	 */
	public static function initErrorHandlers()
	{
		/**
		 * Sets error handler to be Prado::phpErrorHandler
		 */
		set_error_handler(array('PradoBase','phpErrorHandler'));
		/**
		 * Sets shutdown function to be Prado::phpFatalErrorHandler
		 */
		register_shutdown_function(array('PradoBase','phpFatalErrorHandler'));
		/**
		 * Sets exception handler to be Prado::exceptionHandler
		 */
		set_exception_handler(array('PradoBase','exceptionHandler'));
		/**
		 * Disable php's builtin error reporting to avoid duplicated reports
		 */
		ini_set('display_errors', 0);
	}

	/**
	 * Class autoload loader.
	 * This method is provided to be invoked within an __autoload() magic method.
	 * @param string class name
	 */
	public static function autoload($className)
	{
		@include_once($className.self::CLASS_FILE_EXT);
	}

	/**
	 * @param integer the type of "powered logo". Valid values include 0 and 1.
	 * @return string a string that can be displayed on your Web page showing powered-by-PRADO information
	 */
	public static function poweredByPrado($logoType=0)
	{
		$logoName=$logoType==1?'powered2':'powered';
		if(self::$_application!==null)
		{
			$am=self::$_application->getAssetManager();
			$url=$am->publishFilePath(self::getPathOfNamespace('System.'.$logoName,'.gif'));
		}
		else
			$url='http://pradosoft.github.io/docs/'.$logoName.'.gif';
		return '<a title="Powered by PRADO" href="https://github.com/pradosoft/prado" target="_blank"><img src="'.$url.'" style="border-width:0px;" alt="Powered by PRADO" /></a>';
	}

	/**
	 * PHP error handler.
	 * This method should be registered as PHP error handler using
	 * {@link set_error_handler}. The method throws an exception that
	 * contains the error information.
	 * @param integer the level of the error raised
	 * @param string the error message
	 * @param string the filename that the error was raised in
	 * @param integer the line number the error was raised at
	 */
	public static function phpErrorHandler($errno,$errstr,$errfile,$errline)
	{
		if(error_reporting() & $errno)
			throw new TPhpErrorException($errno,$errstr,$errfile,$errline);
	}

	/**
	 * PHP shutdown function used to catch fatal errors.
	 * This method should be registered as PHP error handler using
	 * {@link register_shutdown_function}. The method throws an exception that
	 * contains the error information.
	 */
	public static function phpFatalErrorHandler()
	{
		$error = error_get_last();
		if($error && 
			TPhpErrorException::isFatalError($error) &&
			error_reporting() & $error['type'])
		{
			self::exceptionHandler(new TPhpErrorException($error['type'],$error['message'],$error['file'],$error['line']));
		}
	}

	/**
	 * Default exception handler.
	 * This method should be registered as default exception handler using
	 * {@link set_exception_handler}. The method tries to use the errorhandler
	 * module of the Prado application to handle the exception.
	 * If the application or the module does not exist, it simply echoes the
	 * exception.
	 * @param Exception exception that is not caught
	 */
	public static function exceptionHandler($exception)
	{
		if(self::$_application!==null && ($errorHandler=self::$_application->getErrorHandler())!==null)
		{
			$errorHandler->handleError(null,$exception);
		}
		else
		{
			echo $exception;
		}
		exit(1);
	}

	/**
	 * Stores the application instance in the class static member.
	 * This method helps implement a singleton pattern for TApplication.
	 * Repeated invocation of this method or the application constructor
	 * will cause the throw of an exception.
	 * This method should only be used by framework developers.
	 * @param TApplication the application instance
	 * @throws TInvalidOperationException if this method is invoked twice or more.
	 */
	public static function setApplication($application)
	{
		if(self::$_application!==null && !defined('PRADO_TEST_RUN'))
			throw new TInvalidOperationException('prado_application_singleton_required');
		self::$_application=$application;
	}

	/**
	 * @return TApplication the application singleton, null if the singleton has not be created yet.
	 */
	public static function getApplication()
	{
		return self::$_application;
	}

	/**
	 * @return string the path of the framework
	 */
	public static function getFrameworkPath()
	{
		return PRADO_DIR;
	}

	/**
	 * Creates a component with the specified type.
	 * A component type can be either the component class name
	 * or a namespace referring to the path of the component class file.
	 * For example, 'TButton', 'System.Web.UI.WebControls.TButton' are both
	 * valid component type.
	 * This method can also pass parameters to component constructors.
	 * All parameters passed to this method except the first one (the component type)
	 * will be supplied as component constructor parameters.
	 * @param string component type
	 * @return TComponent component instance of the specified type
	 * @throws TInvalidDataValueException if the component type is unknown
	 */
	public static function createComponent($type)
	{
		if(!isset(self::$classExists[$type]))
			self::$classExists[$type] = class_exists($type, false);

		if( !isset(self::$_usings[$type]) && !self::$classExists[$type]) {
			self::using($type);
			self::$classExists[$type] = class_exists($type, false);
		}

		if( ($pos = strrpos($type, '.')) !== false)
			$type = substr($type,$pos+1);

		if(($n=func_num_args())>1)
		{
			$args = func_get_args();
			switch($n) {
				case 2:
					return new $type($args[1]);
				break;
				case 3:
					return new $type($args[1], $args[2]);
				break;
				case 4:
					return new $type($args[1], $args[2], $args[3]);
				break;
				case 5:
					return new $type($args[1], $args[2], $args[3], $args[4]);
				break;
				default:
					$s='$args[1]';
					for($i=2;$i<$n;++$i)
						$s.=",\$args[$i]";
					eval("\$component=new $type($s);");
					return $component;
				break;
			}
		}
		else
			return new $type;
	}

	/**
	 * Uses a namespace.
	 * A namespace ending with an asterisk '*' refers to a directory, otherwise it represents a PHP file.
	 * If the namespace corresponds to a directory, the directory will be appended
	 * to the include path. If the namespace corresponds to a file, it will be included (include_once).
	 * @param string namespace to be used
	 * @param boolean whether to check the existence of the class after the class file is included
	 * @throws TInvalidDataValueException if the namespace is invalid
	 */
	public static function using($namespace,$checkClassExistence=true)
	{
		if(isset(self::$_usings[$namespace]) || class_exists($namespace,false))
			return;
		if(($pos=strrpos($namespace,'.'))===false)  // a class name
		{
			try
			{
				include_once($namespace.self::CLASS_FILE_EXT);
			}
			catch(Exception $e)
			{
				if($checkClassExistence && !class_exists($namespace,false))
					throw new TInvalidOperationException('prado_component_unknown',$namespace,$e->getMessage());
				else
					throw $e;
			}
		}
		else if(($path=self::getPathOfNamespace($namespace,self::CLASS_FILE_EXT))!==null)
		{
			$className=substr($namespace,$pos+1);
			if($className==='*')  // a directory
			{
				self::$_usings[$namespace]=$path;
				set_include_path(get_include_path().PATH_SEPARATOR.$path);
			}
			else  // a file
			{
				self::$_usings[$namespace]=$path;
				if(!$checkClassExistence || !class_exists($className,false))
				{
					try
					{
						include_once($path);
					}
					catch(Exception $e)
					{
						if($checkClassExistence && !class_exists($className,false))
							throw new TInvalidOperationException('prado_component_unknown',$className,$e->getMessage());
						else
							throw $e;
					}
				}
			}
		}
		else
			throw new TInvalidDataValueException('prado_using_invalid',$namespace);
	}

	/**
	 * Translates a namespace into a file path.
	 * The first segment of the namespace is considered as a path alias
	 * which is replaced with the actual path. The rest segments are
	 * subdirectory names appended to the aliased path.
	 * If the namespace ends with an asterisk '*', it represents a directory;
	 * Otherwise it represents a file whose extension name is specified by the second parameter (defaults to empty).
	 * Note, this method does not ensure the existence of the resulting file path.
	 * @param string namespace
	 * @param string extension to be appended if the namespace refers to a file
	 * @return string file path corresponding to the namespace, null if namespace is invalid
	 */
	public static function getPathOfNamespace($namespace, $ext='')
	{
		if(self::CLASS_FILE_EXT === $ext || empty($ext))
		{
			if(isset(self::$_usings[$namespace]))
				return self::$_usings[$namespace];

			if(isset(self::$_aliases[$namespace]))
				return self::$_aliases[$namespace];
		}

		$segs = explode('.',$namespace);
		$alias = array_shift($segs);

		if(null !== ($file = array_pop($segs)) && null !== ($root = self::getPathOfAlias($alias)))
			return rtrim($root.DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR ,$segs),'/\\').(($file === '*') ? '' : DIRECTORY_SEPARATOR.$file.$ext);

		return null;
	}

	/**
	 * @param string alias to the path
	 * @return string the path corresponding to the alias, null if alias not defined.
	 */
	public static function getPathOfAlias($alias)
	{
		return isset(self::$_aliases[$alias])?self::$_aliases[$alias]:null;
	}

	protected static function getPathAliases()
	{
		return self::$_aliases;
	}

	/**
	 * @param string alias to the path
	 * @param string the path corresponding to the alias
	 * @throws TInvalidOperationException if the alias is already defined
	 * @throws TInvalidDataValueException if the path is not a valid file path
	 */
	public static function setPathOfAlias($alias,$path)
	{
		if(isset(self::$_aliases[$alias]) && !defined('PRADO_TEST_RUN'))
			throw new TInvalidOperationException('prado_alias_redefined',$alias);
		else if(($rp=realpath($path))!==false && is_dir($rp))
		{
			if(strpos($alias,'.')===false)
				self::$_aliases[$alias]=$rp;
			else
				throw new TInvalidDataValueException('prado_aliasname_invalid',$alias);
		}
		else
			throw new TInvalidDataValueException('prado_alias_invalid',$alias,$path);
	}

	/**
	 * Fatal error handler.
	 * This method displays an error message together with the current call stack.
	 * The application will exit after calling this method.
	 * @param string error message
	 */
	public static function fatalError($msg)
	{
		echo '<h1>Fatal Error</h1>';
		echo '<p>'.$msg.'</p>';
		if(!function_exists('debug_backtrace'))
			return;
		echo '<h2>Debug Backtrace</h2>';
		echo '<pre>';
		$index=-1;
		foreach(debug_backtrace() as $t)
		{
			$index++;
			if($index==0)  // hide the backtrace of this function
				continue;
			echo '#'.$index.' ';
			if(isset($t['file']))
				echo basename($t['file']) . ':' . $t['line'];
			else
				 echo '<PHP inner-code>';
			echo ' -- ';
			if(isset($t['class']))
				echo $t['class'] . $t['type'];
			echo $t['function'] . '(';
			if(isset($t['args']) && sizeof($t['args']) > 0)
			{
				$count=0;
				foreach($t['args'] as $item)
				{
					if(is_string($item))
					{
						$str=htmlentities(str_replace("\r\n", "", $item), ENT_QUOTES);
						if (strlen($item) > 70)
							echo "'". substr($str, 0, 70) . "...'";
						else
							echo "'" . $str . "'";
					}
					else if (is_int($item) || is_float($item))
						echo $item;
					else if (is_object($item))
						echo get_class($item);
					else if (is_array($item))
						echo 'array(' . count($item) . ')';
					else if (is_bool($item))
						echo $item ? 'true' : 'false';
					else if ($item === null)
						echo 'NULL';
					else if (is_resource($item))
						echo get_resource_type($item);
					$count++;
					if (count($t['args']) > $count)
						echo ', ';
				}
			}
			echo ")\n";
		}
		echo '</pre>';
		exit(1);
	}

	/**
	 * Returns a list of user preferred languages.
	 * The languages are returned as an array. Each array element
	 * represents a single language preference. The languages are ordered
	 * according to user preferences. The first language is the most preferred.
	 * @return array list of user preferred languages.
	 */
	public static function getUserLanguages()
	{
		static $languages=null;
		if($languages===null)
		{
			if(!isset($_SERVER['HTTP_ACCEPT_LANGUAGE']))
				$languages[0]='en';
			else
			{
				$languages=array();
				foreach(explode(',',$_SERVER['HTTP_ACCEPT_LANGUAGE']) as $language)
				{
					$array=explode(';q=',trim($language));
					$languages[trim($array[0])]=isset($array[1])?(float)$array[1]:1.0;
				}
				arsort($languages);
				$languages=array_keys($languages);
				if(empty($languages))
					$languages[0]='en';
			}
		}
		return $languages;
	}

	/**
	 * Returns the most preferred language by the client user.
	 * @return string the most preferred language by the client user, defaults to English.
	 */
	public static function getPreferredLanguage()
	{
		static $language=null;
		if($language===null)
		{
			$langs=Prado::getUserLanguages();
			$lang=explode('-',$langs[0]);
			if(empty($lang[0]) || !ctype_alpha($lang[0]))
				$language='en';
			else
				$language=$lang[0];
		}
		return $language;
	}

	/**
	 * Writes a log message.
	 * This method wraps {@link log()} by checking the application mode.
	 * When the application is in Debug mode, debug backtrace information is appended
	 * to the message and the message is logged at DEBUG level.
	 * When the application is in Performance mode, this method does nothing.
	 * Otherwise, the message is logged at INFO level.
	 * @param string message to be logged
	 * @param string category of the message
	 * @param (string|TControl) control of the message
	 * @see log, getLogger
	 */
	public static function trace($msg,$category='Uncategorized',$ctl=null)
	{
		if(self::$_application && self::$_application->getMode()===TApplicationMode::Performance)
			return;
		if(!self::$_application || self::$_application->getMode()===TApplicationMode::Debug)
		{
			$trace=debug_backtrace();
			if(isset($trace[0]['file']) && isset($trace[0]['line']))
				$msg.=" (line {$trace[0]['line']}, {$trace[0]['file']})";
			$level=TLogger::DEBUG;
		}
		else
			$level=TLogger::INFO;
		self::log($msg,$level,$category,$ctl);
	}

	/**
	 * Logs a message.
	 * Messages logged by this method may be retrieved via {@link TLogger::getLogs}
	 * and may be recorded in different media, such as file, email, database, using
	 * {@link TLogRouter}.
	 * @param string message to be logged
	 * @param integer level of the message. Valid values include
	 * TLogger::DEBUG, TLogger::INFO, TLogger::NOTICE, TLogger::WARNING,
	 * TLogger::ERROR, TLogger::ALERT, TLogger::FATAL.
	 * @param string category of the message
	 * @param (string|TControl) control of the message
	 */
	public static function log($msg,$level=TLogger::INFO,$category='Uncategorized',$ctl=null)
	{
		if(self::$_logger===null)
			self::$_logger=new TLogger;
		self::$_logger->log($msg,$level,$category,$ctl);
	}

	/**
	 * @return TLogger message logger
	 */
	public static function getLogger()
	{
		if(self::$_logger===null)
			self::$_logger=new TLogger;
		return self::$_logger;
	}

	/**
	 * Converts a variable into a string representation.
	 * This method achieves the similar functionality as var_dump and print_r
	 * but is more robust when handling complex objects such as PRADO controls.
	 * @param mixed variable to be dumped
	 * @param integer maximum depth that the dumper should go into the variable. Defaults to 10.
	 * @param boolean whether to syntax highlight the output. Defaults to false.
	 * @return string the string representation of the variable
	 */
	public static function varDump($var,$depth=10,$highlight=false)
	{
		Prado::using('System.Util.TVarDumper');
		return TVarDumper::dump($var,$depth,$highlight);
	}

	/**
	 * Localize a text to the locale/culture specified in the globalization handler.
	 * @param string text to be localized.
	 * @param array a set of parameters to substitute.
	 * @param string a different catalogue to find the localize text.
	 * @param string the input AND output charset.
	 * @return string localized text.
	 * @see TTranslate::formatter()
	 * @see TTranslate::init()
	 */
	public static function localize($text, $parameters=array(), $catalogue=null, $charset=null)
	{
		Prado::using('System.I18N.Translation');
		$app = Prado::getApplication()->getGlobalization(false);

		$params = array();
		foreach($parameters as $key => $value)
			$params['{'.$key.'}'] = $value;

		//no translation handler provided
		if($app===null || ($config = $app->getTranslationConfiguration())===null)
			return strtr($text, $params);

		if ($catalogue===null)
			$catalogue=isset($config['catalogue'])?$config['catalogue']:'messages';

		Translation::init($catalogue);

		//globalization charset
		$appCharset = $app===null ? '' : $app->getCharset();

		//default charset
		$defaultCharset = ($app===null) ? 'UTF-8' : $app->getDefaultCharset();

		//fall back
		if(empty($charset)) $charset = $appCharset;
		if(empty($charset)) $charset = $defaultCharset;

		return Translation::formatter($catalogue)->format($text,$params,$catalogue,$charset);
	}
}


/**
 * Includes the classes essential for PradoBase class
 */
PradoBase::using('System.TComponent');
PradoBase::using('System.Exceptions.TException');
PradoBase::using('System.Util.TLogger');