Browse Source

第一次提交

huangsanjia 3 năm trước cách đây
mục cha
commit
a7a0b7e8f6

+ 1 - 0
extend/.htaccess

@@ -0,0 +1 @@
+deny from all

+ 265 - 0
extend/fast/Auth.php

@@ -0,0 +1,265 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2011 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: luofei614 <weibo.com/luofei614>
+// +----------------------------------------------------------------------
+// | 修改者: anuo (本权限类在原3.2.3的基础上修改过来的)
+// +----------------------------------------------------------------------
+
+namespace fast;
+
+use think\Db;
+use think\Config;
+use think\Session;
+use think\Request;
+
+/**
+ * 权限认证类
+ * 功能特性:
+ * 1,是对规则进行认证,不是对节点进行认证。用户可以把节点当作规则名称实现对节点进行认证。
+ *      $auth=new Auth();  $auth->check('规则名称','用户id')
+ * 2,可以同时对多条规则进行认证,并设置多条规则的关系(or或者and)
+ *      $auth=new Auth();  $auth->check('规则1,规则2','用户id','and')
+ *      第三个参数为and时表示,用户需要同时具有规则1和规则2的权限。 当第三个参数为or时,表示用户值需要具备其中一个条件即可。默认为or
+ * 3,一个用户可以属于多个用户组(think_auth_group_access表 定义了用户所属用户组)。我们需要设置每个用户组拥有哪些规则(think_auth_group 定义了用户组权限)
+ * 4,支持规则表达式。
+ *      在think_auth_rule 表中定义一条规则,condition字段就可以定义规则表达式。 如定义{score}>5  and {score}<100
+ * 表示用户的分数在5-100之间时这条规则才会通过。
+ */
+class Auth
+{
+
+    /**
+     * @var object 对象实例
+     */
+    protected static $instance;
+    protected $rules = [];
+
+    /**
+     * 当前请求实例
+     * @var Request
+     */
+    protected $request;
+    //默认配置
+    protected $config = [
+        'auth_on'           => 1, // 权限开关
+        'auth_type'         => 1, // 认证方式,1为实时认证;2为登录认证。
+        'auth_group'        => 'auth_group', // 用户组数据表名
+        'auth_group_access' => 'auth_group_access', // 用户-用户组关系表
+        'auth_rule'         => 'auth_rule', // 权限规则表
+        'auth_user'         => 'user', // 用户信息表
+    ];
+
+    public function __construct()
+    {
+        if ($auth = Config::get('auth')) {
+            $this->config = array_merge($this->config, $auth);
+        }
+        // 初始化request
+        $this->request = Request::instance();
+    }
+
+    /**
+     * 初始化
+     * @access public
+     * @param array $options 参数
+     * @return Auth
+     */
+    public static function instance($options = [])
+    {
+        if (is_null(self::$instance)) {
+            self::$instance = new static($options);
+        }
+
+        return self::$instance;
+    }
+
+    /**
+     * 检查权限
+     * @param string|array $name     需要验证的规则列表,支持逗号分隔的权限规则或索引数组
+     * @param int          $uid      认证用户的id
+     * @param string       $relation 如果为 'or' 表示满足任一条规则即通过验证;如果为 'and'则表示需满足所有规则才能通过验证
+     * @param string       $mode     执行验证的模式,可分为url,normal
+     * @return bool 通过验证返回true;失败返回false
+     */
+    public function check($name, $uid, $relation = 'or', $mode = 'url')
+    {
+        if (!$this->config['auth_on']) {
+            return true;
+        }
+        // 获取用户需要验证的所有有效规则列表
+        $rulelist = $this->getRuleList($uid);
+        if (in_array('*', $rulelist)) {
+            return true;
+        }
+
+        if (is_string($name)) {
+            $name = strtolower($name);
+            if (strpos($name, ',') !== false) {
+                $name = explode(',', $name);
+            } else {
+                $name = [$name];
+            }
+        }
+        $list = []; //保存验证通过的规则名
+        if ('url' == $mode) {
+            $REQUEST = unserialize(strtolower(serialize($this->request->param())));
+        }
+        foreach ($rulelist as $rule) {
+            $query = preg_replace('/^.+\?/U', '', $rule);
+            if ('url' == $mode && $query != $rule) {
+                parse_str($query, $param); //解析规则中的param
+                $intersect = array_intersect_assoc($REQUEST, $param);
+                $rule = preg_replace('/\?.*$/U', '', $rule);
+                if (in_array($rule, $name) && $intersect == $param) {
+                    //如果节点相符且url参数满足
+                    $list[] = $rule;
+                }
+            } else {
+                if (in_array($rule, $name)) {
+                    $list[] = $rule;
+                }
+            }
+        }
+        if ('or' == $relation && !empty($list)) {
+            return true;
+        }
+        $diff = array_diff($name, $list);
+        if ('and' == $relation && empty($diff)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * 根据用户id获取用户组,返回值为数组
+     * @param int $uid  用户id
+     * @return array       用户所属的用户组 array(
+     *                  array('uid'=>'用户id','group_id'=>'用户组id','name'=>'用户组名称','rules'=>'用户组拥有的规则id,多个,号隔开'),
+     *                  ...)
+     */
+    public function getGroups($uid)
+    {
+        static $groups = [];
+        if (isset($groups[$uid])) {
+            return $groups[$uid];
+        }
+
+        // 执行查询
+        $user_groups = Db::name($this->config['auth_group_access'])
+            ->alias('aga')
+            ->join('__' . strtoupper($this->config['auth_group']) . '__ ag', 'aga.group_id = ag.id', 'LEFT')
+            ->field('aga.uid,aga.group_id,ag.id,ag.pid,ag.name,ag.rules')
+            ->where("aga.uid='{$uid}' and ag.status='normal'")
+            ->select();
+        $groups[$uid] = $user_groups ?: [];
+        return $groups[$uid];
+    }
+
+    /**
+     * 获得权限规则列表
+     * @param int $uid 用户id
+     * @return array
+     */
+    public function getRuleList($uid)
+    {
+        static $_rulelist = []; //保存用户验证通过的权限列表
+        if (isset($_rulelist[$uid])) {
+            return $_rulelist[$uid];
+        }
+        if (2 == $this->config['auth_type'] && Session::has('_rule_list_' . $uid)) {
+            return Session::get('_rule_list_' . $uid);
+        }
+
+        // 读取用户规则节点
+        $ids = $this->getRuleIds($uid);
+        if (empty($ids)) {
+            $_rulelist[$uid] = [];
+            return [];
+        }
+
+        // 筛选条件
+        $where = [
+            'status' => 'normal'
+        ];
+        if (!in_array('*', $ids)) {
+            $where['id'] = ['in', $ids];
+        }
+        //读取用户组所有权限规则
+        $this->rules = Db::name($this->config['auth_rule'])->where($where)->field('id,pid,condition,icon,name,title,ismenu')->select();
+
+        //循环规则,判断结果。
+        $rulelist = []; //
+        if (in_array('*', $ids)) {
+            $rulelist[] = "*";
+        }
+        foreach ($this->rules as $rule) {
+            //超级管理员无需验证condition
+            if (!empty($rule['condition']) && !in_array('*', $ids)) {
+                //根据condition进行验证
+                $user = $this->getUserInfo($uid); //获取用户信息,一维数组
+                $nums = 0;
+                $condition = str_replace(['&&', '||'], "\r\n", $rule['condition']);
+                $condition = preg_replace('/\{(\w*?)\}/', '\\1', $condition);
+                $conditionArr = explode("\r\n", $condition);
+                foreach ($conditionArr as $index => $item) {
+                    preg_match("/^(\w+)\s?([\>\<\=]+)\s?(.*)$/", trim($item), $matches);
+                    if ($matches && isset($user[$matches[1]]) && version_compare($user[$matches[1]], $matches[3], $matches[2])) {
+                        $nums++;
+                    }
+                }
+                if ($conditionArr && ((stripos($rule['condition'], "||") !== false && $nums > 0) || count($conditionArr) == $nums)) {
+                    $rulelist[$rule['id']] = strtolower($rule['name']);
+                }
+            } else {
+                //只要存在就记录
+                $rulelist[$rule['id']] = strtolower($rule['name']);
+            }
+        }
+        $_rulelist[$uid] = $rulelist;
+        //登录验证则需要保存规则列表
+        if (2 == $this->config['auth_type']) {
+            //规则列表结果保存到session
+            Session::set('_rule_list_' . $uid, $rulelist);
+        }
+        return array_unique($rulelist);
+    }
+
+    public function getRuleIds($uid)
+    {
+        //读取用户所属用户组
+        $groups = $this->getGroups($uid);
+        $ids = []; //保存用户所属用户组设置的所有权限规则id
+        foreach ($groups as $g) {
+            $ids = array_merge($ids, explode(',', trim($g['rules'], ',')));
+        }
+        $ids = array_unique($ids);
+        return $ids;
+    }
+
+    /**
+     * 获得用户资料
+     * @param int $uid 用户id
+     * @return mixed
+     */
+    protected function getUserInfo($uid)
+    {
+        static $user_info = [];
+
+        $user = Db::name($this->config['auth_user']);
+        // 获取用户表主键
+        $_pk = is_string($user->getPk()) ? $user->getPk() : 'uid';
+        if (!isset($user_info[$uid])) {
+            $user_info[$uid] = $user->where($_pk, $uid)->find();
+        }
+
+        return $user_info[$uid];
+    }
+}

+ 226 - 0
extend/fast/Date.php

@@ -0,0 +1,226 @@
+<?php
+
+namespace fast;
+
+use DateTime;
+use DateTimeZone;
+
+/**
+ * 日期时间处理类
+ */
+class Date
+{
+    const YEAR = 31536000;
+    const MONTH = 2592000;
+    const WEEK = 604800;
+    const DAY = 86400;
+    const HOUR = 3600;
+    const MINUTE = 60;
+
+    /**
+     * 计算两个时区间相差的时长,单位为秒
+     *
+     * $seconds = self::offset('America/Chicago', 'GMT');
+     *
+     * [!!] A list of time zones that PHP supports can be found at
+     * <http://php.net/timezones>.
+     *
+     * @param string $remote timezone that to find the offset of
+     * @param string $local  timezone used as the baseline
+     * @param mixed  $now    UNIX timestamp or date string
+     * @return  integer
+     */
+    public static function offset($remote, $local = null, $now = null)
+    {
+        if ($local === null) {
+            // Use the default timezone
+            $local = date_default_timezone_get();
+        }
+        if (is_int($now)) {
+            // Convert the timestamp into a string
+            $now = date(DateTime::RFC2822, $now);
+        }
+        // Create timezone objects
+        $zone_remote = new DateTimeZone($remote);
+        $zone_local = new DateTimeZone($local);
+        // Create date objects from timezones
+        $time_remote = new DateTime($now, $zone_remote);
+        $time_local = new DateTime($now, $zone_local);
+        // Find the offset
+        $offset = $zone_remote->getOffset($time_remote) - $zone_local->getOffset($time_local);
+        return $offset;
+    }
+
+    /**
+     * 计算两个时间戳之间相差的时间
+     *
+     * $span = self::span(60, 182, 'minutes,seconds'); // array('minutes' => 2, 'seconds' => 2)
+     * $span = self::span(60, 182, 'minutes'); // 2
+     *
+     * @param int    $remote timestamp to find the span of
+     * @param int    $local  timestamp to use as the baseline
+     * @param string $output formatting string
+     * @return  string   when only a single output is requested
+     * @return  array    associative list of all outputs requested
+     * @from https://github.com/kohana/ohanzee-helpers/blob/master/src/Date.php
+     */
+    public static function span($remote, $local = null, $output = 'years,months,weeks,days,hours,minutes,seconds')
+    {
+        // Normalize output
+        $output = trim(strtolower((string)$output));
+        if (!$output) {
+            // Invalid output
+            return false;
+        }
+        // Array with the output formats
+        $output = preg_split('/[^a-z]+/', $output);
+        // Convert the list of outputs to an associative array
+        $output = array_combine($output, array_fill(0, count($output), 0));
+        // Make the output values into keys
+        extract(array_flip($output), EXTR_SKIP);
+        if ($local === null) {
+            // Calculate the span from the current time
+            $local = time();
+        }
+        // Calculate timespan (seconds)
+        $timespan = abs($remote - $local);
+        if (isset($output['years'])) {
+            $timespan -= self::YEAR * ($output['years'] = (int)floor($timespan / self::YEAR));
+        }
+        if (isset($output['months'])) {
+            $timespan -= self::MONTH * ($output['months'] = (int)floor($timespan / self::MONTH));
+        }
+        if (isset($output['weeks'])) {
+            $timespan -= self::WEEK * ($output['weeks'] = (int)floor($timespan / self::WEEK));
+        }
+        if (isset($output['days'])) {
+            $timespan -= self::DAY * ($output['days'] = (int)floor($timespan / self::DAY));
+        }
+        if (isset($output['hours'])) {
+            $timespan -= self::HOUR * ($output['hours'] = (int)floor($timespan / self::HOUR));
+        }
+        if (isset($output['minutes'])) {
+            $timespan -= self::MINUTE * ($output['minutes'] = (int)floor($timespan / self::MINUTE));
+        }
+        // Seconds ago, 1
+        if (isset($output['seconds'])) {
+            $output['seconds'] = $timespan;
+        }
+        if (count($output) === 1) {
+            // Only a single output was requested, return it
+            return array_pop($output);
+        }
+        // Return array
+        return $output;
+    }
+
+    /**
+     * 格式化 UNIX 时间戳为人易读的字符串
+     *
+     * @param int    Unix 时间戳
+     * @param mixed $local 本地时间
+     *
+     * @return    string    格式化的日期字符串
+     */
+    public static function human($remote, $local = null)
+    {
+        $time_diff = (is_null($local) || $local ? time() : $local) - $remote;
+        $tense = $time_diff < 0 ? 'after' : 'ago';
+        $time_diff = abs($time_diff);
+        $chunks = [
+            [60 * 60 * 24 * 365, 'year'],
+            [60 * 60 * 24 * 30, 'month'],
+            [60 * 60 * 24 * 7, 'week'],
+            [60 * 60 * 24, 'day'],
+            [60 * 60, 'hour'],
+            [60, 'minute'],
+            [1, 'second']
+        ];
+        $name = 'second';
+        $count = 0;
+
+        for ($i = 0, $j = count($chunks); $i < $j; $i++) {
+            $seconds = $chunks[$i][0];
+            $name = $chunks[$i][1];
+            if (($count = floor($time_diff / $seconds)) != 0) {
+                break;
+            }
+        }
+        return __("%d $name%s $tense", $count, ($count > 1 ? 's' : ''));
+    }
+
+    /**
+     * 获取一个基于时间偏移的Unix时间戳
+     *
+     * @param string $type     时间类型,默认为day,可选minute,hour,day,week,month,quarter,year
+     * @param int    $offset   时间偏移量 默认为0,正数表示当前type之后,负数表示当前type之前
+     * @param string $position 时间的开始或结束,默认为begin,可选前(begin,start,first,front),end
+     * @param int    $year     基准年,默认为null,即以当前年为基准
+     * @param int    $month    基准月,默认为null,即以当前月为基准
+     * @param int    $day      基准天,默认为null,即以当前天为基准
+     * @param int    $hour     基准小时,默认为null,即以当前年小时基准
+     * @param int    $minute   基准分钟,默认为null,即以当前分钟为基准
+     * @return int 处理后的Unix时间戳
+     */
+    public static function unixtime($type = 'day', $offset = 0, $position = 'begin', $year = null, $month = null, $day = null, $hour = null, $minute = null)
+    {
+        $year = is_null($year) ? date('Y') : $year;
+        $month = is_null($month) ? date('m') : $month;
+        $day = is_null($day) ? date('d') : $day;
+        $hour = is_null($hour) ? date('H') : $hour;
+        $minute = is_null($minute) ? date('i') : $minute;
+        $position = in_array($position, array('begin', 'start', 'first', 'front'));
+
+        $baseTime = mktime(0, 0, 0, $month, $day, $year);
+
+        switch ($type) {
+            case 'minute':
+                $time = $position ? mktime($hour, $minute + $offset, 0, $month, $day, $year) : mktime($hour, $minute + $offset, 59, $month, $day, $year);
+                break;
+            case 'hour':
+                $time = $position ? mktime($hour + $offset, 0, 0, $month, $day, $year) : mktime($hour + $offset, 59, 59, $month, $day, $year);
+                break;
+            case 'day':
+                $time = $position ? mktime(0, 0, 0, $month, $day + $offset, $year) : mktime(23, 59, 59, $month, $day + $offset, $year);
+                break;
+            case 'week':
+                $weekIndex = date("w", $baseTime);
+                $time = $position ?
+                    strtotime($offset . " weeks", strtotime(date('Y-m-d', strtotime("-" . ($weekIndex ? $weekIndex - 1 : 6) . " days", $baseTime)))) :
+                    strtotime($offset . " weeks", strtotime(date('Y-m-d 23:59:59', strtotime("+" . (6 - ($weekIndex ? $weekIndex - 1 : 6)) . " days", $baseTime))));
+                break;
+            case 'month':
+                $_timestamp = mktime(0, 0, 0, $month + $offset, 1, $year);
+                $time = $position ? $_timestamp : mktime(23, 59, 59, $month + $offset, self::days_in_month(date("m", $_timestamp), date("Y", $_timestamp)), $year);
+                break;
+            case 'quarter':
+                $_month = date("m", mktime(0, 0, 0, (ceil(date('n', mktime(0, 0, 0, $month, $day, $year)) / 3) + $offset) * 3, $day, $year));
+                $time = $position ?
+                    mktime(0, 0, 0, 1 + ((ceil(date('n', $baseTime) / 3) + $offset) - 1) * 3, 1, $year) :
+                    mktime(23, 59, 59, (ceil(date('n', $baseTime) / 3) + $offset) * 3, self::days_in_month((ceil(date('n', $baseTime) / 3) + $offset) * 3, $year), $year);
+                break;
+            case 'year':
+                $time = $position ? mktime(0, 0, 0, 1, 1, $year + $offset) : mktime(23, 59, 59, 12, 31, $year + $offset);
+                break;
+            default:
+                $time = mktime($hour, $minute, 0, $month, $day, $year);
+                break;
+        }
+        return $time;
+    }
+
+    /**
+     * 获取指定年月拥有的天数
+     * @param int $month
+     * @param int $year
+     * @return false|int|string
+     */
+    public static function days_in_month($month, $year)
+    {
+        if (function_exists("cal_days_in_month")) {
+            return cal_days_in_month(CAL_GREGORIAN, $month, $year);
+        } else {
+            return date('t', mktime(0, 0, 0, $month, 1, $year));
+        }
+    }
+}

+ 1289 - 0
extend/fast/Form.php

@@ -0,0 +1,1289 @@
+<?php
+
+namespace fast;
+
+use ArrayAccess;
+
+/**
+ * 表单元素生成
+ * @class   Form
+ * @package fast
+ * @method static string token() 生成Token
+ * @method static string label(string $name, string $value = null, array $options = []) label标签
+ * @method static string input($type, $name, string $value = null, array $options = []) 按类型生成文本框
+ * @method static string text(string $name, string $value = null, array $options = []) 普通文本框
+ * @method static string password(string $name, array $options = []) 密码文本框
+ * @method static string hidden(string $name, string $value = null, array $options = []) 隐藏文本框
+ * @method static string email(string $name, string $value = null, array $options = []) Email文本框
+ * @method static string url(string $name, string $value = null, array $options = []) URL文本框
+ * @method static string file(string $name, array $options = []) 文件上传组件
+ * @method static string textarea(string $name, string $value = null, array $options = []) 多行文本框
+ * @method static string editor(string $name, string $value = null, array $options = []) 富文本编辑器
+ * @method static string select(string $name, array $list = [], string $selected = null, array $options = []) 下拉列表组件
+ * @method static string selects(string $name, array $list = [], string $selected = null, array $options = []) 下拉列表组件(多选)
+ * @method static string selectpicker(string $name, array $list = [], string $selected = null, array $options = []) 下拉列表组件(友好)
+ * @method static string selectpickers(string $name, array $list = [], string $selected = null, array $options = []) 下拉列表组件(友好)(多选)
+ * @method static string selectpage(string $name, string $value, string $url, string $field = null, string $primaryKey = null, array $options = []) 动态下拉列表组件
+ * @method static string selectpages(string $name, string $value, string $url, string $field = null, string $primaryKey = null, array $options = []) 动态下拉列表组件(多选)
+ * @method static string citypicker(string $name, string $value, array $options = []) 城市选择组件
+ * @method static string switcher(string $name, string $value, array $options = []) 切换组件
+ * @method static string datepicker(string $name, string $value, array $options = []) 日期选择组件
+ * @method static string timepicker(string $name, string $value, array $options = []) 时间选择组件
+ * @method static string datetimepicker(string $name, string $value, array $options = []) 日期时间选择组件
+ * @method static string daterange(string $name, string $value, array $options = []) 日期区间组件
+ * @method static string timerange(string $name, string $value, array $options = []) 时间区间组件
+ * @method static string datetimerange(string $name, string $value, array $options = []) 日期时间区间组件
+ * @method static string fieldlist(string $name, string $value, string $title = null, string $template = null, array $options = []) 字段列表组件
+ * @method static string cxselect(string $url, array $names = [], array $values = [], array $options = []) 联动组件
+ * @method static string selectRange(string $name, string $begin, string $end, string $selected = null, array $options = []) 选择数字区间
+ * @method static string selectYear(string $name, string $begin, string $end, string $selected = null, array $options = []) 选择年
+ * @method static string selectMonth(string $name, string $selected = null, array $options = [], string $format = '%m') 选择月
+ * @method static string checkbox(string $name, string $value = '1', string $checked = null, array $options = []) 单个复选框
+ * @method static string checkboxs(string $name, array $list = [], string $checked = null, array $options = []) 一组复选框
+ * @method static string radio(string $name, string $value = null, string $checked = null, array $options = [])) 单个单选框
+ * @method static string radios(string $name, array $list = [], string $checked = null, array $options = [])) 一组单选框
+ * @method static string image(string $name = null, string $value, array $inputAttr = [], array $uploadAttr = [], array $chooseAttr = [], array $previewAttr = []) 上传图片组件
+ * @method static string images(string $name = null, string $value, array $inputAttr = [], array $uploadAttr = [], array $chooseAttr = [], array $previewAttr = []) 上传图片组件(多图))
+ * @method static string upload(string $name = null, string $value, array $inputAttr = [], array $uploadAttr = [], array $chooseAttr = [], array $previewAttr = []) 上传文件组件
+ * @method static string uploads(string $name = null, string $value, array $inputAttr = [], array $uploadAttr = [], array $chooseAttr = [], array $previewAttr = []) 上传文件组件(多文件))
+ * @method static string button(string $value = null, array $options = []) 表单button
+ */
+class Form
+{
+
+    /**
+     * @param $name
+     * @param $arguments
+     * @return FormBuilder
+     */
+    public static function __callStatic($name, $arguments)
+    {
+        return call_user_func_array([FormBuilder::instance(), $name], $arguments);
+    }
+}
+
+/**
+ *
+ * 表单元素生成
+ * @from https://github.com/illuminate/html
+ * @package fast
+ */
+class FormBuilder
+{
+
+    /**
+     * Token
+     *
+     * @var string
+     */
+    protected $csrfToken = array('name' => '__token__');
+
+    /**
+     * 已创建的标签名称
+     *
+     * @var array
+     */
+    protected $labels = [];
+
+    /**
+     * 跳过的填充value值的类型
+     *
+     * @var array
+     */
+    protected $skipValueTypes = array('file', 'password', 'checkbox', 'radio');
+
+    /**
+     * 转义HTML
+     * @var boolean
+     */
+    protected $escapeHtml = true;
+    protected static $instance;
+
+    /**
+     * 获取单例
+     * @param array $options
+     * @return static
+     */
+    public static function instance($options = [])
+    {
+        if (is_null(self::$instance)) {
+            self::$instance = new static($options);
+        }
+
+        return self::$instance;
+    }
+
+    /**
+     * 设置是否转义
+     * @param boolean $escape
+     */
+    public function setEscapeHtml($escape)
+    {
+        $this->escapeHtml = $escape;
+    }
+
+    /**
+     * 获取转义编码后的值
+     * @param string $value
+     * @return string
+     */
+    public function escape($value)
+    {
+        if (!$this->escapeHtml) {
+            return $value;
+        }
+        if (is_array($value)) {
+            $value = json_encode($value, JSON_UNESCAPED_UNICODE);
+        }
+        return htmlspecialchars($value, ENT_QUOTES, 'UTF-8', false);
+    }
+
+    /**
+     * 生成Token
+     *
+     * @param string $name
+     * @param string $type
+     * @return string
+     */
+    public function token($name = '__token__', $type = 'md5')
+    {
+        if (function_exists('token')) {
+            return token($name, $type);
+        }
+
+        return '';
+    }
+
+    /**
+     * 生成Label标签
+     *
+     * @param string $name
+     * @param string $value
+     * @param array  $options
+     * @return string
+     */
+    public function label($name, $value = null, $options = [])
+    {
+        $this->labels[] = $name;
+
+        $options = $this->attributes($options);
+        $value = $this->escape($this->formatLabel($name, $value));
+
+        return '<label for="' . $name . '"' . $options . '>' . $value . '</label>';
+    }
+
+    /**
+     * Format the label value.
+     *
+     * @param string      $name
+     * @param string|null $value
+     * @return string
+     */
+    protected function formatLabel($name, $value)
+    {
+        return $value ?: ucwords(str_replace('_', ' ', $name));
+    }
+
+    /**
+     * 生成文本框(按类型)
+     *
+     * @param string $type
+     * @param string $name
+     * @param string $value
+     * @param array  $options
+     * @return string
+     */
+    public function input($type, $name, $value = null, $options = [])
+    {
+        if (!isset($options['name'])) {
+            $options['name'] = $name;
+        }
+
+        $id = $this->getIdAttribute($name, $options);
+
+        if (!in_array($type, $this->skipValueTypes)) {
+            $value = $this->getValueAttribute($name, $value);
+            $options['class'] = isset($options['class']) ? $options['class'] . (stripos($options['class'], 'form-control') !== false ? '' : ' form-control') : 'form-control';
+        }
+
+        $merge = compact('type', 'value', 'id');
+        $options = array_merge($options, $merge);
+
+        return '<input' . $this->attributes($options) . '>';
+    }
+
+    /**
+     * 生成普通文本框
+     *
+     * @param string $name
+     * @param string $value
+     * @param array  $options
+     * @return string
+     */
+    public function text($name, $value = null, $options = [])
+    {
+        return $this->input('text', $name, $value, $options);
+    }
+
+    /**
+     * 生成密码文本框
+     *
+     * @param string $name
+     * @param array  $options
+     * @return string
+     */
+    public function password($name, $options = [])
+    {
+        return $this->input('password', $name, '', $options);
+    }
+
+    /**
+     * 生成隐藏文本框
+     *
+     * @param string $name
+     * @param string $value
+     * @param array  $options
+     * @return string
+     */
+    public function hidden($name, $value = null, $options = [])
+    {
+        return $this->input('hidden', $name, $value, $options);
+    }
+
+    /**
+     * 生成Email文本框
+     *
+     * @param string $name
+     * @param string $value
+     * @param array  $options
+     * @return string
+     */
+    public function email($name, $value = null, $options = [])
+    {
+        return $this->input('email', $name, $value, $options);
+    }
+
+    /**
+     * 生成URL文本框
+     *
+     * @param string $name
+     * @param string $value
+     * @param array  $options
+     * @return string
+     */
+    public function url($name, $value = null, $options = [])
+    {
+        return $this->input('url', $name, $value, $options);
+    }
+
+    /**
+     * 生成上传文件组件
+     *
+     * @param string $name
+     * @param array  $options
+     * @return string
+     */
+    public function file($name, $options = [])
+    {
+        return $this->input('file', $name, null, $options);
+    }
+
+    /**
+     * 生成多行文本框
+     *
+     * @param string $name
+     * @param string $value
+     * @param array  $options
+     * @return string
+     */
+    public function textarea($name, $value = null, $options = [])
+    {
+        if (!isset($options['name'])) {
+            $options['name'] = $name;
+        }
+
+        $options = $this->setTextAreaSize($options);
+        $options['id'] = $this->getIdAttribute($name, $options);
+        $value = (string)$this->getValueAttribute($name, $value);
+
+        unset($options['size']);
+
+        $options['class'] = isset($options['class']) ? $options['class'] . (stripos($options['class'], 'form-control') !== false ? '' : ' form-control') : 'form-control';
+        $options = $this->attributes($options);
+
+        return '<textarea' . $options . '>' . $this->escape($value) . '</textarea>';
+    }
+
+    /**
+     * 生成富文本编辑器
+     *
+     * @param string $name
+     * @param string $value
+     * @param array  $options
+     * @return string
+     */
+    public function editor($name, $value = null, $options = [])
+    {
+        $options['class'] = isset($options['class']) ? $options['class'] . ' editor' : 'editor';
+        return $this->textarea($name, $value, $options);
+    }
+
+    /**
+     * 设置默认的文本框行列数
+     *
+     * @param array $options
+     * @return array
+     */
+    protected function setTextAreaSize($options)
+    {
+        if (isset($options['size'])) {
+            return $this->setQuickTextAreaSize($options);
+        }
+
+        $cols = array_get($options, 'cols', 50);
+        $rows = array_get($options, 'rows', 5);
+
+        return array_merge($options, compact('cols', 'rows'));
+    }
+
+    /**
+     * 根据size设置行数和列数
+     *
+     * @param array $options
+     * @return array
+     */
+    protected function setQuickTextAreaSize($options)
+    {
+        $segments = explode('x', $options['size']);
+        return array_merge($options, array('cols' => $segments[0], 'rows' => $segments[1]));
+    }
+
+    /**
+     * 生成滑块
+     *
+     * @param string $name
+     * @param string $min
+     * @param string $max
+     * @param string $step
+     * @param string $value
+     * @param array  $options
+     * @return string
+     */
+    public function slider($name, $min, $max, $step, $value = null, $options = [])
+    {
+        $options = array_merge($options, ['data-slider-min' => $min, 'data-slider-max' => $max, 'data-slider-step' => $step, 'data-slider-value' => $value ? $value : '']);
+        $options['class'] = isset($options['class']) ? $options['class'] . (stripos($options['class'], 'form-control') !== false ? '' : ' slider form-control') : 'slider form-control';
+        return $this->input('text', $name, $value, $options);
+    }
+
+    /**
+     * 生成下拉列表框
+     *
+     * @param string $name
+     * @param array  $list
+     * @param mixed  $selected
+     * @param array  $options
+     * @return string
+     */
+    public function select($name, $list = [], $selected = null, $options = [])
+    {
+        $selected = $this->getValueAttribute($name, $selected);
+
+        $options['id'] = $this->getIdAttribute($name, $options);
+
+        if (!isset($options['name'])) {
+            $options['name'] = $name;
+        }
+
+        $html = [];
+        foreach ($list as $value => $display) {
+            $html[] = $this->getSelectOption($display, $value, $selected);
+        }
+        $options['class'] = isset($options['class']) ? $options['class'] . (stripos($options['class'], 'form-control') !== false ? '' : ' form-control') : 'form-control';
+
+        $options = $this->attributes($options);
+        $list = implode('', $html);
+
+        return "<select{$options}>{$list}</select>";
+    }
+
+    /**
+     * 下拉列表(多选)
+     *
+     * @param string $name
+     * @param array  $list
+     * @param mixed  $selected
+     * @param array  $options
+     * @return string
+     */
+    public function selects($name, $list = [], $selected = null, $options = [])
+    {
+        $options[] = 'multiple';
+        return $this->select($name, $list, $selected, $options);
+    }
+
+    /**
+     * 下拉列表(友好)
+     *
+     * @param string $name
+     * @param array  $list
+     * @param mixed  $selected
+     * @param array  $options
+     * @return string
+     */
+    public function selectpicker($name, $list = [], $selected = null, $options = [])
+    {
+        $options['class'] = isset($options['class']) ? $options['class'] . ' selectpicker' : 'selectpicker';
+        return $this->select($name, $list, $selected, $options);
+    }
+
+    /**
+     * 下拉列表(友好)(多选)
+     *
+     * @param string $name
+     * @param array  $list
+     * @param mixed  $selected
+     * @param array  $options
+     * @return string
+     */
+    public function selectpickers($name, $list = [], $selected = null, $options = [])
+    {
+        $options[] = 'multiple';
+        return $this->selectpicker($name, $list, $selected, $options);
+    }
+
+    /**
+     * 生成动态下拉列表
+     *
+     * @param string $name       名称
+     * @param mixed  $value
+     * @param string $url        数据源地址
+     * @param string $field      显示的字段名称,默认为name
+     * @param string $primaryKey 主键,数据库中保存的值,默认为id
+     * @param array  $options
+     * @return string
+     */
+    public function selectpage($name, $value, $url, $field = null, $primaryKey = null, $options = [])
+    {
+        $options = array_merge($options, ['data-source' => $url, 'data-field' => $field ? $field : 'name', 'data-primary-key' => $primaryKey ? $primaryKey : 'id']);
+        $options['class'] = isset($options['class']) ? $options['class'] . ' selectpage' : 'selectpage';
+        return $this->text($name, $value, $options);
+    }
+
+
+    /**
+     * 生成动态下拉列表(复选)
+     *
+     * @param string $name       名称
+     * @param mixed  $value
+     * @param string $url        数据源地址
+     * @param string $field      显示的字段名称,默认为name
+     * @param string $primaryKey 主键,数据库中保存的值,默认为id
+     * @param array  $options
+     * @return string
+     */
+    public function selectpages($name, $value, $url, $field = null, $primaryKey = null, $options = [])
+    {
+        $options['data-multiple'] = "true";
+        return $this->selectpage($name, $value, $url, $field, $primaryKey, $options);
+    }
+
+    /**
+     * 生成城市选择框
+     *
+     * @param string $name
+     * @param mixed  $value
+     * @param array  $options
+     * @return string
+     */
+    public function citypicker($name, $value, $options = [])
+    {
+        $options['data-toggle'] = 'city-picker';
+        return "<div class='control-relative'>" . $this->text($name, $value, $options) . "</div>";
+    }
+
+    /**
+     * 生成switch组件
+     *
+     * @param string $name
+     * @param mixed  $value
+     * @param array  $options
+     * @return string
+     */
+    public function switcher($name, $value, $options = [])
+    {
+        $domname = str_replace(['[', ']', '.'], '', $name);
+        $btn = $this->hidden($name, $value, ['id' => "c-{$domname}"]);
+        $yes = 1;
+        $no = 0;
+        if (isset($options['yes']) && isset($options['no'])) {
+            $yes = $options['yes'];
+            $no = $options['no'];
+        }
+        $selected = $no == $value ? "fa-flip-horizontal text-gray" : "";
+        $disabled = (isset($options['disabled']) && $options['disabled']) || in_array('disabled', $options) ? "disabled" : '';
+        $color = isset($options['color']) ? $options['color'] : 'success';
+        unset($options['yes'], $options['no'], $options['color'], $options['disabled']);
+        $attr = $this->attributes($options);
+        $html = <<<EOD
+{$btn}
+<a href="javascript:;" data-toggle="switcher" class="btn-switcher {$disabled}" data-input-id="c-{$domname}" data-yes="{$yes}" data-no="{$no}" {$attr}><i class="fa fa-toggle-on text-{$color} {$selected} fa-2x"></i></a>
+EOD;
+        return $html;
+    }
+
+    /**
+     * 日期选择器
+     * @param string $name
+     * @param mixed  $value
+     * @param array  $options
+     * @return string
+     */
+    public function datepicker($name, $value, $options = [])
+    {
+        $defaults = [
+            'data-date-format' => "YYYY-MM-DD",
+        ];
+        $options = array_merge($defaults, $options);
+        $value = is_numeric($value) ? date("Y-m-d", $value) : $value;
+        return $this->datetimepicker($name, $value, $options);
+    }
+
+    /**
+     * 时间选择器
+     *
+     * @param string $name
+     * @param mixed  $value
+     * @param array  $options
+     * @return string
+     */
+    public function timepicker($name, $value, $options = [])
+    {
+        $defaults = [
+            'data-date-format' => "HH:mm:ss",
+        ];
+        $options = array_merge($defaults, $options);
+        $value = is_numeric($value) ? date("H:i:s", $value) : $value;
+        return $this->datetimepicker($name, $value, $options);
+    }
+
+    /**
+     * 日期时间选择器
+     *
+     * @param string $name
+     * @param mixed  $value
+     * @param array  $options
+     * @return string
+     */
+    public function datetimepicker($name, $value, $options = [])
+    {
+        $defaults = [
+            'data-date-format' => "YYYY-MM-DD HH:mm:ss",
+            'data-use-current' => "true",
+        ];
+        $value = is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value;
+        $options = array_merge($defaults, $options);
+        $options['class'] = isset($options['class']) ? $options['class'] . ' datetimepicker' : 'datetimepicker';
+        return $this->text($name, $value, $options);
+    }
+
+    /**
+     * 日期区间
+     *
+     * @param string $name
+     * @param string $value
+     * @param array  $options
+     * @return string
+     */
+    public function daterange($name, $value, $options = [])
+    {
+        $defaults = [
+            'data-locale' => [
+                'format' => 'YYYY-MM-DD'
+            ]
+        ];
+        $options = array_merge($defaults, $options);
+        return $this->datetimerange($name, $value, $options);
+    }
+
+    /**
+     * 时间区间
+     *
+     * @param string $name
+     * @param string $value
+     * @param array  $options
+     * @return string
+     */
+    public function timerange($name, $value, $options = [])
+    {
+        $defaults = [
+            'data-locale'                  => [
+                'format' => 'HH:mm:ss'
+            ],
+            'data-ranges'                  => [],
+            'data-show-custom-range-label' => "false",
+            'data-time-picker'             => "true",
+        ];
+        $options = array_merge($defaults, $options);
+        return $this->datetimerange($name, $value, $options);
+    }
+
+    /**
+     * 日期时间区间
+     *
+     * @param string $name
+     * @param string $value
+     * @param array  $options
+     * @return string
+     */
+    public function datetimerange($name, $value, $options = [])
+    {
+        $defaults = [
+            'data-locale' => [
+                'format' => 'YYYY-MM-DD HH:mm:ss'
+            ]
+        ];
+        $options = array_merge($defaults, $options);
+        $options['class'] = isset($options['class']) ? $options['class'] . ' datetimerange' : 'datetimerange';
+        return $this->text($name, $value, $options);
+    }
+
+    /**
+     * 生成字段列表组件
+     *
+     * @param string $name
+     * @param mixed  $value
+     * @param array  $title
+     * @param string $template
+     * @param array  $options
+     * @return string
+     */
+    public function fieldlist($name, $value, $title = null, $template = null, $options = [])
+    {
+        $append = __('Append');
+        $template = $template ? 'data-template="' . $template . '"' : '';
+        $attributes = $this->attributes($options);
+        if (is_null($title)) {
+            $title = [__('Key'), __('Value')];
+        }
+        $ins = implode("\n", array_map(function ($value) {
+            return "<ins>{$value}</ins>";
+        }, $title));
+        $value = is_array($value) ? json_encode($value) : $value;
+        $html = <<<EOD
+<dl class="fieldlist" data-name="{$name}" {$template} {$attributes}>
+    <dd>
+        {$ins}
+    </dd>
+    <dd><a href="javascript:;" class="btn btn-sm btn-success btn-append"><i class="fa fa-plus"></i> {$append}</a></dd>
+    <textarea name="{$name}" class="form-control hide" cols="30" rows="5">{$value}</textarea>
+</dl>
+EOD;
+        return $html;
+    }
+
+    /**
+     * 生成联动下拉列表
+     *
+     * @param string $url     联动获取数据源的URL地址
+     * @param array  $names   联动字段名称
+     * @param array  $values  联动字段默认选中的值
+     * @param array  $options 扩展属性
+     * @return string
+     */
+    public function cxselect($url, $names = [], $values = [], $options = [])
+    {
+        $classes = [];
+        $cxselect = [];
+        $attributes = $this->attributes($options);
+        foreach ($names as $index => $value) {
+            $level = $index + 1;
+            $class = "cxselect-{$level}";
+            $classes[] = $class;
+            $selectValue = isset($values[$value]) ? $values[$value] : (isset($values[$index]) ? $values[$index] : '');
+
+            $cxselect[] = <<<EOD
+<select class="{$class} form-control" name="{$value}" data-value="{$selectValue}" data-url="{$url}?level={$level}&name={$value}" {$attributes}></select>
+EOD;
+        }
+        $cxselect = implode("\n", $cxselect);
+        $selects = implode(',', $classes);
+        $html = <<<EOD
+<div class="form-inline" data-toggle="cxselect" data-selects="{$selects}">
+{$cxselect}
+</div>
+EOD;
+        return $html;
+    }
+
+    /**
+     * 创建一个下拉列表选择区间组件
+     *
+     * @param string $name
+     * @param string $begin
+     * @param string $end
+     * @param string $selected
+     * @param array  $options
+     * @return string
+     */
+    public function selectRange($name, $begin, $end, $selected = null, $options = [])
+    {
+        $range = array_combine($range = range($begin, $end), $range);
+        return $this->select($name, $range, $selected, $options);
+    }
+
+    /**
+     * 生成选择年组件
+     *
+     * @param string $name
+     * @param string $begin
+     * @param string $end
+     * @param string $selected
+     * @param array  $options
+     * @return string
+     */
+    public function selectYear($name, $begin, $end, $selected, $options)
+    {
+        return call_user_func_array(array($this, 'selectRange'), func_get_args());
+    }
+
+    /**
+     * 生成选择月组件
+     *
+     * @param string $name
+     * @param string $selected
+     * @param array  $options
+     * @param string $format
+     * @return string
+     */
+    public function selectMonth($name, $selected = null, $options = [], $format = '%m')
+    {
+        $months = [];
+
+        foreach (range(1, 12) as $month) {
+            $months[$month] = strftime($format, mktime(0, 0, 0, $month, 1));
+        }
+
+        return $this->select($name, $months, $selected, $options);
+    }
+
+    /**
+     * 根据传递的值生成option
+     *
+     * @param string $display
+     * @param string $value
+     * @param string $selected
+     * @return string
+     */
+    public function getSelectOption($display, $value, $selected)
+    {
+        if (is_array($display)) {
+            return $this->optionGroup($display, $value, $selected);
+        }
+
+        return $this->option($display, $value, $selected);
+    }
+
+    /**
+     * 生成optionGroup
+     *
+     * @param array  $list
+     * @param string $label
+     * @param string $selected
+     * @return string
+     */
+    protected function optionGroup($list, $label, $selected)
+    {
+        $html = [];
+
+        foreach ($list as $value => $display) {
+            $html[] = $this->option($display, $value, $selected);
+        }
+
+        return '<optgroup label="' . $this->escape($label) . '">' . implode('', $html) . '</optgroup>';
+    }
+
+    /**
+     * 生成option选项
+     *
+     * @param string $display
+     * @param string $value
+     * @param string $selected
+     * @return string
+     */
+    protected function option($display, $value, $selected)
+    {
+        $selected = $this->getSelectedValue($value, $selected);
+
+        $options = array('value' => $this->escape($value), 'selected' => $selected);
+
+        return '<option' . $this->attributes($options) . '>' . $this->escape($display) . '</option>';
+    }
+
+    /**
+     * 检测value是否选中
+     *
+     * @param string $value
+     * @param string $selected
+     * @return string
+     */
+    protected function getSelectedValue($value, $selected)
+    {
+        if (is_array($selected)) {
+            return in_array($value, $selected) ? 'selected' : null;
+        }
+
+        return ((string)$value == (string)$selected) ? 'selected' : null;
+    }
+
+    /**
+     * 生成复选按钮
+     *
+     * @param string $name
+     * @param mixed  $value
+     * @param bool   $checked
+     * @param array  $options
+     * @return string
+     */
+    public function checkbox($name, $value = 1, $checked = null, $options = [])
+    {
+        if ($checked) {
+            $options['checked'] = 'checked';
+        }
+
+        return $this->input('checkbox', $name, $value, $options);
+    }
+
+    /**
+     * 生成一组筛选框
+     *
+     * @param string $name
+     * @param array  $list
+     * @param mixed  $checked
+     * @param array  $options
+     * @return string
+     */
+    public function checkboxs($name, $list, $checked, $options = [])
+    {
+        $html = [];
+        $checked = is_null($checked) ? [] : $checked;
+        $checked = is_array($checked) ? $checked : explode(',', $checked);
+        foreach ($list as $k => $v) {
+            $options['id'] = "{$name}-{$k}";
+            $html[] = sprintf(Form::label("{$name}-{$k}", "%s {$v}"), Form::checkbox("{$name}[{$k}]", $k, in_array($k, $checked), $options));
+        }
+        return '<div class="checkbox">' . implode(' ', $html) . '</div>';
+    }
+
+    /**
+     * 生成单选按钮
+     *
+     * @param string $name
+     * @param mixed  $value
+     * @param bool   $checked
+     * @param array  $options
+     * @return string
+     */
+    public function radio($name, $value = null, $checked = null, $options = [])
+    {
+        if (is_null($value)) {
+            $value = $name;
+        }
+
+        if ($checked) {
+            $options['checked'] = 'checked';
+        }
+
+        return $this->input('radio', $name, $value, $options);
+    }
+
+    /**
+     * 生成一组单选框
+     *
+     * @param string $name
+     * @param array  $list
+     * @param mixed  $checked
+     * @param array  $options
+     * @return string
+     */
+    public function radios($name, $list, $checked = null, $options = [])
+    {
+        $html = [];
+        $checked = is_null($checked) ? key($list) : $checked;
+        $checked = is_array($checked) ? $checked : explode(',', $checked);
+        foreach ($list as $k => $v) {
+            $options['id'] = "{$name}-{$k}";
+            $html[] = sprintf(Form::label("{$name}-{$k}", "%s {$v}"), Form::radio($name, $k, in_array($k, $checked), $options));
+        }
+        return '<div class="radio">' . implode(' ', $html) . '</div>';
+    }
+
+    /**
+     * 生成上传图片组件(单图)
+     *
+     * @param string $name
+     * @param string $value
+     * @param array  $inputAttr
+     * @param array  $uploadAttr
+     * @param array  $chooseAttr
+     * @param array  $previewAttr
+     * @return string
+     */
+    public function image($name = null, $value = null, $inputAttr = [], $uploadAttr = [], $chooseAttr = [], $previewAttr = [])
+    {
+        $default = [
+            'data-mimetype' => 'image/gif,image/jpeg,image/png,image/jpg,image/bmp'
+        ];
+        $uploadAttr = is_array($uploadAttr) ? array_merge($default, $uploadAttr) : $uploadAttr;
+        $chooseAttr = is_array($chooseAttr) ? array_merge($default, $chooseAttr) : $chooseAttr;
+        return $this->uploader($name, $value, $inputAttr, $uploadAttr, $chooseAttr, $previewAttr);
+    }
+
+    /**
+     * 生成上传图片组件(多图)
+     *
+     * @param string $name
+     * @param string $value
+     * @param array  $inputAttr
+     * @param array  $uploadAttr
+     * @param array  $chooseAttr
+     * @param array  $previewAttr
+     * @return string
+     */
+    public function images($name = null, $value = null, $inputAttr = [], $uploadAttr = [], $chooseAttr = [], $previewAttr = [])
+    {
+        $default = [
+            'data-multiple' => 'true',
+            'data-mimetype' => 'image/gif,image/jpeg,image/png,image/jpg,image/bmp'
+        ];
+        $uploadAttr = is_array($uploadAttr) ? array_merge($default, $uploadAttr) : $uploadAttr;
+        $chooseAttr = is_array($chooseAttr) ? array_merge($default, $chooseAttr) : $chooseAttr;
+        return $this->uploader($name, $value, $inputAttr, $uploadAttr, $chooseAttr, $previewAttr);
+    }
+
+    /**
+     * 生成上传文件组件(单文件)
+     *
+     * @param string $name
+     * @param string $value
+     * @param array  $inputAttr
+     * @param array  $uploadAttr
+     * @param array  $chooseAttr
+     * @param array  $previewAttr
+     * @return string
+     */
+    public function upload($name = null, $value = null, $inputAttr = [], $uploadAttr = [], $chooseAttr = [], $previewAttr = [])
+    {
+        return $this->uploader($name, $value, $inputAttr, $uploadAttr, $chooseAttr, $previewAttr);
+    }
+
+    /**
+     * 生成上传文件组件(多文件)
+     *
+     * @param string $name
+     * @param string $value
+     * @param array  $inputAttr
+     * @param array  $uploadAttr
+     * @param array  $chooseAttr
+     * @param array  $previewAttr
+     * @return string
+     */
+    public function uploads($name = null, $value = null, $inputAttr = [], $uploadAttr = [], $chooseAttr = [], $previewAttr = [])
+    {
+        $default = [
+            'data-multiple' => 'true',
+        ];
+        $uploadAttr = is_array($uploadAttr) ? array_merge($default, $uploadAttr) : $uploadAttr;
+        $chooseAttr = is_array($chooseAttr) ? array_merge($default, $chooseAttr) : $chooseAttr;
+        return $this->uploader($name, $value, $inputAttr, $uploadAttr, $chooseAttr, $previewAttr);
+    }
+
+    protected function uploader($name = null, $value = null, $inputAttr = [], $uploadAttr = [], $chooseAttr = [], $previewAttr = [])
+    {
+        $domname = str_replace(['[', ']', '.'], '', $name);
+        $options = [
+            'id'            => "plupload-{$domname}",
+            'class'         => "btn btn-danger plupload",
+            'data-input-id' => "c-{$domname}",
+        ];
+        $upload = $uploadAttr === false ? false : true;
+        $choose = $chooseAttr === false ? false : true;
+        $preview = $previewAttr === false ? false : true;
+        if ($preview) {
+            $options['data-preview-id'] = "p-{$domname}";
+        }
+        $uploadBtn = $upload ? $this->button('<i class="fa fa-upload"></i> ' . __('Upload'), array_merge($options, $uploadAttr)) : '';
+        $options = [
+            'id'            => "fachoose-{$domname}",
+            'class'         => "btn btn-danger fachoose",
+            'data-input-id' => "c-{$domname}",
+        ];
+        if ($preview) {
+            $options['data-preview-id'] = "p-{$domname}";
+        }
+        $chooseBtn = $choose ? $this->button('<i class="fa fa-list"></i> ' . __('Choose'), array_merge($options, $chooseAttr)) : '';
+        $previewAttrHtml = $this->attributes($previewAttr);
+        $previewArea = $preview ? '<ul class="row list-inline plupload-preview" id="p-' . $domname . '" ' . $previewAttrHtml . '></ul>' : '';
+        $input = $this->text($name, $value, array_merge(['size' => 50, 'id' => "c-{$domname}"], $inputAttr));
+        $html = <<<EOD
+<div class="input-group">
+                {$input}
+                <div class="input-group-addon no-border no-padding">
+                    <span>{$uploadBtn}</span>                  
+                    <span>{$chooseBtn}</span>
+                </div>
+                <span class="msg-box n-right" for="c-{$domname}"></span>
+            </div>
+            {$previewArea}
+EOD;
+        return $html;
+    }
+
+    /**
+     * 生成一个按钮
+     *
+     * @param string $value
+     * @param array  $options
+     * @return string
+     */
+    public function button($value = null, $options = [])
+    {
+        if (!array_key_exists('type', $options)) {
+            $options['type'] = 'button';
+        }
+
+        return '<button' . $this->attributes($options) . '>' . $value . '</button>';
+    }
+
+    /**
+     * 获取ID属性值
+     *
+     * @param string $name
+     * @param array  $attributes
+     * @return string
+     */
+    public function getIdAttribute($name, $attributes)
+    {
+        if (array_key_exists('id', $attributes)) {
+            return $attributes['id'];
+        }
+
+        if (in_array($name, $this->labels)) {
+            return $name;
+        }
+    }
+
+    /**
+     * 获取Value属性值
+     *
+     * @param string $name
+     * @param string $value
+     * @return string
+     */
+    public function getValueAttribute($name, $value = null)
+    {
+        if (is_null($name)) {
+            return $value;
+        }
+
+        if (!is_null($value)) {
+            return $value;
+        }
+    }
+
+    /**
+     * 数组转换成一个HTML属性字符串。
+     *
+     * @param array $attributes
+     * @return string
+     */
+    public function attributes($attributes)
+    {
+        $html = [];
+        // 假设我们的keys 和 value 是相同的,
+        // 拿HTML“required”属性来说,假设是['required']数组,
+        // 会已 required="required" 拼接起来,而不是用数字keys去拼接
+        foreach ((array)$attributes as $key => $value) {
+            $element = $this->attributeElement($key, $value);
+            if (!is_null($element)) {
+                $html[] = $element;
+            }
+        }
+        return count($html) > 0 ? ' ' . implode(' ', $html) : '';
+    }
+
+    /**
+     * 拼接成一个属性。
+     *
+     * @param string $key
+     * @param string $value
+     * @return string
+     */
+    protected function attributeElement($key, $value)
+    {
+        if (is_numeric($key)) {
+            $key = $value;
+        }
+        if (!is_null($value)) {
+            if (is_array($value) || stripos($value, '"') !== false) {
+                $value = is_array($value) ? json_encode($value, JSON_UNESCAPED_UNICODE) : $value;
+                return $key . "='" . $value . "'";
+            } else {
+                return $key . '="' . $value . '"';
+            }
+        }
+    }
+}
+
+class Arr
+{
+
+    /**
+     * Determine whether the given value is array accessible.
+     *
+     * @param mixed $value
+     * @return bool
+     */
+    public static function accessible($value)
+    {
+        return is_array($value) || $value instanceof ArrayAccess;
+    }
+
+    /**
+     * Determine if the given key exists in the provided array.
+     *
+     * @param \ArrayAccess|array $array
+     * @param string|int         $key
+     * @return bool
+     */
+    public static function exists($array, $key)
+    {
+        if ($array instanceof ArrayAccess) {
+            return $array->offsetExists($key);
+        }
+        return array_key_exists($key, $array);
+    }
+
+    /**
+     * Get an item from an array using "dot" notation.
+     *
+     * @param \ArrayAccess|array $array
+     * @param string             $key
+     * @param mixed              $default
+     * @return mixed
+     */
+    public static function get($array, $key, $default = null)
+    {
+        if (!static::accessible($array)) {
+            return $default;
+        }
+        if (is_null($key)) {
+            return $array;
+        }
+        if (static::exists($array, $key)) {
+            return $array[$key];
+        }
+        foreach (explode('.', $key) as $segment) {
+            if (static::accessible($array) && static::exists($array, $segment)) {
+                $array = $array[$segment];
+            } else {
+                return $default;
+            }
+        }
+        return $array;
+    }
+
+    /**
+     * Get all of the given array except for a specified array of items.
+     *
+     * @param array        $array
+     * @param array|string $keys
+     * @return array
+     */
+    public static function except($array, $keys)
+    {
+        static::forget($array, $keys);
+        return $array;
+    }
+
+    /**
+     * Remove one or many array items from a given array using "dot" notation.
+     *
+     * @param array        $array
+     * @param array|string $keys
+     * @return void
+     */
+    public static function forget(&$array, $keys)
+    {
+        $original = &$array;
+        $keys = (array)$keys;
+        if (count($keys) === 0) {
+            return;
+        }
+        foreach ($keys as $key) {
+            // if the exact key exists in the top-level, remove it
+            if (static::exists($array, $key)) {
+                unset($array[$key]);
+                continue;
+            }
+            $parts = explode('.', $key);
+            // clean up before each pass
+            $array = &$original;
+            while (count($parts) > 1) {
+                $part = array_shift($parts);
+                if (isset($array[$part]) && is_array($array[$part])) {
+                    $array = &$array[$part];
+                } else {
+                    continue 2;
+                }
+            }
+            unset($array[array_shift($parts)]);
+        }
+    }
+}
+
+if (!function_exists('array_get')) {
+
+    /**
+     * Get an item from an array using "dot" notation.
+     *
+     * @param \ArrayAccess|array $array
+     * @param string             $key
+     * @param mixed              $default
+     * @return mixed
+     */
+    function array_get($array, $key, $default = null)
+    {
+        return Arr::get($array, $key, $default);
+    }
+}
+if (!function_exists('e')) {
+
+    /**
+     * Escape HTML special characters in a string.
+     *
+     *
+     * @return string
+     */
+    function e($value)
+    {
+        if (is_array($value)) {
+            $value = json_encode($value, JSON_UNESCAPED_UNICODE);
+        }
+        return htmlspecialchars($value, ENT_QUOTES, 'UTF-8', false);
+    }
+}
+if (!function_exists('array_except')) {
+
+    /**
+     * Get all of the given array except for a specified array of items.
+     *
+     * @param array        $array
+     * @param array|string $keys
+     * @return array
+     */
+    function array_except($array, $keys)
+    {
+        return Arr::except($array, $keys);
+    }
+}

+ 185 - 0
extend/fast/Http.php

@@ -0,0 +1,185 @@
+<?php
+
+namespace fast;
+
+/**
+ * Http 请求类
+ */
+class Http
+{
+
+    /**
+     * 发送一个POST请求
+     * @param string $url     请求URL
+     * @param array  $params  请求参数
+     * @param array  $options 扩展参数
+     * @return mixed|string
+     */
+    public static function post($url, $params = [], $options = [])
+    {
+        $req = self::sendRequest($url, $params, 'POST', $options);
+        return $req['ret'] ? $req['msg'] : '';
+    }
+
+    /**
+     * 发送一个GET请求
+     * @param string $url     请求URL
+     * @param array  $params  请求参数
+     * @param array  $options 扩展参数
+     * @return mixed|string
+     */
+    public static function get($url, $params = [], $options = [])
+    {
+        $req = self::sendRequest($url, $params, 'GET', $options);
+        return $req['ret'] ? $req['msg'] : '';
+    }
+
+    /**
+     * CURL发送Request请求,含POST和REQUEST
+     * @param string $url     请求的链接
+     * @param mixed  $params  传递的参数
+     * @param string $method  请求的方法
+     * @param mixed  $options CURL的参数
+     * @return array
+     */
+    public static function sendRequest($url, $params = [], $method = 'POST', $options = [])
+    {
+        $method = strtoupper($method);
+        $protocol = substr($url, 0, 5);
+        $query_string = is_array($params) ? http_build_query($params) : $params;
+
+        $ch = curl_init();
+        $defaults = [];
+        if ('GET' == $method) {
+            $geturl = $query_string ? $url . (stripos($url, "?") !== false ? "&" : "?") . $query_string : $url;
+            $defaults[CURLOPT_URL] = $geturl;
+        } else {
+            $defaults[CURLOPT_URL] = $url;
+            if ($method == 'POST') {
+                $defaults[CURLOPT_POST] = 1;
+            } else {
+                $defaults[CURLOPT_CUSTOMREQUEST] = $method;
+            }
+            $defaults[CURLOPT_POSTFIELDS] = $params;
+        }
+
+        $defaults[CURLOPT_HEADER] = false;
+        $defaults[CURLOPT_USERAGENT] = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.98 Safari/537.36";
+        $defaults[CURLOPT_FOLLOWLOCATION] = true;
+        $defaults[CURLOPT_RETURNTRANSFER] = true;
+        $defaults[CURLOPT_CONNECTTIMEOUT] = 3;
+        $defaults[CURLOPT_TIMEOUT] = 3;
+
+        // disable 100-continue
+        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:'));
+
+        if ('https' == $protocol) {
+            $defaults[CURLOPT_SSL_VERIFYPEER] = false;
+            $defaults[CURLOPT_SSL_VERIFYHOST] = false;
+        }
+
+        curl_setopt_array($ch, (array)$options + $defaults);
+
+        $ret = curl_exec($ch);
+        $err = curl_error($ch);
+
+        if (false === $ret || !empty($err)) {
+            $errno = curl_errno($ch);
+            $info = curl_getinfo($ch);
+            curl_close($ch);
+            return [
+                'ret'   => false,
+                'errno' => $errno,
+                'msg'   => $err,
+                'info'  => $info,
+            ];
+        }
+        curl_close($ch);
+        return [
+            'ret' => true,
+            'msg' => $ret,
+        ];
+    }
+
+    /**
+     * 异步发送一个请求
+     * @param string $url    请求的链接
+     * @param mixed  $params 请求的参数
+     * @param string $method 请求的方法
+     * @return boolean TRUE
+     */
+    public static function sendAsyncRequest($url, $params = [], $method = 'POST')
+    {
+        $method = strtoupper($method);
+        $method = $method == 'POST' ? 'POST' : 'GET';
+        //构造传递的参数
+        if (is_array($params)) {
+            $post_params = [];
+            foreach ($params as $k => &$v) {
+                if (is_array($v)) {
+                    $v = implode(',', $v);
+                }
+                $post_params[] = $k . '=' . urlencode($v);
+            }
+            $post_string = implode('&', $post_params);
+        } else {
+            $post_string = $params;
+        }
+        $parts = parse_url($url);
+        //构造查询的参数
+        if ($method == 'GET' && $post_string) {
+            $parts['query'] = isset($parts['query']) ? $parts['query'] . '&' . $post_string : $post_string;
+            $post_string = '';
+        }
+        $parts['query'] = isset($parts['query']) && $parts['query'] ? '?' . $parts['query'] : '';
+        //发送socket请求,获得连接句柄
+        $fp = fsockopen($parts['host'], isset($parts['port']) ? $parts['port'] : 80, $errno, $errstr, 3);
+        if (!$fp) {
+            return false;
+        }
+        //设置超时时间
+        stream_set_timeout($fp, 3);
+        $out = "{$method} {$parts['path']}{$parts['query']} HTTP/1.1\r\n";
+        $out .= "Host: {$parts['host']}\r\n";
+        $out .= "Content-Type: application/x-www-form-urlencoded\r\n";
+        $out .= "Content-Length: " . strlen($post_string) . "\r\n";
+        $out .= "Connection: Close\r\n\r\n";
+        if ($post_string !== '') {
+            $out .= $post_string;
+        }
+        fwrite($fp, $out);
+        //不用关心服务器返回结果
+        //echo fread($fp, 1024);
+        fclose($fp);
+        return true;
+    }
+
+    /**
+     * 发送文件到客户端
+     * @param string $file
+     * @param bool   $delaftersend
+     * @param bool   $exitaftersend
+     */
+    public static function sendToBrowser($file, $delaftersend = true, $exitaftersend = true)
+    {
+        if (file_exists($file) && is_readable($file)) {
+            header('Content-Description: File Transfer');
+            header('Content-Type: application/octet-stream');
+            header('Content-Disposition: attachment;filename = ' . basename($file));
+            header('Content-Transfer-Encoding: binary');
+            header('Expires: 0');
+            header('Cache-Control: must-revalidate, post-check = 0, pre-check = 0');
+            header('Pragma: public');
+            header('Content-Length: ' . filesize($file));
+            ob_clean();
+            flush();
+            readfile($file);
+            if ($delaftersend) {
+                unlink($file);
+            }
+            if ($exitaftersend) {
+                exit;
+            }
+        }
+    }
+}

+ 36 - 0
extend/fast/Pinyin.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace fast;
+
+/**
+ * 中文转拼音类
+ */
+class Pinyin
+{
+
+    /**
+     * 获取文字的拼音
+     * @param string  $chinese   中文汉字
+     * @param boolean $onlyfirst 是否只返回拼音首字母
+     * @param string  $delimiter 分隔符
+     * @param bool    $ucfirst   是否首字母大写
+     * @return string
+     */
+    public static function get($chinese, $onlyfirst = false, $delimiter = '', $ucfirst = false)
+    {
+
+        $pinyin = new \Overtrue\Pinyin\Pinyin();
+        if ($onlyfirst) {
+            $result = $pinyin->abbr($chinese, $delimiter);
+        } else {
+            $result = $pinyin->permalink($chinese, $delimiter);
+        }
+        if ($ucfirst) {
+            $pinyinArr = explode($delimiter, $result);
+            $result = implode($delimiter, array_map('ucfirst', $pinyinArr));
+        }
+
+        return $result;
+    }
+
+}

+ 171 - 0
extend/fast/Random.php

@@ -0,0 +1,171 @@
+<?php
+
+namespace fast;
+
+/**
+ * 随机生成类
+ */
+class Random
+{
+
+    /**
+     * 生成数字和字母
+     *
+     * @param int $len 长度
+     * @return string
+     */
+    public static function alnum($len = 6)
+    {
+        return self::build('alnum', $len);
+    }
+
+    /**
+     * 仅生成字符
+     *
+     * @param int $len 长度
+     * @return string
+     */
+    public static function alpha($len = 6)
+    {
+        return self::build('alpha', $len);
+    }
+
+    /**
+     * 生成指定长度的随机数字
+     *
+     * @param int $len 长度
+     * @return string
+     */
+    public static function numeric($len = 4)
+    {
+        return self::build('numeric', $len);
+    }
+
+    /**
+     * 生成指定长度的无0随机数字
+     *
+     * @param int $len 长度
+     * @return string
+     */
+    public static function nozero($len = 4)
+    {
+        return self::build('nozero', $len);
+    }
+
+    /**
+     * 能用的随机数生成
+     * @param string $type 类型 alpha/alnum/numeric/nozero/unique/md5/encrypt/sha1
+     * @param int    $len  长度
+     * @return string
+     */
+    public static function build($type = 'alnum', $len = 8)
+    {
+        switch ($type) {
+            case 'alpha':
+            case 'alnum':
+            case 'numeric':
+            case 'nozero':
+                switch ($type) {
+                    case 'alpha':
+                        $pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+                        break;
+                    case 'alnum':
+                        $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+                        break;
+                    case 'numeric':
+                        $pool = '0123456789';
+                        break;
+                    case 'nozero':
+                        $pool = '123456789';
+                        break;
+                }
+                return substr(str_shuffle(str_repeat($pool, ceil($len / strlen($pool)))), 0, $len);
+            case 'unique':
+            case 'md5':
+                return md5(uniqid(mt_rand()));
+            case 'encrypt':
+            case 'sha1':
+                return sha1(uniqid(mt_rand(), true));
+        }
+    }
+
+    /**
+     * 根据数组元素的概率获得键名
+     *
+     * @param array $ps     array('p1'=>20, 'p2'=>30, 'p3'=>50);
+     * @param int   $num    默认为1,即随机出来的数量
+     * @param bool  $unique 默认为true,即当num>1时,随机出的数量是否唯一
+     * @return mixed 当num为1时返回键名,反之返回一维数组
+     */
+    public static function lottery($ps, $num = 1, $unique = true)
+    {
+        if (!$ps) {
+            return $num == 1 ? '' : [];
+        }
+        if ($num >= count($ps) && $unique) {
+            $res = array_keys($ps);
+            return $num == 1 ? $res[0] : $res;
+        }
+        $max_exp = 0;
+        $res = [];
+        foreach ($ps as $key => $value) {
+            $value = substr($value, 0, stripos($value, ".") + 6);
+            $exp = strlen(strchr($value, '.')) - 1;
+            if ($exp > $max_exp) {
+                $max_exp = $exp;
+            }
+        }
+        $pow_exp = pow(10, $max_exp);
+        if ($pow_exp > 1) {
+            reset($ps);
+            foreach ($ps as $key => $value) {
+                $ps[$key] = $value * $pow_exp;
+            }
+        }
+        $pro_sum = array_sum($ps);
+        if ($pro_sum < 1) {
+            return $num == 1 ? '' : [];
+        }
+        for ($i = 0; $i < $num; $i++) {
+            $rand_num = mt_rand(1, $pro_sum);
+            reset($ps);
+            foreach ($ps as $key => $value) {
+                if ($rand_num <= $value) {
+                    break;
+                } else {
+                    $rand_num -= $value;
+                }
+            }
+            if ($num == 1) {
+                $res = $key;
+                break;
+            } else {
+                $res[$i] = $key;
+            }
+            if ($unique) {
+                $pro_sum -= $value;
+                unset($ps[$key]);
+            }
+        }
+        return $res;
+    }
+
+    /**
+     * 获取全球唯一标识
+     * @return string
+     */
+    public static function uuid()
+    {
+        return sprintf(
+            '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
+            mt_rand(0, 0xffff),
+            mt_rand(0, 0xffff),
+            mt_rand(0, 0xffff),
+            mt_rand(0, 0x0fff) | 0x4000,
+            mt_rand(0, 0x3fff) | 0x8000,
+            mt_rand(0, 0xffff),
+            mt_rand(0, 0xffff),
+            mt_rand(0, 0xffff)
+        );
+    }
+}

+ 179 - 0
extend/fast/Rsa.php

@@ -0,0 +1,179 @@
+<?php
+
+namespace fast;
+
+/**
+ * RSA签名类
+ */
+class Rsa
+{
+    public $publicKey = '';
+    public $privateKey = '';
+    private $_privKey;
+
+    /**
+     * * private key
+     */
+    private $_pubKey;
+
+    /**
+     * * public key
+     */
+    private $_keyPath;
+
+    /**
+     * * the keys saving path
+     */
+
+    /**
+     * * the construtor,the param $path is the keys saving path
+     * @param string $publicKey  公钥
+     * @param string $privateKey 私钥
+     */
+    public function __construct($publicKey = null, $privateKey = null)
+    {
+        $this->setKey($publicKey, $privateKey);
+    }
+
+    /**
+     * 设置公钥和私钥
+     * @param string $publicKey  公钥
+     * @param string $privateKey 私钥
+     */
+    public function setKey($publicKey = null, $privateKey = null)
+    {
+        if (!is_null($publicKey)) {
+            $this->publicKey = $publicKey;
+        }
+        if (!is_null($privateKey)) {
+            $this->privateKey = $privateKey;
+        }
+    }
+
+    /**
+     * * setup the private key
+     */
+    private function setupPrivKey()
+    {
+        if (is_resource($this->_privKey)) {
+            return true;
+        }
+        $pem = chunk_split($this->privateKey, 64, "\n");
+        $pem = "-----BEGIN PRIVATE KEY-----\n" . $pem . "-----END PRIVATE KEY-----\n";
+        $this->_privKey = openssl_pkey_get_private($pem);
+        return true;
+    }
+
+    /**
+     * * setup the public key
+     */
+    private function setupPubKey()
+    {
+        if (is_resource($this->_pubKey)) {
+            return true;
+        }
+        $pem = chunk_split($this->publicKey, 64, "\n");
+        $pem = "-----BEGIN PUBLIC KEY-----\n" . $pem . "-----END PUBLIC KEY-----\n";
+        $this->_pubKey = openssl_pkey_get_public($pem);
+        return true;
+    }
+
+    /**
+     * * encrypt with the private key
+     */
+    public function privEncrypt($data)
+    {
+        if (!is_string($data)) {
+            return null;
+        }
+        $this->setupPrivKey();
+        $r = openssl_private_encrypt($data, $encrypted, $this->_privKey);
+        if ($r) {
+            return base64_encode($encrypted);
+        }
+        return null;
+    }
+
+    /**
+     * * decrypt with the private key
+     */
+    public function privDecrypt($encrypted)
+    {
+        if (!is_string($encrypted)) {
+            return null;
+        }
+        $this->setupPrivKey();
+        $encrypted = base64_decode($encrypted);
+        $r = openssl_private_decrypt($encrypted, $decrypted, $this->_privKey);
+        if ($r) {
+            return $decrypted;
+        }
+        return null;
+    }
+
+    /**
+     * * encrypt with public key
+     */
+    public function pubEncrypt($data)
+    {
+        if (!is_string($data)) {
+            return null;
+        }
+        $this->setupPubKey();
+        $r = openssl_public_encrypt($data, $encrypted, $this->_pubKey);
+        if ($r) {
+            return base64_encode($encrypted);
+        }
+        return null;
+    }
+
+    /**
+     * * decrypt with the public key
+     */
+    public function pubDecrypt($crypted)
+    {
+        if (!is_string($crypted)) {
+            return null;
+        }
+        $this->setupPubKey();
+        $crypted = base64_decode($crypted);
+        $r = openssl_public_decrypt($crypted, $decrypted, $this->_pubKey);
+        if ($r) {
+            return $decrypted;
+        }
+        return null;
+    }
+
+    /**
+     * 构造签名
+     * @param string $dataString 被签名数据
+     * @return string
+     */
+    public function sign($dataString)
+    {
+        $this->setupPrivKey();
+        $signature = false;
+        openssl_sign($dataString, $signature, $this->_privKey);
+        return base64_encode($signature);
+    }
+
+    /**
+     * 验证签名
+     * @param string $dataString 被签名数据
+     * @param string $signString 已经签名的字符串
+     * @return number 1签名正确 0签名错误
+     */
+    public function verify($dataString, $signString)
+    {
+        $this->setupPubKey();
+        $signature = base64_decode($signString);
+        $flg = openssl_verify($dataString, $signature, $this->_pubKey);
+        return $flg;
+    }
+
+    public function __destruct()
+    {
+        is_resource($this->_privKey) && @openssl_free_key($this->_privKey);
+        is_resource($this->_pubKey) && @openssl_free_key($this->_pubKey);
+    }
+}

+ 438 - 0
extend/fast/Tree.php

@@ -0,0 +1,438 @@
+<?php
+
+namespace fast;
+
+use think\Config;
+
+/**
+ * 通用的树型类
+ * @author XiaoYao <476552238li@gmail.com>
+ */
+class Tree
+{
+    protected static $instance;
+    //默认配置
+    protected $config = [];
+    public $options = [];
+
+    /**
+     * 生成树型结构所需要的2维数组
+     * @var array
+     */
+    public $arr = [];
+
+    /**
+     * 生成树型结构所需修饰符号,可以换成图片
+     * @var array
+     */
+    public $icon = array('│', '├', '└');
+    public $nbsp = "&nbsp;";
+    public $pidname = 'pid';
+
+    public function __construct($options = [])
+    {
+        if ($config = Config::get('tree')) {
+            $this->options = array_merge($this->config, $config);
+        }
+        $this->options = array_merge($this->config, $options);
+    }
+
+    /**
+     * 初始化
+     * @access public
+     * @param array $options 参数
+     * @return Tree
+     */
+    public static function instance($options = [])
+    {
+        if (is_null(self::$instance)) {
+            self::$instance = new static($options);
+        }
+
+        return self::$instance;
+    }
+
+    /**
+     * 初始化方法
+     * @param array  $arr     2维数组,例如:
+     *      array(
+     *      1 => array('id'=>'1','pid'=>0,'name'=>'一级栏目一'),
+     *      2 => array('id'=>'2','pid'=>0,'name'=>'一级栏目二'),
+     *      3 => array('id'=>'3','pid'=>1,'name'=>'二级栏目一'),
+     *      4 => array('id'=>'4','pid'=>1,'name'=>'二级栏目二'),
+     *      5 => array('id'=>'5','pid'=>2,'name'=>'二级栏目三'),
+     *      6 => array('id'=>'6','pid'=>3,'name'=>'三级栏目一'),
+     *      7 => array('id'=>'7','pid'=>3,'name'=>'三级栏目二')
+     *      )
+     * @param string $pidname 父字段名称
+     * @param string $nbsp    空格占位符
+     * @return Tree
+     */
+    public function init($arr = [], $pidname = null, $nbsp = null)
+    {
+        $this->arr = $arr;
+        if (!is_null($pidname)) {
+            $this->pidname = $pidname;
+        }
+        if (!is_null($nbsp)) {
+            $this->nbsp = $nbsp;
+        }
+        return $this;
+    }
+
+    /**
+     * 得到子级数组
+     * @param int
+     * @return array
+     */
+    public function getChild($myid)
+    {
+        $newarr = [];
+        foreach ($this->arr as $value) {
+            if (!isset($value['id'])) {
+                continue;
+            }
+            if ($value[$this->pidname] == $myid) {
+                $newarr[$value['id']] = $value;
+            }
+        }
+        return $newarr;
+    }
+
+    /**
+     * 读取指定节点的所有孩子节点
+     * @param int     $myid     节点ID
+     * @param boolean $withself 是否包含自身
+     * @return array
+     */
+    public function getChildren($myid, $withself = false)
+    {
+        $newarr = [];
+        foreach ($this->arr as $value) {
+            if (!isset($value['id'])) {
+                continue;
+            }
+            if ((string)$value[$this->pidname] == (string)$myid) {
+                $newarr[] = $value;
+                $newarr = array_merge($newarr, $this->getChildren($value['id']));
+            } elseif ($withself && (string)$value['id'] == (string)$myid) {
+                $newarr[] = $value;
+            }
+        }
+        return $newarr;
+    }
+
+    /**
+     * 读取指定节点的所有孩子节点ID
+     * @param int     $myid     节点ID
+     * @param boolean $withself 是否包含自身
+     * @return array
+     */
+    public function getChildrenIds($myid, $withself = false)
+    {
+        $childrenlist = $this->getChildren($myid, $withself);
+        $childrenids = [];
+        foreach ($childrenlist as $k => $v) {
+            $childrenids[] = $v['id'];
+        }
+        return $childrenids;
+    }
+
+    /**
+     * 得到当前位置父辈数组
+     * @param int
+     * @return array
+     */
+    public function getParent($myid)
+    {
+        $pid = 0;
+        $newarr = [];
+        foreach ($this->arr as $value) {
+            if (!isset($value['id'])) {
+                continue;
+            }
+            if ($value['id'] == $myid) {
+                $pid = $value[$this->pidname];
+                break;
+            }
+        }
+        if ($pid) {
+            foreach ($this->arr as $value) {
+                if ($value['id'] == $pid) {
+                    $newarr[] = $value;
+                    break;
+                }
+            }
+        }
+        return $newarr;
+    }
+
+    /**
+     * 得到当前位置所有父辈数组
+     * @param int
+     * @param bool $withself 是否包含自己
+     * @return array
+     */
+    public function getParents($myid, $withself = false)
+    {
+        $pid = 0;
+        $newarr = [];
+        foreach ($this->arr as $value) {
+            if (!isset($value['id'])) {
+                continue;
+            }
+            if ($value['id'] == $myid) {
+                if ($withself) {
+                    $newarr[] = $value;
+                }
+                $pid = $value[$this->pidname];
+                break;
+            }
+        }
+        if ($pid) {
+            $arr = $this->getParents($pid, true);
+            $newarr = array_merge($arr, $newarr);
+        }
+        return $newarr;
+    }
+
+    /**
+     * 读取指定节点所有父类节点ID
+     * @param int     $myid
+     * @param boolean $withself
+     * @return array
+     */
+    public function getParentsIds($myid, $withself = false)
+    {
+        $parentlist = $this->getParents($myid, $withself);
+        $parentsids = [];
+        foreach ($parentlist as $k => $v) {
+            $parentsids[] = $v['id'];
+        }
+        return $parentsids;
+    }
+
+    /**
+     * 树型结构Option
+     * @param int    $myid        表示获得这个ID下的所有子级
+     * @param string $itemtpl     条目模板 如:"<option value=@id @selected @disabled>@spacer@name</option>"
+     * @param mixed  $selectedids 被选中的ID,比如在做树型下拉框的时候需要用到
+     * @param mixed  $disabledids 被禁用的ID,比如在做树型下拉框的时候需要用到
+     * @param string $itemprefix  每一项前缀
+     * @param string $toptpl      顶级栏目的模板
+     * @return string
+     */
+    public function getTree($myid, $itemtpl = "<option value=@id @selected @disabled>@spacer@name</option>", $selectedids = '', $disabledids = '', $itemprefix = '', $toptpl = '')
+    {
+        $ret = '';
+        $number = 1;
+        $childs = $this->getChild($myid);
+        if ($childs) {
+            $total = count($childs);
+            foreach ($childs as $value) {
+                $id = $value['id'];
+                $j = $k = '';
+                if ($number == $total) {
+                    $j .= $this->icon[2];
+                    $k = $itemprefix ? $this->nbsp : '';
+                } else {
+                    $j .= $this->icon[1];
+                    $k = $itemprefix ? $this->icon[0] : '';
+                }
+                $spacer = $itemprefix ? $itemprefix . $j : '';
+                $selected = $selectedids && in_array($id, (is_array($selectedids) ? $selectedids : explode(',', $selectedids))) ? 'selected' : '';
+                $disabled = $disabledids && in_array($id, (is_array($disabledids) ? $disabledids : explode(',', $disabledids))) ? 'disabled' : '';
+                $value = array_merge($value, array('selected' => $selected, 'disabled' => $disabled, 'spacer' => $spacer));
+                $value = array_combine(array_map(function ($k) {
+                    return '@' . $k;
+                }, array_keys($value)), $value);
+                $nstr = strtr((($value["@{$this->pidname}"] == 0 || $this->getChild($id)) && $toptpl ? $toptpl : $itemtpl), $value);
+                $ret .= $nstr;
+                $ret .= $this->getTree($id, $itemtpl, $selectedids, $disabledids, $itemprefix . $k . $this->nbsp, $toptpl);
+                $number++;
+            }
+        }
+        return $ret;
+    }
+
+    /**
+     * 树型结构UL
+     * @param int    $myid        表示获得这个ID下的所有子级
+     * @param string $itemtpl     条目模板 如:"<li value=@id @selected @disabled>@name @childlist</li>"
+     * @param string $selectedids 选中的ID
+     * @param string $disabledids 禁用的ID
+     * @param string $wraptag     子列表包裹标签
+     * @param string $wrapattr    子列表包裹属性
+     * @return string
+     */
+    public function getTreeUl($myid, $itemtpl, $selectedids = '', $disabledids = '', $wraptag = 'ul', $wrapattr = '')
+    {
+        $str = '';
+        $childs = $this->getChild($myid);
+        if ($childs) {
+            foreach ($childs as $value) {
+                $id = $value['id'];
+                unset($value['child']);
+                $selected = $selectedids && in_array($id, (is_array($selectedids) ? $selectedids : explode(',', $selectedids))) ? 'selected' : '';
+                $disabled = $disabledids && in_array($id, (is_array($disabledids) ? $disabledids : explode(',', $disabledids))) ? 'disabled' : '';
+                $value = array_merge($value, array('selected' => $selected, 'disabled' => $disabled));
+                $value = array_combine(array_map(function ($k) {
+                    return '@' . $k;
+                }, array_keys($value)), $value);
+                $nstr = strtr($itemtpl, $value);
+                $childdata = $this->getTreeUl($id, $itemtpl, $selectedids, $disabledids, $wraptag, $wrapattr);
+                $childlist = $childdata ? "<{$wraptag} {$wrapattr}>" . $childdata . "</{$wraptag}>" : "";
+                $str .= strtr($nstr, array('@childlist' => $childlist));
+            }
+        }
+        return $str;
+    }
+
+    /**
+     * 菜单数据
+     * @param int    $myid
+     * @param string $itemtpl
+     * @param mixed  $selectedids
+     * @param mixed  $disabledids
+     * @param string $wraptag
+     * @param string $wrapattr
+     * @param int    $deeplevel
+     * @return string
+     */
+    public function getTreeMenu($myid, $itemtpl, $selectedids = '', $disabledids = '', $wraptag = 'ul', $wrapattr = '', $deeplevel = 0)
+    {
+        $str = '';
+        $childs = $this->getChild($myid);
+        if ($childs) {
+            foreach ($childs as $value) {
+                $id = $value['id'];
+                unset($value['child']);
+                $selected = in_array($id, (is_array($selectedids) ? $selectedids : explode(',', $selectedids))) ? 'selected' : '';
+                $disabled = in_array($id, (is_array($disabledids) ? $disabledids : explode(',', $disabledids))) ? 'disabled' : '';
+                $value = array_merge($value, array('selected' => $selected, 'disabled' => $disabled));
+                $value = array_combine(array_map(function ($k) {
+                    return '@' . $k;
+                }, array_keys($value)), $value);
+                $bakvalue = array_intersect_key($value, array_flip(['@url', '@caret', '@class']));
+                $value = array_diff_key($value, $bakvalue);
+                $nstr = strtr($itemtpl, $value);
+                $value = array_merge($value, $bakvalue);
+                $childdata = $this->getTreeMenu($id, $itemtpl, $selectedids, $disabledids, $wraptag, $wrapattr, $deeplevel + 1);
+                $childlist = $childdata ? "<{$wraptag} {$wrapattr}>" . $childdata . "</{$wraptag}>" : "";
+                $childlist = strtr($childlist, array('@class' => $childdata ? 'last' : ''));
+                $value = array(
+                    '@childlist' => $childlist,
+                    '@url'       => $childdata || !isset($value['@url']) ? "javascript:;" : $value['@url'],
+                    '@addtabs'   => $childdata || !isset($value['@url']) ? "" : (stripos($value['@url'], "?") !== false ? "&" : "?") . "ref=addtabs",
+                    '@caret'     => ($childdata && (!isset($value['@badge']) || !$value['@badge']) ? '<i class="fa fa-angle-left"></i>' : ''),
+                    '@badge'     => isset($value['@badge']) ? $value['@badge'] : '',
+                    '@class'     => ($selected ? ' active' : '') . ($disabled ? ' disabled' : '') . ($childdata ? ' treeview' . (config('fastadmin.show_submenu') ? ' treeview-open' : '') : ''),
+                );
+                $str .= strtr($nstr, $value);
+            }
+        }
+        return $str;
+    }
+
+    /**
+     * 特殊
+     * @param integer $myid        要查询的ID
+     * @param string  $itemtpl1    第一种HTML代码方式
+     * @param string  $itemtpl2    第二种HTML代码方式
+     * @param mixed   $selectedids 默认选中
+     * @param mixed   $disabledids 禁用
+     * @param string  $itemprefix  前缀
+     * @return string
+     */
+    public function getTreeSpecial($myid, $itemtpl1, $itemtpl2, $selectedids = 0, $disabledids = 0, $itemprefix = '')
+    {
+        $ret = '';
+        $number = 1;
+        $childs = $this->getChild($myid);
+        if ($childs) {
+            $total = count($childs);
+            foreach ($childs as $id => $value) {
+                $j = $k = '';
+                if ($number == $total) {
+                    $j .= $this->icon[2];
+                    $k = $itemprefix ? $this->nbsp : '';
+                } else {
+                    $j .= $this->icon[1];
+                    $k = $itemprefix ? $this->icon[0] : '';
+                }
+                $spacer = $itemprefix ? $itemprefix . $j : '';
+                $selected = $selectedids && in_array($id, (is_array($selectedids) ? $selectedids : explode(',', $selectedids))) ? 'selected' : '';
+                $disabled = $disabledids && in_array($id, (is_array($disabledids) ? $disabledids : explode(',', $disabledids))) ? 'disabled' : '';
+                $value = array_merge($value, array('selected' => $selected, 'disabled' => $disabled, 'spacer' => $spacer));
+                $value = array_combine(array_map(function ($k) {
+                    return '@' . $k;
+                }, array_keys($value)), $value);
+                $nstr = strtr(!isset($value['@disabled']) || !$value['@disabled'] ? $itemtpl1 : $itemtpl2, $value);
+
+                $ret .= $nstr;
+                $ret .= $this->getTreeSpecial($id, $itemtpl1, $itemtpl2, $selectedids, $disabledids, $itemprefix . $k . $this->nbsp);
+                $number++;
+            }
+        }
+        return $ret;
+    }
+
+    /**
+     *
+     * 获取树状数组
+     * @param string $myid       要查询的ID
+     * @param string $itemprefix 前缀
+     * @return array
+     */
+    public function getTreeArray($myid, $itemprefix = '')
+    {
+        $childs = $this->getChild($myid);
+        $n = 0;
+        $data = [];
+        $number = 1;
+        if ($childs) {
+            $total = count($childs);
+            foreach ($childs as $id => $value) {
+                $j = $k = '';
+                if ($number == $total) {
+                    $j .= $this->icon[2];
+                    $k = $itemprefix ? $this->nbsp : '';
+                } else {
+                    $j .= $this->icon[1];
+                    $k = $itemprefix ? $this->icon[0] : '';
+                }
+                $spacer = $itemprefix ? $itemprefix . $j : '';
+                $value['spacer'] = $spacer;
+                $data[$n] = $value;
+                $data[$n]['childlist'] = $this->getTreeArray($id, $itemprefix . $k . $this->nbsp);
+                $n++;
+                $number++;
+            }
+        }
+        return $data;
+    }
+
+    /**
+     * 将getTreeArray的结果返回为二维数组
+     * @param array  $data
+     * @param string $field
+     * @return array
+     */
+    public function getTreeList($data = [], $field = 'name')
+    {
+        $arr = [];
+        foreach ($data as $k => $v) {
+            $childlist = isset($v['childlist']) ? $v['childlist'] : [];
+            unset($v['childlist']);
+            $v[$field] = $v['spacer'] . ' ' . $v[$field];
+            $v['haschild'] = $childlist ? 1 : 0;
+            if ($v['id']) {
+                $arr[] = $v;
+            }
+            if ($childlist) {
+                $arr = array_merge($arr, $this->getTreeList($childlist, $field));
+            }
+        }
+        return $arr;
+    }
+}

+ 79 - 0
extend/fast/Version.php

@@ -0,0 +1,79 @@
+<?php
+
+namespace fast;
+
+/**
+ * 版本检测和对比
+ */
+class Version
+{
+
+    /**
+     * 检测版本是否的版本要求的数据中
+     *
+     * @param string $version
+     * @param array  $data
+     * @return bool
+     */
+    public static function check($version, $data = [])
+    {
+        //版本号以.分隔
+        $data = is_array($data) ? $data : [$data];
+        if ($data) {
+            if (in_array("*", $data) || in_array($version, $data)) {
+                return true;
+            }
+            $ver = explode('.', $version);
+            if ($ver) {
+                $versize = count($ver);
+                //验证允许的版本
+                foreach ($data as $m) {
+                    $c = explode('.', $m);
+                    if (!$c || $versize != count($c)) {
+                        continue;
+                    }
+                    $i = 0;
+                    foreach ($c as $a => $k) {
+                        if (!self::compare($ver[$a], $k)) {
+                            continue 2;
+                        } else {
+                            $i++;
+                        }
+                    }
+                    if ($i == $versize) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 比较两个版本号
+     *
+     * @param string $v1
+     * @param string $v2
+     * @return boolean
+     */
+    public static function compare($v1, $v2)
+    {
+        if ($v2 == "*" || $v1 == $v2) {
+            return true;
+        } else {
+            $values = [];
+            $k = explode(',', $v2);
+            foreach ($k as $v) {
+                if (strpos($v, '-') !== false) {
+                    list($start, $stop) = explode('-', $v);
+                    for ($i = $start; $i <= $stop; $i++) {
+                        $values[] = $i;
+                    }
+                } else {
+                    $values[] = $v;
+                }
+            }
+            return in_array($v1, $values) ? true : false;
+        }
+    }
+}