init
This commit is contained in:
commit
5b724e6c41
11
.bowerrc
Normal file
11
.bowerrc
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"directory": "public/assets/libs",
|
||||||
|
"ignoredDependencies": [
|
||||||
|
"es6-promise",
|
||||||
|
"file-saver",
|
||||||
|
"html2canvas",
|
||||||
|
"jspdf",
|
||||||
|
"jspdf-autotable",
|
||||||
|
"pdfmake"
|
||||||
|
]
|
||||||
|
}
|
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/nbproject/
|
||||||
|
/thinkphp/
|
||||||
|
/runtime/*
|
||||||
|
/application/admin/command/Install/*.lock
|
||||||
|
/public/assets/libs/
|
||||||
|
/public/assets/addons/*
|
||||||
|
/public/uploads/*
|
||||||
|
.idea
|
||||||
|
composer.lock
|
||||||
|
*.log
|
||||||
|
*.css.map
|
||||||
|
!.gitkeep
|
||||||
|
.env
|
||||||
|
.svn
|
||||||
|
.vscode
|
||||||
|
node_modules
|
1
addons/.gitkeep
Normal file
1
addons/.gitkeep
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
1
addons/.htaccess
Normal file
1
addons/.htaccess
Normal file
@ -0,0 +1 @@
|
|||||||
|
deny from all
|
1
addons/command/.addonrc
Normal file
1
addons/command/.addonrc
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"files":["application\\admin\\controller\\Command.php","application\\admin\\lang\\zh-cn\\command.php","application\\admin\\model\\Command.php","application\\admin\\validate\\Command.php","application\\admin\\view\\command\\add.html","application\\admin\\view\\command\\detail.html","application\\admin\\view\\command\\index.html","public\\assets\\js\\backend\\command.js"],"license":"regular","licenseto":"22920","licensekey":"IRVioOLcHkNf1FUP WsUbXhff8ERbHyc2LwijQg==","domains":["des8.com"],"licensecodes":[],"validations":["b020d8667fd8c7d4326fcf512c7a10aa"],"menus":["command","command\/index","command\/add","command\/detail","command\/execute","command\/del","command\/multi"]}
|
69
addons/command/Command.php
Normal file
69
addons/command/Command.php
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addons\command;
|
||||||
|
|
||||||
|
use app\common\library\Menu;
|
||||||
|
use think\Addons;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在线命令插件
|
||||||
|
*/
|
||||||
|
class Command extends Addons
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件安装方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function install()
|
||||||
|
{
|
||||||
|
$menu = [
|
||||||
|
[
|
||||||
|
'name' => 'command',
|
||||||
|
'title' => '在线命令管理',
|
||||||
|
'icon' => 'fa fa-terminal',
|
||||||
|
'sublist' => [
|
||||||
|
['name' => 'command/index', 'title' => '查看'],
|
||||||
|
['name' => 'command/add', 'title' => '添加'],
|
||||||
|
['name' => 'command/detail', 'title' => '详情'],
|
||||||
|
['name' => 'command/execute', 'title' => '运行'],
|
||||||
|
['name' => 'command/del', 'title' => '删除'],
|
||||||
|
['name' => 'command/multi', 'title' => '批量更新'],
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
Menu::create($menu);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件卸载方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function uninstall()
|
||||||
|
{
|
||||||
|
Menu::delete('command');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件启用方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function enable()
|
||||||
|
{
|
||||||
|
Menu::enable('command');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件禁用方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function disable()
|
||||||
|
{
|
||||||
|
Menu::disable('command');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
4
addons/command/config.php
Normal file
4
addons/command/config.php
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
];
|
15
addons/command/controller/Index.php
Normal file
15
addons/command/controller/Index.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addons\command\controller;
|
||||||
|
|
||||||
|
use think\addons\Controller;
|
||||||
|
|
||||||
|
class Index extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$this->error("当前插件暂无前台页面");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
10
addons/command/info.ini
Normal file
10
addons/command/info.ini
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
name = command
|
||||||
|
title = 在线命令
|
||||||
|
intro = 可在线执行FastAdmin的命令行相关命令
|
||||||
|
author = Karson
|
||||||
|
website = https://www.fastadmin.net
|
||||||
|
version = 1.0.6
|
||||||
|
state = 1
|
||||||
|
url = /addons/command
|
||||||
|
license = regular
|
||||||
|
licenseto = 22920
|
12
addons/command/install.sql
Normal file
12
addons/command/install.sql
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `__PREFIX__command` (
|
||||||
|
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID',
|
||||||
|
`type` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '类型',
|
||||||
|
`params` varchar(1500) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '参数',
|
||||||
|
`command` varchar(1500) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '命令',
|
||||||
|
`content` text COMMENT '返回结果',
|
||||||
|
`executetime` int(10) UNSIGNED DEFAULT NULL COMMENT '执行时间',
|
||||||
|
`createtime` int(10) UNSIGNED DEFAULT NULL COMMENT '创建时间',
|
||||||
|
`updatetime` int(10) UNSIGNED DEFAULT NULL COMMENT '更新时间',
|
||||||
|
`status` enum('successed','failured') CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'failured' COMMENT '状态',
|
||||||
|
PRIMARY KEY (`id`) USING BTREE
|
||||||
|
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '在线命令表';
|
28
addons/command/library/Output.php
Normal file
28
addons/command/library/Output.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addons\command\library;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Output
|
||||||
|
*/
|
||||||
|
class Output extends \think\console\Output
|
||||||
|
{
|
||||||
|
|
||||||
|
protected $message = [];
|
||||||
|
|
||||||
|
public function __construct($driver = 'console')
|
||||||
|
{
|
||||||
|
parent::__construct($driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function block($style, $message)
|
||||||
|
{
|
||||||
|
$this->message[] = $message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMessage()
|
||||||
|
{
|
||||||
|
return $this->message;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
1
addons/crontab/.addonrc
Normal file
1
addons/crontab/.addonrc
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"files":["application\\admin\\controller\\general\\Crontab.php","application\\admin\\controller\\general\\CrontabLog.php","application\\admin\\lang\\zh-cn\\general\\crontab.php","application\\admin\\lang\\zh-cn\\general\\crontab_log.php","application\\admin\\model\\Crontab.php","application\\admin\\model\\CrontabLog.php","application\\admin\\view\\general\\crontab\\add.html","application\\admin\\view\\general\\crontab\\edit.html","application\\admin\\view\\general\\crontab\\index.html","application\\admin\\view\\general\\crontab_log\\detail.html","application\\admin\\view\\general\\crontab_log\\index.html","public\\assets\\js\\backend\\general\\crontab.js","public\\assets\\js\\backend\\general\\crontab_log.js"],"license":"regular","licenseto":"22920","licensekey":"7F0mh6uzp1AMwqKt yIPCkb2\/5kBMy85yN6Pwaw==","domains":["des8.com"],"licensecodes":[],"validations":["b020d8667fd8c7d4326fcf512c7a10aa"],"menus":["general\/crontab","general\/crontab\/index","general\/crontab\/add","general\/crontab\/edit","general\/crontab\/del","general\/crontab\/multi"]}
|
77
addons/crontab/Crontab.php
Normal file
77
addons/crontab/Crontab.php
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addons\crontab;
|
||||||
|
|
||||||
|
use app\common\library\Menu;
|
||||||
|
use think\Addons;
|
||||||
|
use think\Loader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定时任务
|
||||||
|
*/
|
||||||
|
class Crontab extends Addons
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件安装方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function install()
|
||||||
|
{
|
||||||
|
$menu = [
|
||||||
|
[
|
||||||
|
'name' => 'general/crontab',
|
||||||
|
'title' => '定时任务',
|
||||||
|
'icon' => 'fa fa-tasks',
|
||||||
|
'remark' => '按照设定的时间进行任务的执行,目前支持三种任务:请求URL、执行SQL、执行Shell。',
|
||||||
|
'sublist' => [
|
||||||
|
['name' => 'general/crontab/index', 'title' => '查看'],
|
||||||
|
['name' => 'general/crontab/add', 'title' => '添加'],
|
||||||
|
['name' => 'general/crontab/edit', 'title' => '编辑 '],
|
||||||
|
['name' => 'general/crontab/del', 'title' => '删除'],
|
||||||
|
['name' => 'general/crontab/multi', 'title' => '批量更新'],
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
Menu::create($menu, 'general');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件卸载方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function uninstall()
|
||||||
|
{
|
||||||
|
Menu::delete('general/crontab');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件启用方法
|
||||||
|
*/
|
||||||
|
public function enable()
|
||||||
|
{
|
||||||
|
Menu::enable('general/crontab');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件禁用方法
|
||||||
|
*/
|
||||||
|
public function disable()
|
||||||
|
{
|
||||||
|
Menu::disable('general/crontab');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加命名空间
|
||||||
|
*/
|
||||||
|
public function appInit()
|
||||||
|
{
|
||||||
|
//添加命名空间
|
||||||
|
if (!class_exists('\Cron\CronExpression')) {
|
||||||
|
Loader::addNamespace('Cron', ADDON_PATH . 'crontab' . DS . 'library' . DS . 'Cron' . DS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
4
addons/crontab/config.php
Normal file
4
addons/crontab/config.php
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
];
|
200
addons/crontab/controller/Autotask.php
Normal file
200
addons/crontab/controller/Autotask.php
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addons\crontab\controller;
|
||||||
|
|
||||||
|
use addons\crontab\model\Crontab;
|
||||||
|
use Cron\CronExpression;
|
||||||
|
use fast\Http;
|
||||||
|
use think\Controller;
|
||||||
|
use think\Db;
|
||||||
|
use think\Exception;
|
||||||
|
use think\Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定时任务接口
|
||||||
|
*
|
||||||
|
* 以Crontab方式每分钟定时执行,且只可以Cli方式运行
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
class Autotask extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化方法,最前且始终执行
|
||||||
|
*/
|
||||||
|
public function _initialize()
|
||||||
|
{
|
||||||
|
// 只可以以cli方式执行
|
||||||
|
if (!$this->request->isCli()) {
|
||||||
|
$this->error('Autotask script only work at client!');
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::_initialize();
|
||||||
|
|
||||||
|
// 清除错误
|
||||||
|
error_reporting(0);
|
||||||
|
|
||||||
|
// 设置永不超时
|
||||||
|
set_time_limit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行定时任务
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$time = time();
|
||||||
|
$logDir = LOG_PATH . 'crontab' . DS;
|
||||||
|
if (!is_dir($logDir)) {
|
||||||
|
mkdir($logDir, 0755);
|
||||||
|
}
|
||||||
|
//筛选未过期且未完成的任务
|
||||||
|
$crontabList = Crontab::where('status', '=', 'normal')->order('weigh DESC,id DESC')->select();
|
||||||
|
$execTime = time();
|
||||||
|
foreach ($crontabList as $crontab) {
|
||||||
|
$update = [];
|
||||||
|
$execute = false;
|
||||||
|
if ($time < $crontab['begintime']) {
|
||||||
|
//任务未开始
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($crontab['maximums'] && $crontab['executes'] > $crontab['maximums']) {
|
||||||
|
//任务已超过最大执行次数
|
||||||
|
$update['status'] = 'completed';
|
||||||
|
} else {
|
||||||
|
if ($crontab['endtime'] > 0 && $time > $crontab['endtime']) {
|
||||||
|
//任务已过期
|
||||||
|
$update['status'] = 'expired';
|
||||||
|
} else {
|
||||||
|
//重复执行
|
||||||
|
//如果未到执行时间则继续循环
|
||||||
|
$cron = CronExpression::factory($crontab['schedule']);
|
||||||
|
if (!$cron->isDue(date("YmdHi", $execTime)) || date("YmdHi", $execTime) === date("YmdHi", $crontab['executetime'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$execute = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果允许执行
|
||||||
|
if ($execute) {
|
||||||
|
$update['executetime'] = $time;
|
||||||
|
$update['executes'] = $crontab['executes'] + 1;
|
||||||
|
$update['status'] = ($crontab['maximums'] > 0 && $update['executes'] >= $crontab['maximums']) ? 'completed' : 'normal';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果需要更新状态
|
||||||
|
if (!$update) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 更新状态
|
||||||
|
$crontab->save($update);
|
||||||
|
|
||||||
|
// 将执行放在后面是为了避免超时导致多次执行
|
||||||
|
if (!$execute) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$result = false;
|
||||||
|
$message = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($crontab['type'] == 'url') {
|
||||||
|
if (substr($crontab['content'], 0, 1) == "/") {
|
||||||
|
// 本地项目URL
|
||||||
|
$message = shell_exec('php ' . ROOT_PATH . 'public/index.php ' . $crontab['content']);
|
||||||
|
$result = $message ? true : false;
|
||||||
|
} else {
|
||||||
|
$arr = explode(" ", $crontab['content']);
|
||||||
|
$url = $arr[0];
|
||||||
|
$params = isset($arr[1]) ? $arr[1] : '';
|
||||||
|
$method = isset($arr[2]) ? $arr[2] : 'POST';
|
||||||
|
try {
|
||||||
|
// 远程异步调用URL
|
||||||
|
$ret = Http::sendRequest($url, $params, $method);
|
||||||
|
$result = $ret['ret'];
|
||||||
|
$message = $ret['msg'];
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$message = $e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} elseif ($crontab['type'] == 'sql') {
|
||||||
|
$ret = $this->sql($crontab['content']);
|
||||||
|
$result = $ret['ret'];
|
||||||
|
$message = $ret['msg'];
|
||||||
|
} elseif ($crontab['type'] == 'shell') {
|
||||||
|
// 执行Shell
|
||||||
|
$message = shell_exec($crontab['content']);
|
||||||
|
$result = $message ? true : false;
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$message = $e->getMessage();
|
||||||
|
}
|
||||||
|
$log = [
|
||||||
|
'crontab_id' => $crontab['id'],
|
||||||
|
'executetime' => $time,
|
||||||
|
'completetime' => time(),
|
||||||
|
'content' => $message,
|
||||||
|
'status' => $result ? 'success' : 'failure',
|
||||||
|
];
|
||||||
|
Db::name("crontab_log")->insert($log);
|
||||||
|
}
|
||||||
|
return "Execute completed!\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行SQL语句
|
||||||
|
*/
|
||||||
|
protected function sql($sql)
|
||||||
|
{
|
||||||
|
//这里需要强制重连数据库,使用已有的连接会报2014错误
|
||||||
|
$connect = Db::connect([], true);
|
||||||
|
$connect->execute("select 1");
|
||||||
|
|
||||||
|
// 执行SQL
|
||||||
|
$sqlquery = str_replace('__PREFIX__', config('database.prefix'), $sql);
|
||||||
|
$sqls = preg_split("/;[ \t]{0,}\n/i", $sqlquery);
|
||||||
|
|
||||||
|
$result = false;
|
||||||
|
$message = '';
|
||||||
|
$connect->startTrans();
|
||||||
|
try {
|
||||||
|
foreach ($sqls as $key => $val) {
|
||||||
|
if (trim($val) == '' || substr($val, 0, 2) == '--' || substr($val, 0, 2) == '/*') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$message .= "\nSQL:{$val}\n";
|
||||||
|
$val = rtrim($val, ';');
|
||||||
|
if (preg_match("/^(select|explain)(.*)/i ", $val)) {
|
||||||
|
$count = $connect->execute($val);
|
||||||
|
if ($count > 0) {
|
||||||
|
$resultlist = Db::query($val);
|
||||||
|
} else {
|
||||||
|
$resultlist = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$message .= "Total:{$count}\n";
|
||||||
|
$j = 1;
|
||||||
|
foreach ($resultlist as $m => $n) {
|
||||||
|
$message .= "\n";
|
||||||
|
$message .= "Row:{$j}\n";
|
||||||
|
foreach ($n as $k => $v) {
|
||||||
|
$message .= "{$k}:{$v}\n";
|
||||||
|
}
|
||||||
|
$j++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$count = $connect->getPdo()->exec($val);
|
||||||
|
$message = "Affected rows:{$count}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$connect->commit();
|
||||||
|
$result = true;
|
||||||
|
} catch (\PDOException $e) {
|
||||||
|
$message = $e->getMessage();
|
||||||
|
$connect->rollback();
|
||||||
|
$result = false;
|
||||||
|
}
|
||||||
|
return ['ret' => $result, 'msg' => $message];
|
||||||
|
}
|
||||||
|
}
|
16
addons/crontab/controller/Index.php
Normal file
16
addons/crontab/controller/Index.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addons\crontab\controller;
|
||||||
|
|
||||||
|
use think\addons\Controller;
|
||||||
|
|
||||||
|
class Index extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$this->error("当前插件暂无前台页面");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
10
addons/crontab/info.ini
Normal file
10
addons/crontab/info.ini
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
name = crontab
|
||||||
|
title = 定时任务管理
|
||||||
|
intro = 便捷的后台定时任务管理
|
||||||
|
author = FastAdmin
|
||||||
|
website = https://www.fastadmin.net
|
||||||
|
version = 1.0.7
|
||||||
|
state = 1
|
||||||
|
url = /addons/crontab
|
||||||
|
license = regular
|
||||||
|
licenseto = 22920
|
35
addons/crontab/install.sql
Normal file
35
addons/crontab/install.sql
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `__PREFIX__crontab` (
|
||||||
|
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID',
|
||||||
|
`type` varchar(10) NOT NULL DEFAULT '' COMMENT '事件类型',
|
||||||
|
`title` varchar(100) NOT NULL DEFAULT '' COMMENT '事件标题',
|
||||||
|
`content` text NOT NULL COMMENT '事件内容',
|
||||||
|
`schedule` varchar(100) NOT NULL DEFAULT '' COMMENT 'Crontab格式',
|
||||||
|
`sleep` tinyint(1) UNSIGNED NOT NULL DEFAULT '0' COMMENT '延迟秒数执行',
|
||||||
|
`maximums` int(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '最大执行次数 0为不限',
|
||||||
|
`executes` int(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '已经执行的次数',
|
||||||
|
`createtime` bigint(16) DEFAULT NULL COMMENT '创建时间',
|
||||||
|
`updatetime` bigint(16) DEFAULT NULL COMMENT '更新时间',
|
||||||
|
`begintime` bigint(16) DEFAULT NULL COMMENT '开始时间',
|
||||||
|
`endtime` bigint(16) DEFAULT NULL COMMENT '结束时间',
|
||||||
|
`executetime` bigint(16) DEFAULT NULL COMMENT '最后执行时间',
|
||||||
|
`weigh` int(10) NOT NULL DEFAULT '0' COMMENT '权重',
|
||||||
|
`status` enum('completed','expired','hidden','normal') NOT NULL DEFAULT 'normal' COMMENT '状态',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='定时任务表';
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
INSERT INTO `__PREFIX__crontab` (`id`, `type`, `title`, `content`, `schedule`, `sleep`, `maximums`, `executes`, `createtime`, `updatetime`, `begintime`, `endtime`, `executetime`, `weigh`, `status`) VALUES
|
||||||
|
(1, 'url', '请求百度', 'https://www.baidu.com', '* * * * *', 0, 0, 0, 1497070825, 1501253101, 1483200000, 1830268800, 1501253101, 1, 'normal'),
|
||||||
|
(2, 'sql', '查询一条SQL', 'SELECT 1;', '* * * * *', 0, 0, 0, 1497071095, 1501253101, 1483200000, 1830268800, 1501253101, 2, 'normal');
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `__PREFIX__crontab_log` (
|
||||||
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`crontab_id` int(10) DEFAULT NULL COMMENT '任务ID',
|
||||||
|
`executetime` bigint(16) DEFAULT NULL COMMENT '执行时间',
|
||||||
|
`completetime` bigint(16) DEFAULT NULL COMMENT '结束时间',
|
||||||
|
`content` text COMMENT '执行结果',
|
||||||
|
`status` enum('success','failure') DEFAULT 'failure' COMMENT '状态',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `crontab_id` (`crontab_id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='定时任务日志表';
|
252
addons/crontab/library/Cron/AbstractField.php
Normal file
252
addons/crontab/library/Cron/AbstractField.php
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Cron;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract CRON expression field
|
||||||
|
*/
|
||||||
|
abstract class AbstractField implements FieldInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Full range of values that are allowed for this field type
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $fullRange = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Literal values we need to convert to integers
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $literals = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start value of the full range
|
||||||
|
* @var integer
|
||||||
|
*/
|
||||||
|
protected $rangeStart;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End value of the full range
|
||||||
|
* @var integer
|
||||||
|
*/
|
||||||
|
protected $rangeEnd;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->fullRange = range($this->rangeStart, $this->rangeEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check to see if a field is satisfied by a value
|
||||||
|
*
|
||||||
|
* @param string $dateValue Date value to check
|
||||||
|
* @param string $value Value to test
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isSatisfied($dateValue, $value)
|
||||||
|
{
|
||||||
|
if ($this->isIncrementsOfRanges($value)) {
|
||||||
|
return $this->isInIncrementsOfRanges($dateValue, $value);
|
||||||
|
} elseif ($this->isRange($value)) {
|
||||||
|
return $this->isInRange($dateValue, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value == '*' || $dateValue == $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a value is a range
|
||||||
|
*
|
||||||
|
* @param string $value Value to test
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isRange($value)
|
||||||
|
{
|
||||||
|
return strpos($value, '-') !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a value is an increments of ranges
|
||||||
|
*
|
||||||
|
* @param string $value Value to test
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isIncrementsOfRanges($value)
|
||||||
|
{
|
||||||
|
return strpos($value, '/') !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if a value is within a range
|
||||||
|
*
|
||||||
|
* @param string $dateValue Set date value
|
||||||
|
* @param string $value Value to test
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isInRange($dateValue, $value)
|
||||||
|
{
|
||||||
|
$parts = array_map('trim', explode('-', $value, 2));
|
||||||
|
|
||||||
|
return $dateValue >= $parts[0] && $dateValue <= $parts[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if a value is within an increments of ranges (offset[-to]/step size)
|
||||||
|
*
|
||||||
|
* @param string $dateValue Set date value
|
||||||
|
* @param string $value Value to test
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isInIncrementsOfRanges($dateValue, $value)
|
||||||
|
{
|
||||||
|
$chunks = array_map('trim', explode('/', $value, 2));
|
||||||
|
$range = $chunks[0];
|
||||||
|
$step = isset($chunks[1]) ? $chunks[1] : 0;
|
||||||
|
|
||||||
|
// No step or 0 steps aren't cool
|
||||||
|
if (is_null($step) || '0' === $step || 0 === $step) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expand the * to a full range
|
||||||
|
if ('*' == $range) {
|
||||||
|
$range = $this->rangeStart . '-' . $this->rangeEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the requested small range
|
||||||
|
$rangeChunks = explode('-', $range, 2);
|
||||||
|
$rangeStart = $rangeChunks[0];
|
||||||
|
$rangeEnd = isset($rangeChunks[1]) ? $rangeChunks[1] : $rangeStart;
|
||||||
|
|
||||||
|
if ($rangeStart < $this->rangeStart || $rangeStart > $this->rangeEnd || $rangeStart > $rangeEnd) {
|
||||||
|
throw new \OutOfRangeException('Invalid range start requested');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($rangeEnd < $this->rangeStart || $rangeEnd > $this->rangeEnd || $rangeEnd < $rangeStart) {
|
||||||
|
throw new \OutOfRangeException('Invalid range end requested');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($step > ($rangeEnd - $rangeStart) + 1) {
|
||||||
|
throw new \OutOfRangeException('Step cannot be greater than total range');
|
||||||
|
}
|
||||||
|
|
||||||
|
$thisRange = range($rangeStart, $rangeEnd, $step);
|
||||||
|
|
||||||
|
return in_array($dateValue, $thisRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a range of values for the given cron expression
|
||||||
|
*
|
||||||
|
* @param string $expression The expression to evaluate
|
||||||
|
* @param int $max Maximum offset for range
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getRangeForExpression($expression, $max)
|
||||||
|
{
|
||||||
|
$values = array();
|
||||||
|
|
||||||
|
if ($this->isRange($expression) || $this->isIncrementsOfRanges($expression)) {
|
||||||
|
if (!$this->isIncrementsOfRanges($expression)) {
|
||||||
|
list ($offset, $to) = explode('-', $expression);
|
||||||
|
$stepSize = 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$range = array_map('trim', explode('/', $expression, 2));
|
||||||
|
$stepSize = isset($range[1]) ? $range[1] : 0;
|
||||||
|
$range = $range[0];
|
||||||
|
$range = explode('-', $range, 2);
|
||||||
|
$offset = $range[0];
|
||||||
|
$to = isset($range[1]) ? $range[1] : $max;
|
||||||
|
}
|
||||||
|
$offset = $offset == '*' ? 0 : $offset;
|
||||||
|
for ($i = $offset; $i <= $to; $i += $stepSize) {
|
||||||
|
$values[] = $i;
|
||||||
|
}
|
||||||
|
sort($values);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$values = array($expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $values;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function convertLiterals($value)
|
||||||
|
{
|
||||||
|
if (count($this->literals)) {
|
||||||
|
$key = array_search($value, $this->literals);
|
||||||
|
if ($key !== false) {
|
||||||
|
return $key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks to see if a value is valid for the field
|
||||||
|
*
|
||||||
|
* @param string $value
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function validate($value)
|
||||||
|
{
|
||||||
|
$value = $this->convertLiterals($value);
|
||||||
|
|
||||||
|
// All fields allow * as a valid value
|
||||||
|
if ('*' === $value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// You cannot have a range and a list at the same time
|
||||||
|
if (strpos($value, ',') !== false && strpos($value, '-') !== false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strpos($value, '/') !== false) {
|
||||||
|
list($range, $step) = explode('/', $value);
|
||||||
|
return $this->validate($range) && filter_var($step, FILTER_VALIDATE_INT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strpos($value, '-') !== false) {
|
||||||
|
if (substr_count($value, '-') > 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$chunks = explode('-', $value);
|
||||||
|
$chunks[0] = $this->convertLiterals($chunks[0]);
|
||||||
|
$chunks[1] = $this->convertLiterals($chunks[1]);
|
||||||
|
|
||||||
|
if ('*' == $chunks[0] || '*' == $chunks[1]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->validate($chunks[0]) && $this->validate($chunks[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate each chunk of a list individually
|
||||||
|
if (strpos($value, ',') !== false) {
|
||||||
|
foreach (explode(',', $value) as $listItem) {
|
||||||
|
if (!$this->validate($listItem)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should have a numeric by now, so coerce this into an integer
|
||||||
|
if (filter_var($value, FILTER_VALIDATE_INT) !== false) {
|
||||||
|
$value = (int) $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return in_array($value, $this->fullRange, true);
|
||||||
|
}
|
||||||
|
}
|
402
addons/crontab/library/Cron/CronExpression.php
Normal file
402
addons/crontab/library/Cron/CronExpression.php
Normal file
@ -0,0 +1,402 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Cron;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use DateTimeZone;
|
||||||
|
use Exception;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CRON expression parser that can determine whether or not a CRON expression is
|
||||||
|
* due to run, the next run date and previous run date of a CRON expression.
|
||||||
|
* The determinations made by this class are accurate if checked run once per
|
||||||
|
* minute (seconds are dropped from date time comparisons).
|
||||||
|
*
|
||||||
|
* Schedule parts must map to:
|
||||||
|
* minute [0-59], hour [0-23], day of month, month [1-12|JAN-DEC], day of week
|
||||||
|
* [1-7|MON-SUN], and an optional year.
|
||||||
|
*
|
||||||
|
* @link http://en.wikipedia.org/wiki/Cron
|
||||||
|
*/
|
||||||
|
class CronExpression
|
||||||
|
{
|
||||||
|
const MINUTE = 0;
|
||||||
|
const HOUR = 1;
|
||||||
|
const DAY = 2;
|
||||||
|
const MONTH = 3;
|
||||||
|
const WEEKDAY = 4;
|
||||||
|
const YEAR = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array CRON expression parts
|
||||||
|
*/
|
||||||
|
private $cronParts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var FieldFactory CRON field factory
|
||||||
|
*/
|
||||||
|
private $fieldFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int Max iteration count when searching for next run date
|
||||||
|
*/
|
||||||
|
private $maxIterationCount = 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array Order in which to test of cron parts
|
||||||
|
*/
|
||||||
|
private static $order = array(self::YEAR, self::MONTH, self::DAY, self::WEEKDAY, self::HOUR, self::MINUTE);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory method to create a new CronExpression.
|
||||||
|
*
|
||||||
|
* @param string $expression The CRON expression to create. There are
|
||||||
|
* several special predefined values which can be used to substitute the
|
||||||
|
* CRON expression:
|
||||||
|
*
|
||||||
|
* `@yearly`, `@annually` - Run once a year, midnight, Jan. 1 - 0 0 1 1 *
|
||||||
|
* `@monthly` - Run once a month, midnight, first of month - 0 0 1 * *
|
||||||
|
* `@weekly` - Run once a week, midnight on Sun - 0 0 * * 0
|
||||||
|
* `@daily` - Run once a day, midnight - 0 0 * * *
|
||||||
|
* `@hourly` - Run once an hour, first minute - 0 * * * *
|
||||||
|
* @param FieldFactory $fieldFactory Field factory to use
|
||||||
|
*
|
||||||
|
* @return CronExpression
|
||||||
|
*/
|
||||||
|
public static function factory($expression, FieldFactory $fieldFactory = null)
|
||||||
|
{
|
||||||
|
$mappings = array(
|
||||||
|
'@yearly' => '0 0 1 1 *',
|
||||||
|
'@annually' => '0 0 1 1 *',
|
||||||
|
'@monthly' => '0 0 1 * *',
|
||||||
|
'@weekly' => '0 0 * * 0',
|
||||||
|
'@daily' => '0 0 * * *',
|
||||||
|
'@hourly' => '0 * * * *'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isset($mappings[$expression])) {
|
||||||
|
$expression = $mappings[$expression];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new static($expression, $fieldFactory ?: new FieldFactory());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a CronExpression.
|
||||||
|
*
|
||||||
|
* @param string $expression The CRON expression to validate.
|
||||||
|
*
|
||||||
|
* @return bool True if a valid CRON expression was passed. False if not.
|
||||||
|
* @see \Cron\CronExpression::factory
|
||||||
|
*/
|
||||||
|
public static function isValidExpression($expression)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
self::factory($expression);
|
||||||
|
} catch (InvalidArgumentException $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a CRON expression
|
||||||
|
*
|
||||||
|
* @param string $expression CRON expression (e.g. '8 * * * *')
|
||||||
|
* @param FieldFactory $fieldFactory Factory to create cron fields
|
||||||
|
*/
|
||||||
|
public function __construct($expression, FieldFactory $fieldFactory)
|
||||||
|
{
|
||||||
|
$this->fieldFactory = $fieldFactory;
|
||||||
|
$this->setExpression($expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set or change the CRON expression
|
||||||
|
*
|
||||||
|
* @param string $value CRON expression (e.g. 8 * * * *)
|
||||||
|
*
|
||||||
|
* @return CronExpression
|
||||||
|
* @throws \InvalidArgumentException if not a valid CRON expression
|
||||||
|
*/
|
||||||
|
public function setExpression($value)
|
||||||
|
{
|
||||||
|
$this->cronParts = preg_split('/\s/', $value, -1, PREG_SPLIT_NO_EMPTY);
|
||||||
|
if (count($this->cronParts) < 5) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
$value . ' is not a valid CRON expression'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->cronParts as $position => $part) {
|
||||||
|
$this->setPart($position, $part);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set part of the CRON expression
|
||||||
|
*
|
||||||
|
* @param int $position The position of the CRON expression to set
|
||||||
|
* @param string $value The value to set
|
||||||
|
*
|
||||||
|
* @return CronExpression
|
||||||
|
* @throws \InvalidArgumentException if the value is not valid for the part
|
||||||
|
*/
|
||||||
|
public function setPart($position, $value)
|
||||||
|
{
|
||||||
|
if (!$this->fieldFactory->getField($position)->validate($value)) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
'Invalid CRON field value ' . $value . ' at position ' . $position
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->cronParts[$position] = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set max iteration count for searching next run dates
|
||||||
|
*
|
||||||
|
* @param int $maxIterationCount Max iteration count when searching for next run date
|
||||||
|
*
|
||||||
|
* @return CronExpression
|
||||||
|
*/
|
||||||
|
public function setMaxIterationCount($maxIterationCount)
|
||||||
|
{
|
||||||
|
$this->maxIterationCount = $maxIterationCount;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a next run date relative to the current date or a specific date
|
||||||
|
*
|
||||||
|
* @param string|\DateTime $currentTime Relative calculation date
|
||||||
|
* @param int $nth Number of matches to skip before returning a
|
||||||
|
* matching next run date. 0, the default, will return the current
|
||||||
|
* date and time if the next run date falls on the current date and
|
||||||
|
* time. Setting this value to 1 will skip the first match and go to
|
||||||
|
* the second match. Setting this value to 2 will skip the first 2
|
||||||
|
* matches and so on.
|
||||||
|
* @param bool $allowCurrentDate Set to TRUE to return the current date if
|
||||||
|
* it matches the cron expression.
|
||||||
|
* @param null|string $timeZone Timezone to use instead of the system default
|
||||||
|
*
|
||||||
|
* @return \DateTime
|
||||||
|
* @throws \RuntimeException on too many iterations
|
||||||
|
*/
|
||||||
|
public function getNextRunDate($currentTime = 'now', $nth = 0, $allowCurrentDate = false, $timeZone = null)
|
||||||
|
{
|
||||||
|
return $this->getRunDate($currentTime, $nth, false, $allowCurrentDate, $timeZone);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a previous run date relative to the current date or a specific date
|
||||||
|
*
|
||||||
|
* @param string|\DateTime $currentTime Relative calculation date
|
||||||
|
* @param int $nth Number of matches to skip before returning
|
||||||
|
* @param bool $allowCurrentDate Set to TRUE to return the
|
||||||
|
* current date if it matches the cron expression
|
||||||
|
* @param null|string $timeZone Timezone to use instead of the system default
|
||||||
|
*
|
||||||
|
* @return \DateTime
|
||||||
|
* @throws \RuntimeException on too many iterations
|
||||||
|
* @see \Cron\CronExpression::getNextRunDate
|
||||||
|
*/
|
||||||
|
public function getPreviousRunDate($currentTime = 'now', $nth = 0, $allowCurrentDate = false, $timeZone = null)
|
||||||
|
{
|
||||||
|
return $this->getRunDate($currentTime, $nth, true, $allowCurrentDate, $timeZone);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get multiple run dates starting at the current date or a specific date
|
||||||
|
*
|
||||||
|
* @param int $total Set the total number of dates to calculate
|
||||||
|
* @param string|\DateTime $currentTime Relative calculation date
|
||||||
|
* @param bool $invert Set to TRUE to retrieve previous dates
|
||||||
|
* @param bool $allowCurrentDate Set to TRUE to return the
|
||||||
|
* current date if it matches the cron expression
|
||||||
|
* @param null|string $timeZone Timezone to use instead of the system default
|
||||||
|
*
|
||||||
|
* @return array Returns an array of run dates
|
||||||
|
*/
|
||||||
|
public function getMultipleRunDates($total, $currentTime = 'now', $invert = false, $allowCurrentDate = false, $timeZone = null)
|
||||||
|
{
|
||||||
|
$matches = array();
|
||||||
|
for ($i = 0; $i < max(0, $total); $i++) {
|
||||||
|
try {
|
||||||
|
$matches[] = $this->getRunDate($currentTime, $i, $invert, $allowCurrentDate, $timeZone);
|
||||||
|
} catch (RuntimeException $e) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all or part of the CRON expression
|
||||||
|
*
|
||||||
|
* @param string $part Specify the part to retrieve or NULL to get the full
|
||||||
|
* cron schedule string.
|
||||||
|
*
|
||||||
|
* @return string|null Returns the CRON expression, a part of the
|
||||||
|
* CRON expression, or NULL if the part was specified but not found
|
||||||
|
*/
|
||||||
|
public function getExpression($part = null)
|
||||||
|
{
|
||||||
|
if (null === $part) {
|
||||||
|
return implode(' ', $this->cronParts);
|
||||||
|
} elseif (array_key_exists($part, $this->cronParts)) {
|
||||||
|
return $this->cronParts[$part];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to output the full expression.
|
||||||
|
*
|
||||||
|
* @return string Full CRON expression
|
||||||
|
*/
|
||||||
|
public function __toString()
|
||||||
|
{
|
||||||
|
return $this->getExpression();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the cron is due to run based on the current date or a
|
||||||
|
* specific date. This method assumes that the current number of
|
||||||
|
* seconds are irrelevant, and should be called once per minute.
|
||||||
|
*
|
||||||
|
* @param string|\DateTime $currentTime Relative calculation date
|
||||||
|
* @param null|string $timeZone Timezone to use instead of the system default
|
||||||
|
*
|
||||||
|
* @return bool Returns TRUE if the cron is due to run or FALSE if not
|
||||||
|
*/
|
||||||
|
public function isDue($currentTime = 'now', $timeZone = null)
|
||||||
|
{
|
||||||
|
if (is_null($timeZone)) {
|
||||||
|
$timeZone = date_default_timezone_get();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('now' === $currentTime) {
|
||||||
|
$currentDate = date('Y-m-d H:i');
|
||||||
|
$currentTime = strtotime($currentDate);
|
||||||
|
} elseif ($currentTime instanceof DateTime) {
|
||||||
|
$currentDate = clone $currentTime;
|
||||||
|
// Ensure time in 'current' timezone is used
|
||||||
|
$currentDate->setTimezone(new DateTimeZone($timeZone));
|
||||||
|
$currentDate = $currentDate->format('Y-m-d H:i');
|
||||||
|
$currentTime = strtotime($currentDate);
|
||||||
|
} elseif ($currentTime instanceof DateTimeImmutable) {
|
||||||
|
$currentDate = DateTime::createFromFormat('U', $currentTime->format('U'));
|
||||||
|
$currentDate->setTimezone(new DateTimeZone($timeZone));
|
||||||
|
$currentDate = $currentDate->format('Y-m-d H:i');
|
||||||
|
$currentTime = strtotime($currentDate);
|
||||||
|
} else {
|
||||||
|
$currentTime = new DateTime($currentTime);
|
||||||
|
$currentTime->setTime($currentTime->format('H'), $currentTime->format('i'), 0);
|
||||||
|
$currentDate = $currentTime->format('Y-m-d H:i');
|
||||||
|
$currentTime = $currentTime->getTimeStamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return $this->getNextRunDate($currentDate, 0, true)->getTimestamp() == $currentTime;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the next or previous run date of the expression relative to a date
|
||||||
|
*
|
||||||
|
* @param string|\DateTime $currentTime Relative calculation date
|
||||||
|
* @param int $nth Number of matches to skip before returning
|
||||||
|
* @param bool $invert Set to TRUE to go backwards in time
|
||||||
|
* @param bool $allowCurrentDate Set to TRUE to return the
|
||||||
|
* current date if it matches the cron expression
|
||||||
|
* @param string|null $timeZone Timezone to use instead of the system default
|
||||||
|
*
|
||||||
|
* @return \DateTime
|
||||||
|
* @throws \RuntimeException on too many iterations
|
||||||
|
*/
|
||||||
|
protected function getRunDate($currentTime = null, $nth = 0, $invert = false, $allowCurrentDate = false, $timeZone = null)
|
||||||
|
{
|
||||||
|
if (is_null($timeZone)) {
|
||||||
|
$timeZone = date_default_timezone_get();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($currentTime instanceof DateTime) {
|
||||||
|
$currentDate = clone $currentTime;
|
||||||
|
} elseif ($currentTime instanceof DateTimeImmutable) {
|
||||||
|
$currentDate = DateTime::createFromFormat('U', $currentTime->format('U'));
|
||||||
|
$currentDate->setTimezone($currentTime->getTimezone());
|
||||||
|
} else {
|
||||||
|
$currentDate = new DateTime($currentTime ?: 'now');
|
||||||
|
$currentDate->setTimezone(new DateTimeZone($timeZone));
|
||||||
|
}
|
||||||
|
|
||||||
|
$currentDate->setTime($currentDate->format('H'), $currentDate->format('i'), 0);
|
||||||
|
$nextRun = clone $currentDate;
|
||||||
|
$nth = (int) $nth;
|
||||||
|
|
||||||
|
// We don't have to satisfy * or null fields
|
||||||
|
$parts = array();
|
||||||
|
$fields = array();
|
||||||
|
foreach (self::$order as $position) {
|
||||||
|
$part = $this->getExpression($position);
|
||||||
|
if (null === $part || '*' === $part) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$parts[$position] = $part;
|
||||||
|
$fields[$position] = $this->fieldFactory->getField($position);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a hard limit to bail on an impossible date
|
||||||
|
for ($i = 0; $i < $this->maxIterationCount; $i++) {
|
||||||
|
|
||||||
|
foreach ($parts as $position => $part) {
|
||||||
|
$satisfied = false;
|
||||||
|
// Get the field object used to validate this part
|
||||||
|
$field = $fields[$position];
|
||||||
|
// Check if this is singular or a list
|
||||||
|
if (strpos($part, ',') === false) {
|
||||||
|
$satisfied = $field->isSatisfiedBy($nextRun, $part);
|
||||||
|
} else {
|
||||||
|
foreach (array_map('trim', explode(',', $part)) as $listPart) {
|
||||||
|
if ($field->isSatisfiedBy($nextRun, $listPart)) {
|
||||||
|
$satisfied = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the field is not satisfied, then start over
|
||||||
|
if (!$satisfied) {
|
||||||
|
$field->increment($nextRun, $invert, $part);
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip this match if needed
|
||||||
|
if ((!$allowCurrentDate && $nextRun == $currentDate) || --$nth > -1) {
|
||||||
|
$this->fieldFactory->getField(0)->increment($nextRun, $invert, isset($parts[0]) ? $parts[0] : null);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $nextRun;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
|
throw new RuntimeException('Impossible CRON expression');
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
|
}
|
||||||
|
}
|
131
addons/crontab/library/Cron/DayOfMonthField.php
Normal file
131
addons/crontab/library/Cron/DayOfMonthField.php
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Cron;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Day of month field. Allows: * , / - ? L W
|
||||||
|
*
|
||||||
|
* 'L' stands for "last" and specifies the last day of the month.
|
||||||
|
*
|
||||||
|
* The 'W' character is used to specify the weekday (Monday-Friday) nearest the
|
||||||
|
* given day. As an example, if you were to specify "15W" as the value for the
|
||||||
|
* day-of-month field, the meaning is: "the nearest weekday to the 15th of the
|
||||||
|
* month". So if the 15th is a Saturday, the trigger will fire on Friday the
|
||||||
|
* 14th. If the 15th is a Sunday, the trigger will fire on Monday the 16th. If
|
||||||
|
* the 15th is a Tuesday, then it will fire on Tuesday the 15th. However if you
|
||||||
|
* specify "1W" as the value for day-of-month, and the 1st is a Saturday, the
|
||||||
|
* trigger will fire on Monday the 3rd, as it will not 'jump' over the boundary
|
||||||
|
* of a month's days. The 'W' character can only be specified when the
|
||||||
|
* day-of-month is a single day, not a range or list of days.
|
||||||
|
*
|
||||||
|
* @author Michael Dowling <mtdowling@gmail.com>
|
||||||
|
*/
|
||||||
|
class DayOfMonthField extends AbstractField
|
||||||
|
{
|
||||||
|
protected $rangeStart = 1;
|
||||||
|
protected $rangeEnd = 31;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the nearest day of the week for a given day in a month
|
||||||
|
*
|
||||||
|
* @param int $currentYear Current year
|
||||||
|
* @param int $currentMonth Current month
|
||||||
|
* @param int $targetDay Target day of the month
|
||||||
|
*
|
||||||
|
* @return \DateTime Returns the nearest date
|
||||||
|
*/
|
||||||
|
private static function getNearestWeekday($currentYear, $currentMonth, $targetDay)
|
||||||
|
{
|
||||||
|
$tday = str_pad($targetDay, 2, '0', STR_PAD_LEFT);
|
||||||
|
$target = DateTime::createFromFormat('Y-m-d', "$currentYear-$currentMonth-$tday");
|
||||||
|
$currentWeekday = (int) $target->format('N');
|
||||||
|
|
||||||
|
if ($currentWeekday < 6) {
|
||||||
|
return $target;
|
||||||
|
}
|
||||||
|
|
||||||
|
$lastDayOfMonth = $target->format('t');
|
||||||
|
|
||||||
|
foreach (array(-1, 1, -2, 2) as $i) {
|
||||||
|
$adjusted = $targetDay + $i;
|
||||||
|
if ($adjusted > 0 && $adjusted <= $lastDayOfMonth) {
|
||||||
|
$target->setDate($currentYear, $currentMonth, $adjusted);
|
||||||
|
if ($target->format('N') < 6 && $target->format('m') == $currentMonth) {
|
||||||
|
return $target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isSatisfiedBy(DateTime $date, $value)
|
||||||
|
{
|
||||||
|
// ? states that the field value is to be skipped
|
||||||
|
if ($value == '?') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fieldValue = $date->format('d');
|
||||||
|
|
||||||
|
// Check to see if this is the last day of the month
|
||||||
|
if ($value == 'L') {
|
||||||
|
return $fieldValue == $date->format('t');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to see if this is the nearest weekday to a particular value
|
||||||
|
if (strpos($value, 'W')) {
|
||||||
|
// Parse the target day
|
||||||
|
$targetDay = substr($value, 0, strpos($value, 'W'));
|
||||||
|
// Find out if the current day is the nearest day of the week
|
||||||
|
return $date->format('j') == self::getNearestWeekday(
|
||||||
|
$date->format('Y'),
|
||||||
|
$date->format('m'),
|
||||||
|
$targetDay
|
||||||
|
)->format('j');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->isSatisfied($date->format('d'), $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function increment(DateTime $date, $invert = false)
|
||||||
|
{
|
||||||
|
if ($invert) {
|
||||||
|
$date->modify('previous day');
|
||||||
|
$date->setTime(23, 59);
|
||||||
|
} else {
|
||||||
|
$date->modify('next day');
|
||||||
|
$date->setTime(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function validate($value)
|
||||||
|
{
|
||||||
|
$basicChecks = parent::validate($value);
|
||||||
|
|
||||||
|
// Validate that a list don't have W or L
|
||||||
|
if (strpos($value, ',') !== false && (strpos($value, 'W') !== false || strpos($value, 'L') !== false)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$basicChecks) {
|
||||||
|
|
||||||
|
if ($value === 'L') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/^(.*)W$/', $value, $matches)) {
|
||||||
|
return $this->validate($matches[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $basicChecks;
|
||||||
|
}
|
||||||
|
}
|
170
addons/crontab/library/Cron/DayOfWeekField.php
Normal file
170
addons/crontab/library/Cron/DayOfWeekField.php
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Cron;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Day of week field. Allows: * / , - ? L #
|
||||||
|
*
|
||||||
|
* Days of the week can be represented as a number 0-7 (0|7 = Sunday)
|
||||||
|
* or as a three letter string: SUN, MON, TUE, WED, THU, FRI, SAT.
|
||||||
|
*
|
||||||
|
* 'L' stands for "last". It allows you to specify constructs such as
|
||||||
|
* "the last Friday" of a given month.
|
||||||
|
*
|
||||||
|
* '#' is allowed for the day-of-week field, and must be followed by a
|
||||||
|
* number between one and five. It allows you to specify constructs such as
|
||||||
|
* "the second Friday" of a given month.
|
||||||
|
*/
|
||||||
|
class DayOfWeekField extends AbstractField
|
||||||
|
{
|
||||||
|
protected $rangeStart = 0;
|
||||||
|
protected $rangeEnd = 7;
|
||||||
|
|
||||||
|
protected $nthRange;
|
||||||
|
|
||||||
|
protected $literals = [1 => 'MON', 2 => 'TUE', 3 => 'WED', 4 => 'THU', 5 => 'FRI', 6 => 'SAT', 7 => 'SUN'];
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->nthRange = range(1, 5);
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isSatisfiedBy(DateTime $date, $value)
|
||||||
|
{
|
||||||
|
if ($value == '?') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert text day of the week values to integers
|
||||||
|
$value = $this->convertLiterals($value);
|
||||||
|
|
||||||
|
$currentYear = $date->format('Y');
|
||||||
|
$currentMonth = $date->format('m');
|
||||||
|
$lastDayOfMonth = $date->format('t');
|
||||||
|
|
||||||
|
// Find out if this is the last specific weekday of the month
|
||||||
|
if (strpos($value, 'L')) {
|
||||||
|
$weekday = str_replace('7', '0', substr($value, 0, strpos($value, 'L')));
|
||||||
|
$tdate = clone $date;
|
||||||
|
$tdate->setDate($currentYear, $currentMonth, $lastDayOfMonth);
|
||||||
|
while ($tdate->format('w') != $weekday) {
|
||||||
|
$tdateClone = new DateTime();
|
||||||
|
$tdate = $tdateClone
|
||||||
|
->setTimezone($tdate->getTimezone())
|
||||||
|
->setDate($currentYear, $currentMonth, --$lastDayOfMonth);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $date->format('j') == $lastDayOfMonth;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle # hash tokens
|
||||||
|
if (strpos($value, '#')) {
|
||||||
|
list($weekday, $nth) = explode('#', $value);
|
||||||
|
|
||||||
|
if (!is_numeric($nth)) {
|
||||||
|
throw new InvalidArgumentException("Hashed weekdays must be numeric, {$nth} given");
|
||||||
|
} else {
|
||||||
|
$nth = (int) $nth;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0 and 7 are both Sunday, however 7 matches date('N') format ISO-8601
|
||||||
|
if ($weekday === '0') {
|
||||||
|
$weekday = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
$weekday = $this->convertLiterals($weekday);
|
||||||
|
|
||||||
|
// Validate the hash fields
|
||||||
|
if ($weekday < 0 || $weekday > 7) {
|
||||||
|
throw new InvalidArgumentException("Weekday must be a value between 0 and 7. {$weekday} given");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in_array($nth, $this->nthRange)) {
|
||||||
|
throw new InvalidArgumentException("There are never more than 5 or less than 1 of a given weekday in a month, {$nth} given");
|
||||||
|
}
|
||||||
|
|
||||||
|
// The current weekday must match the targeted weekday to proceed
|
||||||
|
if ($date->format('N') != $weekday) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tdate = clone $date;
|
||||||
|
$tdate->setDate($currentYear, $currentMonth, 1);
|
||||||
|
$dayCount = 0;
|
||||||
|
$currentDay = 1;
|
||||||
|
while ($currentDay < $lastDayOfMonth + 1) {
|
||||||
|
if ($tdate->format('N') == $weekday) {
|
||||||
|
if (++$dayCount >= $nth) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$tdate->setDate($currentYear, $currentMonth, ++$currentDay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $date->format('j') == $currentDay;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle day of the week values
|
||||||
|
if (strpos($value, '-')) {
|
||||||
|
$parts = explode('-', $value);
|
||||||
|
if ($parts[0] == '7') {
|
||||||
|
$parts[0] = '0';
|
||||||
|
} elseif ($parts[1] == '0') {
|
||||||
|
$parts[1] = '7';
|
||||||
|
}
|
||||||
|
$value = implode('-', $parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test to see which Sunday to use -- 0 == 7 == Sunday
|
||||||
|
$format = in_array(7, str_split($value)) ? 'N' : 'w';
|
||||||
|
$fieldValue = $date->format($format);
|
||||||
|
|
||||||
|
return $this->isSatisfied($fieldValue, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function increment(DateTime $date, $invert = false)
|
||||||
|
{
|
||||||
|
if ($invert) {
|
||||||
|
$date->modify('-1 day');
|
||||||
|
$date->setTime(23, 59, 0);
|
||||||
|
} else {
|
||||||
|
$date->modify('+1 day');
|
||||||
|
$date->setTime(0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function validate($value)
|
||||||
|
{
|
||||||
|
$basicChecks = parent::validate($value);
|
||||||
|
|
||||||
|
if (!$basicChecks) {
|
||||||
|
// Handle the # value
|
||||||
|
if (strpos($value, '#') !== false) {
|
||||||
|
$chunks = explode('#', $value);
|
||||||
|
$chunks[0] = $this->convertLiterals($chunks[0]);
|
||||||
|
|
||||||
|
if (parent::validate($chunks[0]) && is_numeric($chunks[1]) && in_array($chunks[1], $this->nthRange)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/^(.*)L$/', $value, $matches)) {
|
||||||
|
return $this->validate($matches[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $basicChecks;
|
||||||
|
}
|
||||||
|
}
|
54
addons/crontab/library/Cron/FieldFactory.php
Normal file
54
addons/crontab/library/Cron/FieldFactory.php
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Cron;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CRON field factory implementing a flyweight factory
|
||||||
|
* @link http://en.wikipedia.org/wiki/Cron
|
||||||
|
*/
|
||||||
|
class FieldFactory
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array Cache of instantiated fields
|
||||||
|
*/
|
||||||
|
private $fields = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an instance of a field object for a cron expression position
|
||||||
|
*
|
||||||
|
* @param int $position CRON expression position value to retrieve
|
||||||
|
*
|
||||||
|
* @return FieldInterface
|
||||||
|
* @throws InvalidArgumentException if a position is not valid
|
||||||
|
*/
|
||||||
|
public function getField($position)
|
||||||
|
{
|
||||||
|
if (!isset($this->fields[$position])) {
|
||||||
|
switch ($position) {
|
||||||
|
case 0:
|
||||||
|
$this->fields[$position] = new MinutesField();
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
$this->fields[$position] = new HoursField();
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
$this->fields[$position] = new DayOfMonthField();
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
$this->fields[$position] = new MonthField();
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
$this->fields[$position] = new DayOfWeekField();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
$position . ' is not a valid position'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->fields[$position];
|
||||||
|
}
|
||||||
|
}
|
40
addons/crontab/library/Cron/FieldInterface.php
Normal file
40
addons/crontab/library/Cron/FieldInterface.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Cron;
|
||||||
|
use DateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CRON field interface
|
||||||
|
*/
|
||||||
|
interface FieldInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Check if the respective value of a DateTime field satisfies a CRON exp
|
||||||
|
*
|
||||||
|
* @param DateTime $date DateTime object to check
|
||||||
|
* @param string $value CRON expression to test against
|
||||||
|
*
|
||||||
|
* @return bool Returns TRUE if satisfied, FALSE otherwise
|
||||||
|
*/
|
||||||
|
public function isSatisfiedBy(DateTime $date, $value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a CRON expression is not satisfied, this method is used to increment
|
||||||
|
* or decrement a DateTime object by the unit of the cron field
|
||||||
|
*
|
||||||
|
* @param DateTime $date DateTime object to change
|
||||||
|
* @param bool $invert (optional) Set to TRUE to decrement
|
||||||
|
*
|
||||||
|
* @return FieldInterface
|
||||||
|
*/
|
||||||
|
public function increment(DateTime $date, $invert = false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a CRON expression for a given field
|
||||||
|
*
|
||||||
|
* @param string $value CRON expression value to validate
|
||||||
|
*
|
||||||
|
* @return bool Returns TRUE if valid, FALSE otherwise
|
||||||
|
*/
|
||||||
|
public function validate($value);
|
||||||
|
}
|
69
addons/crontab/library/Cron/HoursField.php
Normal file
69
addons/crontab/library/Cron/HoursField.php
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Cron;
|
||||||
|
use DateTime;
|
||||||
|
use DateTimeZone;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hours field. Allows: * , / -
|
||||||
|
*/
|
||||||
|
class HoursField extends AbstractField
|
||||||
|
{
|
||||||
|
protected $rangeStart = 0;
|
||||||
|
protected $rangeEnd = 23;
|
||||||
|
|
||||||
|
public function isSatisfiedBy(DateTime $date, $value)
|
||||||
|
{
|
||||||
|
return $this->isSatisfied($date->format('H'), $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function increment(DateTime $date, $invert = false, $parts = null)
|
||||||
|
{
|
||||||
|
// Change timezone to UTC temporarily. This will
|
||||||
|
// allow us to go back or forwards and hour even
|
||||||
|
// if DST will be changed between the hours.
|
||||||
|
if (is_null($parts) || $parts == '*') {
|
||||||
|
$timezone = $date->getTimezone();
|
||||||
|
$date->setTimezone(new DateTimeZone('UTC'));
|
||||||
|
if ($invert) {
|
||||||
|
$date->modify('-1 hour');
|
||||||
|
} else {
|
||||||
|
$date->modify('+1 hour');
|
||||||
|
}
|
||||||
|
$date->setTimezone($timezone);
|
||||||
|
|
||||||
|
$date->setTime($date->format('H'), $invert ? 59 : 0);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$parts = strpos($parts, ',') !== false ? explode(',', $parts) : array($parts);
|
||||||
|
$hours = array();
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
$hours = array_merge($hours, $this->getRangeForExpression($part, 23));
|
||||||
|
}
|
||||||
|
|
||||||
|
$current_hour = $date->format('H');
|
||||||
|
$position = $invert ? count($hours) - 1 : 0;
|
||||||
|
if (count($hours) > 1) {
|
||||||
|
for ($i = 0; $i < count($hours) - 1; $i++) {
|
||||||
|
if ((!$invert && $current_hour >= $hours[$i] && $current_hour < $hours[$i + 1]) ||
|
||||||
|
($invert && $current_hour > $hours[$i] && $current_hour <= $hours[$i + 1])) {
|
||||||
|
$position = $invert ? $i : $i + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$hour = $hours[$position];
|
||||||
|
if ((!$invert && $date->format('H') >= $hour) || ($invert && $date->format('H') <= $hour)) {
|
||||||
|
$date->modify(($invert ? '-' : '+') . '1 day');
|
||||||
|
$date->setTime($invert ? 23 : 0, $invert ? 59 : 0);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$date->setTime($hour, $invert ? 59 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
60
addons/crontab/library/Cron/MinutesField.php
Normal file
60
addons/crontab/library/Cron/MinutesField.php
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Cron;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minutes field. Allows: * , / -
|
||||||
|
*/
|
||||||
|
class MinutesField extends AbstractField
|
||||||
|
{
|
||||||
|
protected $rangeStart = 0;
|
||||||
|
protected $rangeEnd = 59;
|
||||||
|
|
||||||
|
public function isSatisfiedBy(DateTime $date, $value)
|
||||||
|
{
|
||||||
|
return $this->isSatisfied($date->format('i'), $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function increment(DateTime $date, $invert = false, $parts = null)
|
||||||
|
{
|
||||||
|
if (is_null($parts)) {
|
||||||
|
if ($invert) {
|
||||||
|
$date->modify('-1 minute');
|
||||||
|
} else {
|
||||||
|
$date->modify('+1 minute');
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$parts = strpos($parts, ',') !== false ? explode(',', $parts) : array($parts);
|
||||||
|
$minutes = array();
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
$minutes = array_merge($minutes, $this->getRangeForExpression($part, 59));
|
||||||
|
}
|
||||||
|
|
||||||
|
$current_minute = $date->format('i');
|
||||||
|
$position = $invert ? count($minutes) - 1 : 0;
|
||||||
|
if (count($minutes) > 1) {
|
||||||
|
for ($i = 0; $i < count($minutes) - 1; $i++) {
|
||||||
|
if ((!$invert && $current_minute >= $minutes[$i] && $current_minute < $minutes[$i + 1]) ||
|
||||||
|
($invert && $current_minute > $minutes[$i] && $current_minute <= $minutes[$i + 1])) {
|
||||||
|
$position = $invert ? $i : $i + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!$invert && $current_minute >= $minutes[$position]) || ($invert && $current_minute <= $minutes[$position])) {
|
||||||
|
$date->modify(($invert ? '-' : '+') . '1 hour');
|
||||||
|
$date->setTime($date->format('H'), $invert ? 59 : 0);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$date->setTime($date->format('H'), $minutes[$position]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
38
addons/crontab/library/Cron/MonthField.php
Normal file
38
addons/crontab/library/Cron/MonthField.php
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Cron;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Month field. Allows: * , / -
|
||||||
|
*/
|
||||||
|
class MonthField extends AbstractField
|
||||||
|
{
|
||||||
|
protected $rangeStart = 1;
|
||||||
|
protected $rangeEnd = 12;
|
||||||
|
protected $literals = [1 => 'JAN', 2 => 'FEB', 3 => 'MAR', 4 => 'APR', 5 => 'MAY', 6 => 'JUN', 7 => 'JUL',
|
||||||
|
8 => 'AUG', 9 => 'SEP', 10 => 'OCT', 11 => 'NOV', 12 => 'DEC'];
|
||||||
|
|
||||||
|
public function isSatisfiedBy(DateTime $date, $value)
|
||||||
|
{
|
||||||
|
$value = $this->convertLiterals($value);
|
||||||
|
|
||||||
|
return $this->isSatisfied($date->format('m'), $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function increment(DateTime $date, $invert = false)
|
||||||
|
{
|
||||||
|
if ($invert) {
|
||||||
|
$date->modify('last day of previous month');
|
||||||
|
$date->setTime(23, 59);
|
||||||
|
} else {
|
||||||
|
$date->modify('first day of next month');
|
||||||
|
$date->setTime(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
54
addons/crontab/model/Crontab.php
Normal file
54
addons/crontab/model/Crontab.php
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addons\crontab\model;
|
||||||
|
|
||||||
|
use think\Model;
|
||||||
|
|
||||||
|
class Crontab extends Model
|
||||||
|
{
|
||||||
|
|
||||||
|
// 开启自动写入时间戳字段
|
||||||
|
protected $autoWriteTimestamp = 'integer';
|
||||||
|
// 定义时间戳字段名
|
||||||
|
protected $createTime = 'createtime';
|
||||||
|
protected $updateTime = 'updatetime';
|
||||||
|
// 定义字段类型
|
||||||
|
protected $type = [
|
||||||
|
];
|
||||||
|
// 追加属性
|
||||||
|
protected $append = [
|
||||||
|
'type_text'
|
||||||
|
];
|
||||||
|
|
||||||
|
public static function getTypeList()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'url' => __('Request Url'),
|
||||||
|
'sql' => __('Execute Sql Script'),
|
||||||
|
'shell' => __('Execute Shell'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTypeTextAttr($value, $data)
|
||||||
|
{
|
||||||
|
$typelist = self::getTypeList();
|
||||||
|
$value = $value ? $value : $data['type'];
|
||||||
|
return $value && isset($typelist[$value]) ? $typelist[$value] : $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setBegintimeAttr($value)
|
||||||
|
{
|
||||||
|
return $value && !is_numeric($value) ? strtotime($value) : $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setEndtimeAttr($value)
|
||||||
|
{
|
||||||
|
return $value && !is_numeric($value) ? strtotime($value) : $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setExecutetimeAttr($value)
|
||||||
|
{
|
||||||
|
return $value && !is_numeric($value) ? strtotime($value) : $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
1
addons/database/.addonrc
Normal file
1
addons/database/.addonrc
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"files":["application\\admin\\controller\\general\\Database.php","application\\admin\\lang\\zh-cn\\general\\database.php","application\\admin\\view\\general\\database\\index.html","public\\assets\\js\\backend\\general\\database.js"],"license":"regular","licenseto":"22920","licensekey":"Jag2qKpAiEM0Tnv1 sLIdiX6tSgJw1OCrCoQwsQ==","domains":["des8.com"],"licensecodes":[],"validations":["b020d8667fd8c7d4326fcf512c7a10aa"],"menus":["general\/database","general\/database\/index","general\/database\/query","general\/database\/backup","general\/database\/restore"]}
|
65
addons/database/Database.php
Normal file
65
addons/database/Database.php
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addons\database;
|
||||||
|
|
||||||
|
use app\common\library\Menu;
|
||||||
|
use think\Addons;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库插件
|
||||||
|
*/
|
||||||
|
class Database extends Addons
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件安装方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function install()
|
||||||
|
{
|
||||||
|
$menu = [
|
||||||
|
[
|
||||||
|
'name' => 'general/database',
|
||||||
|
'title' => '数据库管理',
|
||||||
|
'icon' => 'fa fa-database',
|
||||||
|
'remark' => '可进行一些简单的数据库表优化或修复,查看表结构和数据,也可以进行SQL语句的操作',
|
||||||
|
'sublist' => [
|
||||||
|
['name' => 'general/database/index', 'title' => '查看'],
|
||||||
|
['name' => 'general/database/query', 'title' => '查询'],
|
||||||
|
['name' => 'general/database/backup', 'title' => '备份'],
|
||||||
|
['name' => 'general/database/restore', 'title' => '恢复'],
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
Menu::create($menu, 'general');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件卸载方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function uninstall()
|
||||||
|
{
|
||||||
|
|
||||||
|
Menu::delete('general/database');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件启用方法
|
||||||
|
*/
|
||||||
|
public function enable()
|
||||||
|
{
|
||||||
|
Menu::enable('general/database');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件禁用方法
|
||||||
|
*/
|
||||||
|
public function disable()
|
||||||
|
{
|
||||||
|
Menu::disable('general/database');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
43
addons/database/config.php
Normal file
43
addons/database/config.php
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return array(
|
||||||
|
array(
|
||||||
|
'name' => 'backupDir',
|
||||||
|
'title' => '备份存放目录',
|
||||||
|
'type' => 'string',
|
||||||
|
'content' =>
|
||||||
|
array(),
|
||||||
|
'value' => '../data/',
|
||||||
|
'rule' => 'required',
|
||||||
|
'msg' => '',
|
||||||
|
'tip' => '备份目录,请使用相对目录',
|
||||||
|
'ok' => '',
|
||||||
|
'extend' => '',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'name' => 'backupIgnoreTables',
|
||||||
|
'title' => '备份忽略的表',
|
||||||
|
'type' => 'string',
|
||||||
|
'content' =>
|
||||||
|
array(),
|
||||||
|
'value' => 'fa_admin_log',
|
||||||
|
'rule' => '',
|
||||||
|
'msg' => '',
|
||||||
|
'tip' => '忽略备份的表,多个表以,进行分隔',
|
||||||
|
'ok' => '',
|
||||||
|
'extend' => '',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'name' => '__tips__',
|
||||||
|
'title' => '温馨提示',
|
||||||
|
'type' => '',
|
||||||
|
'content' =>
|
||||||
|
array(),
|
||||||
|
'value' => '请做好数据库离线备份工作,建议此插件仅用于开发阶段,项目正式上线建议卸载此插件',
|
||||||
|
'rule' => '',
|
||||||
|
'msg' => '',
|
||||||
|
'tip' => '',
|
||||||
|
'ok' => '',
|
||||||
|
'extend' => '',
|
||||||
|
),
|
||||||
|
);
|
16
addons/database/controller/Index.php
Normal file
16
addons/database/controller/Index.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addons\database\controller;
|
||||||
|
|
||||||
|
use think\addons\Controller;
|
||||||
|
|
||||||
|
class Index extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$this->error("当前插件暂无前台页面");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
10
addons/database/info.ini
Normal file
10
addons/database/info.ini
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
name = database
|
||||||
|
title = 数据库管理
|
||||||
|
intro = 数据库管理插件
|
||||||
|
author = FastAdmin
|
||||||
|
website = https://www.fastadmin.net
|
||||||
|
version = 1.0.12
|
||||||
|
state = 1
|
||||||
|
url = /addons/database
|
||||||
|
license = regular
|
||||||
|
licenseto = 22920
|
219
addons/database/library/Backup.php
Normal file
219
addons/database/library/Backup.php
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addons\database\library;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use fast\Random;
|
||||||
|
use PDO;
|
||||||
|
use ZipArchive;
|
||||||
|
|
||||||
|
class Backup
|
||||||
|
{
|
||||||
|
|
||||||
|
private $host = '';
|
||||||
|
private $user = '';
|
||||||
|
private $name = '';
|
||||||
|
private $pass = '';
|
||||||
|
private $port = '';
|
||||||
|
private $tables = ['*'];
|
||||||
|
private $ignoreTables = [];
|
||||||
|
private $db;
|
||||||
|
private $ds = "\n";
|
||||||
|
|
||||||
|
public function __construct($host = null, $user = null, $name = null, $pass = null, $port = 3306)
|
||||||
|
{
|
||||||
|
if ($host !== null) {
|
||||||
|
$this->host = $host;
|
||||||
|
$this->name = $name;
|
||||||
|
$this->port = $port;
|
||||||
|
$this->pass = $pass;
|
||||||
|
$this->user = $user;
|
||||||
|
}
|
||||||
|
$this->db = new PDO('mysql:host=' . $this->host . ';dbname=' . $this->name . '; port=' . $port, $this->user, $this->pass, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
|
||||||
|
|
||||||
|
$this->db->exec('SET NAMES "utf8"');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置备份表
|
||||||
|
* @param $table
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setTable($table)
|
||||||
|
{
|
||||||
|
if ($table) {
|
||||||
|
$this->tables = is_array($table) ? $table : explode(',', $table);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置忽略备份的表
|
||||||
|
* @param $table
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setIgnoreTable($table)
|
||||||
|
{
|
||||||
|
if ($table) {
|
||||||
|
$this->ignoreTables = is_array($table) ? $table : explode(',', preg_replace('/\s+/', '', $table));
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function backup($backUpdir = 'download/')
|
||||||
|
{
|
||||||
|
$sql = $this->_init();
|
||||||
|
$zip = new ZipArchive();
|
||||||
|
$date = date('YmdHis');
|
||||||
|
if (!is_dir($backUpdir)) {
|
||||||
|
@mkdir($backUpdir, 0755);
|
||||||
|
}
|
||||||
|
$name = "backup-{$this->name}-{$date}-" . Random::alnum(6);
|
||||||
|
$filename = $backUpdir . $name . ".zip";
|
||||||
|
|
||||||
|
if ($zip->open($filename, ZIPARCHIVE::CREATE) !== true) {
|
||||||
|
throw new Exception("Could not open <$filename>\n");
|
||||||
|
}
|
||||||
|
$zip->addFromString($name . ".sql", $sql);
|
||||||
|
$zip->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function _init()
|
||||||
|
{
|
||||||
|
# COUNT
|
||||||
|
$ct = 0;
|
||||||
|
# CONTENT
|
||||||
|
$sqldump = '';
|
||||||
|
# COPYRIGHT & OPTIONS
|
||||||
|
$sqldump .= "-- SQL Dump by Erik Edgren\n";
|
||||||
|
$sqldump .= "-- version 1.0\n";
|
||||||
|
$sqldump .= "--\n";
|
||||||
|
$sqldump .= "-- SQL Dump created: " . date('F jS, Y \@ g:i a') . "\n\n";
|
||||||
|
$sqldump .= "SET SQL_MODE=\"NO_AUTO_VALUE_ON_ZERO\";";
|
||||||
|
$sqldump .= "\n\n\n\n-- --------------------------------------------------------\n\n\n\n";
|
||||||
|
$tables = $this->db->query("SHOW FULL TABLES WHERE Table_Type != 'VIEW'");
|
||||||
|
# LOOP: Get the tables
|
||||||
|
foreach ($tables AS $table) {
|
||||||
|
// 忽略表
|
||||||
|
if (in_array($table[0], $this->ignoreTables)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
# COUNT
|
||||||
|
$ct++;
|
||||||
|
/** ** ** ** ** **/
|
||||||
|
# DATABASE: Count the rows in each tables
|
||||||
|
$count_rows = $this->db->prepare("SELECT * FROM `" . $table[0] . "`");
|
||||||
|
$count_rows->execute();
|
||||||
|
$c_rows = $count_rows->columnCount();
|
||||||
|
# DATABASE: Count the columns in each tables
|
||||||
|
$count_columns = $this->db->prepare("SELECT COUNT(*) FROM `" . $table[0] . "`");
|
||||||
|
$count_columns->execute();
|
||||||
|
$c_columns = $count_columns->fetchColumn();
|
||||||
|
/** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **/
|
||||||
|
# MYSQL DUMP: Remove tables if they exists
|
||||||
|
$sqldump .= "--\n";
|
||||||
|
$sqldump .= "-- Remove the table if it exists\n";
|
||||||
|
$sqldump .= "--\n\n";
|
||||||
|
$sqldump .= "DROP TABLE IF EXISTS `" . $table[0] . "`;\n\n\n";
|
||||||
|
/** ** ** ** ** **/
|
||||||
|
# MYSQL DUMP: Create table if they do not exists
|
||||||
|
$sqldump .= "--\n";
|
||||||
|
$sqldump .= "-- Create the table if it not exists\n";
|
||||||
|
$sqldump .= "--\n\n";
|
||||||
|
# LOOP: Get the fields for the table
|
||||||
|
foreach ($this->db->query("SHOW CREATE TABLE `" . $table[0] . "`") AS $field) {
|
||||||
|
$sqldump .= str_replace('CREATE TABLE', 'CREATE TABLE IF NOT EXISTS', $field['Create Table']);
|
||||||
|
}
|
||||||
|
# MYSQL DUMP: New rows
|
||||||
|
$sqldump .= ";\n\n\n";
|
||||||
|
/** ** ** ** ** **/
|
||||||
|
# CHECK: There are one or more columns
|
||||||
|
if ($c_columns != 0) {
|
||||||
|
# MYSQL DUMP: List the data for each table
|
||||||
|
$sqldump .= "--\n";
|
||||||
|
$sqldump .= "-- List the data for the table\n";
|
||||||
|
$sqldump .= "--\n\n";
|
||||||
|
# MYSQL DUMP: Insert into each table
|
||||||
|
$sqldump .= "INSERT INTO `" . $table[0] . "` (";
|
||||||
|
# ARRAY
|
||||||
|
$rows = [];
|
||||||
|
$numeric = [];
|
||||||
|
# LOOP: Get the tables
|
||||||
|
foreach ($this->db->query("DESCRIBE `" . $table[0] . "`") AS $row) {
|
||||||
|
$rows[] = "`" . $row[0] . "`";
|
||||||
|
$numeric[] = (bool)preg_match('#^[^(]*(BYTE|COUNTER|SERIAL|INT|LONG$|CURRENCY|REAL|MONEY|FLOAT|DOUBLE|DECIMAL|NUMERIC|NUMBER)#i', $row[1]);
|
||||||
|
}
|
||||||
|
$sqldump .= implode(', ', $rows);
|
||||||
|
$sqldump .= ") VALUES\n";
|
||||||
|
# COUNT
|
||||||
|
$c = 0;
|
||||||
|
# LOOP: Get the tables
|
||||||
|
foreach ($this->db->query("SELECT * FROM `" . $table[0] . "`") AS $data) {
|
||||||
|
# COUNT
|
||||||
|
$c++;
|
||||||
|
/** ** ** ** ** **/
|
||||||
|
$sqldump .= "(";
|
||||||
|
# ARRAY
|
||||||
|
$cdata = [];
|
||||||
|
# LOOP
|
||||||
|
for ($i = 0; $i < $c_rows; $i++) {
|
||||||
|
$value = $data[$i];
|
||||||
|
|
||||||
|
if (is_null($value)) {
|
||||||
|
$cdata[] = "NULL";
|
||||||
|
} elseif ($numeric[$i]) {
|
||||||
|
$cdata[] = $value;
|
||||||
|
} else {
|
||||||
|
$cdata[] = $this->db->quote($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$sqldump .= implode(', ', $cdata);
|
||||||
|
$sqldump .= ")";
|
||||||
|
$sqldump .= ($c % 600 != 0 ? ($c_columns != $c ? ',' : ';') : '');
|
||||||
|
# CHECK
|
||||||
|
if ($c % 600 == 0) {
|
||||||
|
$sqldump .= ";\n\n";
|
||||||
|
} else {
|
||||||
|
$sqldump .= "\n";
|
||||||
|
}
|
||||||
|
# CHECK
|
||||||
|
if ($c % 600 == 0) {
|
||||||
|
$sqldump .= "INSERT INTO `" . $table[0] . "`(";
|
||||||
|
# ARRAY
|
||||||
|
$rows = [];
|
||||||
|
# LOOP: Get the tables
|
||||||
|
foreach ($this->db->query("DESCRIBE `" . $table[0] . "`") AS $row) {
|
||||||
|
$rows[] = "`" . $row[0] . "`";
|
||||||
|
}
|
||||||
|
$sqldump .= implode(', ', $rows);
|
||||||
|
$sqldump .= ") VALUES\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$sqldump .= "\n\n\n";
|
||||||
|
// Backup views
|
||||||
|
$tables = $this->db->query("SHOW FULL TABLES WHERE Table_Type = 'VIEW'");
|
||||||
|
# LOOP: Get the tables
|
||||||
|
foreach ($tables AS $table) {
|
||||||
|
// 忽略表
|
||||||
|
if (in_array($table[0], $this->ignoreTables)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach ($this->db->query("SHOW CREATE VIEW `" . $table[0] . "`") AS $field) {
|
||||||
|
$sqldump .= "--\n";
|
||||||
|
$sqldump .= "-- Remove the view if it exists\n";
|
||||||
|
$sqldump .= "--\n\n";
|
||||||
|
$sqldump .= "DROP VIEW IF EXISTS `{$field[0]}`;\n\n";
|
||||||
|
$sqldump .= "--\n";
|
||||||
|
$sqldump .= "-- Create the view if it not exists\n";
|
||||||
|
$sqldump .= "--\n\n";
|
||||||
|
$sqldump .= "{$field[1]};\n\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $sqldump;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
1
addons/editable/.addonrc
Normal file
1
addons/editable/.addonrc
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"files":["public\\assets\\addons\\editable\\css\\bootstrap-editable.css","public\\assets\\addons\\editable\\img\\clear.png","public\\assets\\addons\\editable\\img\\loading.gif","public\\assets\\addons\\editable\\js\\bootstrap-editable.min.js"],"license":"regular","licenseto":"22920","licensekey":"NWTku40HXfrzsE9n Wm2hNICyxOekQTnpoFQU+Q==","domains":["des8.com"],"licensecodes":[],"validations":["b020d8667fd8c7d4326fcf512c7a10aa"]}
|
54
addons/editable/Editable.php
Normal file
54
addons/editable/Editable.php
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addons\editable;
|
||||||
|
|
||||||
|
use app\common\library\Menu;
|
||||||
|
use think\Addons;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件
|
||||||
|
*/
|
||||||
|
class Editable extends Addons
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件安装方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function install()
|
||||||
|
{
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件卸载方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function uninstall()
|
||||||
|
{
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件启用方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function enable()
|
||||||
|
{
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件禁用方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function disable()
|
||||||
|
{
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
26
addons/editable/bootstrap.js
vendored
Normal file
26
addons/editable/bootstrap.js
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
require.config({
|
||||||
|
paths: {
|
||||||
|
'editable': '../libs/bootstrap-table/dist/extensions/editable/bootstrap-table-editable.min',
|
||||||
|
'x-editable': '../addons/editable/js/bootstrap-editable.min',
|
||||||
|
},
|
||||||
|
shim: {
|
||||||
|
'editable': {
|
||||||
|
deps: ['x-editable', 'bootstrap-table']
|
||||||
|
},
|
||||||
|
"x-editable": {
|
||||||
|
deps: ["css!../addons/editable/css/bootstrap-editable.css"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if ($("table.table").size() > 0) {
|
||||||
|
require(['editable', 'table'], function (Editable, Table) {
|
||||||
|
$.fn.bootstrapTable.defaults.onEditableSave = function (field, row, oldValue, $el) {
|
||||||
|
var data = {};
|
||||||
|
data["row[" + field + "]"] = row[field];
|
||||||
|
Fast.api.ajax({
|
||||||
|
url: this.extend.edit_url + "/ids/" + row[this.pk],
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
5
addons/editable/config.php
Normal file
5
addons/editable/config.php
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
];
|
15
addons/editable/controller/Index.php
Normal file
15
addons/editable/controller/Index.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addons\editable\controller;
|
||||||
|
|
||||||
|
use think\addons\Controller;
|
||||||
|
|
||||||
|
class Index extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$this->error("当前插件暂无前台页面");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
10
addons/editable/info.ini
Normal file
10
addons/editable/info.ini
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
name = editable
|
||||||
|
title = 无刷新表格行内编辑
|
||||||
|
intro = 基于x-editable的无刷新表格行内编辑
|
||||||
|
author = Karson
|
||||||
|
website = https://www.fastadmin.net
|
||||||
|
version = 1.0.0
|
||||||
|
state = 1
|
||||||
|
url = /addons/editable
|
||||||
|
license = regular
|
||||||
|
licenseto = 22920
|
1
addons/example/.addonrc
Normal file
1
addons/example/.addonrc
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"files":["application\\admin\\controller\\example\\Baidumap.php","application\\admin\\controller\\example\\Bootstraptable.php","application\\admin\\controller\\example\\Colorbadge.php","application\\admin\\controller\\example\\Controllerjump.php","application\\admin\\controller\\example\\Customform.php","application\\admin\\controller\\example\\Customsearch.php","application\\admin\\controller\\example\\Cxselect.php","application\\admin\\controller\\example\\Echarts.php","application\\admin\\controller\\example\\Multitable.php","application\\admin\\controller\\example\\Relationmodel.php","application\\admin\\controller\\example\\Tablelink.php","application\\admin\\controller\\example\\Tabletemplate.php","application\\admin\\model\\Area.php","application\\admin\\view\\example\\baidumap\\index.html","application\\admin\\view\\example\\baidumap\\map.html","application\\admin\\view\\example\\bootstraptable\\detail.html","application\\admin\\view\\example\\bootstraptable\\edit.html","application\\admin\\view\\example\\bootstraptable\\index.html","application\\admin\\view\\example\\colorbadge\\index.html","application\\admin\\view\\example\\controllerjump\\index.html","application\\admin\\view\\example\\customform\\index.html","application\\admin\\view\\example\\customsearch\\index.html","application\\admin\\view\\example\\cxselect\\index.html","application\\admin\\view\\example\\echarts\\index.html","application\\admin\\view\\example\\multitable\\index.html","application\\admin\\view\\example\\relationmodel\\index.html","application\\admin\\view\\example\\tablelink\\index.html","application\\admin\\view\\example\\tabletemplate\\index.html","public\\assets\\js\\backend\\example\\baidumap.js","public\\assets\\js\\backend\\example\\bootstraptable.js","public\\assets\\js\\backend\\example\\colorbadge.js","public\\assets\\js\\backend\\example\\controllerjump.js","public\\assets\\js\\backend\\example\\customform.js","public\\assets\\js\\backend\\example\\customsearch.js","public\\assets\\js\\backend\\example\\cxselect.js","public\\assets\\js\\backend\\example\\echarts.js","public\\assets\\js\\backend\\example\\multitable.js","public\\assets\\js\\backend\\example\\relationmodel.js","public\\assets\\js\\backend\\example\\tablelink.js","public\\assets\\js\\backend\\example\\tabletemplate.js","public\\assets\\addons\\example\\css\\common.css","public\\assets\\addons\\example\\img\\plus.png","public\\assets\\addons\\example\\js\\async.js"],"license":"regular","licenseto":"22920","licensekey":"mDdxTckJCZS0VYLq AtRG6RpaMwBiYTBXBPSbxw==","domains":["des8.com"],"licensecodes":[],"validations":["b020d8667fd8c7d4326fcf512c7a10aa"],"menus":["example","example\/bootstraptable","example\/bootstraptable\/index","example\/bootstraptable\/detail","example\/bootstraptable\/change","example\/bootstraptable\/del","example\/bootstraptable\/multi","example\/customsearch","example\/customsearch\/index","example\/customsearch\/del","example\/customsearch\/multi","example\/customform","example\/customform\/index","example\/tablelink","example\/tablelink\/index","example\/colorbadge","example\/colorbadge\/index","example\/colorbadge\/del","example\/colorbadge\/multi","example\/controllerjump","example\/controllerjump\/index","example\/controllerjump\/del","example\/controllerjump\/multi","example\/cxselect","example\/cxselect\/index","example\/cxselect\/del","example\/cxselect\/multi","example\/multitable","example\/multitable\/index","example\/multitable\/del","example\/multitable\/multi","example\/relationmodel","example\/relationmodel\/index","example\/relationmodel\/del","example\/relationmodel\/multi","example\/tabletemplate","example\/tabletemplate\/index","example\/tabletemplate\/detail","example\/tabletemplate\/del","example\/tabletemplate\/multi","example\/baidumap","example\/baidumap\/index","example\/baidumap\/map","example\/baidumap\/del","example\/echarts","example\/echarts\/index"]}
|
183
addons/example/Example.php
Normal file
183
addons/example/Example.php
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addons\example;
|
||||||
|
|
||||||
|
use app\common\library\Menu;
|
||||||
|
use think\Addons;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example
|
||||||
|
*/
|
||||||
|
class Example extends Addons
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件安装方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function install()
|
||||||
|
{
|
||||||
|
$menu = [
|
||||||
|
[
|
||||||
|
'name' => 'example',
|
||||||
|
'title' => '开发示例管理',
|
||||||
|
'icon' => 'fa fa-magic',
|
||||||
|
'sublist' => [
|
||||||
|
[
|
||||||
|
'name' => 'example/bootstraptable',
|
||||||
|
'title' => '表格完整示例',
|
||||||
|
'icon' => 'fa fa-table',
|
||||||
|
'sublist' => [
|
||||||
|
['name' => 'example/bootstraptable/index', 'title' => '查看'],
|
||||||
|
['name' => 'example/bootstraptable/detail', 'title' => '详情'],
|
||||||
|
['name' => 'example/bootstraptable/change', 'title' => '变更'],
|
||||||
|
['name' => 'example/bootstraptable/del', 'title' => '删除'],
|
||||||
|
['name' => 'example/bootstraptable/multi', 'title' => '批量更新'],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'example/customsearch',
|
||||||
|
'title' => '自定义搜索',
|
||||||
|
'icon' => 'fa fa-table',
|
||||||
|
'sublist' => [
|
||||||
|
['name' => 'example/customsearch/index', 'title' => '查看'],
|
||||||
|
['name' => 'example/customsearch/del', 'title' => '删除'],
|
||||||
|
['name' => 'example/customsearch/multi', 'title' => '批量更新'],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'example/customform',
|
||||||
|
'title' => '自定义表单示例',
|
||||||
|
'icon' => 'fa fa-edit',
|
||||||
|
'sublist' => [
|
||||||
|
['name' => 'example/customform/index', 'title' => '查看'],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'example/tablelink',
|
||||||
|
'title' => '表格联动示例',
|
||||||
|
'icon' => 'fa fa-table',
|
||||||
|
'remark' => '点击左侧日志列表,右侧的表格数据会显示指定管理员的日志列表',
|
||||||
|
'sublist' => [
|
||||||
|
['name' => 'example/tablelink/index', 'title' => '查看'],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'example/colorbadge',
|
||||||
|
'title' => '彩色角标',
|
||||||
|
'icon' => 'fa fa-table',
|
||||||
|
'remark' => '左侧彩色的角标会根据当前数据量的大小进行更新',
|
||||||
|
'sublist' => [
|
||||||
|
['name' => 'example/colorbadge/index', 'title' => '查看'],
|
||||||
|
['name' => 'example/colorbadge/del', 'title' => '删除'],
|
||||||
|
['name' => 'example/colorbadge/multi', 'title' => '批量更新'],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'example/controllerjump',
|
||||||
|
'title' => '控制器间跳转',
|
||||||
|
'icon' => 'fa fa-table',
|
||||||
|
'remark' => '点击IP地址可以跳转到新的选项卡中查看指定IP的数据',
|
||||||
|
'sublist' => [
|
||||||
|
['name' => 'example/controllerjump/index', 'title' => '查看'],
|
||||||
|
['name' => 'example/controllerjump/del', 'title' => '删除'],
|
||||||
|
['name' => 'example/controllerjump/multi', 'title' => '批量更新'],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'example/cxselect',
|
||||||
|
'title' => '多级联动',
|
||||||
|
'icon' => 'fa fa-table',
|
||||||
|
'remark' => '基于jquery.cxselect实现的多级联动',
|
||||||
|
'sublist' => [
|
||||||
|
['name' => 'example/cxselect/index', 'title' => '查看'],
|
||||||
|
['name' => 'example/cxselect/del', 'title' => '删除'],
|
||||||
|
['name' => 'example/cxselect/multi', 'title' => '批量更新'],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'example/multitable',
|
||||||
|
'title' => '多表格示例',
|
||||||
|
'icon' => 'fa fa-table',
|
||||||
|
'remark' => '展示在一个页面显示多个Bootstrap-table表格',
|
||||||
|
'sublist' => [
|
||||||
|
['name' => 'example/multitable/index', 'title' => '查看'],
|
||||||
|
['name' => 'example/multitable/del', 'title' => '删除'],
|
||||||
|
['name' => 'example/multitable/multi', 'title' => '批量更新'],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'example/relationmodel',
|
||||||
|
'title' => '关联模型示例',
|
||||||
|
'icon' => 'fa fa-table',
|
||||||
|
'remark' => '列表中的头像、用户名和昵称字段均从关联表中取出',
|
||||||
|
'sublist' => [
|
||||||
|
['name' => 'example/relationmodel/index', 'title' => '查看'],
|
||||||
|
['name' => 'example/relationmodel/del', 'title' => '删除'],
|
||||||
|
['name' => 'example/relationmodel/multi', 'title' => '批量更新'],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'example/tabletemplate',
|
||||||
|
'title' => '表格模板示例',
|
||||||
|
'icon' => 'fa fa-table',
|
||||||
|
'remark' => '',
|
||||||
|
'sublist' => [
|
||||||
|
['name' => 'example/tabletemplate/index', 'title' => '查看'],
|
||||||
|
['name' => 'example/tabletemplate/detail', 'title' => '详情'],
|
||||||
|
['name' => 'example/tabletemplate/del', 'title' => '删除'],
|
||||||
|
['name' => 'example/tabletemplate/multi', 'title' => '批量更新'],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'example/baidumap',
|
||||||
|
'title' => '百度地图示例',
|
||||||
|
'icon' => 'fa fa-map-pin',
|
||||||
|
'sublist' => [
|
||||||
|
['name' => 'example/baidumap/index', 'title' => '查看'],
|
||||||
|
['name' => 'example/baidumap/map', 'title' => '详情'],
|
||||||
|
['name' => 'example/baidumap/del', 'title' => '删除'],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'example/echarts',
|
||||||
|
'title' => '统计图表示例',
|
||||||
|
'icon' => 'fa fa-bar-chart',
|
||||||
|
'sublist' => [
|
||||||
|
['name' => 'example/echarts/index', 'title' => '查看'],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
Menu::create($menu);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件卸载方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function uninstall()
|
||||||
|
{
|
||||||
|
Menu::delete('example');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件启用方法
|
||||||
|
*/
|
||||||
|
public function enable()
|
||||||
|
{
|
||||||
|
Menu::enable('example');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件禁用方法
|
||||||
|
*/
|
||||||
|
public function disable()
|
||||||
|
{
|
||||||
|
Menu::disable('example');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
12
addons/example/bootstrap.js
vendored
Normal file
12
addons/example/bootstrap.js
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
require.config({
|
||||||
|
paths: {
|
||||||
|
'async': '../addons/example/js/async',
|
||||||
|
'BMap': ['//api.map.baidu.com/api?v=2.0&ak='],
|
||||||
|
},
|
||||||
|
shim: {
|
||||||
|
'BMap': {
|
||||||
|
deps: ['jquery'],
|
||||||
|
exports: 'BMap'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
130
addons/example/config.php
Normal file
130
addons/example/config.php
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'name' => 'condition1',
|
||||||
|
'title' => '条件1',
|
||||||
|
'type' => 'radio',
|
||||||
|
'group' => '选项组一',
|
||||||
|
'content' => [
|
||||||
|
'value1' => '值1',
|
||||||
|
'value2' => '值2',
|
||||||
|
],
|
||||||
|
'value' => 'value2',
|
||||||
|
'rule' => 'required',
|
||||||
|
'msg' => '',
|
||||||
|
'tip' => '',
|
||||||
|
'ok' => '',
|
||||||
|
'extend' => '',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'condition2',
|
||||||
|
'title' => '条件2',
|
||||||
|
'type' => 'checkbox',
|
||||||
|
'group' => '选项组一',
|
||||||
|
'visible' => 'condition1=value1',
|
||||||
|
'content' => [
|
||||||
|
'value1' => '值1',
|
||||||
|
'value2' => '值2',
|
||||||
|
'value3' => '值3',
|
||||||
|
],
|
||||||
|
'value' => 'value1,value2',
|
||||||
|
'rule' => 'required',
|
||||||
|
'msg' => '',
|
||||||
|
'tip' => '',
|
||||||
|
'ok' => '',
|
||||||
|
'extend' => '',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'condition3',
|
||||||
|
'title' => '条件3',
|
||||||
|
'type' => 'select',
|
||||||
|
'group' => '选项组一',
|
||||||
|
'visible' => 'condition1=value2',
|
||||||
|
'content' => [
|
||||||
|
'value1' => '值1',
|
||||||
|
'value2' => '值2',
|
||||||
|
],
|
||||||
|
'value' => 'value1',
|
||||||
|
'rule' => 'required',
|
||||||
|
'msg' => '',
|
||||||
|
'tip' => '',
|
||||||
|
'ok' => '',
|
||||||
|
'extend' => '',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'condition4',
|
||||||
|
'title' => '条件4',
|
||||||
|
'type' => 'selects',
|
||||||
|
'group' => '选项组一',
|
||||||
|
'content' => [
|
||||||
|
'value1' => '值1',
|
||||||
|
'value2' => '值2',
|
||||||
|
'value3' => '值3',
|
||||||
|
],
|
||||||
|
'value' => 'value1,value2',
|
||||||
|
'rule' => 'required',
|
||||||
|
'msg' => '',
|
||||||
|
'tip' => '',
|
||||||
|
'ok' => '',
|
||||||
|
'extend' => '',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'title',
|
||||||
|
'title' => '标题',
|
||||||
|
'type' => 'string',
|
||||||
|
'group' => '选项组一',
|
||||||
|
'visible' => 'condition3=value1',
|
||||||
|
'content' => [],
|
||||||
|
'value' => '3x',
|
||||||
|
'rule' => 'required',
|
||||||
|
'msg' => '',
|
||||||
|
'tip' => '',
|
||||||
|
'ok' => '',
|
||||||
|
'extend' => '',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'domain',
|
||||||
|
'title' => '绑定二级域名前缀',
|
||||||
|
'type' => 'string',
|
||||||
|
'group' => '选项组二',
|
||||||
|
'content' => [],
|
||||||
|
'value' => '',
|
||||||
|
'rule' => 'required',
|
||||||
|
'msg' => '',
|
||||||
|
'tip' => '',
|
||||||
|
'ok' => '',
|
||||||
|
'extend' => '',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'rewrite',
|
||||||
|
'title' => '伪静态',
|
||||||
|
'type' => 'array',
|
||||||
|
'group' => '选项组二',
|
||||||
|
'content' => [],
|
||||||
|
'value' => [
|
||||||
|
'index/index' => '/example$',
|
||||||
|
'demo/index' => '/example/d/[:name]',
|
||||||
|
'demo/demo1' => '/example/d1/[:name]',
|
||||||
|
'demo/demo2' => '/example/d2/[:name]',
|
||||||
|
],
|
||||||
|
'rule' => 'required',
|
||||||
|
'msg' => '',
|
||||||
|
'tip' => '',
|
||||||
|
'ok' => '',
|
||||||
|
'extend' => '',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => '__tips__',
|
||||||
|
'title' => '温馨提示',
|
||||||
|
'type' => 'string',
|
||||||
|
'content' => [
|
||||||
|
],
|
||||||
|
'value' => '这里是提示的文本内容',
|
||||||
|
'rule' => '',
|
||||||
|
'msg' => '',
|
||||||
|
'tip' => '',
|
||||||
|
'ok' => '',
|
||||||
|
'extend' => '',
|
||||||
|
],
|
||||||
|
];
|
32
addons/example/controller/Demo.php
Normal file
32
addons/example/controller/Demo.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addons\example\controller;
|
||||||
|
|
||||||
|
use think\addons\Controller;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试控制器
|
||||||
|
*/
|
||||||
|
class Demo extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
protected $layout = 'default';
|
||||||
|
protected $noNeedLogin = ['index', 'demo1'];
|
||||||
|
protected $noNeedRight = ['*'];
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
return $this->view->fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function demo1()
|
||||||
|
{
|
||||||
|
return $this->view->fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function demo2()
|
||||||
|
{
|
||||||
|
return $this->view->fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
17
addons/example/controller/Index.php
Normal file
17
addons/example/controller/Index.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addons\example\controller;
|
||||||
|
|
||||||
|
use think\addons\Controller;
|
||||||
|
|
||||||
|
class Index extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
protected $layout = 'default';
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
return $this->view->fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
10
addons/example/info.ini
Normal file
10
addons/example/info.ini
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
name = example
|
||||||
|
title = 开发示例
|
||||||
|
intro = FastAdmin多个开发示例
|
||||||
|
author = FastAdmin
|
||||||
|
website = https://www.fastadmin.net
|
||||||
|
version = 1.1.1
|
||||||
|
state = 1
|
||||||
|
url = /addons/example
|
||||||
|
license = regular
|
||||||
|
licenseto = 22920
|
3795
addons/example/install.sql
Normal file
3795
addons/example/install.sql
Normal file
File diff suppressed because it is too large
Load Diff
33
addons/example/view/demo/demo1.html
Normal file
33
addons/example/view/demo/demo1.html
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<!-- Page Content -->
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<!-- Page Heading/Breadcrumbs -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<h1 class="page-header">无需登录页面
|
||||||
|
<small>开发者示例</small>
|
||||||
|
</h1>
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li><a href="{:addon_url('example/index/index')}">插件首页</a>
|
||||||
|
</li>
|
||||||
|
<li class="active">无需登录页面</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- /.row -->
|
||||||
|
|
||||||
|
<!-- Content Row -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<p class="well">当前登录页面无需登录即可查看,当前请求的name值为:{$Request.param.name|htmlentities}</p>
|
||||||
|
{if $user}
|
||||||
|
<p class="well text-danger">但是如果你登录后可以浏览到这段隐藏的信息</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- /.row -->
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- /.container -->
|
31
addons/example/view/demo/demo2.html
Normal file
31
addons/example/view/demo/demo2.html
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<!-- Page Content -->
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<!-- Page Heading/Breadcrumbs -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<h1 class="page-header">需登录页面
|
||||||
|
<small>开发者示例</small>
|
||||||
|
</h1>
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li><a href="{:addon_url('example/index/index')}">插件首页</a>
|
||||||
|
</li>
|
||||||
|
<li class="active">需登录页面</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- /.row -->
|
||||||
|
|
||||||
|
<!-- Content Row -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<p class="well">当前登录页面需要登录后才可以查看,你可以退出后再访问此页面,会提醒登录,当前请求的name值为:{$Request.param.name|htmlentities}</p>
|
||||||
|
<p class="well">你好!{$user.nickname|htmlentities},<a href="{:url('index/user/logout')}">注销登录</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- /.row -->
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- /.container -->
|
67
addons/example/view/demo/index.html
Normal file
67
addons/example/view/demo/index.html
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<!-- Page Content -->
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<!-- Page Heading/Breadcrumbs -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<h1 class="page-header">使用模板标签和变量
|
||||||
|
<small>开发者示例</small>
|
||||||
|
</h1>
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li><a href="{:addon_url('example/index/index')}">插件首页</a>
|
||||||
|
</li>
|
||||||
|
<li class="active">使用模板标签和变量</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- /.row -->
|
||||||
|
|
||||||
|
<!-- Content Row -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<p class="well">当前请求的name值为:{$Request.param.name|htmlentities}</p>
|
||||||
|
{literal}
|
||||||
|
<pre>
|
||||||
|
在插件视图中可以使用所有ThinkPHP5内支持的模板标签和变量,如
|
||||||
|
|
||||||
|
{$Think.server.script_name} // 输出$_SERVER['SCRIPT_NAME']变量
|
||||||
|
{$Think.session.user_id} // 输出$_SESSION['user_id']变量
|
||||||
|
{$Think.get.pageNumber} // 输出$_GET['pageNumber']变量
|
||||||
|
{$Think.cookie.name} // 输出$_COOKIE['name']变量
|
||||||
|
|
||||||
|
// 调用Request对象的get方法 传入参数为id
|
||||||
|
{$Request.get.id}
|
||||||
|
// 调用Request对象的param方法 传入参数为name
|
||||||
|
{$Request.param.name}
|
||||||
|
// 调用Request对象的param方法 传入参数为user.nickname
|
||||||
|
{$Request.param.user.nickname}
|
||||||
|
// 调用Request对象的root方法
|
||||||
|
{$Request.root}
|
||||||
|
// 调用Request对象的root方法,并且传入参数true
|
||||||
|
{$Request.root.true}
|
||||||
|
// 调用Request对象的path方法
|
||||||
|
{$Request.path}
|
||||||
|
// 调用Request对象的module方法
|
||||||
|
{$Request.module}
|
||||||
|
// 调用Request对象的controller方法
|
||||||
|
{$Request.controller}
|
||||||
|
// 调用Request对象的action方法
|
||||||
|
{$Request.action}
|
||||||
|
// 调用Request对象的ext方法
|
||||||
|
{$Request.ext}
|
||||||
|
// 调用Request对象的host方法
|
||||||
|
{$Request.host}
|
||||||
|
// 调用Request对象的ip方法
|
||||||
|
{$Request.ip}
|
||||||
|
// 调用Request对象的header方法
|
||||||
|
{$Request.header.accept-encoding}
|
||||||
|
</pre>
|
||||||
|
{/literal}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- /.row -->
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- /.container -->
|
111
addons/example/view/index/index.html
Normal file
111
addons/example/view/index/index.html
Normal file
File diff suppressed because one or more lines are too long
124
addons/example/view/layout/default.html
Normal file
124
addons/example/view/layout/default.html
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="description" content="">
|
||||||
|
<meta name="author" content="">
|
||||||
|
|
||||||
|
<title>开发示例 - {$site.name|htmlentities}</title>
|
||||||
|
|
||||||
|
<!-- Bootstrap Core CSS -->
|
||||||
|
<link href="__CDN__/assets/libs/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
|
||||||
|
<!-- Custom CSS -->
|
||||||
|
<link href="__ADDON__/css/common.css" rel="stylesheet">
|
||||||
|
|
||||||
|
<!-- Custom Fonts -->
|
||||||
|
<link href="__CDN__/assets/libs/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
||||||
|
|
||||||
|
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||||
|
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="https://cdn.bootcdn.net/ajax/libs/html5shiv/3.7.0/html5shiv.min.js"></script>
|
||||||
|
<script src="https://cdn.bootcdn.net/ajax/libs/respond.js/1.4.2/respond.min.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- Navigation -->
|
||||||
|
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
|
||||||
|
<div class="container">
|
||||||
|
<!-- Brand and toggle get grouped for better mobile display -->
|
||||||
|
<div class="navbar-header">
|
||||||
|
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
|
||||||
|
<span class="sr-only">Toggle navigation</span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
</button>
|
||||||
|
<a class="navbar-brand" href="{:addon_url('example/index/index')}">{$site.name|htmlentities}</a>
|
||||||
|
</div>
|
||||||
|
<!-- Collect the nav links, forms, and other content for toggling -->
|
||||||
|
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||||
|
<ul class="nav navbar-nav navbar-right">
|
||||||
|
<li>
|
||||||
|
<a href="{:addon_url('example/index/index')}">插件首页</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{:addon_url('example/demo/demo1', [':name'=>'s1'])}">无需登录页面</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{:addon_url('example/demo/demo2', [':name'=>'s2'])}">需登录页面</a>
|
||||||
|
</li>
|
||||||
|
{if $user}
|
||||||
|
<li class="dropdown">
|
||||||
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown">欢迎你! {$user.nickname|htmlentities}<b class="caret"></b></a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li>
|
||||||
|
<a href="{:url('index/user/index')}">会员中心</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{:url('index/user/profile')}">个人资料</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{:url('index/user/logout')}">退出登录</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
{else /}
|
||||||
|
<li class="dropdown">
|
||||||
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown">会员中心 <b class="caret"></b></a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li>
|
||||||
|
<a href="{:url('index/user/login')}">登录</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{:url('index/user/register')}">注册</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
{/if}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<!-- /.navbar-collapse -->
|
||||||
|
</div>
|
||||||
|
<!-- /.container -->
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{__CONTENT__}
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<p>Copyright © {$site.name|htmlentities} 2022</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- /.container -->
|
||||||
|
|
||||||
|
<!-- jQuery -->
|
||||||
|
<script src="__CDN__/assets/libs/jquery/dist/jquery.min.js"></script>
|
||||||
|
|
||||||
|
<!-- Bootstrap Core JavaScript -->
|
||||||
|
<script src="__CDN__/assets/libs/bootstrap/dist/js/bootstrap.min.js"></script>
|
||||||
|
|
||||||
|
<!-- Script to Activate the Carousel -->
|
||||||
|
<script>
|
||||||
|
$('.carousel').carousel({
|
||||||
|
interval: 5000 //changes the speed
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
1
addons/import/.addonrc
Normal file
1
addons/import/.addonrc
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"files":["application\\admin\\controller\\import\\Log.php","application\\admin\\lang\\zh-cn\\import\\log.php","application\\admin\\model\\import\\Log.php","application\\admin\\validate\\import\\Log.php","application\\admin\\view\\import\\log\\add.html","application\\admin\\view\\import\\log\\edit.html","application\\admin\\view\\import\\log\\index.html","public\\assets\\js\\backend\\import\\log.js","public\\assets\\js\\backend\\import\\logxx.js"],"license":"regular","licenseto":"22920","licensekey":"ZfDLtNKBY9j8ACJH qbnuhWnVNAEz6yqmN2cDDg==","domains":["des8.com"],"licensecodes":[],"validations":["b020d8667fd8c7d4326fcf512c7a10aa"],"menus":["import","import\/log","import\/log\/add","import\/log\/edit","import\/log\/preview","import\/log\/index","import\/log\/del","import\/log\/multi"]}
|
73
addons/import/Import.php
Normal file
73
addons/import/Import.php
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addons\import;
|
||||||
|
|
||||||
|
use think\Addons;
|
||||||
|
use app\common\library\Menu;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* import 可视化数据导入辅助
|
||||||
|
*/
|
||||||
|
class Import extends Addons
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 插件安装方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function install()
|
||||||
|
{
|
||||||
|
$menu = [
|
||||||
|
[
|
||||||
|
'name' => 'import/log',
|
||||||
|
'title' => '数据导入',
|
||||||
|
'remark' => '数据导入辅助',
|
||||||
|
'icon' => 'fa fa-file-excel-o',
|
||||||
|
'sublist' => [
|
||||||
|
['name' => 'import/log/index', 'title' => '查看'],
|
||||||
|
['name' => 'import/log/add', 'title' => '添加'],
|
||||||
|
['name' => 'import/log/edit', 'title' => '修改'],
|
||||||
|
['name' => 'import/log/fileData', 'title' => '读取文件数据'],
|
||||||
|
['name' => 'import/log/preview', 'title' => '预览'],
|
||||||
|
['name' => 'import/log/del', 'title' => '删除'],
|
||||||
|
],
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$menu=[];
|
||||||
|
$config_file= ADDON_PATH ."import" . DS.'config'.DS. "menu.php";
|
||||||
|
if (is_file($config_file)) {
|
||||||
|
$menu = include $config_file;
|
||||||
|
}
|
||||||
|
if($menu){
|
||||||
|
Menu::create($menu);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件卸载方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function uninstall()
|
||||||
|
{
|
||||||
|
Menu::delete('import/log');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件启用方法
|
||||||
|
*/
|
||||||
|
public function enable()
|
||||||
|
{
|
||||||
|
Menu::enable('import/log');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件禁用方法
|
||||||
|
*/
|
||||||
|
public function disable()
|
||||||
|
{
|
||||||
|
Menu::disable('import/log');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
28
addons/import/bootstrap.js
vendored
Normal file
28
addons/import/bootstrap.js
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
require([], function () {
|
||||||
|
//绑定data-toggle=importguide属性点击事件
|
||||||
|
|
||||||
|
$(document).on('click', "[data-toggle='importguide']", function () {
|
||||||
|
var that = this;
|
||||||
|
var callback = $(that).data('callback');
|
||||||
|
var table = $(that).data("table") ? $(that).data("table") : "";
|
||||||
|
var update = $(that).data("update") ? $(that).data("update") : 0;
|
||||||
|
var to = $(that).data("to") ? $(that).data("to") : 0;
|
||||||
|
var url = "import/log/add";
|
||||||
|
url += (table) ? '?table=' + table : '';
|
||||||
|
url += (update) ? '&update=' + update : '';
|
||||||
|
url += (to) ? '&to=' + to : '';
|
||||||
|
Fast.api.open(url, $(that).attr('title')?$(that).attr('title'):'导入向导', {
|
||||||
|
area:['95%', '90%'],
|
||||||
|
callback: function (res) {
|
||||||
|
try {
|
||||||
|
//执行回调函数
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
callback.call(that, res);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
20
addons/import/config.php
Normal file
20
addons/import/config.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'name' => 'exclude',
|
||||||
|
'title' => '禁用导入的数据表',
|
||||||
|
'type' => 'text',
|
||||||
|
'content' => [],
|
||||||
|
'value' => 'fa_admin'."\r\n"
|
||||||
|
.'fa_attachment'."\r\n"
|
||||||
|
.'fa_auth_group'."\r\n"
|
||||||
|
.'fa_auth_group_access'."\r\n"
|
||||||
|
.'fa_auth_rule',
|
||||||
|
'rule' => '',
|
||||||
|
'msg' => '',
|
||||||
|
'tip' => '多个分行填列',
|
||||||
|
'ok' => '',
|
||||||
|
'extend' => '',
|
||||||
|
],
|
||||||
|
];
|
6
addons/import/config/cache.php
Normal file
6
addons/import/config/cache.php
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?php
|
||||||
|
return array (
|
||||||
|
'table_name' => 'fa_import_log',
|
||||||
|
'self_path' => '',
|
||||||
|
'update_data' => '',
|
||||||
|
);
|
133
addons/import/config/menu.php
Normal file
133
addons/import/config/menu.php
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* 菜单配置文件
|
||||||
|
*/
|
||||||
|
|
||||||
|
return array (
|
||||||
|
0 =>
|
||||||
|
array (
|
||||||
|
'type' => 'file',
|
||||||
|
'name' => 'import',
|
||||||
|
'title' => '批量导入日志',
|
||||||
|
'icon' => 'fa fa-list',
|
||||||
|
'url' => '',
|
||||||
|
'condition' => '',
|
||||||
|
'remark' => '',
|
||||||
|
'ismenu' => 1,
|
||||||
|
'menutype' => NULL,
|
||||||
|
'extend' => '',
|
||||||
|
'py' => 'pldrrz',
|
||||||
|
'pinyin' => 'piliangdaorurizhi',
|
||||||
|
'sublist' =>
|
||||||
|
array (
|
||||||
|
0 =>
|
||||||
|
array (
|
||||||
|
'type' => 'file',
|
||||||
|
'name' => 'import/log',
|
||||||
|
'title' => '数据导入记录',
|
||||||
|
'icon' => 'fa fa-circle-o',
|
||||||
|
'url' => '',
|
||||||
|
'condition' => '',
|
||||||
|
'remark' => '',
|
||||||
|
'ismenu' => 1,
|
||||||
|
'menutype' => NULL,
|
||||||
|
'extend' => '',
|
||||||
|
'py' => 'sjdrjl',
|
||||||
|
'pinyin' => 'shujudaorujilu',
|
||||||
|
'sublist' =>
|
||||||
|
array (
|
||||||
|
0 =>
|
||||||
|
array (
|
||||||
|
'type' => 'file',
|
||||||
|
'name' => 'import/log/add',
|
||||||
|
'title' => '添加',
|
||||||
|
'icon' => 'fa fa-circle-o',
|
||||||
|
'url' => '',
|
||||||
|
'condition' => '',
|
||||||
|
'remark' => '',
|
||||||
|
'ismenu' => 0,
|
||||||
|
'menutype' => NULL,
|
||||||
|
'extend' => '',
|
||||||
|
'py' => 'tj',
|
||||||
|
'pinyin' => 'tianjia',
|
||||||
|
),
|
||||||
|
1 =>
|
||||||
|
array (
|
||||||
|
'type' => 'file',
|
||||||
|
'name' => 'import/log/edit',
|
||||||
|
'title' => '编辑',
|
||||||
|
'icon' => 'fa fa-circle-o',
|
||||||
|
'url' => '',
|
||||||
|
'condition' => '',
|
||||||
|
'remark' => '',
|
||||||
|
'ismenu' => 0,
|
||||||
|
'menutype' => NULL,
|
||||||
|
'extend' => '',
|
||||||
|
'py' => 'bj',
|
||||||
|
'pinyin' => 'bianji',
|
||||||
|
),
|
||||||
|
2 =>
|
||||||
|
array (
|
||||||
|
'type' => 'file',
|
||||||
|
'name' => 'import/log/preview',
|
||||||
|
'title' => 'Preview',
|
||||||
|
'icon' => 'fa fa-circle-o',
|
||||||
|
'url' => '',
|
||||||
|
'condition' => '',
|
||||||
|
'remark' => '',
|
||||||
|
'ismenu' => 0,
|
||||||
|
'menutype' => NULL,
|
||||||
|
'extend' => '',
|
||||||
|
'py' => 'P',
|
||||||
|
'pinyin' => 'Preview',
|
||||||
|
),
|
||||||
|
3 =>
|
||||||
|
array (
|
||||||
|
'type' => 'file',
|
||||||
|
'name' => 'import/log/index',
|
||||||
|
'title' => '查看',
|
||||||
|
'icon' => 'fa fa-circle-o',
|
||||||
|
'url' => '',
|
||||||
|
'condition' => '',
|
||||||
|
'remark' => '',
|
||||||
|
'ismenu' => 0,
|
||||||
|
'menutype' => NULL,
|
||||||
|
'extend' => '',
|
||||||
|
'py' => 'zk',
|
||||||
|
'pinyin' => 'zhakan',
|
||||||
|
),
|
||||||
|
4 =>
|
||||||
|
array (
|
||||||
|
'type' => 'file',
|
||||||
|
'name' => 'import/log/del',
|
||||||
|
'title' => '删除',
|
||||||
|
'icon' => 'fa fa-circle-o',
|
||||||
|
'url' => '',
|
||||||
|
'condition' => '',
|
||||||
|
'remark' => '',
|
||||||
|
'ismenu' => 0,
|
||||||
|
'menutype' => NULL,
|
||||||
|
'extend' => '',
|
||||||
|
'py' => 'sc',
|
||||||
|
'pinyin' => 'shanchu',
|
||||||
|
),
|
||||||
|
5 =>
|
||||||
|
array (
|
||||||
|
'type' => 'file',
|
||||||
|
'name' => 'import/log/multi',
|
||||||
|
'title' => '批量更新',
|
||||||
|
'icon' => 'fa fa-circle-o',
|
||||||
|
'url' => '',
|
||||||
|
'condition' => '',
|
||||||
|
'remark' => '',
|
||||||
|
'ismenu' => 0,
|
||||||
|
'menutype' => NULL,
|
||||||
|
'extend' => '',
|
||||||
|
'py' => 'plgx',
|
||||||
|
'pinyin' => 'pilianggengxin',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
11
addons/import/info.ini
Normal file
11
addons/import/info.ini
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
name = import
|
||||||
|
title = 数据导入辅助
|
||||||
|
intro = 可视化的数据导入
|
||||||
|
author = King
|
||||||
|
website = https://ask.fastadmin.net/u/7320
|
||||||
|
version = 1.0.4
|
||||||
|
state = 1
|
||||||
|
url = /addons/import
|
||||||
|
first_menu = import
|
||||||
|
license = regular
|
||||||
|
licenseto = 22920
|
15
addons/import/install.sql
Normal file
15
addons/import/install.sql
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `__PREFIX__import_log` (
|
||||||
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`table` varchar(255) NOT NULL DEFAULT '0' COMMENT '目标表',
|
||||||
|
`sheet` int(1) DEFAULT '0' COMMENT '第几个工作表',
|
||||||
|
`row` int(11) DEFAULT NULL COMMENT '从第几行导入',
|
||||||
|
`head_type` enum('comment','name') NOT NULL DEFAULT 'comment' COMMENT '匹配方式',
|
||||||
|
`path` varchar(255) NOT NULL DEFAULT '0' COMMENT '文件路径',
|
||||||
|
`admin_id` int(10) NOT NULL DEFAULT '0' COMMENT '操作员',
|
||||||
|
`createtime` int(10) DEFAULT NULL COMMENT '添加时间',
|
||||||
|
`updatetime` int(10) DEFAULT NULL COMMENT '更新时间',
|
||||||
|
`status` enum('normal','hidden') NOT NULL DEFAULT 'hidden' COMMENT '状态',
|
||||||
|
`type` varchar(50) DEFAULT NULL COMMENT '根据字段更新导入',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=46 DEFAULT CHARSET=utf8 COMMENT='数据导入辅助';
|
||||||
|
|
1
addons/lang/.addonrc
Normal file
1
addons/lang/.addonrc
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"files":["application\\admin\\controller\\Lang.php","application\\admin\\lang\\zh-cn\\lang.php","application\\admin\\model\\Lang.php","application\\admin\\model\\Langa.php","application\\admin\\validate\\Lang.php","application\\admin\\view\\lang\\add.html","application\\admin\\view\\lang\\edit.html","application\\admin\\view\\lang\\index.html","public\\assets\\js\\backend\\lang.js"],"license":"regular","licenseto":"22920","licensekey":"zOXLajelMbxVP3cw blo\/ZlBjfpo0pGZs4VjnXQ==","domains":["des8.com"],"licensecodes":[],"validations":["b020d8667fd8c7d4326fcf512c7a10aa"],"menus":["lang","lang\/index","lang\/add","lang\/del","lang\/edit"]}
|
67
addons/lang/Lang.php
Normal file
67
addons/lang/Lang.php
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addons\lang;
|
||||||
|
|
||||||
|
use app\common\library\Menu;
|
||||||
|
use think\Addons;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件
|
||||||
|
*/
|
||||||
|
class Lang extends Addons
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件安装方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function install()
|
||||||
|
{
|
||||||
|
$menu = [
|
||||||
|
[
|
||||||
|
'name' => 'lang',
|
||||||
|
'title' => '在线语言文件管理',
|
||||||
|
'icon' => 'fa fa-terminal',
|
||||||
|
'sublist' => [
|
||||||
|
['name' => 'lang/index', 'title' => '查看'],
|
||||||
|
['name' => 'lang/add', 'title' => '添加'],
|
||||||
|
['name' => 'lang/del', 'title' => '删除'],
|
||||||
|
['name' => 'lang/edit', 'title' => '修改记录'],
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
Menu::create($menu);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件卸载方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function uninstall()
|
||||||
|
{
|
||||||
|
Menu::delete('lang');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件启用方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function enable()
|
||||||
|
{
|
||||||
|
Menu::enable('lang');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件禁用方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function disable()
|
||||||
|
{
|
||||||
|
Menu::disable('lang');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
40
addons/lang/config.php
Normal file
40
addons/lang/config.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
// [
|
||||||
|
// //配置唯一标识
|
||||||
|
// 'name' => 'usernmae',
|
||||||
|
// //显示的标题
|
||||||
|
// 'title' => '用户名',
|
||||||
|
// //类型
|
||||||
|
// 'type' => 'string',
|
||||||
|
// //数据字典
|
||||||
|
// 'content' => [
|
||||||
|
// ],
|
||||||
|
// //值
|
||||||
|
// 'value' => '',
|
||||||
|
// //验证规则
|
||||||
|
// 'rule' => 'required',
|
||||||
|
// //错误消息
|
||||||
|
// 'msg' => '',
|
||||||
|
// //提示消息
|
||||||
|
// 'tip' => '',
|
||||||
|
// //成功消息
|
||||||
|
// 'ok' => '',
|
||||||
|
// //扩展信息
|
||||||
|
// 'extend' => ''
|
||||||
|
// ],
|
||||||
|
// [
|
||||||
|
// 'name' => 'password',
|
||||||
|
// 'title' => '密码',
|
||||||
|
// 'type' => 'string',
|
||||||
|
// 'content' => [
|
||||||
|
// ],
|
||||||
|
// 'value' => '',
|
||||||
|
// 'rule' => 'required',
|
||||||
|
// 'msg' => '',
|
||||||
|
// 'tip' => '',
|
||||||
|
// 'ok' => '',
|
||||||
|
// 'extend' => ''
|
||||||
|
// ],
|
||||||
|
];
|
15
addons/lang/controller/Index.php
Normal file
15
addons/lang/controller/Index.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addons\lang\controller;
|
||||||
|
|
||||||
|
use think\addons\Controller;
|
||||||
|
|
||||||
|
class Index extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$this->error("当前插件暂无前台页面");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
10
addons/lang/info.ini
Normal file
10
addons/lang/info.ini
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
name = lang
|
||||||
|
title = 语言文件管理
|
||||||
|
intro = 管理lang目录下语言包
|
||||||
|
author = NEKGod
|
||||||
|
website = https://www.fastadmin.net
|
||||||
|
version = 1.0.0
|
||||||
|
state = 1
|
||||||
|
url = /addons/lang
|
||||||
|
license = regular
|
||||||
|
licenseto = 22920
|
9
addons/lang/install.sql
Normal file
9
addons/lang/install.sql
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `__PREFIX__lang` (
|
||||||
|
`id` int(10) NOT NULL AUTO_INCREMENT,
|
||||||
|
`file_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '文件目录',
|
||||||
|
`raw_lang_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '原记录',
|
||||||
|
`lang_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '改记录',
|
||||||
|
`createtime` int(11) DEFAULT NULL COMMENT '修改时间',
|
||||||
|
`status` enum('1','0') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '1' COMMENT '状态:1=成功,0=失败',
|
||||||
|
PRIMARY KEY (`id`) USING BTREE
|
||||||
|
) ENGINE = MyISAM AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
|
1
addons/log/.addonrc
Normal file
1
addons/log/.addonrc
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"files":["application\\admin\\controller\\general\\Logs.php","application\\admin\\library\\Log.php","application\\admin\\view\\general\\logs\\index.html","public\\assets\\js\\backend\\general\\logs.js"],"license":"regular","licenseto":"22920","licensekey":"hHNieCkwotJpKRuW PeN8NGh\/TAMZa+5+w3aIcA==","domains":["des8.com"],"licensecodes":[],"validations":["b020d8667fd8c7d4326fcf512c7a10aa"],"menus":["general\/logs","general\/logs\/index","general\/logs\/del","general\/logs\/detail"]}
|
74
addons/log/Log.php
Normal file
74
addons/log/Log.php
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addons\log;
|
||||||
|
|
||||||
|
use app\common\library\Menu;
|
||||||
|
use think\Addons;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件
|
||||||
|
*/
|
||||||
|
class Log extends Addons
|
||||||
|
{
|
||||||
|
|
||||||
|
// 初始化更改日志级别配置项
|
||||||
|
public function appInit(&$params)
|
||||||
|
{
|
||||||
|
$logConfig = get_addon_config('log');
|
||||||
|
$level = explode(',', $logConfig['level']);
|
||||||
|
config('log.level', $level);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件安装方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function install()
|
||||||
|
{
|
||||||
|
$menu = [
|
||||||
|
[
|
||||||
|
'name' => 'general/logs',
|
||||||
|
'title' => '日志管理',
|
||||||
|
'ismenu' => 1,
|
||||||
|
'icon' => 'fa fa-pied-piper-alt',
|
||||||
|
'sublist' => [
|
||||||
|
['name' => 'general/logs/index', 'title' => '查看'],
|
||||||
|
['name' => 'general/logs/del', 'title' => '删除'],
|
||||||
|
['name' => 'general/logs/detail', 'title' => '详情']
|
||||||
|
],
|
||||||
|
]
|
||||||
|
];
|
||||||
|
Menu::create($menu, 'general');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件卸载方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function uninstall()
|
||||||
|
{
|
||||||
|
Menu::delete('general/logs');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件启用方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function enable()
|
||||||
|
{
|
||||||
|
Menu::enable('general/logs');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件禁用方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function disable()
|
||||||
|
{
|
||||||
|
Menu::disable('general/logs');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
25
addons/log/config.php
Normal file
25
addons/log/config.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return array(
|
||||||
|
0 =>
|
||||||
|
array(
|
||||||
|
'name' => 'level',
|
||||||
|
'title' => '日志级别',
|
||||||
|
'type' => 'selects',
|
||||||
|
'content' =>
|
||||||
|
array(
|
||||||
|
'log' => 'log',
|
||||||
|
'error' => 'error',
|
||||||
|
'notice' => 'notice',
|
||||||
|
'info' => 'info',
|
||||||
|
'debug' => 'debug',
|
||||||
|
'sql' => 'sql',
|
||||||
|
),
|
||||||
|
'value' => 'error',
|
||||||
|
'rule' => 'required',
|
||||||
|
'msg' => '',
|
||||||
|
'tip' => '',
|
||||||
|
'ok' => '',
|
||||||
|
'extend' => '',
|
||||||
|
),
|
||||||
|
);
|
13
addons/log/controller/Index.php
Normal file
13
addons/log/controller/Index.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addons\log\controller;
|
||||||
|
|
||||||
|
use think\addons\Controller;
|
||||||
|
|
||||||
|
class Index extends Controller
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$this->error("当前插件暂无前台页面");
|
||||||
|
}
|
||||||
|
}
|
10
addons/log/info.ini
Normal file
10
addons/log/info.ini
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
name = log
|
||||||
|
title = 日志管理
|
||||||
|
intro = 后台日志管理
|
||||||
|
author = hnh
|
||||||
|
website = https://blog.hnh117.com
|
||||||
|
version = 1.0.4
|
||||||
|
state = 1
|
||||||
|
url = /addons/log
|
||||||
|
license = regular
|
||||||
|
licenseto = 22920
|
1
addons/simditor/.addonrc
Normal file
1
addons/simditor/.addonrc
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"files":["public\\assets\\addons\\simditor\\css\\simditor.min.css","public\\assets\\addons\\simditor\\images\\image.png","public\\assets\\addons\\simditor\\js\\simditor.min.js"],"license":"regular","licenseto":"22920","licensekey":"sxYOTQukbD4WgP1n PUTnr1JgvQnluAA\/TzHirA==","domains":["des8.com"],"licensecodes":[],"validations":["b020d8667fd8c7d4326fcf512c7a10aa"]}
|
31
addons/simditor/Simditor.php
Normal file
31
addons/simditor/Simditor.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addons\simditor;
|
||||||
|
|
||||||
|
use think\Addons;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件
|
||||||
|
*/
|
||||||
|
class Simditor extends Addons
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件安装方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function install()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件卸载方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function uninstall()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
48
addons/simditor/bootstrap.js
vendored
Normal file
48
addons/simditor/bootstrap.js
vendored
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
require.config({
|
||||||
|
paths: {
|
||||||
|
'simditor': '../addons/simditor/js/simditor.min',
|
||||||
|
},
|
||||||
|
shim: {
|
||||||
|
'simditor': [
|
||||||
|
'css!../addons/simditor/css/simditor.min.css'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
require(['form'], function (Form) {
|
||||||
|
var _bindevent = Form.events.bindevent;
|
||||||
|
Form.events.bindevent = function (form) {
|
||||||
|
_bindevent.apply(this, [form]);
|
||||||
|
if ($(".editor", form).size() > 0) {
|
||||||
|
//修改上传的接口调用
|
||||||
|
require(['upload', 'simditor'], function (Upload, Simditor) {
|
||||||
|
var editor, mobileToolbar, toolbar;
|
||||||
|
Simditor.locale = 'zh-CN';
|
||||||
|
Simditor.list = {};
|
||||||
|
toolbar = ['title', 'bold', 'italic', 'underline', 'strikethrough', 'fontScale', 'color', '|', 'ol', 'ul', 'blockquote', 'code', 'table', '|', 'link', 'image', 'hr', '|', 'indent', 'outdent', 'alignment'];
|
||||||
|
mobileToolbar = ["bold", "underline", "strikethrough", "color", "ul", "ol"];
|
||||||
|
$(".editor", form).each(function () {
|
||||||
|
var id = $(this).attr("id");
|
||||||
|
editor = new Simditor({
|
||||||
|
textarea: this,
|
||||||
|
toolbarFloat: false,
|
||||||
|
toolbar: toolbar,
|
||||||
|
pasteImage: true,
|
||||||
|
defaultImage: Config.__CDN__ + '/assets/addons/simditor/images/image.png',
|
||||||
|
upload: {url: '/'}
|
||||||
|
});
|
||||||
|
editor.uploader.on('beforeupload', function (e, file) {
|
||||||
|
Upload.api.send(file.obj, function (data) {
|
||||||
|
var url = Fast.api.cdnurl(data.url);
|
||||||
|
editor.uploader.trigger("uploadsuccess", [file, {success: true, file_path: url}]);
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
editor.on("blur", function () {
|
||||||
|
this.textarea.trigger("blur");
|
||||||
|
});
|
||||||
|
Simditor.list[id] = editor;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
4
addons/simditor/build/build.sh
Normal file
4
addons/simditor/build/build.sh
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
/usr/local/bin/node r.js -o ./js.js name=simditor baseUrl=../src/js out=../assets/js/simditor.min.js
|
||||||
|
/usr/local/bin/node r.js -o ./css.js cssIn=../src/css/simditor.css out=../assets/css/simditor.min.css
|
4
addons/simditor/build/css.js
Normal file
4
addons/simditor/build/css.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
({
|
||||||
|
optimizeCss: "default",
|
||||||
|
optimize: "uglify"
|
||||||
|
})
|
10
addons/simditor/build/js.js
Normal file
10
addons/simditor/build/js.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
({
|
||||||
|
name: "simditor",
|
||||||
|
paths: {
|
||||||
|
'jquery': 'empty:',
|
||||||
|
'simditor': 'simditor',
|
||||||
|
'simple-module': 'module',
|
||||||
|
'simple-uploader': 'uploader',
|
||||||
|
'simple-hotkeys': 'hotkeys',
|
||||||
|
},
|
||||||
|
});
|
27959
addons/simditor/build/r.js
Normal file
27959
addons/simditor/build/r.js
Normal file
File diff suppressed because one or more lines are too long
4
addons/simditor/config.php
Normal file
4
addons/simditor/config.php
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
];
|
10
addons/simditor/info.ini
Normal file
10
addons/simditor/info.ini
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
name = simditor
|
||||||
|
title = Simditor
|
||||||
|
intro = 简洁清晰的富文本插件
|
||||||
|
author = Karson
|
||||||
|
website = http://www.fastadmin.net
|
||||||
|
version = 1.0.5
|
||||||
|
state = 1
|
||||||
|
url = /addons/simditor
|
||||||
|
license = regular
|
||||||
|
licenseto = 22920
|
460
addons/simditor/src/css/mobile.css
Normal file
460
addons/simditor/src/css/mobile.css
Normal file
@ -0,0 +1,460 @@
|
|||||||
|
@media screen and (max-device-width: 240px) and (min-device-width: 220px) {
|
||||||
|
body {
|
||||||
|
width: 240px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
body .wrapper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
body .wrapper header {
|
||||||
|
padding: 30px 0 20px;
|
||||||
|
}
|
||||||
|
body .wrapper header h1 {
|
||||||
|
background-size: 200px auto;
|
||||||
|
background-position: 50px 0;
|
||||||
|
padding-top: 90px;
|
||||||
|
height: 45px;
|
||||||
|
}
|
||||||
|
body .wrapper header h1 a {
|
||||||
|
background-size: 160px auto;
|
||||||
|
background-position: 10px 0;
|
||||||
|
}
|
||||||
|
body .wrapper header p.desc {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
body .wrapper footer {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
body .wrapper #page-demo {
|
||||||
|
width: 96%;
|
||||||
|
margin: 0 2%;
|
||||||
|
}
|
||||||
|
body .wrapper #link-fork {
|
||||||
|
z-index: -1;
|
||||||
|
width: 80px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
body .wrapper #link-fork img {
|
||||||
|
max-width: 80px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (max-device-width: 320px) and (min-device-width: 300px) {
|
||||||
|
body {
|
||||||
|
width: 320px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
body .wrapper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
body .wrapper header {
|
||||||
|
padding: 30px 0 20px;
|
||||||
|
}
|
||||||
|
body .wrapper header h1 {
|
||||||
|
background-size: 200px auto;
|
||||||
|
background-position: 50px 0;
|
||||||
|
padding-top: 90px;
|
||||||
|
height: 45px;
|
||||||
|
}
|
||||||
|
body .wrapper header h1 a {
|
||||||
|
background-size: 160px auto;
|
||||||
|
background-position: 10px 0;
|
||||||
|
}
|
||||||
|
body .wrapper header p.desc {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
body .wrapper footer {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
body .wrapper #page-demo {
|
||||||
|
width: 96%;
|
||||||
|
margin: 0 2%;
|
||||||
|
}
|
||||||
|
body .wrapper #link-fork {
|
||||||
|
z-index: -1;
|
||||||
|
width: 80px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
body .wrapper #link-fork img {
|
||||||
|
max-width: 80px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (max-device-width: 360px) and (min-device-width: 340px) {
|
||||||
|
body {
|
||||||
|
width: 360px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
body .wrapper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
body .wrapper header {
|
||||||
|
padding: 30px 0 20px;
|
||||||
|
}
|
||||||
|
body .wrapper header h1 {
|
||||||
|
background-size: 200px auto;
|
||||||
|
background-position: 50px 0;
|
||||||
|
padding-top: 90px;
|
||||||
|
height: 45px;
|
||||||
|
}
|
||||||
|
body .wrapper header h1 a {
|
||||||
|
background-size: 160px auto;
|
||||||
|
background-position: 10px 0;
|
||||||
|
}
|
||||||
|
body .wrapper header p.desc {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
body .wrapper footer {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
body .wrapper #page-demo {
|
||||||
|
width: 96%;
|
||||||
|
margin: 0 2%;
|
||||||
|
}
|
||||||
|
body .wrapper #link-fork {
|
||||||
|
z-index: -1;
|
||||||
|
width: 80px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
body .wrapper #link-fork img {
|
||||||
|
max-width: 80px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (max-device-width: 480px) and (min-device-width: 460px) {
|
||||||
|
body {
|
||||||
|
width: 480px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
body .wrapper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
body .wrapper header {
|
||||||
|
padding: 30px 0 20px;
|
||||||
|
}
|
||||||
|
body .wrapper header h1 {
|
||||||
|
background-size: 200px auto;
|
||||||
|
background-position: 50px 0;
|
||||||
|
padding-top: 90px;
|
||||||
|
height: 45px;
|
||||||
|
}
|
||||||
|
body .wrapper header h1 a {
|
||||||
|
background-size: 160px auto;
|
||||||
|
background-position: 10px 0;
|
||||||
|
}
|
||||||
|
body .wrapper header p.desc {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
body .wrapper footer {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
body .wrapper #page-demo {
|
||||||
|
width: 96%;
|
||||||
|
margin: 0 2%;
|
||||||
|
}
|
||||||
|
body .wrapper #link-fork {
|
||||||
|
z-index: -1;
|
||||||
|
width: 80px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
body .wrapper #link-fork img {
|
||||||
|
max-width: 80px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (max-device-width: 640px) and (min-device-width: 620px) {
|
||||||
|
body {
|
||||||
|
width: 320px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
body .wrapper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
body .wrapper header {
|
||||||
|
padding: 30px 0 20px;
|
||||||
|
}
|
||||||
|
body .wrapper header h1 {
|
||||||
|
background-size: 200px auto;
|
||||||
|
background-position: 50px 0;
|
||||||
|
padding-top: 90px;
|
||||||
|
height: 45px;
|
||||||
|
}
|
||||||
|
body .wrapper header h1 a {
|
||||||
|
background-size: 160px auto;
|
||||||
|
background-position: 10px 0;
|
||||||
|
}
|
||||||
|
body .wrapper header p.desc {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
body .wrapper footer {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
body .wrapper #page-demo {
|
||||||
|
width: 96%;
|
||||||
|
margin: 0 2%;
|
||||||
|
}
|
||||||
|
body .wrapper #link-fork {
|
||||||
|
z-index: -1;
|
||||||
|
width: 80px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
body .wrapper #link-fork img {
|
||||||
|
max-width: 80px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (max-device-width: 720px) and (min-device-width: 700px) {
|
||||||
|
body {
|
||||||
|
width: 360px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
body .wrapper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
body .wrapper header {
|
||||||
|
padding: 30px 0 20px;
|
||||||
|
}
|
||||||
|
body .wrapper header h1 {
|
||||||
|
background-size: 200px auto;
|
||||||
|
background-position: 50px 0;
|
||||||
|
padding-top: 90px;
|
||||||
|
height: 45px;
|
||||||
|
}
|
||||||
|
body .wrapper header h1 a {
|
||||||
|
background-size: 160px auto;
|
||||||
|
background-position: 10px 0;
|
||||||
|
}
|
||||||
|
body .wrapper header p.desc {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
body .wrapper footer {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
body .wrapper #page-demo {
|
||||||
|
width: 96%;
|
||||||
|
margin: 0 2%;
|
||||||
|
}
|
||||||
|
body .wrapper #link-fork {
|
||||||
|
z-index: -1;
|
||||||
|
width: 80px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
body .wrapper #link-fork img {
|
||||||
|
max-width: 80px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (max-device-width: 800px) and (min-device-width: 780px) {
|
||||||
|
body {
|
||||||
|
width: 400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
body .wrapper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
body .wrapper header {
|
||||||
|
padding: 30px 0 20px;
|
||||||
|
}
|
||||||
|
body .wrapper header h1 {
|
||||||
|
background-size: 200px auto;
|
||||||
|
background-position: 50px 0;
|
||||||
|
padding-top: 90px;
|
||||||
|
height: 45px;
|
||||||
|
}
|
||||||
|
body .wrapper header h1 a {
|
||||||
|
background-size: 160px auto;
|
||||||
|
background-position: 10px 0;
|
||||||
|
}
|
||||||
|
body .wrapper header p.desc {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
body .wrapper footer {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
body .wrapper #page-demo {
|
||||||
|
width: 96%;
|
||||||
|
margin: 0 2%;
|
||||||
|
}
|
||||||
|
body .wrapper #link-fork {
|
||||||
|
z-index: -1;
|
||||||
|
width: 88.8888888889px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
body .wrapper #link-fork img {
|
||||||
|
max-width: 88.8888888889px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (max-device-width: 960px) and (min-device-width: 940px) {
|
||||||
|
body {
|
||||||
|
width: 480px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
body .wrapper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
body .wrapper header {
|
||||||
|
padding: 30px 0 20px;
|
||||||
|
}
|
||||||
|
body .wrapper header h1 {
|
||||||
|
background-size: 200px auto;
|
||||||
|
background-position: 50px 0;
|
||||||
|
padding-top: 90px;
|
||||||
|
height: 45px;
|
||||||
|
}
|
||||||
|
body .wrapper header h1 a {
|
||||||
|
background-size: 160px auto;
|
||||||
|
background-position: 10px 0;
|
||||||
|
}
|
||||||
|
body .wrapper header p.desc {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
body .wrapper footer {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
body .wrapper #page-demo {
|
||||||
|
width: 96%;
|
||||||
|
margin: 0 2%;
|
||||||
|
}
|
||||||
|
body .wrapper #link-fork {
|
||||||
|
z-index: -1;
|
||||||
|
width: 100px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
body .wrapper #link-fork img {
|
||||||
|
max-width: 100px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (max-device-width: 1024px) and (min-device-width: 1004px) {
|
||||||
|
body {
|
||||||
|
width: 512px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
body .wrapper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
body .wrapper header {
|
||||||
|
padding: 30px 0 20px;
|
||||||
|
}
|
||||||
|
body .wrapper header h1 {
|
||||||
|
background-size: 200px auto;
|
||||||
|
background-position: 50px 0;
|
||||||
|
padding-top: 90px;
|
||||||
|
height: 45px;
|
||||||
|
}
|
||||||
|
body .wrapper header h1 a {
|
||||||
|
background-size: 160px auto;
|
||||||
|
background-position: 10px 0;
|
||||||
|
}
|
||||||
|
body .wrapper header p.desc {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
body .wrapper footer {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
body .wrapper #page-demo {
|
||||||
|
width: 96%;
|
||||||
|
margin: 0 2%;
|
||||||
|
}
|
||||||
|
body .wrapper #link-fork {
|
||||||
|
z-index: -1;
|
||||||
|
width: 100px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
body .wrapper #link-fork img {
|
||||||
|
max-width: 100px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (max-device-width: 1280px) and (min-device-width: 1260px) {
|
||||||
|
body {
|
||||||
|
width: 640px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
body .wrapper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
body .wrapper header {
|
||||||
|
padding: 30px 0 20px;
|
||||||
|
}
|
||||||
|
body .wrapper header h1 {
|
||||||
|
background-size: 200px auto;
|
||||||
|
background-position: 50px 0;
|
||||||
|
padding-top: 90px;
|
||||||
|
height: 45px;
|
||||||
|
}
|
||||||
|
body .wrapper header h1 a {
|
||||||
|
background-size: 160px auto;
|
||||||
|
background-position: 10px 0;
|
||||||
|
}
|
||||||
|
body .wrapper header p.desc {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
body .wrapper footer {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
body .wrapper #page-demo {
|
||||||
|
width: 96%;
|
||||||
|
margin: 0 2%;
|
||||||
|
}
|
||||||
|
body .wrapper #link-fork {
|
||||||
|
z-index: -1;
|
||||||
|
width: 100px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
body .wrapper #link-fork img {
|
||||||
|
max-width: 100px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (device-aspect-ratio: 40 / 71) and (orientation: landscape) {
|
||||||
|
body {
|
||||||
|
width: 568px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (device-aspect-ratio: 2 / 3) and (orientation: landscape) {
|
||||||
|
body {
|
||||||
|
width: 480px;
|
||||||
|
}
|
||||||
|
}
|
745
addons/simditor/src/css/simditor.css
Normal file
745
addons/simditor/src/css/simditor.css
Normal file
File diff suppressed because one or more lines are too long
BIN
addons/simditor/src/images/image.png
Normal file
BIN
addons/simditor/src/images/image.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
241
addons/simditor/src/js/hotkeys.js
Normal file
241
addons/simditor/src/js/hotkeys.js
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
(function (root, factory) {
|
||||||
|
if (typeof define === 'function' && define.amd) {
|
||||||
|
// AMD. Register as an anonymous module unless amdModuleId is set
|
||||||
|
define('simple-hotkeys', ["jquery","simple-module"], function ($, SimpleModule) {
|
||||||
|
return (root['hotkeys'] = factory($, SimpleModule));
|
||||||
|
});
|
||||||
|
} else if (typeof exports === 'object') {
|
||||||
|
// Node. Does not work with strict CommonJS, but
|
||||||
|
// only CommonJS-like environments that support module.exports,
|
||||||
|
// like Node.
|
||||||
|
module.exports = factory(require("jquery"),require("simple-module"));
|
||||||
|
} else {
|
||||||
|
root.simple = root.simple || {};
|
||||||
|
root.simple['hotkeys'] = factory(jQuery,SimpleModule);
|
||||||
|
}
|
||||||
|
}(this, function ($, SimpleModule) {
|
||||||
|
|
||||||
|
var Hotkeys, hotkeys,
|
||||||
|
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
|
||||||
|
hasProp = {}.hasOwnProperty;
|
||||||
|
|
||||||
|
Hotkeys = (function(superClass) {
|
||||||
|
extend(Hotkeys, superClass);
|
||||||
|
|
||||||
|
function Hotkeys() {
|
||||||
|
return Hotkeys.__super__.constructor.apply(this, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
Hotkeys.count = 0;
|
||||||
|
|
||||||
|
Hotkeys.keyNameMap = {
|
||||||
|
8: "Backspace",
|
||||||
|
9: "Tab",
|
||||||
|
13: "Enter",
|
||||||
|
16: "Shift",
|
||||||
|
17: "Control",
|
||||||
|
18: "Alt",
|
||||||
|
19: "Pause",
|
||||||
|
20: "CapsLock",
|
||||||
|
27: "Esc",
|
||||||
|
32: "Spacebar",
|
||||||
|
33: "PageUp",
|
||||||
|
34: "PageDown",
|
||||||
|
35: "End",
|
||||||
|
36: "Home",
|
||||||
|
37: "Left",
|
||||||
|
38: "Up",
|
||||||
|
39: "Right",
|
||||||
|
40: "Down",
|
||||||
|
45: "Insert",
|
||||||
|
46: "Del",
|
||||||
|
91: "Meta",
|
||||||
|
93: "Meta",
|
||||||
|
48: "0",
|
||||||
|
49: "1",
|
||||||
|
50: "2",
|
||||||
|
51: "3",
|
||||||
|
52: "4",
|
||||||
|
53: "5",
|
||||||
|
54: "6",
|
||||||
|
55: "7",
|
||||||
|
56: "8",
|
||||||
|
57: "9",
|
||||||
|
65: "A",
|
||||||
|
66: "B",
|
||||||
|
67: "C",
|
||||||
|
68: "D",
|
||||||
|
69: "E",
|
||||||
|
70: "F",
|
||||||
|
71: "G",
|
||||||
|
72: "H",
|
||||||
|
73: "I",
|
||||||
|
74: "J",
|
||||||
|
75: "K",
|
||||||
|
76: "L",
|
||||||
|
77: "M",
|
||||||
|
78: "N",
|
||||||
|
79: "O",
|
||||||
|
80: "P",
|
||||||
|
81: "Q",
|
||||||
|
82: "R",
|
||||||
|
83: "S",
|
||||||
|
84: "T",
|
||||||
|
85: "U",
|
||||||
|
86: "V",
|
||||||
|
87: "W",
|
||||||
|
88: "X",
|
||||||
|
89: "Y",
|
||||||
|
90: "Z",
|
||||||
|
96: "0",
|
||||||
|
97: "1",
|
||||||
|
98: "2",
|
||||||
|
99: "3",
|
||||||
|
100: "4",
|
||||||
|
101: "5",
|
||||||
|
102: "6",
|
||||||
|
103: "7",
|
||||||
|
104: "8",
|
||||||
|
105: "9",
|
||||||
|
106: "Multiply",
|
||||||
|
107: "Add",
|
||||||
|
109: "Subtract",
|
||||||
|
110: "Decimal",
|
||||||
|
111: "Divide",
|
||||||
|
112: "F1",
|
||||||
|
113: "F2",
|
||||||
|
114: "F3",
|
||||||
|
115: "F4",
|
||||||
|
116: "F5",
|
||||||
|
117: "F6",
|
||||||
|
118: "F7",
|
||||||
|
119: "F8",
|
||||||
|
120: "F9",
|
||||||
|
121: "F10",
|
||||||
|
122: "F11",
|
||||||
|
123: "F12",
|
||||||
|
124: "F13",
|
||||||
|
125: "F14",
|
||||||
|
126: "F15",
|
||||||
|
127: "F16",
|
||||||
|
128: "F17",
|
||||||
|
129: "F18",
|
||||||
|
130: "F19",
|
||||||
|
131: "F20",
|
||||||
|
132: "F21",
|
||||||
|
133: "F22",
|
||||||
|
134: "F23",
|
||||||
|
135: "F24",
|
||||||
|
59: ";",
|
||||||
|
61: "=",
|
||||||
|
186: ";",
|
||||||
|
187: "=",
|
||||||
|
188: ",",
|
||||||
|
190: ".",
|
||||||
|
191: "/",
|
||||||
|
192: "`",
|
||||||
|
219: "[",
|
||||||
|
220: "\\",
|
||||||
|
221: "]",
|
||||||
|
222: "'"
|
||||||
|
};
|
||||||
|
|
||||||
|
Hotkeys.aliases = {
|
||||||
|
"escape": "esc",
|
||||||
|
"delete": "del",
|
||||||
|
"return": "enter",
|
||||||
|
"ctrl": "control",
|
||||||
|
"space": "spacebar",
|
||||||
|
"ins": "insert",
|
||||||
|
"cmd": "meta",
|
||||||
|
"command": "meta",
|
||||||
|
"wins": "meta",
|
||||||
|
"windows": "meta"
|
||||||
|
};
|
||||||
|
|
||||||
|
Hotkeys.normalize = function(shortcut) {
|
||||||
|
var i, j, key, keyname, keys, len;
|
||||||
|
keys = shortcut.toLowerCase().replace(/\s+/gi, "").split("+");
|
||||||
|
for (i = j = 0, len = keys.length; j < len; i = ++j) {
|
||||||
|
key = keys[i];
|
||||||
|
keys[i] = this.aliases[key] || key;
|
||||||
|
}
|
||||||
|
keyname = keys.pop();
|
||||||
|
keys.sort().push(keyname);
|
||||||
|
return keys.join("_");
|
||||||
|
};
|
||||||
|
|
||||||
|
Hotkeys.prototype.opts = {
|
||||||
|
el: document
|
||||||
|
};
|
||||||
|
|
||||||
|
Hotkeys.prototype._init = function() {
|
||||||
|
this.id = ++this.constructor.count;
|
||||||
|
this._map = {};
|
||||||
|
this._delegate = typeof this.opts.el === "string" ? document : this.opts.el;
|
||||||
|
return $(this._delegate).on("keydown.simple-hotkeys-" + this.id, this.opts.el, (function(_this) {
|
||||||
|
return function(e) {
|
||||||
|
var ref;
|
||||||
|
return (ref = _this._getHander(e)) != null ? ref.call(_this, e) : void 0;
|
||||||
|
};
|
||||||
|
})(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
Hotkeys.prototype._getHander = function(e) {
|
||||||
|
var keyname, shortcut;
|
||||||
|
if (!(keyname = this.constructor.keyNameMap[e.which])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
shortcut = "";
|
||||||
|
if (e.altKey) {
|
||||||
|
shortcut += "alt_";
|
||||||
|
}
|
||||||
|
if (e.ctrlKey) {
|
||||||
|
shortcut += "control_";
|
||||||
|
}
|
||||||
|
if (e.metaKey) {
|
||||||
|
shortcut += "meta_";
|
||||||
|
}
|
||||||
|
if (e.shiftKey) {
|
||||||
|
shortcut += "shift_";
|
||||||
|
}
|
||||||
|
shortcut += keyname.toLowerCase();
|
||||||
|
return this._map[shortcut];
|
||||||
|
};
|
||||||
|
|
||||||
|
Hotkeys.prototype.respondTo = function(subject) {
|
||||||
|
if (typeof subject === 'string') {
|
||||||
|
return this._map[this.constructor.normalize(subject)] != null;
|
||||||
|
} else {
|
||||||
|
return this._getHander(subject) != null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Hotkeys.prototype.add = function(shortcut, handler) {
|
||||||
|
this._map[this.constructor.normalize(shortcut)] = handler;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
Hotkeys.prototype.remove = function(shortcut) {
|
||||||
|
delete this._map[this.constructor.normalize(shortcut)];
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
Hotkeys.prototype.destroy = function() {
|
||||||
|
$(this._delegate).off(".simple-hotkeys-" + this.id);
|
||||||
|
this._map = {};
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
return Hotkeys;
|
||||||
|
|
||||||
|
})(SimpleModule);
|
||||||
|
|
||||||
|
hotkeys = function(opts) {
|
||||||
|
return new Hotkeys(opts);
|
||||||
|
};
|
||||||
|
|
||||||
|
return hotkeys;
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
172
addons/simditor/src/js/module.js
Normal file
172
addons/simditor/src/js/module.js
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
(function (root, factory) {
|
||||||
|
if (typeof define === 'function' && define.amd) {
|
||||||
|
// AMD. Register as an anonymous module unless amdModuleId is set
|
||||||
|
define('simple-module', ["jquery"], function (a0) {
|
||||||
|
return (root['Module'] = factory(a0));
|
||||||
|
});
|
||||||
|
} else if (typeof exports === 'object') {
|
||||||
|
// Node. Does not work with strict CommonJS, but
|
||||||
|
// only CommonJS-like environments that support module.exports,
|
||||||
|
// like Node.
|
||||||
|
module.exports = factory(require("jquery"));
|
||||||
|
} else {
|
||||||
|
root['SimpleModule'] = factory(jQuery);
|
||||||
|
}
|
||||||
|
}(this, function ($) {
|
||||||
|
|
||||||
|
var Module,
|
||||||
|
slice = [].slice;
|
||||||
|
|
||||||
|
Module = (function() {
|
||||||
|
Module.extend = function(obj) {
|
||||||
|
var key, ref, val;
|
||||||
|
if (!((obj != null) && typeof obj === 'object')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (key in obj) {
|
||||||
|
val = obj[key];
|
||||||
|
if (key !== 'included' && key !== 'extended') {
|
||||||
|
this[key] = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (ref = obj.extended) != null ? ref.call(this) : void 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
Module.include = function(obj) {
|
||||||
|
var key, ref, val;
|
||||||
|
if (!((obj != null) && typeof obj === 'object')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (key in obj) {
|
||||||
|
val = obj[key];
|
||||||
|
if (key !== 'included' && key !== 'extended') {
|
||||||
|
this.prototype[key] = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (ref = obj.included) != null ? ref.call(this) : void 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
Module.connect = function(cls) {
|
||||||
|
if (typeof cls !== 'function') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!cls.pluginName) {
|
||||||
|
throw new Error('Module.connect: cannot connect plugin without pluginName');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cls.prototype._connected = true;
|
||||||
|
if (!this._connectedClasses) {
|
||||||
|
this._connectedClasses = [];
|
||||||
|
}
|
||||||
|
this._connectedClasses.push(cls);
|
||||||
|
if (cls.pluginName) {
|
||||||
|
return this[cls.pluginName] = cls;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Module.prototype.opts = {};
|
||||||
|
|
||||||
|
function Module(opts) {
|
||||||
|
var base, cls, i, instance, instances, len, name;
|
||||||
|
this.opts = $.extend({}, this.opts, opts);
|
||||||
|
(base = this.constructor)._connectedClasses || (base._connectedClasses = []);
|
||||||
|
instances = (function() {
|
||||||
|
var i, len, ref, results;
|
||||||
|
ref = this.constructor._connectedClasses;
|
||||||
|
results = [];
|
||||||
|
for (i = 0, len = ref.length; i < len; i++) {
|
||||||
|
cls = ref[i];
|
||||||
|
name = cls.pluginName.charAt(0).toLowerCase() + cls.pluginName.slice(1);
|
||||||
|
if (cls.prototype._connected) {
|
||||||
|
cls.prototype._module = this;
|
||||||
|
}
|
||||||
|
results.push(this[name] = new cls());
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}).call(this);
|
||||||
|
if (this._connected) {
|
||||||
|
this.opts = $.extend({}, this.opts, this._module.opts);
|
||||||
|
} else {
|
||||||
|
this._init();
|
||||||
|
for (i = 0, len = instances.length; i < len; i++) {
|
||||||
|
instance = instances[i];
|
||||||
|
if (typeof instance._init === "function") {
|
||||||
|
instance._init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.trigger('initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
Module.prototype._init = function() {};
|
||||||
|
|
||||||
|
Module.prototype.on = function() {
|
||||||
|
var args, ref;
|
||||||
|
args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
|
||||||
|
(ref = $(this)).on.apply(ref, args);
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
Module.prototype.one = function() {
|
||||||
|
var args, ref;
|
||||||
|
args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
|
||||||
|
(ref = $(this)).one.apply(ref, args);
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
Module.prototype.off = function() {
|
||||||
|
var args, ref;
|
||||||
|
args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
|
||||||
|
(ref = $(this)).off.apply(ref, args);
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
Module.prototype.trigger = function() {
|
||||||
|
var args, ref;
|
||||||
|
args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
|
||||||
|
(ref = $(this)).trigger.apply(ref, args);
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
Module.prototype.triggerHandler = function() {
|
||||||
|
var args, ref;
|
||||||
|
args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
|
||||||
|
return (ref = $(this)).triggerHandler.apply(ref, args);
|
||||||
|
};
|
||||||
|
|
||||||
|
Module.prototype._t = function() {
|
||||||
|
var args, ref;
|
||||||
|
args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
|
||||||
|
return (ref = this.constructor)._t.apply(ref, args);
|
||||||
|
};
|
||||||
|
|
||||||
|
Module._t = function() {
|
||||||
|
var args, key, ref, result;
|
||||||
|
key = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
|
||||||
|
result = ((ref = this.i18n[this.locale]) != null ? ref[key] : void 0) || '';
|
||||||
|
if (!(args.length > 0)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result = result.replace(/([^%]|^)%(?:(\d+)\$)?s/g, function(p0, p, position) {
|
||||||
|
if (position) {
|
||||||
|
return p + args[parseInt(position) - 1];
|
||||||
|
} else {
|
||||||
|
return p + args.shift();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result.replace(/%%s/g, '%s');
|
||||||
|
};
|
||||||
|
|
||||||
|
Module.i18n = {
|
||||||
|
'zh-CN': {}
|
||||||
|
};
|
||||||
|
|
||||||
|
Module.locale = 'zh-CN';
|
||||||
|
|
||||||
|
return Module;
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
return Module;
|
||||||
|
|
||||||
|
}));
|
5641
addons/simditor/src/js/simditor.js
Normal file
5641
addons/simditor/src/js/simditor.js
Normal file
File diff suppressed because it is too large
Load Diff
261
addons/simditor/src/js/uploader.js
Normal file
261
addons/simditor/src/js/uploader.js
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
(function (root, factory) {
|
||||||
|
if (typeof define === 'function' && define.amd) {
|
||||||
|
// AMD. Register as an anonymous module unless amdModuleId is set
|
||||||
|
define('simple-uploader', ["jquery","simple-module"], function ($, SimpleModule) {
|
||||||
|
return (root['uploader'] = factory($, SimpleModule));
|
||||||
|
});
|
||||||
|
} else if (typeof exports === 'object') {
|
||||||
|
// Node. Does not work with strict CommonJS, but
|
||||||
|
// only CommonJS-like environments that support module.exports,
|
||||||
|
// like Node.
|
||||||
|
module.exports = factory(require("jquery"),require("simple-module"));
|
||||||
|
} else {
|
||||||
|
root.simple = root.simple || {};
|
||||||
|
root.simple['uploader'] = factory(jQuery,SimpleModule);
|
||||||
|
}
|
||||||
|
}(this, function ($, SimpleModule) {
|
||||||
|
|
||||||
|
var Uploader, uploader,
|
||||||
|
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
|
||||||
|
hasProp = {}.hasOwnProperty;
|
||||||
|
|
||||||
|
Uploader = (function(superClass) {
|
||||||
|
extend(Uploader, superClass);
|
||||||
|
|
||||||
|
function Uploader() {
|
||||||
|
return Uploader.__super__.constructor.apply(this, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
Uploader.count = 0;
|
||||||
|
|
||||||
|
Uploader.prototype.opts = {
|
||||||
|
url: '',
|
||||||
|
params: null,
|
||||||
|
fileKey: 'upload_file',
|
||||||
|
connectionCount: 3
|
||||||
|
};
|
||||||
|
|
||||||
|
Uploader.prototype._init = function() {
|
||||||
|
this.files = [];
|
||||||
|
this.queue = [];
|
||||||
|
this.id = ++Uploader.count;
|
||||||
|
this.on('uploadcomplete', (function(_this) {
|
||||||
|
return function(e, file) {
|
||||||
|
_this.files.splice($.inArray(file, _this.files), 1);
|
||||||
|
if (_this.queue.length > 0 && _this.files.length < _this.opts.connectionCount) {
|
||||||
|
return _this.upload(_this.queue.shift());
|
||||||
|
} else if (_this.files.length === 0) {
|
||||||
|
return _this.uploading = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})(this));
|
||||||
|
return $(window).on('beforeunload.uploader-' + this.id, (function(_this) {
|
||||||
|
return function(e) {
|
||||||
|
if (!_this.uploading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
e.originalEvent.returnValue = _this._t('leaveConfirm');
|
||||||
|
return _this._t('leaveConfirm');
|
||||||
|
};
|
||||||
|
})(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
Uploader.prototype.generateId = (function() {
|
||||||
|
var id;
|
||||||
|
id = 0;
|
||||||
|
return function() {
|
||||||
|
return id += 1;
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
Uploader.prototype.upload = function(file, opts) {
|
||||||
|
var f, i, key, len;
|
||||||
|
if (opts == null) {
|
||||||
|
opts = {};
|
||||||
|
}
|
||||||
|
if (file == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($.isArray(file) || file instanceof FileList) {
|
||||||
|
for (i = 0, len = file.length; i < len; i++) {
|
||||||
|
f = file[i];
|
||||||
|
this.upload(f, opts);
|
||||||
|
}
|
||||||
|
} else if ($(file).is('input:file')) {
|
||||||
|
key = $(file).attr('name');
|
||||||
|
if (key) {
|
||||||
|
opts.fileKey = key;
|
||||||
|
}
|
||||||
|
this.upload($.makeArray($(file)[0].files), opts);
|
||||||
|
} else if (!file.id || !file.obj) {
|
||||||
|
file = this.getFile(file);
|
||||||
|
}
|
||||||
|
if (!(file && file.obj)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$.extend(file, opts);
|
||||||
|
if (this.files.length >= this.opts.connectionCount) {
|
||||||
|
this.queue.push(file);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.triggerHandler('beforeupload', [file]) === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.files.push(file);
|
||||||
|
this._xhrUpload(file);
|
||||||
|
return this.uploading = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
Uploader.prototype.getFile = function(fileObj) {
|
||||||
|
var name, ref, ref1;
|
||||||
|
if (fileObj instanceof window.File || fileObj instanceof window.Blob) {
|
||||||
|
name = (ref = fileObj.fileName) != null ? ref : fileObj.name;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: this.generateId(),
|
||||||
|
url: this.opts.url,
|
||||||
|
params: this.opts.params,
|
||||||
|
fileKey: this.opts.fileKey,
|
||||||
|
name: name,
|
||||||
|
size: (ref1 = fileObj.fileSize) != null ? ref1 : fileObj.size,
|
||||||
|
ext: name ? name.split('.').pop().toLowerCase() : '',
|
||||||
|
obj: fileObj
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
Uploader.prototype._xhrUpload = function(file) {
|
||||||
|
var formData, k, ref, v;
|
||||||
|
formData = new FormData();
|
||||||
|
formData.append(file.fileKey, file.obj);
|
||||||
|
formData.append("original_filename", file.name);
|
||||||
|
if (file.params) {
|
||||||
|
ref = file.params;
|
||||||
|
for (k in ref) {
|
||||||
|
v = ref[k];
|
||||||
|
formData.append(k, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return file.xhr = $.ajax({
|
||||||
|
url: file.url,
|
||||||
|
data: formData,
|
||||||
|
processData: false,
|
||||||
|
contentType: false,
|
||||||
|
type: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-File-Name': encodeURIComponent(file.name)
|
||||||
|
},
|
||||||
|
xhr: function() {
|
||||||
|
var req;
|
||||||
|
req = $.ajaxSettings.xhr();
|
||||||
|
if (req) {
|
||||||
|
req.upload.onprogress = (function(_this) {
|
||||||
|
return function(e) {
|
||||||
|
return _this.progress(e);
|
||||||
|
};
|
||||||
|
})(this);
|
||||||
|
}
|
||||||
|
return req;
|
||||||
|
},
|
||||||
|
progress: (function(_this) {
|
||||||
|
return function(e) {
|
||||||
|
if (!e.lengthComputable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return _this.trigger('uploadprogress', [file, e.loaded, e.total]);
|
||||||
|
};
|
||||||
|
})(this),
|
||||||
|
error: (function(_this) {
|
||||||
|
return function(xhr, status, err) {
|
||||||
|
return _this.trigger('uploaderror', [file, xhr, status]);
|
||||||
|
};
|
||||||
|
})(this),
|
||||||
|
success: (function(_this) {
|
||||||
|
return function(result) {
|
||||||
|
_this.trigger('uploadprogress', [file, file.size, file.size]);
|
||||||
|
_this.trigger('uploadsuccess', [file, result]);
|
||||||
|
return $(document).trigger('uploadsuccess', [file, result, _this]);
|
||||||
|
};
|
||||||
|
})(this),
|
||||||
|
complete: (function(_this) {
|
||||||
|
return function(xhr, status) {
|
||||||
|
return _this.trigger('uploadcomplete', [file, xhr.responseText]);
|
||||||
|
};
|
||||||
|
})(this)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Uploader.prototype.cancel = function(file) {
|
||||||
|
var f, i, len, ref;
|
||||||
|
if (!file.id) {
|
||||||
|
ref = this.files;
|
||||||
|
for (i = 0, len = ref.length; i < len; i++) {
|
||||||
|
f = ref[i];
|
||||||
|
if (f.id === file * 1) {
|
||||||
|
file = f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.trigger('uploadcancel', [file]);
|
||||||
|
if (file.xhr) {
|
||||||
|
file.xhr.abort();
|
||||||
|
}
|
||||||
|
return file.xhr = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
Uploader.prototype.readImageFile = function(fileObj, callback) {
|
||||||
|
var fileReader, img;
|
||||||
|
if (!$.isFunction(callback)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
img = new Image();
|
||||||
|
img.onload = function() {
|
||||||
|
return callback(img);
|
||||||
|
};
|
||||||
|
img.onerror = function() {
|
||||||
|
return callback();
|
||||||
|
};
|
||||||
|
if (window.FileReader && FileReader.prototype.readAsDataURL && /^image/.test(fileObj.type)) {
|
||||||
|
fileReader = new FileReader();
|
||||||
|
fileReader.onload = function(e) {
|
||||||
|
return img.src = e.target.result;
|
||||||
|
};
|
||||||
|
return fileReader.readAsDataURL(fileObj);
|
||||||
|
} else {
|
||||||
|
return callback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Uploader.prototype.destroy = function() {
|
||||||
|
var file, i, len, ref;
|
||||||
|
this.queue.length = 0;
|
||||||
|
ref = this.files;
|
||||||
|
for (i = 0, len = ref.length; i < len; i++) {
|
||||||
|
file = ref[i];
|
||||||
|
this.cancel(file);
|
||||||
|
}
|
||||||
|
$(window).off('.uploader-' + this.id);
|
||||||
|
return $(document).off('.uploader-' + this.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
Uploader.i18n = {
|
||||||
|
'zh-CN': {
|
||||||
|
leaveConfirm: '正在上传文件,如果离开上传会自动取消'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Uploader.locale = 'zh-CN';
|
||||||
|
|
||||||
|
return Uploader;
|
||||||
|
|
||||||
|
})(SimpleModule);
|
||||||
|
|
||||||
|
uploader = function(opts) {
|
||||||
|
return new Uploader(opts);
|
||||||
|
};
|
||||||
|
|
||||||
|
return uploader;
|
||||||
|
|
||||||
|
}));
|
1
addons/thumb/.addonrc
Normal file
1
addons/thumb/.addonrc
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"files":[],"license":"regular","licenseto":"22920","licensekey":"BVR8NF0oEXmc6J9i GUS1Mt2PgUq0KBZru98jxg==","domains":["des8.com"],"licensecodes":[],"validations":["b020d8667fd8c7d4326fcf512c7a10aa"]}
|
109
addons/thumb/Thumb.php
Normal file
109
addons/thumb/Thumb.php
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addons\thumb;
|
||||||
|
|
||||||
|
use think\Addons;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缩略图插件
|
||||||
|
* 2018-02-12
|
||||||
|
* author: dilu
|
||||||
|
* 253407587@qq.com
|
||||||
|
*/
|
||||||
|
class Thumb extends Addons
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件安装方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function install()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件卸载方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function uninstall()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实现钩子方法
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function uploadAfter($param)
|
||||||
|
{
|
||||||
|
// 获取传进来的附件模型数据
|
||||||
|
$data = $param->getData();
|
||||||
|
//对文件进行检测 不是图片类型的不做处理
|
||||||
|
if(!strpos($data['mimetype'],'image'))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//附件id
|
||||||
|
$attachment_id = $data['id'];
|
||||||
|
//获取配置
|
||||||
|
$config = $this->getConfig();
|
||||||
|
//图片质量
|
||||||
|
$quality = isset($config['quality']) ? $config['quality'] : '100';
|
||||||
|
if ($quality > 100 || $quality < 10) {
|
||||||
|
$quality = 100;
|
||||||
|
}
|
||||||
|
if (1 == $config['replace'])//如果是选择替换原文件
|
||||||
|
{
|
||||||
|
//打开文件
|
||||||
|
$image = \think\Image::open(ROOT_PATH . '/public' . $data['url']);
|
||||||
|
$image->thumb($config['size'], $config['size'])->save(ROOT_PATH . '/public' . $data['url'], null, $quality);
|
||||||
|
|
||||||
|
$data = array(
|
||||||
|
'filesize' => filesize(ROOT_PATH . '/public' . $data['url']),
|
||||||
|
'imagewidth' => $image->width(),
|
||||||
|
'imageheight' => $image->height(),
|
||||||
|
'imagetype' => $image->type(),
|
||||||
|
'imageframes' => 0,
|
||||||
|
'mimetype' => $image->mime(),
|
||||||
|
'url' => $data['url'],
|
||||||
|
'uploadtime' => time(),
|
||||||
|
'storage' => 'local',
|
||||||
|
'sha1' => sha1_file(ROOT_PATH . '/public' . $data['url']),
|
||||||
|
'updatetime' => time(),
|
||||||
|
);
|
||||||
|
|
||||||
|
$param->where('id', $attachment_id)->update($data);
|
||||||
|
} else {
|
||||||
|
$image = \think\Image::open(ROOT_PATH . '/public' . $data['url']);
|
||||||
|
//获取后缀
|
||||||
|
$ext = isset($config['ext']) ? $config['ext'] : '-thumb';
|
||||||
|
$url = explode('.', $data['url']);
|
||||||
|
//组装缩略图的url
|
||||||
|
$url = $url[0] . $ext . '.' . $url[1];
|
||||||
|
$image->thumb($config['size'], $config['size'])->save(ROOT_PATH . '/public' . $url, null, $quality);
|
||||||
|
|
||||||
|
$data = array(
|
||||||
|
'filesize' => filesize(ROOT_PATH . '/public' . $url),
|
||||||
|
'imagewidth' => $image->width(),
|
||||||
|
'imageheight' => $image->height(),
|
||||||
|
'imagetype' => $image->type(),
|
||||||
|
'imageframes' => 0,
|
||||||
|
'mimetype' => $image->mime(),
|
||||||
|
'url' => $url,
|
||||||
|
'uploadtime' => time(),
|
||||||
|
'storage' => 'local',
|
||||||
|
'sha1' => sha1_file(ROOT_PATH . '/public' . $url),
|
||||||
|
'createtime' => time(),
|
||||||
|
'updatetime' => time(),
|
||||||
|
);
|
||||||
|
$param->insert($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当前插件的配置信息,配置信息存在当前目录的config.php文件中,见下方
|
||||||
|
//print_r($this->getConfig());
|
||||||
|
// 可以返回模板,模板文件默认读取的为插件目录中的文件。模板名不能为空!
|
||||||
|
//return $this->fetch('view/info');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
66
addons/thumb/config.php
Normal file
66
addons/thumb/config.php
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return array(
|
||||||
|
0 =>
|
||||||
|
array(
|
||||||
|
'name' => 'size',
|
||||||
|
'title' => '裁剪长宽',
|
||||||
|
'type' => 'number',
|
||||||
|
'content' =>
|
||||||
|
array(
|
||||||
|
),
|
||||||
|
'value' => '300',
|
||||||
|
'rule' => 'required',
|
||||||
|
'msg' => '',
|
||||||
|
'tip' => '裁剪的最高长/宽',
|
||||||
|
'ok' => '',
|
||||||
|
'extend' => '',
|
||||||
|
),
|
||||||
|
1 =>
|
||||||
|
array(
|
||||||
|
'name' => 'replace',
|
||||||
|
'title' => '覆盖原图',
|
||||||
|
'type' => 'radio',
|
||||||
|
'content' =>
|
||||||
|
array(
|
||||||
|
0 => '否',
|
||||||
|
1 => '是',
|
||||||
|
),
|
||||||
|
'value' => '1',
|
||||||
|
'rule' => 'required',
|
||||||
|
'msg' => '',
|
||||||
|
'tip' => '此项为是时,后缀不生效',
|
||||||
|
'ok' => '',
|
||||||
|
'extend' => '',
|
||||||
|
),
|
||||||
|
2 =>
|
||||||
|
array(
|
||||||
|
'name' => 'quality',
|
||||||
|
'title' => '图片质量',
|
||||||
|
'type' => 'number',
|
||||||
|
'content' =>
|
||||||
|
array(
|
||||||
|
),
|
||||||
|
'value' => '100',
|
||||||
|
'rule' => 'required',
|
||||||
|
'msg' => '',
|
||||||
|
'tip' => '大于100小于10均按照100处理',
|
||||||
|
'ok' => '',
|
||||||
|
'extend' => '',
|
||||||
|
),
|
||||||
|
3 =>
|
||||||
|
array(
|
||||||
|
'name' => 'ext',
|
||||||
|
'title' => '文件后缀',
|
||||||
|
'type' => 'string',
|
||||||
|
'content' =>
|
||||||
|
array(
|
||||||
|
),
|
||||||
|
'value' => '-thumb',
|
||||||
|
'rule' => 'required',
|
||||||
|
'msg' => '',
|
||||||
|
'tip' => '最终效果a-thumb.jpg',
|
||||||
|
'ok' => '',
|
||||||
|
'extend' => '',
|
||||||
|
),
|
||||||
|
);
|
10
addons/thumb/info.ini
Normal file
10
addons/thumb/info.ini
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
name = thumb
|
||||||
|
title = 本地缩略图
|
||||||
|
intro = 可以在本地上传图片的产生缩略图
|
||||||
|
author = dilu
|
||||||
|
website = http://www.fastadmin.net
|
||||||
|
version = 1.0.1
|
||||||
|
state = 1
|
||||||
|
license = regular
|
||||||
|
licenseto = 22920
|
||||||
|
url = /addons/thumb
|
1
application/.htaccess
Normal file
1
application/.htaccess
Normal file
@ -0,0 +1 @@
|
|||||||
|
deny from all
|
14
application/admin/behavior/AdminLog.php
Normal file
14
application/admin/behavior/AdminLog.php
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\admin\behavior;
|
||||||
|
|
||||||
|
class AdminLog
|
||||||
|
{
|
||||||
|
public function run(&$params)
|
||||||
|
{
|
||||||
|
//只记录POST请求的日志
|
||||||
|
if (request()->isPost() && config('fastadmin.auto_record_log')) {
|
||||||
|
\app\admin\model\AdminLog::record();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
344
application/admin/command/Addon.php
Normal file
344
application/admin/command/Addon.php
Normal file
@ -0,0 +1,344 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\admin\command;
|
||||||
|
|
||||||
|
use think\addons\AddonException;
|
||||||
|
use think\addons\Service;
|
||||||
|
use think\Config;
|
||||||
|
use think\console\Command;
|
||||||
|
use think\console\Input;
|
||||||
|
use think\console\input\Option;
|
||||||
|
use think\console\Output;
|
||||||
|
use think\Db;
|
||||||
|
use think\Exception;
|
||||||
|
use think\exception\PDOException;
|
||||||
|
|
||||||
|
class Addon extends Command
|
||||||
|
{
|
||||||
|
|
||||||
|
protected function configure()
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->setName('addon')
|
||||||
|
->addOption('name', 'a', Option::VALUE_REQUIRED, 'addon name', null)
|
||||||
|
->addOption('action', 'c', Option::VALUE_REQUIRED, 'action(create/enable/disable/uninstall/refresh/package/move)', 'create')
|
||||||
|
->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override', null)
|
||||||
|
->addOption('release', 'r', Option::VALUE_OPTIONAL, 'addon release version', null)
|
||||||
|
->addOption('uid', 'u', Option::VALUE_OPTIONAL, 'fastadmin uid', null)
|
||||||
|
->addOption('token', 't', Option::VALUE_OPTIONAL, 'fastadmin token', null)
|
||||||
|
->addOption('domain', 'd', Option::VALUE_OPTIONAL, 'domain', null)
|
||||||
|
->addOption('local', 'l', Option::VALUE_OPTIONAL, 'local package', null)
|
||||||
|
->setDescription('Addon manager');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(Input $input, Output $output)
|
||||||
|
{
|
||||||
|
$name = $input->getOption('name') ?: '';
|
||||||
|
$action = $input->getOption('action') ?: '';
|
||||||
|
if (stripos($name, 'addons' . DS) !== false) {
|
||||||
|
$name = explode(DS, $name)[1];
|
||||||
|
}
|
||||||
|
//强制覆盖
|
||||||
|
$force = $input->getOption('force');
|
||||||
|
//版本
|
||||||
|
$release = $input->getOption('release') ?: '';
|
||||||
|
//uid
|
||||||
|
$uid = $input->getOption('uid') ?: '';
|
||||||
|
//token
|
||||||
|
$token = $input->getOption('token') ?: '';
|
||||||
|
|
||||||
|
include dirname(__DIR__) . DS . 'common.php';
|
||||||
|
|
||||||
|
if (!$name && !in_array($action, ['refresh'])) {
|
||||||
|
throw new Exception('Addon name could not be empty');
|
||||||
|
}
|
||||||
|
if (!$action || !in_array($action, ['create', 'disable', 'enable', 'install', 'uninstall', 'refresh', 'upgrade', 'package', 'move'])) {
|
||||||
|
throw new Exception('Please input correct action name');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询一次SQL,判断连接是否正常
|
||||||
|
Db::execute("SELECT 1");
|
||||||
|
|
||||||
|
$addonDir = ADDON_PATH . $name . DS;
|
||||||
|
switch ($action) {
|
||||||
|
case 'create':
|
||||||
|
//非覆盖模式时如果存在则报错
|
||||||
|
if (is_dir($addonDir) && !$force) {
|
||||||
|
throw new Exception("addon already exists!\nIf you need to create again, use the parameter --force=true ");
|
||||||
|
}
|
||||||
|
//如果存在先移除
|
||||||
|
if (is_dir($addonDir)) {
|
||||||
|
rmdirs($addonDir);
|
||||||
|
}
|
||||||
|
mkdir($addonDir, 0755, true);
|
||||||
|
mkdir($addonDir . DS . 'controller', 0755, true);
|
||||||
|
$menuList = \app\common\library\Menu::export($name);
|
||||||
|
$createMenu = $this->getCreateMenu($menuList);
|
||||||
|
$prefix = Config::get('database.prefix');
|
||||||
|
$createTableSql = '';
|
||||||
|
try {
|
||||||
|
$result = Db::query("SHOW CREATE TABLE `" . $prefix . $name . "`;");
|
||||||
|
if (isset($result[0]) && isset($result[0]['Create Table'])) {
|
||||||
|
$createTableSql = $result[0]['Create Table'];
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'name' => $name,
|
||||||
|
'addon' => $name,
|
||||||
|
'addonClassName' => ucfirst($name),
|
||||||
|
'addonInstallMenu' => $createMenu ? "\$menu = " . var_export_short($createMenu) . ";\n\tMenu::create(\$menu);" : '',
|
||||||
|
'addonUninstallMenu' => $menuList ? 'Menu::delete("' . $name . '");' : '',
|
||||||
|
'addonEnableMenu' => $menuList ? 'Menu::enable("' . $name . '");' : '',
|
||||||
|
'addonDisableMenu' => $menuList ? 'Menu::disable("' . $name . '");' : '',
|
||||||
|
];
|
||||||
|
$this->writeToFile("addon", $data, $addonDir . ucfirst($name) . '.php');
|
||||||
|
$this->writeToFile("config", $data, $addonDir . 'config.php');
|
||||||
|
$this->writeToFile("info", $data, $addonDir . 'info.ini');
|
||||||
|
$this->writeToFile("controller", $data, $addonDir . 'controller' . DS . 'Index.php');
|
||||||
|
if ($createTableSql) {
|
||||||
|
$createTableSql = str_replace("`" . $prefix, '`__PREFIX__', $createTableSql);
|
||||||
|
file_put_contents($addonDir . 'install.sql', $createTableSql);
|
||||||
|
}
|
||||||
|
|
||||||
|
$output->info("Create Successed!");
|
||||||
|
break;
|
||||||
|
case 'disable':
|
||||||
|
case 'enable':
|
||||||
|
try {
|
||||||
|
//调用启用、禁用的方法
|
||||||
|
Service::$action($name, 0);
|
||||||
|
} catch (AddonException $e) {
|
||||||
|
if ($e->getCode() != -3) {
|
||||||
|
throw new Exception($e->getMessage());
|
||||||
|
}
|
||||||
|
if (!$force) {
|
||||||
|
//如果有冲突文件则提醒
|
||||||
|
$data = $e->getData();
|
||||||
|
foreach ($data['conflictlist'] as $k => $v) {
|
||||||
|
$output->warning($v);
|
||||||
|
}
|
||||||
|
$output->info("Are you sure you want to " . ($action == 'enable' ? 'override' : 'delete') . " all those files? Type 'yes' to continue: ");
|
||||||
|
$line = fgets(defined('STDIN') ? STDIN : fopen('php://stdin', 'r'));
|
||||||
|
if (trim($line) != 'yes') {
|
||||||
|
throw new Exception("Operation is aborted!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//调用启用、禁用的方法
|
||||||
|
Service::$action($name, 1);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
throw new Exception($e->getMessage());
|
||||||
|
}
|
||||||
|
$output->info(ucfirst($action) . " Successed!");
|
||||||
|
break;
|
||||||
|
case 'uninstall':
|
||||||
|
//非覆盖模式时如果存在则报错
|
||||||
|
if (!$force) {
|
||||||
|
throw new Exception("If you need to uninstall addon, use the parameter --force=true ");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Service::uninstall($name, 0);
|
||||||
|
} catch (AddonException $e) {
|
||||||
|
if ($e->getCode() != -3) {
|
||||||
|
throw new Exception($e->getMessage());
|
||||||
|
}
|
||||||
|
if (!$force) {
|
||||||
|
//如果有冲突文件则提醒
|
||||||
|
$data = $e->getData();
|
||||||
|
foreach ($data['conflictlist'] as $k => $v) {
|
||||||
|
$output->warning($v);
|
||||||
|
}
|
||||||
|
$output->info("Are you sure you want to delete all those files? Type 'yes' to continue: ");
|
||||||
|
$line = fgets(defined('STDIN') ? STDIN : fopen('php://stdin', 'r'));
|
||||||
|
if (trim($line) != 'yes') {
|
||||||
|
throw new Exception("Operation is aborted!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Service::uninstall($name, 1);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
throw new Exception($e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
$output->info("Uninstall Successed!");
|
||||||
|
break;
|
||||||
|
case 'refresh':
|
||||||
|
Service::refresh();
|
||||||
|
$output->info("Refresh Successed!");
|
||||||
|
break;
|
||||||
|
case 'package':
|
||||||
|
$infoFile = $addonDir . 'info.ini';
|
||||||
|
if (!is_file($infoFile)) {
|
||||||
|
throw new Exception(__('Addon info file was not found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$info = get_addon_info($name);
|
||||||
|
if (!$info) {
|
||||||
|
throw new Exception(__('Addon info file data incorrect'));
|
||||||
|
}
|
||||||
|
$infoname = isset($info['name']) ? $info['name'] : '';
|
||||||
|
if (!$infoname || !preg_match("/^[a-z]+$/i", $infoname) || $infoname != $name) {
|
||||||
|
throw new Exception(__('Addon info name incorrect'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$infoversion = isset($info['version']) ? $info['version'] : '';
|
||||||
|
if (!$infoversion || !preg_match("/^\d+\.\d+\.\d+$/i", $infoversion)) {
|
||||||
|
throw new Exception(__('Addon info version incorrect'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$addonTmpDir = RUNTIME_PATH . 'addons' . DS;
|
||||||
|
if (!is_dir($addonTmpDir)) {
|
||||||
|
@mkdir($addonTmpDir, 0755, true);
|
||||||
|
}
|
||||||
|
$addonFile = $addonTmpDir . $infoname . '-' . $infoversion . '.zip';
|
||||||
|
if (!class_exists('ZipArchive')) {
|
||||||
|
throw new Exception(__('ZinArchive not install'));
|
||||||
|
}
|
||||||
|
$zip = new \ZipArchive;
|
||||||
|
$zip->open($addonFile, \ZipArchive::CREATE | \ZipArchive::OVERWRITE);
|
||||||
|
|
||||||
|
$files = new \RecursiveIteratorIterator(
|
||||||
|
new \RecursiveDirectoryIterator($addonDir), \RecursiveIteratorIterator::LEAVES_ONLY
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($files as $name => $file) {
|
||||||
|
if (!$file->isDir()) {
|
||||||
|
$filePath = $file->getRealPath();
|
||||||
|
$relativePath = str_replace(DS, '/', substr($filePath, strlen($addonDir)));
|
||||||
|
if (!in_array($file->getFilename(), ['.git', '.DS_Store', 'Thumbs.db'])) {
|
||||||
|
$zip->addFile($filePath, $relativePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$zip->close();
|
||||||
|
$output->info("Package Successed!");
|
||||||
|
break;
|
||||||
|
case 'move':
|
||||||
|
$movePath = [
|
||||||
|
'adminOnlySelfDir' => ['admin/behavior', 'admin/controller', 'admin/library', 'admin/model', 'admin/validate', 'admin/view'],
|
||||||
|
'adminAllSubDir' => ['admin/lang'],
|
||||||
|
'publicDir' => ['public/assets/addons', 'public/assets/js/backend']
|
||||||
|
];
|
||||||
|
$paths = [];
|
||||||
|
$appPath = str_replace('/', DS, APP_PATH);
|
||||||
|
$rootPath = str_replace('/', DS, ROOT_PATH);
|
||||||
|
foreach ($movePath as $k => $items) {
|
||||||
|
switch ($k) {
|
||||||
|
case 'adminOnlySelfDir':
|
||||||
|
foreach ($items as $v) {
|
||||||
|
$v = str_replace('/', DS, $v);
|
||||||
|
$oldPath = $appPath . $v . DS . $name;
|
||||||
|
$newPath = $rootPath . "addons" . DS . $name . DS . "application" . DS . $v . DS . $name;
|
||||||
|
$paths[$oldPath] = $newPath;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'adminAllSubDir':
|
||||||
|
foreach ($items as $v) {
|
||||||
|
$v = str_replace('/', DS, $v);
|
||||||
|
$vPath = $appPath . $v;
|
||||||
|
$list = scandir($vPath);
|
||||||
|
foreach ($list as $_v) {
|
||||||
|
if (!in_array($_v, ['.', '..']) && is_dir($vPath . DS . $_v)) {
|
||||||
|
$oldPath = $appPath . $v . DS . $_v . DS . $name;
|
||||||
|
$newPath = $rootPath . "addons" . DS . $name . DS . "application" . DS . $v . DS . $_v . DS . $name;
|
||||||
|
$paths[$oldPath] = $newPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'publicDir':
|
||||||
|
foreach ($items as $v) {
|
||||||
|
$v = str_replace('/', DS, $v);
|
||||||
|
$oldPath = $rootPath . $v . DS . $name;
|
||||||
|
$newPath = $rootPath . 'addons' . DS . $name . DS . $v . DS . $name;
|
||||||
|
$paths[$oldPath] = $newPath;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($paths as $oldPath => $newPath) {
|
||||||
|
if (is_dir($oldPath)) {
|
||||||
|
if ($force) {
|
||||||
|
if (is_dir($newPath)) {
|
||||||
|
$list = scandir($newPath);
|
||||||
|
foreach ($list as $_v) {
|
||||||
|
if (!in_array($_v, ['.', '..'])) {
|
||||||
|
$file = $newPath . DS . $_v;
|
||||||
|
@chmod($file, 0777);
|
||||||
|
@unlink($file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@rmdir($newPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
copydirs($oldPath, $newPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取创建菜单的数组
|
||||||
|
* @param array $menu
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getCreateMenu($menu)
|
||||||
|
{
|
||||||
|
$result = [];
|
||||||
|
foreach ($menu as $k => & $v) {
|
||||||
|
$arr = [
|
||||||
|
'name' => $v['name'],
|
||||||
|
'title' => $v['title'],
|
||||||
|
];
|
||||||
|
if ($v['icon'] != 'fa fa-circle-o') {
|
||||||
|
$arr['icon'] = $v['icon'];
|
||||||
|
}
|
||||||
|
if ($v['ismenu']) {
|
||||||
|
$arr['ismenu'] = $v['ismenu'];
|
||||||
|
}
|
||||||
|
if (isset($v['childlist']) && $v['childlist']) {
|
||||||
|
$arr['sublist'] = $this->getCreateMenu($v['childlist']);
|
||||||
|
}
|
||||||
|
$result[] = $arr;
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 写入到文件
|
||||||
|
* @param string $name
|
||||||
|
* @param array $data
|
||||||
|
* @param string $pathname
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
protected function writeToFile($name, $data, $pathname)
|
||||||
|
{
|
||||||
|
$search = $replace = [];
|
||||||
|
foreach ($data as $k => $v) {
|
||||||
|
$search[] = "{%{$k}%}";
|
||||||
|
$replace[] = $v;
|
||||||
|
}
|
||||||
|
$stub = file_get_contents($this->getStub($name));
|
||||||
|
$content = str_replace($search, $replace, $stub);
|
||||||
|
|
||||||
|
if (!is_dir(dirname($pathname))) {
|
||||||
|
mkdir(strtolower(dirname($pathname)), 0755, true);
|
||||||
|
}
|
||||||
|
return file_put_contents($pathname, $content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取基础模板
|
||||||
|
* @param string $name
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function getStub($name)
|
||||||
|
{
|
||||||
|
return __DIR__ . '/Addon/stubs/' . $name . '.stub';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
54
application/admin/command/Addon/stubs/addon.stub
Normal file
54
application/admin/command/Addon/stubs/addon.stub
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addons\{%name%};
|
||||||
|
|
||||||
|
use app\common\library\Menu;
|
||||||
|
use think\Addons;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件
|
||||||
|
*/
|
||||||
|
class {%addonClassName%} extends Addons
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件安装方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function install()
|
||||||
|
{
|
||||||
|
{%addonInstallMenu%}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件卸载方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function uninstall()
|
||||||
|
{
|
||||||
|
{%addonUninstallMenu%}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件启用方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function enable()
|
||||||
|
{
|
||||||
|
{%addonEnableMenu%}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件禁用方法
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function disable()
|
||||||
|
{
|
||||||
|
{%addonDisableMenu%}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
44
application/admin/command/Addon/stubs/config.stub
Normal file
44
application/admin/command/Addon/stubs/config.stub
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
//配置唯一标识
|
||||||
|
'name' => 'usernmae',
|
||||||
|
//显示的标题
|
||||||
|
'title' => '用户名',
|
||||||
|
//类型
|
||||||
|
'type' => 'string',
|
||||||
|
//分组
|
||||||
|
'group' => '',
|
||||||
|
//动态显示
|
||||||
|
'visible' => '',
|
||||||
|
//数据字典
|
||||||
|
'content' => [
|
||||||
|
],
|
||||||
|
//值
|
||||||
|
'value' => '',
|
||||||
|
//验证规则
|
||||||
|
'rule' => 'required',
|
||||||
|
//错误消息
|
||||||
|
'msg' => '',
|
||||||
|
//提示消息
|
||||||
|
'tip' => '',
|
||||||
|
//成功消息
|
||||||
|
'ok' => '',
|
||||||
|
//扩展信息
|
||||||
|
'extend' => ''
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'password',
|
||||||
|
'title' => '密码',
|
||||||
|
'type' => 'string',
|
||||||
|
'content' => [
|
||||||
|
],
|
||||||
|
'value' => '',
|
||||||
|
'rule' => 'required',
|
||||||
|
'msg' => '',
|
||||||
|
'tip' => '',
|
||||||
|
'ok' => '',
|
||||||
|
'extend' => ''
|
||||||
|
],
|
||||||
|
];
|
15
application/admin/command/Addon/stubs/controller.stub
Normal file
15
application/admin/command/Addon/stubs/controller.stub
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace addons\{%addon%}\controller;
|
||||||
|
|
||||||
|
use think\addons\Controller;
|
||||||
|
|
||||||
|
class Index extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$this->error("当前插件暂无前台页面");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
7
application/admin/command/Addon/stubs/info.stub
Normal file
7
application/admin/command/Addon/stubs/info.stub
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
name = {%name%}
|
||||||
|
title = 插件名称{%name%}
|
||||||
|
intro = 插件介绍
|
||||||
|
author = yourname
|
||||||
|
website = https://www.fastadmin.net
|
||||||
|
version = 1.0.0
|
||||||
|
state = 1
|
195
application/admin/command/Api.php
Normal file
195
application/admin/command/Api.php
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\admin\command;
|
||||||
|
|
||||||
|
use app\admin\command\Api\library\Builder;
|
||||||
|
use think\Config;
|
||||||
|
use think\console\Command;
|
||||||
|
use think\console\Input;
|
||||||
|
use think\console\input\Option;
|
||||||
|
use think\console\Output;
|
||||||
|
use think\Exception;
|
||||||
|
|
||||||
|
class Api extends Command
|
||||||
|
{
|
||||||
|
protected function configure()
|
||||||
|
{
|
||||||
|
$site = Config::get('site');
|
||||||
|
$this
|
||||||
|
->setName('api')
|
||||||
|
->addOption('url', 'u', Option::VALUE_OPTIONAL, 'default api url', '')
|
||||||
|
->addOption('module', 'm', Option::VALUE_OPTIONAL, 'module name(admin/index/api)', 'api')
|
||||||
|
->addOption('output', 'o', Option::VALUE_OPTIONAL, 'output index file name', 'api.html')
|
||||||
|
->addOption('template', 'e', Option::VALUE_OPTIONAL, '', 'index.html')
|
||||||
|
->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override general file', false)
|
||||||
|
->addOption('title', 't', Option::VALUE_OPTIONAL, 'document title', $site['name'] ?? '')
|
||||||
|
->addOption('class', 'c', Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'extend class', null)
|
||||||
|
->addOption('language', 'l', Option::VALUE_OPTIONAL, 'language', 'zh-cn')
|
||||||
|
->addOption('addon', 'a', Option::VALUE_OPTIONAL, 'addon name', null)
|
||||||
|
->addOption('controller', 'r', Option::VALUE_REQUIRED | Option::VALUE_IS_ARRAY, 'controller name', null)
|
||||||
|
->setDescription('Build Api document from controller');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(Input $input, Output $output)
|
||||||
|
{
|
||||||
|
$apiDir = __DIR__ . DS . 'Api' . DS;
|
||||||
|
|
||||||
|
$force = $input->getOption('force');
|
||||||
|
$url = $input->getOption('url');
|
||||||
|
$language = $input->getOption('language');
|
||||||
|
$template = $input->getOption('template');
|
||||||
|
if (!preg_match("/^([a-z0-9]+)\.html\$/i", $template)) {
|
||||||
|
throw new Exception('template file not correct');
|
||||||
|
}
|
||||||
|
$language = $language ? $language : 'zh-cn';
|
||||||
|
$langFile = $apiDir . 'lang' . DS . $language . '.php';
|
||||||
|
if (!is_file($langFile)) {
|
||||||
|
throw new Exception('language file not found');
|
||||||
|
}
|
||||||
|
$lang = include_once $langFile;
|
||||||
|
// 目标目录
|
||||||
|
$output_dir = ROOT_PATH . 'public' . DS;
|
||||||
|
$output_file = $output_dir . $input->getOption('output');
|
||||||
|
if (is_file($output_file) && !$force) {
|
||||||
|
throw new Exception("api index file already exists!\nIf you need to rebuild again, use the parameter --force=true ");
|
||||||
|
}
|
||||||
|
// 模板文件
|
||||||
|
$template_dir = $apiDir . 'template' . DS;
|
||||||
|
$template_file = $template_dir . $template;
|
||||||
|
if (!is_file($template_file)) {
|
||||||
|
throw new Exception('template file not found');
|
||||||
|
}
|
||||||
|
// 额外的类
|
||||||
|
$classes = $input->getOption('class');
|
||||||
|
// 标题
|
||||||
|
$title = $input->getOption('title');
|
||||||
|
// 模块
|
||||||
|
$module = $input->getOption('module');
|
||||||
|
// 插件
|
||||||
|
$addon = $input->getOption('addon');
|
||||||
|
|
||||||
|
$moduleDir = $addonDir = '';
|
||||||
|
if ($addon) {
|
||||||
|
$addonInfo = get_addon_info($addon);
|
||||||
|
if (!$addonInfo) {
|
||||||
|
throw new Exception('addon not found');
|
||||||
|
}
|
||||||
|
$moduleDir = ADDON_PATH . $addon . DS;
|
||||||
|
} else {
|
||||||
|
$moduleDir = APP_PATH . $module . DS;
|
||||||
|
}
|
||||||
|
if (!is_dir($moduleDir)) {
|
||||||
|
throw new Exception('module not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version_compare(PHP_VERSION, '7.0.0', '<')) {
|
||||||
|
throw new Exception("Requires PHP version 7.0 or newer");
|
||||||
|
}
|
||||||
|
|
||||||
|
//控制器名
|
||||||
|
$controller = $input->getOption('controller') ?: [];
|
||||||
|
if (!$controller) {
|
||||||
|
$controllerDir = $moduleDir . Config::get('url_controller_layer') . DS;
|
||||||
|
$files = new \RecursiveIteratorIterator(
|
||||||
|
new \RecursiveDirectoryIterator($controllerDir),
|
||||||
|
\RecursiveIteratorIterator::LEAVES_ONLY
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($files as $name => $file) {
|
||||||
|
if (!$file->isDir() && $file->getExtension() == 'php') {
|
||||||
|
$filePath = $file->getRealPath();
|
||||||
|
$classes[] = $this->get_class_from_file($filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
foreach ($controller as $index => $item) {
|
||||||
|
$filePath = $moduleDir . Config::get('url_controller_layer') . DS . $item . '.php';
|
||||||
|
$classes[] = $this->get_class_from_file($filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$classes = array_unique(array_filter($classes));
|
||||||
|
|
||||||
|
$config = [
|
||||||
|
'sitename' => config('site.name'),
|
||||||
|
'title' => $title,
|
||||||
|
'author' => config('site.name'),
|
||||||
|
'description' => '',
|
||||||
|
'apiurl' => $url,
|
||||||
|
'language' => $language,
|
||||||
|
];
|
||||||
|
|
||||||
|
$builder = new Builder($classes);
|
||||||
|
$content = $builder->render($template_file, ['config' => $config, 'lang' => $lang]);
|
||||||
|
|
||||||
|
if (!file_put_contents($output_file, $content)) {
|
||||||
|
throw new Exception('Cannot save the content to ' . $output_file);
|
||||||
|
}
|
||||||
|
$output->info("Build Successed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get full qualified class name
|
||||||
|
*
|
||||||
|
* @param string $path_to_file
|
||||||
|
* @return string
|
||||||
|
* @author JBYRNE http://jarretbyrne.com/2015/06/197/
|
||||||
|
*/
|
||||||
|
protected function get_class_from_file($path_to_file)
|
||||||
|
{
|
||||||
|
//Grab the contents of the file
|
||||||
|
$contents = file_get_contents($path_to_file);
|
||||||
|
|
||||||
|
//Start with a blank namespace and class
|
||||||
|
$namespace = $class = "";
|
||||||
|
|
||||||
|
//Set helper values to know that we have found the namespace/class token and need to collect the string values after them
|
||||||
|
$getting_namespace = $getting_class = false;
|
||||||
|
|
||||||
|
//Go through each token and evaluate it as necessary
|
||||||
|
foreach (token_get_all($contents) as $token) {
|
||||||
|
|
||||||
|
//If this token is the namespace declaring, then flag that the next tokens will be the namespace name
|
||||||
|
if (is_array($token) && $token[0] == T_NAMESPACE) {
|
||||||
|
$getting_namespace = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//If this token is the class declaring, then flag that the next tokens will be the class name
|
||||||
|
if (is_array($token) && $token[0] == T_CLASS) {
|
||||||
|
$getting_class = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//While we're grabbing the namespace name...
|
||||||
|
if ($getting_namespace === true) {
|
||||||
|
|
||||||
|
//If the token is a string or the namespace separator...
|
||||||
|
if (is_array($token) && in_array($token[0], [T_STRING, T_NS_SEPARATOR])) {
|
||||||
|
|
||||||
|
//Append the token's value to the name of the namespace
|
||||||
|
$namespace .= $token[1];
|
||||||
|
} elseif ($token === ';') {
|
||||||
|
|
||||||
|
//If the token is the semicolon, then we're done with the namespace declaration
|
||||||
|
$getting_namespace = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//While we're grabbing the class name...
|
||||||
|
if ($getting_class === true) {
|
||||||
|
|
||||||
|
//If the token is a string, it's the name of the class
|
||||||
|
if (is_array($token) && $token[0] == T_STRING) {
|
||||||
|
|
||||||
|
//Store the token's value as the class name
|
||||||
|
$class = $token[1];
|
||||||
|
|
||||||
|
//Got what we need, stope here
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Build the fully-qualified class name and return it
|
||||||
|
return $namespace ? $namespace . '\\' . $class : $class;
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user