PageRenderTime 97ms CodeModel.GetById 3ms app.highlight 80ms RepoModel.GetById 1ms app.codeStats 1ms

/core/classes/core.class.php

https://github.com/lewisliud/myqee
PHP | 2418 lines | 1663 code | 276 blank | 479 comment | 229 complexity | ebf3aecb88d40dc69b15c4ee00a5d398 MD5 | raw file
   1<?php
   2
   3/**
   4 * 返回一个静态资源的URL路径. Core::url_assets() 的别名
   5 *
   6 *   echo url_assets('js/global.js');
   7 *
   8 * @param string $uri
   9 */
  10function url_assets($uri='')
  11{
  12    return Core::url_assets($uri);
  13}
  14
  15/**
  16 * 返回一个URL. Core::url() 的别名
  17 *
  18 *   echo url();    //返回首页地址
  19 *
  20 * @param string $uri
  21 * @return string
  22 */
  23function url($uri='')
  24{
  25    return Core::url($uri);
  26}
  27
  28/**
  29 * 读取配置数据. Core::config() 的别名
  30 *
  31 *   echo config('core');    //返回核心配置
  32 *
  33 * @param string $key
  34 * @return string
  35 */
  36function config($key=null)
  37{
  38    return Core::config($key);
  39}
  40
  41
  42/**
  43 * MyQEE 核心类
  44 *
  45 * @author     呼吸二氧化碳 <jonwang@myqee.com>
  46 * @category   MyQEE
  47 * @package    System
  48 * @subpackage Core
  49 * @copyright  Copyright (c) 2008-2013 myqee.com
  50 * @license    http://www.myqee.com/license.html
  51 */
  52abstract class Core_Core extends Bootstrap
  53{
  54    /**
  55     * MyQEE版本号
  56     *
  57     * @var string
  58     */
  59    const VERSION = '3.0';
  60
  61    /**
  62     * 版本发布状态
  63     *
  64     * stable, rc1, rc2, beta1, beta2, ...
  65     *
  66     * @var string
  67     */
  68    const RELEASE  = 'rc2';
  69
  70    /**
  71     * 项目开发者
  72     *
  73     * @var string
  74     */
  75    const CODER = '呼吸二氧化碳 <jonwang@myqee.com>';
  76
  77    /**
  78     * 页面编码
  79     *
  80     * @var string
  81     */
  82    public static $charset = 'utf-8';
  83
  84    /**
  85     * 页面传入的PATHINFO参数
  86     *
  87     * @var array
  88     */
  89    public static $arguments = array();
  90
  91    /**
  92     * 页面输出内容
  93     *
  94     * @var string
  95     */
  96    public static $output = '';
  97
  98    /**
  99     * 自动加载时各个文件夹后缀
 100     *
 101     * 例如,classes目录为.class,则文件实际后缀为.class.php
 102     * 注意,只有对EXT常理设置的后缀文件有效,默认情况下EXT=.php
 103     *
 104     * @var array
 105     */
 106    public static $autoload_dir_ext = array
 107    (
 108        'config'       => '.config',
 109        'classes'      => '.class',
 110        'controllers'  => '.controller',
 111        'models'       => '.model',
 112        'orm'          => '.orm',
 113        'views'        => '.view',
 114    );
 115
 116    /**
 117     * 缓冲区包含数
 118     * @var int
 119     */
 120    protected static $buffer_level = 0;
 121
 122    /**
 123     * 页面在关闭前需要执行的方法列队
 124     * 通过Core::register_shutdown_function()设置
 125     * @var array
 126     */
 127    protected static $shutdown_function = array();
 128
 129    /**
 130     * getFactory获取的对象寄存器
 131     * @var array
 132     */
 133    protected static $instances = array();
 134
 135    /**
 136     * 执行Core::close_all_connect()方法时会关闭链接的类和方法名的列队
 137     *
 138     * 可通过Core::add_close_connect_class()方法进行设置增加
 139     *
 140     *     array
 141     *     (
 142     *         'Database' => 'close_all_connect',
 143     *     );
 144     *
 145     * @var array
 146     */
 147    protected static $close_connect_class_list = array();
 148
 149    /**
 150     * import_library回调函数列表
 151     *
 152     * @var array
 153     */
 154    protected static $import_library_callback = array();
 155
 156    /**
 157     * change_project回调函数列表
 158     *
 159     * @var array
 160     */
 161    protected static $change_project_callback = array();
 162
 163    /**
 164     * 使用 Core::url() 会附带的参数列表
 165     *
 166     * @var array
 167     */
 168    protected static $_url_auto_args = array();
 169
 170    /**
 171     * 系统启动
 172     *
 173     * @param boolean $auto_execute 是否直接运行
 174     */
 175    public static function setup($auto_execute = true)
 176    {
 177        static $run = null;
 178
 179        if (null===$run)
 180        {
 181            $run = true;
 182
 183            Core::$charset = Core::$config['charset'];
 184
 185            if (!IS_CLI)
 186            {
 187                # 输出powered by信息
 188                $x_powered_by = (isset(Core::$config['hide_x_powered_by_header']) && Core::$config['hide_x_powered_by_header']) ? Core::$config['hide_x_powered_by_header'] : false;
 189
 190                if (is_string($x_powered_by))
 191                {
 192                    $str = 'X-Powered-By: ' . trim(str_replace(array("\r", "\n"), '', $x_powered_by));
 193                }
 194                else if (!$x_powered_by)
 195                {
 196                    $str = 'X-Powered-By: PHP/' . PHP_VERSION . ' MyQEE/' . Core::VERSION .'('. Core::RELEASE .')';
 197                }
 198                else
 199                {
 200                    $str = null;
 201                }
 202
 203                if ($str)
 204                {
 205                    header($str);
 206                }
 207            }
 208
 209            if (IS_DEBUG)
 210            {
 211                Core::debug()->info('SERVER IP:' . $_SERVER["SERVER_ADDR"] . (function_exists('php_uname')?'. SERVER NAME:' . php_uname('a') : ''));
 212
 213                if (Core::$project)
 214                {
 215                    Core::debug()->info('project: ' . Core::$project);
 216                }
 217
 218                if (IS_ADMIN_MODE)
 219                {
 220                    Core::debug()->info('admin mode');
 221                }
 222
 223                if (IS_REST_MODE)
 224                {
 225                    Core::debug()->info('RESTFul mode');
 226                }
 227
 228                Core::debug()->group('include path');
 229                foreach ( Core::include_path() as $value )
 230                {
 231                    Core::debug()->log(Core::debug_path($value));
 232                }
 233                Core::debug()->groupEnd();
 234            }
 235
 236            if ((IS_CLI || IS_DEBUG) && class_exists('ErrException', true))
 237            {
 238                # 注册脚本
 239                register_shutdown_function(array('ErrException', 'shutdown_handler'));
 240                # 捕获错误
 241                set_exception_handler(array('ErrException', 'exception_handler'));
 242                set_error_handler(array('ErrException', 'error_handler'), error_reporting());
 243            }
 244            else
 245            {
 246                # 注册脚本
 247                register_shutdown_function(array('Core', 'shutdown_handler'));
 248                # 捕获错误
 249                set_exception_handler(array('Core', 'exception_handler'));
 250                set_error_handler(array('Core', 'error_handler'), error_reporting());
 251            }
 252
 253            if (!IS_CLI)
 254            {
 255                # 初始化 HttpIO 对象
 256                HttpIO::setup();
 257            }
 258
 259            # 注册输出函数
 260            register_shutdown_function(array('Core', '_output_body'));
 261
 262            if (true===IS_SYSTEM_MODE)
 263            {
 264                if (false===Core::check_system_request_allow())
 265                {
 266                    # 内部请求验证不通过
 267                    Core::show_500('system request hash error');
 268                }
 269            }
 270
 271            if (IS_DEBUG && isset($_REQUEST['debug']) && class_exists('Profiler', true))
 272            {
 273                Profiler::setup();
 274            }
 275
 276            if (!defined('URL_ASSETS'))
 277            {
 278                /**
 279                 * 静态文件URL地址前缀
 280                 *
 281                 * @var string
 282                 */
 283                define('URL_ASSETS', rtrim(Core::config('url.assets', Core::url('/assets/')), '/') . '/');
 284            }
 285        }
 286
 287        if ($auto_execute)
 288        {
 289            Core::run();
 290        }
 291    }
 292
 293    protected static function run()
 294    {
 295        if (IS_CLI || IS_SYSTEM_MODE)
 296        {
 297            Core::execute(Core::$path_info);
 298        }
 299        else
 300        {
 301            ob_start();
 302
 303            try
 304            {
 305                Core::execute(Core::$path_info);
 306            }
 307            catch (Exception $e)
 308            {
 309                $code = $e->getCode();
 310
 311                if (404===$code || E_PAGE_NOT_FOUND===$code)
 312                {
 313                    Core::show_404($e->getMessage());
 314                }
 315                elseif (500===$code)
 316                {
 317                    Core::show_500($e->getMessage());
 318                }
 319                else
 320                {
 321                    Core::show_500($e);
 322                }
 323            }
 324
 325            Core::$output = ob_get_clean();
 326        }
 327    }
 328
 329    /**
 330     * 获取指定key的配置
 331     *
 332     * 若不传key,则返回Core_Config对象,可获取动态配置,例如Core::config()->get();
 333     *
 334     * @param string $key
 335     * @param mixed $default 默认值
 336     * @return Config
 337     * @return array
 338     */
 339    public static function config($key = null, $default = null)
 340    {
 341        if (null===$key)
 342        {
 343            return Core::factory('Config');
 344        }
 345
 346        $c = explode('.', $key);
 347        $cname = array_shift($c);
 348
 349        if (strtolower($cname)=='core')
 350        {
 351            $v = Core::$core_config;
 352        }
 353        elseif (isset(Core::$config[$cname]))
 354        {
 355            $v = Core::$config[$cname];
 356        }
 357        else
 358        {
 359            return $default;
 360        }
 361
 362        if ($c)foreach ($c as $i)
 363        {
 364            if (!isset($v[$i]))return $default;
 365            $v = $v[$i];
 366        }
 367
 368        return $v;
 369    }
 370
 371    /**
 372     * Cookie
 373     * @return Core_Cookie
 374     */
 375    public static function cookie()
 376    {
 377        return Core::factory('Cookie');
 378    }
 379
 380    /**
 381     * 路由处理
 382     *
 383     * @return Core_Route
 384     */
 385    public static function route()
 386    {
 387        return Core::factory('Route');
 388    }
 389
 390    /**
 391     * 返回URL路径
 392     *
 393     * 自3.0起可用 url() 直接快速调用此方法
 394     *
 395     *     Core::url('test/');
 396     *     url('test/');
 397     *
 398     * @param string $url URL
 399     * @param true|string $isfullurl_or_project 若传true,则返回当前项目的完整url(http(s)://开头),若传项目名,比如default,则返回指定项目的完整URL
 400     * @return string
 401     */
 402    public static function url($uri = '' , $isfullurl_or_project = false)
 403    {
 404        list($url, $query) = explode('?', $uri , 2);
 405
 406        $url = Core::$base_url. ltrim($url, '/') . ($url!='' && substr($url,-1)!='/' && false===strpos($url, '.') && Core::$config['url_suffix']?Core::$config['url_suffix']:'') . ($query?'?'.$query:'');
 407
 408        # 返回完整URL
 409        if (true===$isfullurl_or_project && !preg_match('#^http(s)?://#i', $url))
 410        {
 411            $url = HttpIO::PROTOCOL . $_SERVER["HTTP_HOST"] . $url;
 412        }
 413
 414        # 添加自动追加的参数
 415        if (Core::$_url_auto_args)
 416        {
 417            list($url, $hash) = explode('#', $url, 2);
 418            list($u, $q)      = explode('?', $url, 2);
 419            if (!$q)
 420            {
 421                $q = '';
 422                parse_str($q, $q);
 423                $q += Core::$_url_auto_args;
 424            }
 425            else
 426            {
 427                $q = Core::$_url_auto_args;
 428            }
 429
 430            $url = $u .'?'. http_build_query($q, '', '&') . ($hash?'#'.$hash:'');
 431        }
 432
 433        return $url;
 434    }
 435
 436    /**
 437     * 返回静态资源URL路径
 438     *
 439     * @param string $uri
 440     */
 441    public static function url_assets($uri = '')
 442    {
 443        $url = ltrim($uri, './ ');
 444
 445        if (IS_DEBUG & 1)
 446        {
 447            # 本地调试环境
 448            $url_asstes = Core::url('/assets-dev/');
 449        }
 450        else
 451        {
 452            $url_asstes = URL_ASSETS . Core::$project . '/' . (IS_ADMIN_MODE?'~admin/':'');
 453
 454            list($file, $query) = explode('?', $uri.'?', 2);
 455
 456            $uri = $file . '?' . (strlen($query)>0?$query.'&':'') . Core::assets_hash($file);
 457        }
 458
 459        return $url_asstes . $url;
 460    }
 461
 462    /**
 463     * 增加URL默认参数
 464     *
 465     * 比如用在URL跟踪访客统计上,不支持Session的时候通过URL传送的SessionID等
 466     *
 467     * 增加的参数在所有使用 `Core::url()` 返回的内容里都会附带这个参数
 468     *
 469     *      Core::add_url_args('debug', 'test');
 470     *
 471     *      Core::add_url_args(array('debug'=>'test', 't2'=>'v'2));
 472     *
 473     * @param $key   参数名称
 474     * @param $value 参数值
 475     * @since v3.0
 476     */
 477    public static function add_url_args($key, $value)
 478    {
 479        if (is_array($key))
 480        {
 481            Core::$_url_auto_args += $key;
 482        }
 483        else
 484        {
 485            Core::$_url_auto_args[$key] = $value;
 486        }
 487    }
 488
 489    /**
 490     * Include一个指定URI的控制器
 491     *
 492     * @param string $uri
 493     * @return class_name | false
 494     */
 495    public static function load_controller($uri)
 496    {
 497        $found = Core::find_controller($uri);
 498
 499        if ($found)
 500        {
 501            # 返回类的名称
 502            return $found['class'];
 503        }
 504        else
 505        {
 506            return false;
 507        }
 508    }
 509
 510    /**
 511     * 是否系统设置禁用文件写入功能
 512     *
 513     * 可在 `config.php` 中设置 `$config['file_write_mode'] = 'disable';` 如果disable则返回true,否则返回false
 514     *
 515     * @return boolean
 516     */
 517    public static function is_file_write_disabled()
 518    {
 519        if (Core::config('core.file_write_mode')=='disable')
 520        {
 521            return true;
 522        }
 523        else
 524        {
 525            return false;
 526        }
 527    }
 528
 529    /**
 530     * 记录日志
 531     *
 532     * @param string $msg 日志内容
 533     * @param string $type 类型,例如:log,error,debug 等
 534     * @param stirng $file 指定文件名,不指定则默认
 535     * @return boolean
 536     */
 537    public static function log($msg, $type = 'log', $file = null)
 538    {
 539        if (Core::is_file_write_disabled())return true;
 540
 541        # log配置
 542        $log_config = Core::config('log');
 543
 544        # 不记录日志
 545        if (isset($log_config['use']) && !$log_config['use'])
 546        {
 547            return true;
 548        }
 549
 550        # 内容格式化
 551        if ($log_config['format'])
 552        {
 553            $format = $log_config['format'];
 554        }
 555        else
 556        {
 557            # 默认格式
 558            $format = ':time - :host::port - :url - :msg';
 559        }
 560
 561        # 获取日志内容
 562        $data = Core::log_format($msg, $type, $format);
 563
 564        if (IS_DEBUG)
 565        {
 566            # 如果有开启debug模式,输出到浏览器
 567            Core::debug()->log($data, $type);
 568        }
 569
 570        # 保存日志
 571        return Core::write_log($data, $type, $file);
 572    }
 573
 574    /**
 575     * 执行指定URI的控制器
 576     *
 577     * @param string $uri
 578     */
 579    public static function execute($uri)
 580    {
 581        $found = Core::find_controller($uri);
 582
 583        if ($found)
 584        {
 585            if (isset($found['route']))
 586            {
 587                $class_name   = $found['class'];
 588                $class_exists = class_exists($class_name, true);
 589                $arguments    = array();
 590                if (isset($found['route']['action']) && $found['route']['action'])
 591                {
 592                    $arguments[] = $found['route']['action'];
 593                }
 594            }
 595            else
 596            {
 597                require $found['file'];
 598
 599                if ($found['ns']=='team-library' || $found['ns']=='project')
 600                {
 601                    $class_name = $found['class'];
 602                }
 603                else
 604                {
 605                    $class_name = str_replace('.', '_', $found['ns']) . '_' . $found['class'];
 606                }
 607
 608                $class_exists = class_exists($class_name, false);
 609            }
 610
 611            if ($class_exists)
 612            {
 613
 614                $controller = new $class_name();
 615
 616                Controller::$controllers[] = $controller;
 617
 618                # 是否有必要将action从$arguments中移出
 619                $need_shift_action = false;
 620                $arguments = $found['args'];
 621                if ($arguments)
 622                {
 623                    $action = current($arguments);
 624                    if (0===strlen($action))
 625                    {
 626                        $action = 'default';
 627                    }
 628                    else
 629                    {
 630                        $need_shift_action = true;
 631                    }
 632                }
 633                else
 634                {
 635                    $action = 'index';
 636                }
 637
 638                $action_name = 'action_' . $action;
 639
 640                if (!method_exists($controller, $action_name))
 641                {
 642                    if ($action_name!='action_default' && method_exists($controller, 'action_default'))
 643                    {
 644                        $action_name = 'action_default';
 645                    }
 646                    elseif ($action_name!='' && (!$arguments || $arguments===array('')) && method_exists($controller, 'action_index'))
 647                    {
 648                        $action_name = 'action_index';
 649                    }
 650                    elseif (method_exists($controller, '__call'))
 651                    {
 652                        $controller->__call($action_name, $arguments);
 653
 654                        Core::rm_controoler($controller);
 655                        return;
 656                    }
 657                    else
 658                    {
 659                        Core::rm_controoler($controller);
 660
 661                        throw new Exception(__('Page Not Found'), 404);
 662                    }
 663                }
 664                elseif ($need_shift_action)
 665                {
 666                    array_shift($arguments);
 667                }
 668
 669                $ispublicmethod = new ReflectionMethod($controller, $action_name);
 670
 671                if (!$ispublicmethod->isPublic())
 672                {
 673                    Core::rm_controoler($controller);
 674
 675                    throw new Exception(__('Request Method Not Allowed.'), 405);
 676                }
 677                unset($ispublicmethod);
 678
 679                # POST 方式,自动CSRF判断
 680                if (HttpIO::METHOD=='POST')
 681                {
 682                    $auto_check_post_method_referer = isset($controller->auto_check_post_method_referer)?$controller->auto_check_post_method_referer:Core::config('auto_check_post_method_referer', true);
 683
 684                    if ($auto_check_post_method_referer && !HttpIO::csrf_check())
 685                    {
 686                        throw new Exception(__('Not Acceptable.'), 406);
 687                    }
 688                }
 689
 690                if (isset($found['route']))
 691                {
 692                    # 设置Route参数
 693                    foreach ($found['route'] as $k => $v)
 694                    {
 695                        $controller->$k = $v;
 696                    }
 697                }
 698                else
 699                {
 700                    $controller->ids = $found['ids'];
 701                }
 702
 703                # 将参数传递给控制器
 704                $controller->action     = $action_name;
 705                $controller->controller = $found['class'];
 706                $controller->uri        = $uri;
 707                $controller->directory  = $found['dir'];
 708
 709
 710                if (IS_SYSTEM_MODE)
 711                {
 712                    # 系统内部调用参数
 713                    $controller->arguments = @unserialize(HttpIO::POST('data', HttpIO::PARAM_TYPE_OLDDATA));
 714                }
 715                else
 716                {
 717                    $controller->arguments = $arguments;
 718                }
 719
 720                # 设置 HttpIO 参数
 721                HttpIO::set_params_controller($controller);
 722
 723                # 前置方法
 724                if (method_exists($controller, 'before'))
 725                {
 726                    $controller->before();
 727                }
 728
 729                # 执行方法
 730                $count_arguments = count($arguments);
 731                switch ($count_arguments)
 732                {
 733                    case 0:
 734                        $controller->$action_name();
 735                        break;
 736                    case 1:
 737                        $controller->$action_name($arguments[0]);
 738                        break;
 739                    case 2:
 740                        $controller->$action_name($arguments[0], $arguments[1]);
 741                        break;
 742                    case 3:
 743                        $controller->$action_name($arguments[0], $arguments[1], $arguments[2]);
 744                        break;
 745                    case 4:
 746                        $controller->$action_name($arguments[0], $arguments[1], $arguments[2], $arguments[3]);
 747                        break;
 748                    default:
 749                        call_user_func_array(array($controller, $action_name), $arguments);
 750                        break;
 751                }
 752
 753                # 后置方法
 754                if (method_exists($controller, 'after'))
 755                {
 756                    $controller->after();
 757                }
 758
 759                # 移除控制器
 760                Core::rm_controoler($controller);
 761
 762                unset($controller);
 763            }
 764            else
 765            {
 766                throw new Exception(__('Page Not Found'), 404);
 767            }
 768        }
 769        else
 770        {
 771            throw new Exception(__('Page Not Found'), 404);
 772        }
 773    }
 774
 775    protected static function rm_controoler($controller)
 776    {
 777        foreach (Controller::$controllers as $k=>$c)
 778        {
 779            if ($c===$controller)unset(Controller::$controllers[$k]);
 780        }
 781
 782        Controller::$controllers = array_values(Controller::$controllers);
 783    }
 784
 785    /**
 786     * 寻找控制器
 787     *
 788     * @return array
 789     */
 790    protected static function find_controller($uri)
 791    {
 792        $uri = ltrim($uri, '/');
 793
 794        if (Core::$config['url_suffix'] && substr(strtolower($uri), -strlen(Core::$config['url_suffix']))==Core::$config['url_suffix'])
 795        {
 796            $uri = substr($uri, 0, -strlen(Core::$config['url_suffix']));
 797        }
 798
 799        if (!IS_SYSTEM_MODE && isset(Core::$config['route']) && Core::$config['route'])
 800        {
 801            # 有路由配置,首先根据路由配置查询控制器
 802            $found_route = Route::get($uri);
 803
 804            if ($found_route)
 805            {
 806                if (!isset($found_route['controller']) || !$found_route['controller'])
 807                {
 808                    if (IS_DEBUG)Core::debug()->error('The route not match controller');
 809                    Core::show_404();
 810                }
 811
 812                return array
 813                (
 814                    'class'  => 'Controller_' . preg_replace('#[^a-zA-Z0-9_]#', '_', trim($found_route['controller'])),
 815                    'route'  => $found_route,
 816                );
 817            }
 818            else
 819            {
 820                unset($found_route);
 821            }
 822        }
 823
 824        if ($uri!='')
 825        {
 826            $uri_arr = explode('/', strtolower($uri));
 827        }
 828        else
 829        {
 830            $uri_arr = array();
 831        }
 832
 833        if (IS_DEBUG)
 834        {
 835            Core::debug()->log('/'. $uri, 'find controller uri');
 836        }
 837
 838        $include_path = Core::$include_path;
 839
 840        # log
 841        $find_log = $find_path_log = array();
 842
 843        # 控制器目录
 844        $controller_dir = Core::$dir_setting['controller'][0];
 845
 846        if (IS_SYSTEM_MODE)
 847        {
 848            $controller_dir .= '-system';
 849        }
 850        elseif (IS_ADMIN_MODE)
 851        {
 852            $controller_dir .= '-admin';
 853        }
 854        elseif (IS_REST_MODE)
 855        {
 856            $controller_dir .= '-rest';
 857        }
 858        elseif (IS_CLI)
 859        {
 860            $controller_dir .= '-shell';
 861        }
 862
 863        # 首先找到存在的目录
 864        $found_path = array();
 865        foreach ($include_path as $ns => $ipath)
 866        {
 867            if($ipath)foreach ($ipath as $lib_ns => $path)
 868            {
 869                if ($ns==='library')
 870                {
 871                    $tmp_ns = 'library_' . str_replace('.', '_', $lib_ns);
 872                }
 873                else
 874                {
 875                    $tmp_ns = $ns;
 876                }
 877
 878                $tmp_str  = $real_path = $real_class = '';
 879                $tmp_path = $path . $controller_dir . DS;
 880                $ids      = array();
 881
 882                foreach ($uri_arr as $uri_path)
 883                {
 884                    $ds = DS;
 885                    if ($uri_path==='')
 886                    {
 887                        if (count($uri_arr)>1)
 888                        {
 889                            break;
 890                        }
 891                        $real_uri_path = '';
 892                        $ds = '';
 893                    }
 894                    elseif (is_numeric($uri_path))
 895                    {
 896                        $real_uri_path = '_id';
 897                        $ids[] = $uri_path;
 898                    }
 899                    elseif ($uri_path == '_id')
 900                    {
 901                        # 不允许直接在URL中使用_id
 902                        break;
 903                    }
 904                    elseif (preg_match('#[^a-z0-9_]#i', $uri_path))
 905                    {
 906                        # 不允许非a-z0-9_的字符在控制中
 907                        break;
 908                    }
 909                    else
 910                    {
 911                        $real_uri_path = $uri_path;
 912                    }
 913
 914                    $tmpdir = $tmp_path . $real_path . $real_uri_path . $ds;
 915                    if (IS_DEBUG)
 916                    {
 917                        $find_path_log[] = Core::debug_path($tmpdir);
 918                    }
 919                    $real_path  .= $real_uri_path . DS;
 920                    $real_class .= $real_uri_path . '_';
 921                    $tmp_str    .= $uri_path . DS;
 922
 923                    if (is_dir($tmpdir))
 924                    {
 925                        $found_path[$tmp_str][] = array
 926                        (
 927                            $tmp_ns,
 928                            $tmpdir,
 929                            ltrim($real_class, '_'),
 930                            $ids,
 931                        );
 932                    }
 933                    else
 934                    {
 935                        break;
 936                    }
 937                }
 938
 939                // 根目录的
 940                if (is_dir($tmp_path))
 941                {
 942                    $found_path[''][] = array
 943                    (
 944                        $tmp_ns,
 945                        $tmp_path,
 946                        '',
 947                        array(),
 948                    );
 949
 950                    if (IS_DEBUG)
 951                    {
 952                        $find_path_log[] = Core::debug_path($tmp_path);
 953                    }
 954                }
 955            }
 956        }
 957
 958        unset($ids);
 959        $found = null;
 960
 961        # 寻找可能的文件
 962        if ($found_path)
 963        {
 964            # 调整优先级
 965            krsort($found_path);
 966
 967            foreach ($found_path as $path => $all_path)
 968            {
 969                $path_len = strlen($path);
 970                $tmp_p    = substr($uri, $path_len);
 971
 972                if (strlen($tmp_p)>0)
 973                {
 974                    $args = explode('/', substr($uri, $path_len));
 975                }
 976                else
 977                {
 978                    $args = array();
 979                }
 980
 981                $the_id    = array();
 982                $tmp_class = array_shift($args);
 983                $tmp_arg   = $tmp_class;
 984                $directory = rtrim('/'. substr($uri, 0, $path_len) . $tmp_class, '/');
 985
 986                if (0===strlen($tmp_class))
 987                {
 988                    $tmp_class = 'index';
 989                }
 990                elseif (is_numeric($tmp_class))
 991                {
 992                    $the_id = array
 993                    (
 994                        $tmp_class
 995                    );
 996                    $tmp_class = '_id';
 997                }
 998                elseif ($tmp_class == '_id')
 999                {
1000                    continue;
1001                }
1002
1003                $real_class = $tmp_class;
1004                $tmp_class  = strtolower($tmp_class);
1005
1006                // 记录找到的index.controller.php
1007                $found_index_class = null;
1008
1009                if (IS_DEBUG)
1010                {
1011                    $find_log2 = array();
1012                }
1013
1014                foreach ($all_path as $tmp_arr)
1015                {
1016                    list($ns, $tmp_path, $real_path, $ids) = $tmp_arr;
1017                    $tmpfile = $tmp_path . $tmp_class . Core::$dir_setting['controller'][1] . EXT;
1018
1019                    if (IS_DEBUG)
1020                    {
1021                        $find_log[] = Core::debug_path($tmpfile);
1022                    }
1023
1024                    if (is_file($tmpfile))
1025                    {
1026                        if ($the_id)
1027                        {
1028                            $ids = array_merge($ids, $the_id);
1029                        }
1030
1031                        if ($directory && substr($directory, -1-strlen($tmp_class))=='/'.$tmp_class)
1032                        {
1033                            $directory = substr($directory, 0, -1-strlen($tmp_class));
1034                        }
1035
1036                        $found = array
1037                        (
1038                            'file'   => $tmpfile,
1039                            'dir'    => $directory,
1040                            'ns'     => $ns,
1041                            'class'  => 'Controller_' . $real_path . $real_class,
1042                            'args'   => $args,
1043                            'ids'    => $ids,
1044                        );
1045
1046                        break 2;
1047                    }
1048                    elseif (!$found_index_class && $tmp_class!='default')
1049                    {
1050                        // 记录 index.controller.php 控制器
1051                        $tmpfile = $tmp_path . 'default' . Core::$dir_setting['controller'][1] . EXT;
1052                        if (IS_DEBUG)
1053                        {
1054                            $find_log2[] = Core::debug_path($tmpfile);
1055                        }
1056
1057                        if (is_file($tmpfile))
1058                        {
1059                            if (null!==$tmp_arg)
1060                            {
1061                                array_unshift($args, $tmp_arg);
1062
1063                                if (strlen($tmp_arg)>0)
1064                                {
1065                                    $directory = substr($directory, 0, -strlen($tmp_arg) - 1);
1066                                }
1067                            }
1068
1069                            $found_index_class = array
1070                            (
1071                                'file'   => $tmpfile,
1072                                'dir'    => $directory,
1073                                'ns'     => $ns,
1074                                'class'  => 'Controller_' . $real_path . 'Default',
1075                                'args'   => $args,
1076                                'ids'    => $ids,
1077                            );
1078                        }
1079                    }
1080                }
1081
1082                if (IS_DEBUG && $find_log2)
1083                {
1084                    $find_log = array_merge($find_log, $find_log2);
1085                }
1086
1087                // index.controller.php 文件
1088                if (!$found && $found_index_class)
1089                {
1090                    $found = $found_index_class;
1091                    break;
1092                }
1093            }
1094        }
1095
1096        if (IS_DEBUG)
1097        {
1098            Core::debug()->group('find controller path');
1099            foreach ($find_path_log as $value)
1100            {
1101                Core::debug()->log($value);
1102            }
1103            Core::debug()->groupEnd();
1104
1105            Core::debug()->group('find controller files');
1106            foreach ($find_log as $value)
1107            {
1108                Core::debug()->log($value);
1109            }
1110            Core::debug()->groupEnd();
1111
1112            if ($found)
1113            {
1114                $found2 = $found;
1115                $found2['file'] = Core::debug_path($found2['file']);
1116                Core::debug()->log($found2, 'found contoller');
1117            }
1118            else
1119            {
1120                Core::debug()->log('/'. $uri, 'not found contoller');
1121            }
1122        }
1123
1124        if (is_array($found) && isset($found['class']))
1125        {
1126            $found['class'] = preg_replace('#[^a-zA-Z0-9_]#', '_', trim($found['class']));
1127        }
1128
1129        return $found;
1130    }
1131
1132
1133    /**
1134    * 写入日志
1135    *
1136    * 若有特殊写入需求,可以扩展本方法(比如调用数据库类克写到数据库里)
1137    *
1138    * @param string $data
1139    * @param string $type 日志类型
1140    * @param stirng $file 指定文件名,不指定则默认
1141    * @return boolean
1142    */
1143    protected static function write_log($data, $type = 'log', $file = null)
1144    {
1145        static $pro = null;
1146
1147        if (!$type)$type = 'log';
1148
1149        if (null===$pro)
1150        {
1151            if (preg_match('#^(db|cache)://([a-z0-9_]+)/([a-z0-9_]+)$#i', DIR_LOG , $m))
1152            {
1153                $pro = $m;
1154            }
1155            else
1156            {
1157                $pro = false;
1158            }
1159        }
1160
1161        # Log目录采用文件目录
1162        if (false===$pro)
1163        {
1164            $write_mode = Core::config('core.file_write_mode');
1165
1166            # 禁用写入
1167            if ($write_mode=='disable')return true;
1168
1169            # 再判断是否有转换储存处理
1170            if (preg_match('#^(db|cache)://([a-z0-9_]+)/([a-z0-9_]+)$#i', $write_mode , $m))
1171            {
1172                $pro = $m;
1173            }
1174        }
1175
1176        if (false===$pro)
1177        {
1178            # 以文件的形式保存
1179
1180            $log_config = Core::config('log');
1181
1182            if (!$file)
1183            {
1184                if ($log_config['file'])
1185                {
1186                    $file = date($log_config['file']);
1187                }
1188                else
1189                {
1190                    $file = date('Y/m/d/');
1191                }
1192                $file .= $type . '.log';
1193            }
1194
1195            $dir = trim(dirname($file), '/');
1196
1197            # 如果目录不存在,则创建
1198            if (!is_dir(DIR_LOG.$dir))
1199            {
1200                $temp = explode('/', str_replace('\\', '/', $dir) );
1201                $cur_dir = '';
1202                for($i=0; $i<count($temp); $i++)
1203                {
1204                    $cur_dir .= $temp[$i] . "/";
1205                    if (!is_dir(DIR_LOG.$cur_dir))
1206                    {
1207                        @mkdir(DIR_LOG.$cur_dir, 0755);
1208                    }
1209                }
1210            }
1211
1212            return false===@file_put_contents(DIR_LOG . $file, $data . CRLF , FILE_APPEND)?false:true;
1213        }
1214        else
1215        {
1216            # 以db或cache方式保存
1217
1218            if ($pro[1]=='db')
1219            {
1220                $db_data = array
1221                (
1222                    'key'    => md5($file),
1223                    'key_str'=> substr($file, 0, 255),
1224                    'type'   => $type,
1225                    'day'    => date('Ymd'),
1226                    'time'   => TIME,
1227                    'value'  => $data,
1228                );
1229
1230                $obj = new Database($pro[2]);
1231                $status = $obj->insert($pro[3], $db_data) ? true:false;
1232            }
1233            else
1234            {
1235                if ($pro[3])
1236                {
1237                    $pro[1]['prefix'] = trim($pro[3]) . '_';
1238                }
1239
1240                $pro[1]['prefix'] .= $type.'_';
1241
1242                $obj = new Cache($pro[2]);
1243
1244                $status = $obj->set(date('Ymd').'_'.md5($file), $data, 86400*30);        // 存1月
1245            }
1246
1247            return $status;
1248        }
1249    }
1250
1251    /**
1252    * 用于保存日志时格式化内容,如需要特殊格式可以自行扩展
1253    *
1254    * @param string $msg
1255    * @param string $format
1256    * @return string
1257    */
1258    protected static function log_format($msg,$type,$format)
1259    {
1260        $value = array
1261        (
1262            ':time'    => date('Y-m-d H:i:s'),             //当前时间
1263            ':url'     => $_SERVER['SCRIPT_URI'],          //请求的URL
1264            ':msg'     => $msg,                            //日志信息
1265            ':type'    => $type,                           //日志类型
1266            ':host'    => $_SERVER["SERVER_ADDR"],         //服务器
1267            ':port'    => $_SERVER["SERVER_PORT"],         //端口
1268            ':ip'      => HttpIO::IP,                      //请求的IP
1269            ':agent'   => $_SERVER["HTTP_USER_AGENT"],     //客户端信息
1270            ':referer' => $_SERVER["HTTP_REFERER"],        //来源页面
1271        );
1272
1273        return strtr($format, $value);
1274    }
1275
1276    /**
1277     * 获取debug对象
1278     * 可安全用于生产环境,在生产环境下将忽略所有debug信息
1279     * @return Debug
1280     */
1281    public static function debug()
1282    {
1283        static $debug = null;
1284        if (null === $debug)
1285        {
1286            if (!IS_CLI && ( IS_DEBUG || false!==strpos($_SERVER["HTTP_USER_AGENT"],'FirePHP') || isset($_SERVER["HTTP_X_FIREPHP_VERSION"]) ) && class_exists('Debug', true))
1287            {
1288                $debug = Debug::instance();
1289            }
1290            else
1291            {
1292                $debug = new __NoDebug();
1293            }
1294        }
1295        return $debug;
1296    }
1297
1298    /**
1299     * 将真实路径地址输出为调试地址
1300     *
1301     * 显示结果类似 ./system/libraries/Database.class.php
1302     *
1303     * @param  string  path to debug
1304     * @param  boolean $highlight 是否返回高亮前缀,可以传字符颜色,比如#f00
1305     * @return string
1306     */
1307    public static function debug_path($file, $highlight=false)
1308    {
1309        if ($highlight)
1310        {
1311            if (IS_CLI)
1312            {
1313                # 命令行下输出带色彩的前缀
1314                $l = "\x1b[36m";
1315                $r = "\x1b[39m";
1316            }
1317            else
1318            {
1319                $l = '<span style="color:'.(is_string($highlight) && preg_match('/^[a-z0-9#\(\)\.,]+$/i',$highlight) ?$highlight:'#a00').'">';
1320                $r = '</span>';
1321            }
1322        }
1323        else
1324        {
1325            $l = $r = '';
1326        }
1327
1328        $file = str_replace('\\', DS, $file);
1329
1330        if (strpos($file, DIR_CORE) === 0)
1331        {
1332            $file = $l . './core/' . $r . substr($file, strlen(DIR_CORE));
1333        }
1334        elseif (strpos($file, DIR_TEAM_LIBRARY) === 0)
1335        {
1336            $file = $l . './team-library/' . $r . substr($file, strlen(DIR_TEAM_LIBRARY));
1337        }
1338        elseif (strpos($file, DIR_LIBRARY) === 0)
1339        {
1340            $file = $l . './libraries/' . $r . substr($file, strlen(DIR_LIBRARY));
1341        }
1342        elseif (strpos($file, DIR_MODULE) === 0)
1343        {
1344            $file = $l . './modules/' . $r . substr($file, strlen(DIR_MODULE));
1345        }
1346        elseif (strpos($file, DIR_DRIVER) === 0)
1347        {
1348            $file = $l . './drivers/' . $r . substr($file, strlen(DIR_DRIVER));
1349        }
1350        elseif (strpos($file, DIR_PROJECT) === 0)
1351        {
1352            $file = $l . './projects/' . $r . substr($file, strlen(DIR_PROJECT));
1353        }
1354        elseif (strpos($file, DIR_TEMP) === 0)
1355        {
1356            $file = $l . './data/temp/' . $r . substr($file, strlen(DIR_TEMP));
1357        }
1358        elseif (strpos($file, DIR_LOG) === 0)
1359        {
1360            $file = $l . './data/log/' . $r . substr($file, strlen(DIR_LOG));
1361        }
1362        elseif (strpos($file, DIR_CACHE) === 0)
1363        {
1364            $file = $l . './data/cache/' . $r . substr($file, strlen(DIR_CACHE));
1365        }
1366        elseif (strpos($file, DIR_DATA) === 0)
1367        {
1368            $file = $l . './data/' . $r . substr($file, strlen(DIR_DATA));
1369        }
1370        elseif (strpos($file, DIR_ASSETS) === 0)
1371        {
1372            $file = $l . './wwwroot/assets/' . $r . substr($file, strlen(DIR_ASSETS));
1373        }
1374        elseif (strpos($file, DIR_UPLOAD) === 0)
1375        {
1376            $file = $l . './wwwroot/upload/' . $r . substr($file, strlen(DIR_UPLOAD));
1377        }
1378        elseif (strpos($file, DIR_WWWROOT) === 0)
1379        {
1380            $file = $l . './wwwroot/' . $r . substr($file, strlen(DIR_WWWROOT));
1381        }
1382        elseif (strpos($file, DIR_SYSTEM) === 0)
1383        {
1384            $file = $l . './' . $r . substr($file, strlen(DIR_SYSTEM));
1385        }
1386
1387        $file = str_replace('\\', '/', $file);
1388
1389        return $file;
1390    }
1391
1392    /**
1393     * 关闭缓冲区
1394     *
1395     * @param  boolean 是否输出缓冲数据
1396     * @return void
1397     */
1398    public static function close_buffers($flush = true)
1399    {
1400        if (ob_get_level() > Core::$buffer_level)
1401        {
1402            $close = ($flush === true) ? 'ob_end_flush' : 'ob_end_clean';
1403            while (ob_get_level() > Core::$buffer_level)
1404            {
1405                $close();
1406            }
1407            Core::$buffer_level = ob_get_level();
1408        }
1409    }
1410
1411    /**
1412     * 404,可直接将Exception对象传给$msg
1413     *
1414     * @param string/Exception $msg
1415     */
1416    public static function show_404($msg = null)
1417    {
1418        Core::close_buffers(false);
1419
1420        # 避免输出的CSS头试抛出页面无法显示
1421        @header('Content-Type: text/html;charset=' . Core::config('core.charset'), true);
1422
1423        HttpIO::$status = 404;
1424        HttpIO::send_headers();
1425
1426        if (null === $msg)
1427        {
1428            $msg = __('Page Not Found');
1429        }
1430
1431        if (IS_DEBUG && class_exists('ErrException', false))
1432        {
1433            if ($msg instanceof Exception)
1434            {
1435                throw $msg;
1436            }
1437            else
1438            {
1439                throw new Exception($msg, 43);
1440            }
1441        }
1442
1443        if (IS_CLI)
1444        {
1445            echo $msg . CRLF;
1446            exit();
1447        }
1448
1449        try
1450        {
1451            $view = new View('error/404');
1452            $view->message = $msg;
1453            $view->render(true);
1454        }
1455        catch ( Exception $e )
1456        {
1457            list ( $REQUEST_URI ) = explode('?', $_SERVER['REQUEST_URI'], 2);
1458            $REQUEST_URI = htmlspecialchars(rawurldecode($REQUEST_URI));
1459            echo '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">' .
1460            CRLF . '<html>' .
1461            CRLF . '<head>' .
1462            CRLF . '<title>404 Not Found</title>' .
1463            CRLF . '</head>'.
1464            CRLF . '<body>' .
1465            CRLF . '<h1>Not Found</h1>' .
1466            CRLF . '<p>The requested URL ' . $REQUEST_URI . ' was not found on this server.</p>' .
1467            CRLF . '<hr />' .
1468            CRLF . $_SERVER['SERVER_SIGNATURE'] .
1469            CRLF . '</body>' .
1470            CRLF . '</html>';
1471        }
1472
1473        exit();
1474    }
1475
1476    /**
1477     * 系统错误,可直接将Exception对象传给$msg
1478     * @param string/Exception $msg
1479     */
1480    public static function show_500($msg = null)
1481    {
1482        Core::close_buffers(false);
1483
1484        # 避免输出的CSS头试抛出页面无法显示
1485        @header('Content-Type: text/html;charset=' . Core::config('core.charset'), true);
1486
1487        HttpIO::$status = 500;
1488        HttpIO::send_headers();
1489
1490        if (null === $msg)
1491        {
1492            $msg = __('Internal Server Error');
1493        }
1494
1495        if (IS_DEBUG && class_exists('ErrException', false))
1496        {
1497            if ($msg instanceof Exception)
1498            {
1499                throw $msg;
1500            }
1501            else
1502            {
1503                throw new Exception($msg, 0);
1504            }
1505        }
1506
1507        if (IS_CLI)
1508        {
1509            echo "\x1b[36m";
1510            if ($msg instanceof Exception)
1511            {
1512                echo $msg->getMessage() . CRLF;
1513            }
1514            else
1515            {
1516                echo $msg . CRLF;
1517            }
1518
1519            echo "\x1b[39m";
1520            echo CRLF;
1521            exit();
1522        }
1523
1524        try
1525        {
1526            if ($msg instanceof Exception)
1527            {
1528                print_r($msg);exit;
1529                $error     = $msg->getMessage();
1530                $trace_obj = $msg;
1531            }
1532            else
1533            {
1534                $error     = $msg;
1535                $trace_obj = new Exception($msg);
1536            }
1537
1538            $error_config = Core::config('core.error500');
1539
1540            $view = new View('error/500');
1541            if ($error_config && isset($error_config['close']) && $error_config['close']==true)
1542            {
1543                # 不记录
1544                $view->error_saved = false;
1545            }
1546            else
1547            {
1548                $trace_array = array
1549                (
1550                    'project'     => Core::$project,
1551                    'uri'         => HttpIO::$uri,
1552                    'url'         => HttpIO::PROTOCOL . $_SERVER['HTTP_HOST'] . $_SERVER["REQUEST_URI"],
1553                    'post'        => HttpIO::POST(null, HttpIO::PARAM_TYPE_OLDDATA),
1554                    'get'         => $_SERVER['QUERY_STRING'],
1555                    'cookie'      => HttpIO::COOKIE(null, HttpIO::PARAM_TYPE_OLDDATA),
1556                    'client_ip'   => HttpIO::IP,
1557                    'user_agent'  => HttpIO::USER_AGENT,
1558                    'referrer'    => HttpIO::REFERRER,
1559                    'server_ip'   => $_SERVER["SERVER_ADDR"],
1560                    'trace'       => $trace_obj->__toString(),
1561                );
1562
1563                $date     = @date('Y-m-d');
1564                $no       = strtoupper(substr(md5(serialize($trace_array)), 10, 10));
1565                $error_no = $date.'-'.$no;
1566
1567                # 其它数据
1568                $trace_array['server_name'] = (function_exists('php_uname')? php_uname('a') : 'unknown');
1569                $trace_array['time']        = TIME;
1570                $trace_array['use_time']    = microtime(1) - START_TIME;
1571                $trace_array['trace']       = $trace_obj;
1572
1573                $trace_data = base64_encode(gzcompress(serialize($trace_array), 9));
1574                unset($trace_array);
1575
1576                $view->error_saved = true;
1577
1578                # 记录错误日志
1579                try
1580                {
1581                    if (isset($error_config['save_type']) && $error_config['save_type'])
1582                    {
1583                        $save_type = $error_config['save_type'];
1584                    }
1585                    else
1586                    {
1587                        $save_type = 'file';
1588                    }
1589
1590                    if ($save_type=='file')
1591                    {
1592                        # 文件模式
1593                        $write_mode = Core::config('core.file_write_mode');
1594
1595                        if (preg_match('#^(db|cache)://([a-z0-9_]+)/([a-z0-9_]+)$#i', $write_mode , $m))
1596                        {
1597                            $save_type = $m[1];
1598                            $error_config['type_config'] = $m[2];
1599                        }
1600                    }
1601
1602                    switch ($save_type)
1603                    {
1604                        case 'database':
1605                            $obj = $error_config['type_config']?new Database($error_config['type_config']) : new Database();
1606                            $data = array
1607                            (
1608                                'time'        => strtotime($date.' 00:00:00'),
1609                                'no'          => $no,
1610                                'log'         => $trace_data,
1611                                'expire_time' => TIME + 7*86400,
1612                            );
1613                            $obj->insert('error500_log', $data);
1614                            break;
1615                        case 'cache':
1616                            $obj = $error_config['type_config']?new Cache($error_config['type_config']) : new Cache();
1617                            if (!$obj->get($error_no))
1618                            {
1619                                $obj->set($error_no, $trace_data, 7*86400);
1620                            }
1621                            break;
1622                        default:
1623                            $file = DIR_LOG .'error500'. DS . str_replace('-', DS, $date) . DS . $no . '.log';
1624                            if (!is_file($file))
1625                            {
1626                                File::create_file($file, $trace_data, null, null, $error_config['type_config']?$error_config['type_config']:'default');
1627                            }
1628                            break;
1629                    }
1630                }
1631                catch (Exception $e)
1632                {
1633
1634                }
1635            }
1636
1637            $view->error_no = $error_no;
1638            $view->error = $error;
1639            $view->render(true);
1640        }
1641        catch (Exception $e)
1642        {
1643            list ($REQUEST_URI) = explode('?', $_SERVER['REQUEST_URI'], 2);
1644            $REQUEST_URI = htmlspecialchars(rawurldecode($REQUEST_URI));
1645            echo '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">' .
1646            CRLF . '<html>' .
1647            CRLF . '<head>' .
1648            CRLF . '<title>Internal Server Error</title>' .
1649            CRLF . '</head>' .
1650            CRLF . '<body>' .
1651            CRLF . '<h1>Internal Server Error</h1>' .
1652            CRLF . '<p>The requested URL ' . $REQUEST_URI . ' was error on this server.</p>' .
1653            CRLF . '<hr />' .
1654            CRLF . $_SERVER['SERVER_SIGNATURE'] .
1655            CRLF . '</body>' .
1656            CRLF . '</html>';
1657        }
1658
1659        exit();
1660    }
1661
1662    /**
1663     * 返回一个用.表示的字符串的key对应数组的内容
1664     *
1665     * 例如
1666     *
1667     *     $arr = array
1668     *     (
1669     *         'a' => array
1670     *         (
1671     *         	  'b' => 123,
1672     *             'c' => array
1673     *             (
1674     *                 456,
1675     *             ),
1676     *         ),
1677     *     );
1678     *     Core::key_string($arr,'a.b');  //返回123
1679     *
1680     *     Core::key_string($arr,'a');
1681     *     // 返回
1682     *     array
1683     *     (
1684     *        'b' => 123,
1685     *        'c' => array
1686     *        (
1687     *            456,
1688     *         ),
1689     *     );
1690     *
1691     *     Core::key_string($arr,'a.c.0');  //返回456
1692     *
1693     *     Core::key_string($arr,'a.d');  //返回null
1694     *
1695     * @param array $arr
1696     * @param string $key
1697     * @return fixed
1698     */
1699    public static function key_string($arr, $key, $default = null)
1700    {
1701        if (!is_array($arr)) return $default;
1702        $keyArr = explode('.', $key);
1703        foreach ( $keyArr as $key )
1704        {
1705            if ( isset($arr[$key]) )
1706            {
1707                $arr = $arr[$key];
1708            }
1709            else
1710            {
1711                return $default;
1712            }
1713        }
1714        return $arr;
1715    }
1716
1717    /**
1718     * 添加页面在关闭前执行的列队
1719     * 将利用call_user_func或call_user_func_array回调
1720     * 类似 register_shutdown_function
1721     * @param array $function 方法名,可以是数组
1722     * @param array $param_arr 参数,可空
1723     */
1724    public static function register_shutdown_function($function, $param_arr = null)
1725    {
1726        Core::$shutdown_function[] = array($function, $param_arr);
1727    }
1728
1729    public static function shutdown_handler()
1730    {
1731        $error = error_get_last();
1732        if ( $error )
1733        {
1734            static $run = null;
1735            if ( $run === true ) return;
1736            $run = true;
1737            if ( ((E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR) & $error['type']) !== 0 )
1738            {
1739                $error['file'] = Core::debug_path($error['file']);
1740                Core::show_500(var_export($error, true));
1741                exit();
1742            }
1743        }
1744    }
1745
1746    public static function exception_handler(Exception $e)
1747    {
1748        $code = $e->getCode();
1749        if ( $code !== 8 )
1750        {
1751            Core::show_500($e);
1752            exit();
1753        }
1754    }
1755
1756    public static function error_handler($code, $error, $file = null, $line = null)
1757    {
1758        if ( (error_reporting() & $code) !== 0 )
1759        {
1760            throw new ErrorException( $error, $code, 0, $file, $line );
1761        }
1762        return true;
1763    }
1764
1765    /**
1766     * 根据$objName返回一个实例化并静态存储的对象
1767     *
1768     * @param string $obj_name
1769     * @param string $key
1770     */
1771    public static function factory($obj_name, $key = '')
1772    {
1773        if (!isset(Core::$instances[$obj_name][$key]))
1774        {
1775            Core::$instances[$obj_name][$key] = new $obj_name($key);
1776        }
1777
1778        return Core::$instances[$obj_name][$key];
1779    }
1780
1781    /**
1782     * 释放对象以释放内存
1783     *
1784     * 通常在批处理后操作,可有效的释放getFactory静态缓存的对象
1785     *
1786     * @param string $obj_name 对象名称 不传的话则清除全部
1787     * @param string $key 对象关键字 不传的话则清除$objName里的所有对象
1788     */
1789    public static function factory_release($obj_name = null, $key = null)
1790    {
1791        if (IS_CLI || IS_DEBUG)
1792        {
1793            $old_memory = memory_get_usage();
1794        }
1795
1796        if  (null===$obj_name)
1797        {
1798            Core::$instances = array();
1799        }
1800        elseif (isset(Core::$instances[$obj_name]))
1801        {
1802            if (null===$key)
1803            {
1804                unset(Core::$instances[$obj_name]);
1805            }
1806            else
1807            {
1808                unset(Core::$instances[$obj_name][$key]);
1809            }
1810        }
1811
1812        if (IS_CLI)
1813        {
1814            echo __('The release memory:') . ( memory_get_usage() - $old_memory ) . "\n";
1815        }
1816        else if (IS_DEBUG)
1817        {
1818            Core::debug()->info(__('The release memory:') . ( memory_get_usage() - $old_memory) );
1819        }
1820    }
1821
1822    /**
1823     * 将项目切换回初始项目
1824     *
1825     * 当使用Core::change_project()设置切换过项目后,可使用此方法返回初始化时的项目
1826     */
1827    public static function reset_project()
1828    {
1829        if (defined('INITIAL_PROJECT_NAME') && INITIAL_PROJECT_NAME != Core::$project)
1830        {
1831            Core::change_project(INITIAL_PROJECT_NAME);
1832        }
1833    }
1834
1835    /**
1836     * 切换到另外一个项目
1837     *
1838     * 切换其它项目后,相关的config,include_path等都将加载为设定项目的,但是已经加载的class等是不可能销毁的,所以需谨慎使用
1839     *
1840     * @param string $project
1841     * @return boolean
1842     * @throws Exception 失败则抛出异常(比如不存在指定的项目)
1843     */
1844    public static function change_project($project)
1845    {
1846        if (Core::$project==$project)
1847        {
1848            return true;
1849        }
1850
1851        if ( !isset(Core::$core_config['projects'][$project] ) )
1852        {
1853            Core::show_500( __('not found the project: :project.', array(':project'=>$project) ) );
1854        }
1855
1856        if ( !Core::$core_config['projects'][$project]['isuse'] )
1857        {
1858            Core::show_500( __('the project: :project is not open.', array(':project'=>$project) ) );
1859        }
1860
1861        # 记录所有项目设置,当切换回项目时,使用此设置还原
1862        static $all_prjects_setting = array();
1863
1864        if (Core::$project)
1865        {
1866            // 记录上一个项目设置
1867            $all_prjects_setting[Core::$project] = array
1868            (
1869                'config'        => Core::$config,
1870                'include_path'  => Core::$include_path,
1871                'file_list'     => Core::$file_list,
1872                'project_dir'   => Core::$project_dir,
1873                'base_url'      => Core::$base_url,
1874            );
1875        }
1876
1877        # 原来的项目
1878        $old_project = Core::$project;
1879
1880        if (isset($all_prjects_setting[$project]))
1881        {
1882            # 设为当前项目
1883            Core::$project = $project;
1884
1885            # 还原配置
1886            Core::$config         = $all_prjects_setting[$project]['config'];
1887            Core::$include_path   = $all_prjects_setting[$project]['include_path'];
1888            Core::$file_list      = $all_prjects_setting[$project]['file_list'];
1889            Core::$project_dir    = $all_prjects_setting[$project]['project_dir'];
1890            Core::$base_url       = $all_prjects_setting[$project]['base_url'];
1891
1892            # 清除缓存数据
1893            unset($all_prjects_setting[$project]);
1894        }
1895        else
1896        {
1897            $p_config = Core::$core_config['projects'][$project];
1898
1899            if (!isset($p_config['dir']) || !$p_config['dir'])
1900            {
1901                Core::show_500(__('the project ":project" dir is not defined.', array(':project'=>$project)));
1902            }
1903
1904            # 设置include path
1905            $project_dir = DIR_PROJECT . $project . DS;
1906            if (!is_dir($project_dir))
1907            {
1908                Core::show_500(__('not found the project: :project.', array(':project' => $project)));
1909            }
1910
1911            # 项目路径
1912            $project_dir = realpath(DIR_PROJECT . $p_config['dir']);
1913
1914            if (!$project_dir || !is_dir($project_dir))
1915            {
1916                Core::show_500(__('the project dir :dir is not exist.', array(':dir'=>$p_config['dir'])));
1917            }
1918
1919            # 设为当前项目
1920            Core::$project = $project;
1921
1922            $project_dir .= DS;
1923
1924            Core::$project_dir = $project_dir;
1925
1926            # 处理base_url
1927            if (isset($p_config['url']) && $p_config['url'])
1928            {
1929                $url = rtrim(current((array)$p_config['url']),'/');
1930            }
1931            else
1932            {
1933                $url = '/';
1934            }
1935
1936            if (IS_ADMIN_MODE)
1937            {
1938                if (isset($p_config['url_admin']) && $p_config['url_admin'])
1939                {
1940                    $current_url = current((array)$p_config['url_admin']);
1941                    if (false===strpos($current_url[0],'://'))
1942                    {
1943                        $url .= trim($current_url, '/');
1944                        $url  = trim($url, '/') .'/';
1945                    }
1946                }
1947            }
1948
1949            if (IS_REST_MODE)
1950            {
1951                if (isset($p_config['url_rest']) && $p_config['url_rest'])
1952                {
1953                    $current_url = current((array)$p_config['url_rest']);
1954                    if (false===strpos($current_url[0], '://'))
1955                    {
1956                        $url .= trim($current_url, '/');
1957                        $url  = trim($url,'/') .'/';
1958                    }
1959                }
1960            }
1961
1962            Core::$base_url = $url;
1963
1964            # 重置$include_path
1965            Core::$include_path['project'] = array
1966            (
1967                Core::$project => $project_dir
1968            );
1969
1970            # 重新加载类库配置
1971            Core::reload_all_libraries();
1972        }
1973
1974        # 记录debug信息
1975        if (IS_DEBUG)
1976        {
1977            Core::debug()->info($project, '程序已切换到了新项目');
1978        }
1979
1980        # 回调callback
1981        if (Core::$change_project_callback)
1982        {
1983            foreach (Core::$change_project_callback as $fun)
1984            {
1985                call_user_func($fun, $old_project, $project);
1986            }
1987        }
1988
1989        return true;
1990    }
1991
1992    /**
1993     * 增加change_project回调事件
1994     *
1995     *     //将在每次执行 Core::change_project($new_project) 成功后执行 MyClass::myfun($old_project, $new_project) 方法,其中$old_project是原来的项目名
1996     *     Core::change_project_add_callback('MyClass::myfun');
1997     *
1998     * @param string|array $fun
1999     */
2000    public static function change_project_add_callback($fun)
2001    {
2002        Core::$change_project_callback[] = $fun;
2003
2004        if (count(Core::$change_project_callback)>1)
2005        {
2006            # 移除重复的项目
2007            Core::$change_project_callback = array_unique(Core::$change_project_callback);
2008        }
2009    }
2010
2011    /**
2012    * 移除import_library回调事件
2013    *
2014    * @param string|array $fun
2015    */
2016    public static function change_project_remove_callback($fun)
2017    {
2018        if (Core::$change_project_callback)
2019        {
2020            $new_arr = array();
2021            foreach (Core::$change_project_callback as $item)
2022            {
2023                if ($item!==$fun)
2024                {
2025                    $new_arr = $item;
2026                }
2027            }
2028
2029            Core::$change_project_callback = $new_arr;
2030        }
2031    }
2032
2033    /**
2034     * 导入指定类库
2035     *
2036     * 支持多个,当一次导入多个时,从数组最后一个开始导入
2037     *
2038     * 导入的格式必须是类似 com.a.b 的形式,否则会抛出异常,例如: com.myqee.test
2039     *
2040     *      Bootstrap::import_library('com.myqee.test');
2041     *      Bootstrap::import_library(array('com.myqee.test','com.myqee.cms'));
2042     *
2043     * @param string|array $library_name 指定类库 支持多个
2044     * @return boolean
2045     */
2046    public static function import_library($library_name)
2047    {
2048        $library_name = (array)$library_name;
2049
2050        $status = parent::import_library($library_name);
2051
2052        # 回调callback
2053        if ($status>0 && Core::$import_library_callback)
2054        {
2055            foreach (Core::$import_library_callback as $fun)
2056            {
2057                call_user_func($fun, $library_name);
2058            }
2059        }
2060
2061        return $status;
2062    }
2063
2064    /**
2065     * 增加import_library回调事件
2066     *
2067     *     //将在每次执行 Core::import_library($library_name) 成功后执行 MyClass::myfun((array)$library_name) 方法
2068     *     Core::add_import_library_callback('MyClass::myfun');
2069     *
2070     * @param string|array $fun
2071     */
2072    public static function import_library_add_callback($fun)
2073    {
2074        Core::$import_library_callback[] = $fun;
2075
2076        if (count(Core::$import_library_callback)>1)
2077        {
2078            # 移除重复的项目
2079            Core::$import_library_callback = array_unique(Core::$import_library_callback);
2080        }
2081    }
2082
2083    /**
2084     * 移除import_library回调事件
2085     *
2086     * @param string|array $fun
2087     */
2088    public static function import_library_remove_callback($fun)
2089    {
2090        if (Core::$import_library_callback)
2091        {
2092            $new_arr = array();
2093            foreach (Core::$import_library_callback as $item)
2094            {
2095                if ($item!==$fun)
2096                {
2097                    $new_arr = $item;
2098                }
2099            }
2100
2101            Core::$import_library_callback = $new_arr;
2102        }
2103    }
2104
2105    /**
2106     * 执行注册的关闭方法
2107     */
2108    protected static function run_shutdown_function()
2109    {
2110        static $run = null;
2111        if ( null!==$run )
2112        {
2113            return true;
2114        }
2115        $run = true;
2116
2117        if (Core::$shutdown_function)
2118        {
2119            foreach (Core::$shutdown_function as $item)
2120            {
2121                try
2122                {
2123                    call_user_func_array($item[0], (array)$item[1]);
2124                }
2125                catch ( Exception $e )
2126                {
2127
2128                }
2129            }
2130        }
2131    }
2132
2133    /**
2134     * 特殊的合并项目配置
2135     *
2136     * 相当于一维数组之间相加,这里支持多维
2137     *
2138     * @param array $c1
2139     * @param array $c2
2140     * @return array
2141     */
2142    protected static function _merge_project_config($c1, $c2)
2143    {
2144        foreach ($c2 as $k=>$v)
2145        {
2146            if (!isset($c1[$k]))
2147            {
2148                $c1[$k] = $v;
2149            }
2150            elseif ( is_array($c1[$k]) && is_array($v) )
2151            {
2152                $c1[$k] = Core::_merge_project_config($c1[$k] , $v );
2153            }
2154            elseif (is_numeric($k) && is_array($c1[$k]))
2155            {
2156                $c1[$k][] = $v;
2157            }
2158        }
2159        return $c1;
2160    }
2161
2162    /**
2163     * 关闭所有可能的外部链接,比如Database,Memcache等连接
2164     */
2165    public static function close_all_connect()
2166    {
2167        foreach ( Core::$close_connect_class_list as $class_name=>$fun )
2168        {
2169            try
2170            {
2171                call_user_func_array( array($class_name,$fun), array() );
2172            }
2173            catch (Exception $e)
2174            {
2175                Core::debug()->error('close_all_connect error:'.$e->getMessage());
2176            }
2177        }
2178    }
2179
2180    /**
2181     * 增加执行Core::close_all_connect()时会去关闭的类
2182     *
2183     *     Core::add_close_connect_class('Database','close_all_connect');
2184     *     Core::add_close_connect_class('Cache_Driver_Memcache');
2185     *     Core::add_close_connect_class('TestClass','close');
2186     *     //当执行 Core::close_all_connect() 时会调用 Database::close_all_connect() 和 Cache_Driver_Memcache::close_all_connect() 和 TestClass::close() 方法
2187     *
2188     * @param string $class_name
2189     * @param string $fun
2190     */
2191    public static function add_close_connect_class($class_name, $fun='close_all_connect')
2192    {
2193        Core::$close_connect_class_list[$class_name] = $fun;
2194    }
2195
2196    /**
2197     * 检查内部调用HASH是否有效
2198     *
2199     * @return boolean
2200     */
2201    protected static function check_system_request_allow()
2202    {
2203        $hash      = $_SERVER['HTTP_X_MYQEE_SYSTEM_HASH'];      // 请求验证HASH
2204        $time      = $_SERVER['HTTP_X_MYQEE_SYSTEM_TIME'];      // 请求验证时间
2205        $rstr      = $_SERVER['HTTP_X_MYQEE_SYSTEM_RSTR'];      // 请求随机字符串
2206        $project   = $_SERVER['HTTP_X_MYQEE_SYSTEM_PROJECT'];   // 请求的项目
2207        $path_info = $_SERVER['HTTP_X_MYQEE_SYSTEM_PATHINFO'];  // 请求的path_info
2208        $isadmin   = $_SERVER['HTTP_X_MYQEE_SYSTEM_ISADMIN'];   // 是否ADMIN
2209        $isrest    = $_SERVER['HTTP_X_MYQEE_SYSTEM_ISREST'];    // 是否RESTFul请求
2210        if (!$hash || !$time || !$rstr || !$project || !$path_info) return false;
2211
2212        // 请求时效检查
2213        if (microtime(1) - $time > 600)
2214        {
2215            Core::log('system request timeout', 'system-request');
2216            return false;
2217        }
2218
2219        // 验证IP
2220        if ('127.0.0.1'!=HttpIO::IP && HttpIO::IP != $_SERVER["SERVER_ADDR"])
2221        {
2222            $allow_ip = Core::config('core.system_exec_allow_ip');
2223
2224            if (is_array($allow_ip) && $allow_ip)
2225            {
2226                $allow = false;
2227                foreach ($allow_ip as $ip)
2228                {
2229                    if (HttpIO::IP == $ip)
2230                    {
2231                        $allow = true;
2232                        break;
2233                    }
2234
2235                    if (strpos($allow_ip, '*'))
2236                    {
2237                        // 对IP进行匹配
2238                        if (preg_match('#^' . str_replace('\\*', '[^\.]+', preg_quote($allow_ip, '#')) . '$#', HttpIO::IP))
2239                        {
2240                            $allow = true;
2241                            break;
2242                        }
2243                    }
2244                }
2245
2246                if (!$allow)
2247                {
2248                    Core::log('system request not allow ip:' . HttpIO::IP, 'system-request');
2249                    return false;
2250                }
2251            }
2252        }
2253
2254        $body = http_build_query(HttpIO::POST(null, HttpIO::PARAM_TYPE_OLDDATA));
2255
2256        // 系统调用密钥
2257        $system_exec_pass = Core::config('system_exec_key');
2258
2259        $key = Core::config()->get('system_exec_key', 'system', true);
2260
2261        if (!$key || abs(TIME-$key['time'])>86400*10)
2262        {
2263            return false;
2264        }
2265
2266        $other = $path_info .'_'. ($isadmin?1:0) .'_'. ($isrest?1:0) . $key['str'];
2267
2268        if ($system_exec_pass && strlen($system_exec_pass) >= 10)
2269        {
2270            // 如果有则使用系统调用密钥
2271            $newhash = sha1($body . $time . $system_exec_pass . $rstr .'_'. $other);
2272        }
2273        else
2274        {
2275            // 没有,则用系统配置和数据库加密
2276            $newhash = sha1($body . $time . serialize(Core::config('core')) . serialize(Core::config('database')) . $rstr .'_'. $other);
2277        }
2278
2279        if ($newhash==$hash)
2280        {
2281            return true;
2282        }
2283        else
2284        {
2285            Core::log('system request hash error', 'system-request');
2286            return false;
2287        }
2288    }
2289
2290    /**
2291     * 获取asset文件MD5号
2292     *
2293     * @param string $file
2294     * @return md5
2295     */
2296    public static function assets_hash($file)
2297    {
2298        //TODO 须加入文件版本号
2299        return '';
2300    }
2301
2302
2303    /**
2304     * 返回客户端IP数组列表
2305     *
2306     * 也可直接用 `HttpIO::IP` 来获取到当前单个IP
2307     *
2308     * @return array
2309     */
2310    public static function ip()
2311    {
2312        $ip = array();
2313
2314        if (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
2315        {
2316            $ip = explode(',', str_replace(' ', '', $_SERVER['HTTP_X_FORWARDED_FOR']));
2317        }
2318
2319        if(isset($_SERVER['HTTP_CLIENT_IP']))
2320        {
2321            $ip = array_merge($ip, explode(',', str_replace(' ', '', $_SERVER['HTTP_CLIENT_IP'])));
2322        }
2323
2324        if (isset($_SERVER['REMOTE_ADDR']))
2325        {
2326            $ip = array_merge($ip, explode(',', str_replace(' ', '', $_SERVER['REMOTE_ADDR'])));
2327        }
2328
2329        return $ip;
2330    }
2331
2332
2333    /**
2334     * 系统调用内容输出函数(请勿自行执行)
2335     */
2336    public static function _output_body()
2337    {
2338        # 发送header数据
2339        HttpIO::send_headers();
2340
2341        if (IS_DEBUG && isset($_REQUEST['debug']) && class_exists('Profiler', true))
2342        {
2343            # 调试打开时不缓存页面
2344            HttpIO::set_cache_header(0);
2345        }
2346
2347        # 执行注册的关闭方法
2348        ob_start();
2349        Core::run_shutdown_function();
2350        $output = ob_get_clean();
2351
2352        # 在页面输出前关闭所有的连接
2353        Core::close_all_connect();
2354
2355        # 输出内容
2356        echo Core::$output , $output;
2357    }
2358}
2359
2360
2361/**
2362 * 无调试对象
2363 *
2364 * @author     呼吸二氧化碳 <jonwang@myqee.com>
2365 * @category   Core
2366 * @package    Classes
2367 * @subpackage Core
2368 * @copyright  Copyright (c) 2008-2013 myqee.com
2369 * @license    http://www.myqee.com/license.html
2370 */
2371class __NoDebug
2372{
2373    public function __call($m, $v)
2374    {
2375        return $this;
2376    }
2377
2378    public function log($i = null)
2379    {
2380        return $this;
2381    }
2382
2383    public function info($i = null)
2384    {
2385        return $this;
2386    }
2387
2388    public function error($i = null)
2389    {
2390        return $this;
2391    }
2392
2393    public function group($i = null)
2394    {
2395        return $this;
2396    }
2397
2398    public function groupEnd($i = null)
2399    {
2400        return $this;
2401    }
2402
2403    public function table($Label = null, $Table = null)
2404    {
2405        return $this;
2406    }
2407
2408    public function profiler($i = null)
2409    {
2410        return $this;
2411    }
2412
2413    public function is_open()
2414    {
2415        return false;
2416    }
2417}
2418