root/trunk/framework/PradoBase.php

Revision 2519, 18.8 kB (checked in by carl, 5 weeks ago)

#913 - PRADO Copyright notice in HTML source

  • Property svn:keywords set to Id
Line 
1<?php
2/**
3 * PradoBase class file.
4 *
5 * This is the file that establishes the PRADO component model
6 * and error handling mechanism.
7 *
8 * @author Qiang Xue <qiang.xue@gmail.com>
9 * @link http://www.pradosoft.com/
10 * @copyright Copyright &copy; 2005-2008 PradoSoft
11 * @license http://www.pradosoft.com/license/
12 * @version $Id$
13 * @package System
14 */
15
16/**
17 * Defines the PRADO framework installation path.
18 */
19if(!defined('PRADO_DIR'))
20    define('PRADO_DIR',dirname(__FILE__));
21/**
22 * Defines the default permission for writable directories and files
23 */
24if(!defined('PRADO_CHMOD'))
25    define('PRADO_CHMOD',0777);
26
27/**
28 * PradoBase class.
29 *
30 * PradoBase implements a few fundamental static methods.
31 *
32 * To use the static methods, Use Prado as the class name rather than PradoBase.
33 * PradoBase is meant to serve as the base class of Prado. The latter might be
34 * rewritten for customization.
35 *
36 * @author Qiang Xue <qiang.xue@gmail.com>
37 * @version $Id$
38 * @package System
39 * @since 3.0
40 */
41class PradoBase
42{
43    /**
44     * File extension for Prado class files.
45     */
46    const CLASS_FILE_EXT='.php';
47    /**
48     * @var array list of path aliases
49     */
50    private static $_aliases=array('System'=>PRADO_DIR);
51    /**
52     * @var array list of namespaces currently in use
53     */
54    private static $_usings=array();
55    /**
56     * @var TApplication the application instance
57     */
58    private static $_application=null;
59    /**
60     * @var TLogger logger instance
61     */
62    private static $_logger=null;
63
64    /**
65     * @return string the version of Prado framework
66     */
67    public static function getVersion()
68    {
69        return '3.1.3a';
70    }
71
72    /**
73     * Initializes error handlers.
74     * This method set error and exception handlers to be functions
75     * defined in this class.
76     */
77    public static function initErrorHandlers()
78    {
79        /**
80         * Sets error handler to be Prado::phpErrorHandler
81         */
82        set_error_handler(array('PradoBase','phpErrorHandler'),error_reporting());
83        /**
84         * Sets exception handler to be Prado::exceptionHandler
85         */
86        set_exception_handler(array('PradoBase','exceptionHandler'));
87    }
88
89    /**
90     * Class autoload loader.
91     * This method is provided to be invoked within an __autoload() magic method.
92     * @param string class name
93     */
94    public static function autoload($className)
95    {
96        include_once($className.self::CLASS_FILE_EXT);
97        if(!class_exists($className,false) && !interface_exists($className,false))
98            self::fatalError("Class file for '$className' cannot be found.");
99    }
100
101    /**
102     * @param integer the type of "powered logo". Valid values include 0 and 1.
103     * @return string a string that can be displayed on your Web page showing powered-by-PRADO information
104     */
105    public static function poweredByPrado($logoType=0)
106    {
107        $logoName=$logoType==1?'powered2':'powered';
108        if(self::$_application!==null)
109        {
110            $am=self::$_application->getAssetManager();
111            $url=$am->publishFilePath(self::getPathOfNamespace('System.'.$logoName,'.gif'));
112        }
113        else
114            $url='http://www.pradosoft.com/images/'.$logoName.'.gif';
115        return '<a title="Powered by PRADO" href="http://www.pradosoft.com/" target="_blank"><img src="'.$url.'" style="border-width:0px;" alt="Powered by PRADO" /></a>';
116    }
117   
118    public static function metaGenerator()
119    {
120        return 'PRADO - http://www.pradosoft.com/';
121    }
122
123    /**
124     * PHP error handler.
125     * This method should be registered as PHP error handler using
126     * {@link set_error_handler}. The method throws an exception that
127     * contains the error information.
128     * @param integer the level of the error raised
129     * @param string the error message
130     * @param string the filename that the error was raised in
131     * @param integer the line number the error was raised at
132     */
133    public static function phpErrorHandler($errno,$errstr,$errfile,$errline)
134    {
135        if(error_reporting()!=0)
136            throw new TPhpErrorException($errno,$errstr,$errfile,$errline);
137    }
138
139    /**
140     * Default exception handler.
141     * This method should be registered as default exception handler using
142     * {@link set_exception_handler}. The method tries to use the errorhandler
143     * module of the Prado application to handle the exception.
144     * If the application or the module does not exist, it simply echoes the
145     * exception.
146     * @param Exception exception that is not caught
147     */
148    public static function exceptionHandler($exception)
149    {
150        if(self::$_application!==null && ($errorHandler=self::$_application->getErrorHandler())!==null)
151        {
152            $errorHandler->handleError(null,$exception);
153        }
154        else
155        {
156            echo $exception;
157        }
158        exit(1);
159    }
160
161    /**
162     * Stores the application instance in the class static member.
163     * This method helps implement a singleton pattern for TApplication.
164     * Repeated invocation of this method or the application constructor
165     * will cause the throw of an exception.
166     * This method should only be used by framework developers.
167     * @param TApplication the application instance
168     * @throws TInvalidOperationException if this method is invoked twice or more.
169     */
170    public static function setApplication($application)
171    {
172        if(self::$_application!==null)
173            throw new TInvalidOperationException('prado_application_singleton_required');
174        self::$_application=$application;
175    }
176
177    /**
178     * @return TApplication the application singleton, null if the singleton has not be created yet.
179     */
180    public static function getApplication()
181    {
182        return self::$_application;
183    }
184
185    /**
186     * @return string the path of the framework
187     */
188    public static function getFrameworkPath()
189    {
190        return PRADO_DIR;
191    }
192
193    /**
194     * Serializes a data.
195     * The original PHP serialize function has a bug that may not serialize
196     * properly an object.
197     * @param mixed data to be serialized
198     * @return string the serialized data
199     */
200    public static function serialize($data)
201    {
202        $arr[0]=$data;
203        return serialize($arr);
204    }
205
206    /**
207     * Unserializes a data.
208     * The original PHP unserialize function has a bug that may not unserialize
209     * properly an object.
210     * @param string data to be unserialized
211     * @return mixed unserialized data, null if unserialize failed
212     */
213    public static function unserialize($str)
214    {
215        $arr=unserialize($str);
216        return isset($arr[0])?$arr[0]:null;
217    }
218
219    /**
220     * Creates a component with the specified type.
221     * A component type can be either the component class name
222     * or a namespace referring to the path of the component class file.
223     * For example, 'TButton', 'System.Web.UI.WebControls.TButton' are both
224     * valid component type.
225     * This method can also pass parameters to component constructors.
226     * All parameters passed to this method except the first one (the component type)
227     * will be supplied as component constructor parameters.
228     * @param string component type
229     * @return TComponent component instance of the specified type
230     * @throws TInvalidDataValueException if the component type is unknown
231     */
232    public static function createComponent($type)
233    {
234        self::using($type);
235        if(($pos=strrpos($type,'.'))!==false)
236            $type=substr($type,$pos+1);
237        if(($n=func_num_args())>1)
238        {
239            $args=func_get_args();
240            $s='$args[1]';
241            for($i=2;$i<$n;++$i)
242                $s.=",\$args[$i]";
243            eval("\$component=new $type($s);");
244            return $component;
245        }
246        else
247            return new $type;
248    }
249
250    /**
251     * Uses a namespace.
252     * A namespace ending with an asterisk '*' refers to a directory, otherwise it represents a PHP file.
253     * If the namespace corresponds to a directory, the directory will be appended
254     * to the include path. If the namespace corresponds to a file, it will be included (include_once).
255     * @param string namespace to be used
256     * @param boolean whether to check the existence of the class after the class file is included
257     * @throws TInvalidDataValueException if the namespace is invalid
258     */
259    public static function using($namespace,$checkClassExistence=true)
260    {
261        if(isset(self::$_usings[$namespace]) || class_exists($namespace,false))
262            return;
263        if(($pos=strrpos($namespace,'.'))===false// a class name
264        {
265            try
266            {
267                include_once($namespace.self::CLASS_FILE_EXT);
268            }
269            catch(Exception $e)
270            {
271                if($checkClassExistence && !class_exists($namespace,false))
272                    throw new TInvalidOperationException('prado_component_unknown',$namespace,$e->getMessage());
273                else
274                    throw $e;
275            }
276        }
277        else if(($path=self::getPathOfNamespace($namespace,self::CLASS_FILE_EXT))!==null)
278        {
279            $className=substr($namespace,$pos+1);
280            if($className==='*'// a directory
281            {
282                self::$_usings[$namespace]=$path;
283                set_include_path(get_include_path().PATH_SEPARATOR.$path);
284            }
285            else  // a file
286            {
287                self::$_usings[$namespace]=$path;
288                if(!$checkClassExistence || !class_exists($className,false))
289                {
290                    try
291                    {
292                        include_once($path);
293                    }
294                    catch(Exception $e)
295                    {
296                        if($checkClassExistence && !class_exists($className,false))
297                            throw new TInvalidOperationException('prado_component_unknown',$className,$e->getMessage());
298                        else
299                            throw $e;
300                    }
301                }
302            }
303        }
304        else
305            throw new TInvalidDataValueException('prado_using_invalid',$namespace);
306    }
307
308    /**
309     * Translates a namespace into a file path.
310     * The first segment of the namespace is considered as a path alias
311     * which is replaced with the actual path. The rest segments are
312     * subdirectory names appended to the aliased path.
313     * If the namespace ends with an asterisk '*', it represents a directory;
314     * Otherwise it represents a file whose extension name is specified by the second parameter (defaults to empty).
315     * Note, this method does not ensure the existence of the resulting file path.
316     * @param string namespace
317     * @param string extension to be appended if the namespace refers to a file
318     * @return string file path corresponding to the namespace, null if namespace is invalid
319     */
320    public static function getPathOfNamespace($namespace,$ext='')
321    {
322        if(isset(self::$_usings[$namespace]))
323            return self::$_usings[$namespace];
324        else if(isset(self::$_aliases[$namespace]))
325            return self::$_aliases[$namespace];
326        else
327        {
328            $segs=explode('.',$namespace);
329            $alias=array_shift($segs);
330            if(($file=array_pop($segs))!==null && ($root=self::getPathOfAlias($alias))!==null)
331                return rtrim($root.DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR ,$segs),'/\\').(($file==='*')?'':DIRECTORY_SEPARATOR.$file.$ext);
332            else
333                return null;
334        }
335    }
336
337    /**
338     * @param string alias to the path
339     * @return string the path corresponding to the alias, null if alias not defined.
340     */
341    public static function getPathOfAlias($alias)
342    {
343        return isset(self::$_aliases[$alias])?self::$_aliases[$alias]:null;
344    }
345
346    protected static function getPathAliases()
347    {
348        return self::$_aliases;
349    }
350
351    /**
352     * @param string alias to the path
353     * @param string the path corresponding to the alias
354     * @throws TInvalidOperationException if the alias is already defined
355     * @throws TInvalidDataValueException if the path is not a valid file path
356     */
357    public static function setPathOfAlias($alias,$path)
358    {
359        if(isset(self::$_aliases[$alias]))
360            throw new TInvalidOperationException('prado_alias_redefined',$alias);
361        else if(($rp=realpath($path))!==false && is_dir($rp))
362        {
363            if(strpos($alias,'.')===false)
364                self::$_aliases[$alias]=$rp;
365            else
366                throw new TInvalidDataValueException('prado_aliasname_invalid',$alias);
367        }
368        else
369            throw new TInvalidDataValueException('prado_alias_invalid',$alias,$path);
370    }
371
372    /**
373     * Fatal error handler.
374     * This method displays an error message together with the current call stack.
375     * The application will exit after calling this method.
376     * @param string error message
377     */
378    public static function fatalError($msg)
379    {
380        echo '<h1>Fatal Error</h1>';
381        echo '<p>'.$msg.'</p>';
382        if(!function_exists('debug_backtrace'))
383            return;
384        echo '<h2>Debug Backtrace</h2>';
385        echo '<pre>';
386        $index=-1;
387        foreach(debug_backtrace() as $t)
388        {
389            $index++;
390            if($index==0// hide the backtrace of this function
391                continue;
392            echo '#'.$index.' ';
393            if(isset($t['file']))
394                echo basename($t['file']) . ':' . $t['line'];
395            else
396               echo '<PHP inner-code>';
397            echo ' -- ';
398            if(isset($t['class']))
399                echo $t['class'] . $t['type'];
400            echo $t['function'] . '(';
401            if(isset($t['args']) && sizeof($t['args']) > 0)
402            {
403                $count=0;
404                foreach($t['args'] as $item)
405                {
406                    if(is_string($item))
407                    {
408                        $str=htmlentities(str_replace("\r\n", "", $item), ENT_QUOTES);
409                        if (strlen($item) > 70)
410                            echo "'". substr($str, 0, 70) . "...'";
411                        else
412                            echo "'" . $str . "'";
413                    }
414                    else if (is_int($item) || is_float($item))
415                        echo $item;
416                    else if (is_object($item))
417                        echo get_class($item);
418                    else if (is_array($item))
419                        echo 'array(' . count($item) . ')';
420                    else if (is_bool($item))
421                        echo $item ? 'true' : 'false';
422                    else if ($item === null)
423                        echo 'NULL';
424                    else if (is_resource($item))
425                        echo get_resource_type($item);
426                    $count++;
427                    if (count($t['args']) > $count)
428                        echo ', ';
429                }
430            }
431            echo ")\n";
432        }
433        echo '</pre>';
434        exit(1);
435    }
436
437    /**
438     * Returns a list of user preferred languages.
439     * The languages are returned as an array. Each array element
440     * represents a single language preference. The languages are ordered
441     * according to user preferences. The first language is the most preferred.
442     * @return array list of user preferred languages.
443     */
444    public static function getUserLanguages()
445    {
446        static $languages=null;
447        if($languages===null)
448        {
449            if(!isset($_SERVER['HTTP_ACCEPT_LANGUAGE']))
450                $languages[0]='en';
451            else
452            {
453                $languages=array();
454                foreach(explode(',',$_SERVER['HTTP_ACCEPT_LANGUAGE']) as $language)
455                {
456                    $array=split(';q=',trim($language));
457                    $languages[trim($array[0])]=isset($array[1])?(float)$array[1]:1.0;
458                }
459                arsort($languages);
460                $languages=array_keys($languages);
461                if(empty($languages))
462                    $languages[0]='en';
463            }
464        }
465        return $languages;
466    }
467
468    /**
469     * Returns the most preferred language by the client user.
470     * @return string the most preferred language by the client user, defaults to English.
471     */
472    public static function getPreferredLanguage()
473    {
474        static $language=null;
475        if($language===null)
476        {
477            $langs=Prado::getUserLanguages();
478            $lang=explode('-',$langs[0]);
479            if(empty($lang[0]) || !ctype_alpha($lang[0]))
480                $language='en';
481            else
482                $language=$lang[0];
483        }
484        return $language;
485    }
486
487    /**
488     * Writes a log message.
489     * This method wraps {@link log()} by checking the application mode.
490     * When the application is in Debug mode, debug backtrace information is appended
491     * to the message and the message is logged at DEBUG level.
492     * When the application is in Performance mode, this method does nothing.
493     * Otherwise, the message is logged at INFO level.
494     * @param string message to be logged
495     * @param string category of the message
496     * @see log, getLogger
497     */
498    public static function trace($msg,$category='Uncategorized')
499    {
500        if(self::$_application && self::$_application->getMode()===TApplicationMode::Performance)
501            return;
502        if(!self::$_application || self::$_application->getMode()===TApplicationMode::Debug)
503        {
504            $trace=debug_backtrace();
505            if(isset($trace[0]['file']) && isset($trace[0]['line']))
506                $msg.=" (line {$trace[0]['line']}, {$trace[0]['file']})";
507            $level=TLogger::DEBUG;
508        }
509        else
510            $level=TLogger::INFO;
511        self::log($msg,$level,$category);
512    }
513
514    /**
515     * Logs a message.
516     * Messages logged by this method may be retrieved via {@link TLogger::getLogs}
517     * and may be recorded in different media, such as file, email, database, using
518     * {@link TLogRouter}.
519     * @param string message to be logged
520     * @param integer level of the message. Valid values include
521     * TLogger::DEBUG, TLogger::INFO, TLogger::NOTICE, TLogger::WARNING,
522     * TLogger::ERROR, TLogger::ALERT, TLogger::FATAL.
523     * @param string category of the message
524     */
525    public static function log($msg,$level=TLogger::INFO,$category='Uncategorized')
526    {
527        if(self::$_logger===null)
528            self::$_logger=new TLogger;
529        self::$_logger->log($msg,$level,$category);
530    }
531
532    /**
533     * @return TLogger message logger
534     */
535    public static function getLogger()
536    {
537        if(self::$_logger===null)
538            self::$_logger=new TLogger;
539        return self::$_logger;
540    }
541
542    /**
543     * Converts a variable into a string representation.
544     * This method achieves the similar functionality as var_dump and print_r
545     * but is more robust when handling complex objects such as PRADO controls.
546     * @param mixed variable to be dumped
547     * @param integer maximum depth that the dumper should go into the variable. Defaults to 10.
548     * @param boolean whether to syntax highlight the output. Defaults to false.
549     * @return string the string representation of the variable
550     */
551    public static function varDump($var,$depth=10,$highlight=false)
552    {
553        Prado::using('System.Util.TVarDumper');
554        return TVarDumper::dump($var,$depth,$highlight);
555    }
556
557    /**
558     * Localize a text to the locale/culture specified in the globalization handler.
559     * @param string text to be localized.
560     * @param array a set of parameters to substitute.
561     * @param string a different catalogue to find the localize text.
562     * @param string the input AND output charset.
563     * @return string localized text.
564     * @see TTranslate::formatter()
565     * @see TTranslate::init()
566     */
567    public static function localize($text, $parameters=array(), $catalogue=null, $charset=null)
568    {
569        Prado::using('System.I18N.Translation');
570        $app = Prado::getApplication()->getGlobalization(false);
571
572        $params = array();
573        foreach($parameters as $key => $value)
574            $params['{'.$key.'}'] = $value;
575
576        //no translation handler provided
577        if($app===null || ($config = $app->getTranslationConfiguration())===null)
578            return strtr($text, $params);
579
580        if ($catalogue===null)
581            $catalogue=isset($config['catalogue'])?$config['catalogue']:'messages';
582           
583        Translation::init($catalogue);
584
585        //globalization charset
586        $appCharset = $app===null ? '' : $app->getCharset();
587
588        //default charset
589        $defaultCharset = ($app===null) ? 'UTF-8' : $app->getDefaultCharset();
590
591        //fall back
592        if(empty($charset)) $charset = $appCharset;
593