作者 Karson

新增插件的多语言包

新增附件删除的行为
新增命令行安装时的参数配置
新增一键生成菜单时忽略未启用软删除的方法
修复在iOS下列表中列字段过多未启用卡片视图的BUG
修复会员管理中规则管理分页的BUG
修复导航菜单隐藏后仍显示的BUG
优化数据列表在移动端的显示
优化异常页面的显示

sudo: false
language: php
branches:
only:
- stable
cache:
directories:
- $HOME/.composer/cache
before_install:
- composer self-update
install:
- composer install --no-dev --no-interaction --ignore-platform-reqs
- zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Core.zip .
- composer require --update-no-dev --no-interaction "topthink/think-image:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-migration:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-captcha:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-mongo:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-worker:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-helper:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-queue:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-angular:^1.0"
- composer require --dev --update-no-dev --no-interaction "topthink/think-testing:^1.0"
- zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Full.zip .
script:
- php think unit
deploy:
provider: releases
api_key:
secure: TSF6bnl2JYN72UQOORAJYL+CqIryP2gHVKt6grfveQ7d9rleAEoxlq6PWxbvTI4jZ5nrPpUcBUpWIJHNgVcs+bzLFtyh5THaLqm39uCgBbrW7M8rI26L8sBh/6nsdtGgdeQrO/cLu31QoTzbwuz1WfAVoCdCkOSZeXyT/CclH99qV6RYyQYqaD2wpRjrhA5O4fSsEkiPVuk0GaOogFlrQHx+C+lHnf6pa1KxEoN1A0UxxVfGX6K4y5g4WQDO5zT4bLeubkWOXK0G51XSvACDOZVIyLdjApaOFTwamPcD3S1tfvuxRWWvsCD5ljFvb2kSmx5BIBNwN80MzuBmrGIC27XLGOxyMerwKxB6DskNUO9PflKHDPI61DRq0FTy1fv70SFMSiAtUv9aJRT41NQh9iJJ0vC8dl+xcxrWIjU1GG6+l/ZcRqVx9V1VuGQsLKndGhja7SQ+X1slHl76fRq223sMOql7MFCd0vvvxVQ2V39CcFKao/LB1aPH3VhODDEyxwx6aXoTznvC/QPepgWsHOWQzKj9ftsgDbsNiyFlXL4cu8DWUty6rQy8zT2b4O8b1xjcwSUCsy+auEjBamzQkMJFNlZAIUrukL/NbUhQU37TAbwsFyz7X0E/u/VMle/nBCNAzgkMwAUjiHM6FqrKKBRWFbPrSIixjfjkCnrMEPw=
file:
- ThinkPHP_Core.zip
- ThinkPHP_Full.zip
skip_cleanup: true
on:
tags: true
... ... @@ -29,6 +29,24 @@ class {%addonClassName%} extends Addons
}
/**
* 插件启用方法
* @return bool
*/
public function enable()
{
return true;
}
/**
* 插件禁用方法
* @return bool
*/
public function disable()
{
return true;
}
/**
* 实现钩子方法
* @return mixed
*/
... ...
... ... @@ -4,9 +4,6 @@ namespace {%controllerNamespace%};
use app\common\controller\Backend;
use think\Controller;
use think\Request;
/**
* {%tableComment%}
*
... ... @@ -28,8 +25,8 @@ class {%controllerName%} extends Backend
}
/**
* 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个方法
* 因此在当前控制器中可不用编写增删改查的代码,如果需要自己控制这部分逻辑
* 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法
* 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑
* 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改
*/
... ...
... ... @@ -18,17 +18,29 @@ class Install extends Command
protected function configure()
{
$config = Config::get('database');
$this
->setName('install')
->addOption('hostname', 'a', Option::VALUE_OPTIONAL, 'mysql hostname', $config['hostname'])
->addOption('hostport', 'o', Option::VALUE_OPTIONAL, 'mysql hostport', $config['hostport'])
->addOption('database', 'd', Option::VALUE_OPTIONAL, 'mysql database', $config['database'])
->addOption('prefix', 'r', Option::VALUE_OPTIONAL, 'table prefix', $config['prefix'])
->addOption('username', 'u', Option::VALUE_OPTIONAL, 'mysql username', $config['username'])
->addOption('password', 'p', Option::VALUE_OPTIONAL, 'mysql password', $config['password'])
->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override', FALSE)
->setDescription('New installation of FastAdmin');
}
protected function execute(Input $input, Output $output)
{
//覆盖安装
// 覆盖安装
$force = $input->getOption('force');
$hostname = $input->getOption('hostname');
$hostport = $input->getOption('hostport');
$database = $input->getOption('database');
$prefix = $input->getOption('prefix');
$username = $input->getOption('username');
$password = $input->getOption('password');
$installLockFile = __DIR__ . "/Install/install.lock";
if (is_file($installLockFile) && !$force)
... ... @@ -38,11 +50,13 @@ class Install extends Command
$sql = file_get_contents(__DIR__ . '/Install/fastadmin.sql');
$sql = str_replace("`fa_", "`{$prefix}", $sql);
// 先尝试能否自动创建数据库
$config = Config::get('database');
$pdo = new PDO("{$config['type']}:host={$config['hostname']}" . ($config['hostport'] ? ";port={$config['hostport']}" : ''), $config['username'], $config['password']);
$pdo = new PDO("{$config['type']}:host={$hostname}" . ($hostport ? ";port={$hostport}" : ''), $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->query("CREATE DATABASE IF NOT EXISTS `{$config['database']}` CHARACTER SET utf8 COLLATE utf8_general_ci;");
$pdo->query("CREATE DATABASE IF NOT EXISTS `{$database}` CHARACTER SET utf8 COLLATE utf8_general_ci;");
// 查询一次SQL,判断连接是否正常
Db::execute("SELECT 1");
... ... @@ -51,7 +65,22 @@ class Install extends Command
Db::getPdo()->exec($sql);
file_put_contents($installLockFile, 1);
$dbConfigFile = APP_PATH . 'database.php';
$config = @file_get_contents($dbConfigFile);
$callback = function($matches) use($hostname, $hostport, $username, $password, $database, $prefix) {
$field = $matches[1];
$replace = $$field;
if ($matches[1] == 'hostport' && $hostport == 3306)
{
$replace = '';
}
return "'{$matches[1]}'{$matches[2]}=>{$matches[3]}Env::get('database.{$matches[1]}', '{$replace}'),";
};
$config = preg_replace_callback("/'(hostname|database|username|password|hostport|prefix)'(\s+)=>(\s+)Env::get\((.*)\)\,/", $callback, $config);
// 写入数据库配置
file_put_contents($dbConfigFile, $config);
$output->info("Install Successed!");
}
... ...
... ... @@ -187,6 +187,19 @@ class Menu extends Command
//只匹配公共的方法
$methods = $reflector->getMethods(ReflectionMethod::IS_PUBLIC);
$classComment = $reflector->getDocComment();
//判断是否有启用软删除
$softDeleteMethods = ['destroy', 'restore', 'recyclebin'];
$withSofeDelete = false;
preg_match_all("/\\\$this\->model\s*=\s*model\('(\w+)'\);/", $classContent, $matches);
if (isset($matches[1]) && isset($matches[1][0]) && $matches[1][0])
{
\think\Request::instance()->module('admin');
$model = model($matches[1][0]);
if (in_array('trashed', get_class_methods($model)))
{
$withSofeDelete = true;
}
}
//忽略的类
if (stripos($classComment, "@internal") !== FALSE)
{
... ... @@ -216,7 +229,7 @@ class Menu extends Command
//导入中文语言包
\think\Lang::load(dirname(__DIR__) . DS . 'lang/zh-cn.php');
//先入菜单的数据
//先入菜单的数据
$pid = 0;
foreach ($controllerArr as $k => $v)
{
... ... @@ -248,6 +261,11 @@ class Menu extends Command
{
continue;
}
//未启用软删除时过滤相关方法
if (!$withSofeDelete && in_array($n->name, $softDeleteMethods))
{
continue;
}
//只匹配符合的方法
if (!preg_match('/^(\w+)' . Config::get('action_suffix') . '/', $n->name, $matchtwo))
{
... ...
... ... @@ -97,6 +97,7 @@ class Index extends Backend
}
$background = cdnurl(Config::get('fastadmin.login_background'));
$this->view->assign('background', $background);
$this->view->assign('title', __('Login'));
Hook::listen("login_init", $this->request);
return $this->view->fetch();
}
... ...
... ... @@ -78,16 +78,28 @@ class Attachment extends Backend
return $this->view->fetch();
}
/**
* 删除附件
* @param array $ids
*/
public function del($ids = "")
{
if ($ids)
{
$count = $this->model->destroy($ids);
if ($count)
\think\Hook::add('upload_delete', function($params) {
$attachmentFile = ROOT_PATH . '/public' . $params['url'];
if (is_file($attachmentFile))
{
@unlink($attachmentFile);
}
});
$attachmentlist = $this->model->where('id', 'in', $ids)->select();
foreach ($attachmentlist as $attachment)
{
\think\Hook::listen("upload_after", $this);
$this->success();
\think\Hook::listen("upload_delete", $attachment);
$attachment->delete();
}
$this->success();
}
$this->error(__('Parameter %s can not be empty', 'ids'));
}
... ...
... ... @@ -97,6 +97,7 @@ return [
'Create time' => '创建时间',
'Update time' => '更新时间',
'Flag' => '标志',
'Drag to sort' => '拖动进行排序',
'Redirect now' => '立即跳转',
'Common search' => '普通搜索',
'Search %s' => '搜索 %s',
... ...
... ... @@ -4,6 +4,7 @@ return [
'Id' => 'ID',
'Pid' => '父ID',
'Type' => '栏目类型',
'All' => '全部',
'Image' => '图片',
'Keywords' => '关键字',
'Description' => '描述',
... ...
... ... @@ -403,7 +403,7 @@ class Auth extends \fast\Auth
$select_id = 0;
$pinyin = new \Overtrue\Pinyin\Pinyin('Overtrue\Pinyin\MemoryFileDictLoader');
// 必须将结果集转换为数组
$ruleList = collection(model('AuthRule')->where('ismenu', 1)->order('weigh', 'desc')->cache("__menu__")->select())->toArray();
$ruleList = collection(model('AuthRule')->where('status', 'normal')->where('ismenu', 1)->order('weigh', 'desc')->cache("__menu__")->select())->toArray();
foreach ($ruleList as $k => &$v)
{
if (!in_array($v['name'], $userRule))
... ...
... ... @@ -41,7 +41,7 @@
<div class="box-body box-profile">
<div class="profile-avatar-container">
<img class="profile-user-img img-responsive img-circle plupload" src="{$admin.avatar}" alt="">
<img class="profile-user-img img-responsive img-circle plupload" src="__CDN__{$admin.avatar}" alt="">
<div class="profile-avatar-text img-circle">{:__('Click to edit')}</div>
<button id="plupload-avatar" class="plupload" data-input-id="c-avatar"><i class="fa fa-upload"></i> {:__('Upload')}</button>
</div>
... ...
... ... @@ -3,11 +3,12 @@
namespace app\common\behavior;
use think\Config;
use think\Lang;
class Common
{
public function run(&$request)
public function moduleInit(&$request)
{
// 设置mbstring字符编码
mb_internal_encoding("UTF-8");
... ... @@ -53,4 +54,13 @@ class Common
}
}
public function addonBegin(&$request)
{
// 加载插件语言包
Lang::load([
APP_PATH . 'common' . DS . 'lang' . DS . $request->langset() . DS . 'addon' . EXT,
]);
$this->moduleInit($request);
}
}
... ...
<?php
return [
'addon %s not found' => '插件未找到',
'addon %s is disabled' => '插件已禁用',
'addon controller %s not found' => '插件控制器未找到',
'addon action %s not found' => '插件控制器方法未找到',
'addon can not be empty' => '插件不能为空',
];
... ...
... ... @@ -59,6 +59,55 @@ class Menu
*/
public static function delete($name)
{
$ids = self::getAuthRuleIdsByName($name);
if (!$ids)
{
return false;
}
AuthRule::destroy($ids);
return true;
}
/**
* 启用菜单
* @param string $name
* @return boolean
*/
public static function enable($name)
{
$ids = self::getAuthRuleIdsByName($name);
if (!$ids)
{
return false;
}
AuthRule::where('id', 'in', $ids)->update(['status' => 'normal']);
return true;
}
/**
* 禁用菜单
* @param string $name
* @return boolean
*/
public static function disable($name)
{
$ids = self::getAuthRuleIdsByName($name);
if (!$ids)
{
return false;
}
AuthRule::where('id', 'in', $ids)->update(['status' => 'hidden']);
return true;
}
/**
* 根据名称获取规则IDS
* @param string $name
* @return array
*/
public static function getAuthRuleIdsByName($name)
{
$ids = [];
$menu = AuthRule::getByName($name);
if ($menu)
{
... ... @@ -66,13 +115,8 @@ class Menu
$ruleList = collection(model('AuthRule')->order('weigh', 'desc')->field('id,pid,name')->select())->toArray();
// 构造菜单数据
$ids = Tree::instance()->init($ruleList)->getChildrenIds($menu['id'], true);
if ($ids)
{
AuthRule::destroy($ids);
}
return true;
}
return false;
return $ids;
}
}
... ...
<?php
$cdnurl = function_exists('config') ? config('view_replace_str.__CDN__') : '';
$publicurl = function_exists('config') ? config('view_replace_str.__PUBLIC__') : '/';
?>
<!DOCTYPE html>
<html>
<head>
... ... @@ -5,6 +9,7 @@
<title>发生错误</title>
<meta name="robots" content="noindex,nofollow" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<link rel="shortcut icon" href="<?php echo $cdnurl;?>/assets/img/favicon.ico" />
<style>
* {-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;}
html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,abbr,address,cite,code,del,dfn,em,img,ins,kbd,q,samp,small,strong,sub,sup,var,b,i,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,caption,article,aside,canvas,details,figcaption,figure,footer,header,hgroup,menu,nav,section,summary,time,mark,audio,video {margin:0;padding:0;border:0;outline:0;vertical-align:baseline;background:transparent;}
... ... @@ -52,7 +57,7 @@
<div class="content-container">
<div class="head-line">
<img src="/assets/img/error.svg" alt="" width="120" />
<img src="<?php echo $cdnurl;?>/assets/img/error.svg" alt="" width="120" />
</div>
<div class="subheader">
<?php echo htmlentities($message); ?>
... ... @@ -66,8 +71,8 @@
</div>
<div class="buttons-container">
<a href="<?php echo function_exists('url') ? url('/') : '/';?>">返回主页</a>
<a href="<?php echo function_exists('url') ? url('/') : '/';?>">反馈错误</a>
<a href="<?php echo $publicurl;?>">返回主页</a>
<a href="<?php echo $publicurl;?>">反馈错误</a>
</div>
</div>
</body>
... ...
... ... @@ -253,7 +253,7 @@ return [
//自动检测更新
'checkupdate' => false,
//版本号
'version' => '1.0.0.20180204_beta',
'version' => '1.0.0.20180222_beta',
'api_url' => 'http://api.fastadmin.net',
],
];
... ...
... ... @@ -19,8 +19,8 @@ return [
'module_init' => [
'app\\common\\behavior\\Common',
],
// 模块初始化
'addons_init' => [
// 插件开始
'addon_begin' => [
'app\\common\\behavior\\Common',
],
// 操作开始执行
... ...
... ... @@ -186,6 +186,8 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
Layer.closeAll();
Layer.alert(ret.msg);
}, function (data, ret) {
Controller.api.userinfo.set(null);
Layer.closeAll();
Layer.alert(ret.msg);
});
}
... ...
... ... @@ -37,7 +37,10 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
{field: 'status', title: __('Status'), formatter: Table.api.formatter.status},
{field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
]
]
],
pagination: false,
search: false,
commonSearch: false,
});
// 为表格绑定事件
... ...
... ... @@ -345,12 +345,16 @@
var that = this,
html = [];
html.push(sprintf('<div class="columns-%s pull-%s" style="margin-top:10px;">', this.options.buttonsAlign, this.options.buttonsAlign));
html.push(sprintf('<div class="columns-%s pull-%s" style="margin-top:10px;margin-bottom:10px;">', this.options.buttonsAlign, this.options.buttonsAlign));
html.push(sprintf('<button class="btn btn-default%s' + '" type="button" name="commonSearch" title="%s">', that.options.iconSize === undefined ? '' : ' btn-' + that.options.iconSize, that.options.formatCommonSearch()));
html.push(sprintf('<i class="%s %s"></i>', that.options.iconsPrefix, that.options.icons.commonSearchIcon))
html.push('</button></div>');
that.$toolbar.prepend(html.join(''));
if (that.$toolbar.find(".pull-right").size() > 0) {
$(html.join('')).insertBefore(that.$toolbar.find(".pull-right:first"));
} else {
that.$toolbar.append(html.join(''));
}
initCommonSearch(that.columns, that);
... ...
... ... @@ -9432,12 +9432,16 @@ return d.keepInvalid=a,l},l.datepickerInput=function(a){if(0===arguments.length)
var that = this,
html = [];
html.push(sprintf('<div class="columns-%s pull-%s" style="margin-top:10px;">', this.options.buttonsAlign, this.options.buttonsAlign));
html.push(sprintf('<div class="columns-%s pull-%s" style="margin-top:10px;margin-bottom:10px;">', this.options.buttonsAlign, this.options.buttonsAlign));
html.push(sprintf('<button class="btn btn-default%s' + '" type="button" name="commonSearch" title="%s">', that.options.iconSize === undefined ? '' : ' btn-' + that.options.iconSize, that.options.formatCommonSearch()));
html.push(sprintf('<i class="%s %s"></i>', that.options.iconsPrefix, that.options.icons.commonSearchIcon))
html.push('</button></div>');
that.$toolbar.prepend(html.join(''));
if (that.$toolbar.find(".pull-right").size() > 0) {
$(html.join('')).insertBefore(that.$toolbar.find(".pull-right:first"));
} else {
that.$toolbar.append(html.join(''));
}
initCommonSearch(that.columns, that);
... ... @@ -9667,6 +9671,10 @@ define('table',['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstr
defaults = defaults ? defaults : {};
columnDefaults = columnDefaults ? columnDefaults : {};
locales = locales ? locales : {};
// 如果是iOS设备则启用卡片视图
if (navigator.userAgent.match(/(iPod|iPhone|iPad)/)) {
Table.defaults.cardView = true;
}
// 写入bootstrap-table默认配置
$.extend(true, $.fn.bootstrapTable.defaults, Table.defaults, defaults);
// 写入bootstrap-table column配置
... ... @@ -10021,13 +10029,13 @@ define('table',['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstr
var buttons = $.extend([], this.buttons || []);
if (options.extend.dragsort_url !== '') {
buttons.push({name: 'dragsort', icon: 'fa fa-arrows', classname: 'btn btn-xs btn-primary btn-dragsort'});
buttons.push({name: 'dragsort', icon: 'fa fa-arrows', title: __('Drag to sort'), classname: 'btn btn-xs btn-primary btn-dragsort'});
}
if (options.extend.edit_url !== '') {
buttons.push({name: 'edit', icon: 'fa fa-pencil', classname: 'btn btn-xs btn-success btn-editone', url: options.extend.edit_url});
buttons.push({name: 'edit', icon: 'fa fa-pencil', title: __('Edit'), classname: 'btn btn-xs btn-success btn-editone', url: options.extend.edit_url});
}
if (options.extend.del_url !== '') {
buttons.push({name: 'del', icon: 'fa fa-trash', classname: 'btn btn-xs btn-danger btn-delone'});
buttons.push({name: 'del', icon: 'fa fa-trash', title: __('Del'), classname: 'btn btn-xs btn-danger btn-delone'});
}
return Table.api.buttonlink(this, buttons, value, row, index, 'operate');
},
... ...
... ... @@ -69,6 +69,10 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
defaults = defaults ? defaults : {};
columnDefaults = columnDefaults ? columnDefaults : {};
locales = locales ? locales : {};
// 如果是iOS设备则启用卡片视图
if (navigator.userAgent.match(/(iPod|iPhone|iPad)/)) {
Table.defaults.cardView = true;
}
// 写入bootstrap-table默认配置
$.extend(true, $.fn.bootstrapTable.defaults, Table.defaults, defaults);
// 写入bootstrap-table column配置
... ... @@ -423,13 +427,13 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
var buttons = $.extend([], this.buttons || []);
if (options.extend.dragsort_url !== '') {
buttons.push({name: 'dragsort', icon: 'fa fa-arrows', classname: 'btn btn-xs btn-primary btn-dragsort'});
buttons.push({name: 'dragsort', icon: 'fa fa-arrows', title: __('Drag to sort'), classname: 'btn btn-xs btn-primary btn-dragsort'});
}
if (options.extend.edit_url !== '') {
buttons.push({name: 'edit', icon: 'fa fa-pencil', classname: 'btn btn-xs btn-success btn-editone', url: options.extend.edit_url});
buttons.push({name: 'edit', icon: 'fa fa-pencil', title: __('Edit'), classname: 'btn btn-xs btn-success btn-editone', url: options.extend.edit_url});
}
if (options.extend.del_url !== '') {
buttons.push({name: 'del', icon: 'fa fa-trash', classname: 'btn btn-xs btn-danger btn-delone'});
buttons.push({name: 'del', icon: 'fa fa-trash', title: __('Del'), classname: 'btn btn-xs btn-danger btn-delone'});
}
return Table.api.buttonlink(this, buttons, value, row, index, 'operate');
},
... ...