This commit is contained in:
孟凡懂 2022-06-04 17:38:43 +08:00
commit 5b724e6c41
2750 changed files with 623402 additions and 0 deletions

11
.bowerrc Normal file
View File

@ -0,0 +1,11 @@
{
"directory": "public/assets/libs",
"ignoredDependencies": [
"es6-promise",
"file-saver",
"html2canvas",
"jspdf",
"jspdf-autotable",
"pdfmake"
]
}

16
.gitignore vendored Normal file
View 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
View File

@ -0,0 +1 @@

1
addons/.htaccess Normal file
View File

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

1
addons/command/.addonrc Normal file
View 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"]}

View 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;
}
}

View File

@ -0,0 +1,4 @@
<?php
return [
];

View 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
View 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

View 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 = '在线命令表';

View 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
View 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"]}

View 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);
}
}
}

View File

@ -0,0 +1,4 @@
<?php
return [
];

View 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];
}
}

View 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
View 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

View 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='定时任务日志表';

View 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);
}
}

View 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
}
}

View 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;
}
}

View 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;
}
}

View 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];
}
}

View 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);
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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
View 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"]}

View 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');
}
}

View 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' => '',
),
);

View 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
View 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

View 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
View 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"]}

View 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
View 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
});
};
});
}

View File

@ -0,0 +1,5 @@
<?php
return [
];

View 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
View 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
View 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
View 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
View 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
View 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' => '',
],
];

View 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();
}
}

View 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
View 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

File diff suppressed because it is too large Load Diff

View 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 -->

View 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 -->

View 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 -->

File diff suppressed because one or more lines are too long

View 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 &copy; {$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
View 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
View 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
View 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
View 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' => '',
],
];

View File

@ -0,0 +1,6 @@
<?php
return array (
'table_name' => 'fa_import_log',
'self_path' => '',
'update_data' => '',
);

View 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
View 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
View 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
View 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
View 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
View 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' => ''
// ],
];

View 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
View 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
View 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
View 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
View 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
View 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' => '',
),
);

View 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
View 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
View 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"]}

View 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
View 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;
});
});
}
}
});

View 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

View File

@ -0,0 +1,4 @@
({
optimizeCss: "default",
optimize: "uglify"
})

View 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

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,4 @@
<?php
return [
];

10
addons/simditor/info.ini Normal file
View 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

View 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;
}
}

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View 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;
}));

View 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;
}));

File diff suppressed because it is too large Load Diff

View 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
View 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
View 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
View 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
View 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
View File

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

View 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();
}
}
}

View 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';
}
}

View 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;
}
}

View 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' => ''
],
];

View File

@ -0,0 +1,15 @@
<?php
namespace addons\{%addon%}\controller;
use think\addons\Controller;
class Index extends Controller
{
public function index()
{
$this->error("当前插件暂无前台页面");
}
}

View File

@ -0,0 +1,7 @@
name = {%name%}
title = 插件名称{%name%}
intro = 插件介绍
author = yourname
website = https://www.fastadmin.net
version = 1.0.0
state = 1

View 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