Merge branch 'develop' into 1.x

# Conflicts:
#	README.md
#	application/admin/view/index/index.html
#	application/api/controller/User.php
#	application/config.php
#	application/index/view/common/script.html
#	bower.json
#	composer.json
#	public/assets/js/require-backend.min.js
#	public/assets/js/require-frontend.min.js
#	public/assets/js/require-table.js
pull/477/MERGE
Karson 2024-11-28 18:23:42 +08:00
commit 516c18116a
23 changed files with 104 additions and 60 deletions

View File

@ -201,16 +201,20 @@ class Addon extends Command
new \RecursiveDirectoryIterator($addonDir), \RecursiveIteratorIterator::LEAVES_ONLY
);
$addonDir = str_replace(DS, '/', $addonDir);
$excludeDirRegex = "/\/(\.git|\.svn|\.vscode|\.idea|unpackage)\//i";
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);
}
$filePath = str_replace(DS, '/', $file->getPathname());
if ($file->isDir() || preg_match($excludeDirRegex, $filePath))
continue;
$relativePath = substr($filePath, strlen($addonDir));
if (!in_array($file->getFilename(), ['.DS_Store', 'Thumbs.db'])) {
$zip->addFile($filePath, $relativePath);
}
}
$zip->close();
$output->info("Package Resource Path:" . $addonFile);
$output->info("Package Successed!");
break;
case 'move':

View File

@ -920,6 +920,7 @@ class Crud extends Command
$replace = '\'{"custom[type]":"' . $table . '"}\'';
} elseif ($selectpageController == 'admin') {
$attrArr['data-source'] = 'auth/admin/selectpage';
$attrArr['data-field'] = 'nickname';
} elseif ($selectpageController == 'user') {
$attrArr['data-source'] = 'user/user/index';
$attrArr['data-field'] = 'nickname';

View File

@ -1,7 +1,7 @@
public function {%methodName%}($value, $data)
{
$value = $value ? $value : (isset($data['{%field%}']) ? $data['{%field%}'] : '');
$value = $value ?: ($data['{%field%}'] ?? '');
$valueArr = explode(',', $value);
$list = $this->{%listMethodName%}();
return implode(',', array_intersect_key($list, array_flip($valueArr)));

View File

@ -1,6 +1,6 @@
public function {%methodName%}($value, $data)
{
$value = $value ? $value : (isset($data['{%field%}']) ? $data['{%field%}'] : '');
$value = $value ?: ($data['{%field%}'] ?? '');
return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value;
}

View File

@ -1,7 +1,7 @@
public function {%methodName%}($value, $data)
{
$value = $value ? $value : (isset($data['{%field%}']) ? $data['{%field%}'] : '');
$value = $value ?: ($data['{%field%}'] ?? '');
$list = $this->{%listMethodName%}();
return isset($list[$value]) ? $list[$value] : '';
return $list[$value] ?? '';
}

View File

@ -1,7 +1,7 @@
public function {%methodName%}($value, $data)
{
$value = $value ? $value : (isset($data['{%field%}']) ? $data['{%field%}'] : '');
$value = $value ?: ($data['{%field%}'] ?? '');
$list = $this->{%listMethodName%}();
return isset($list[$value]) ? $list[$value] : '';
return $list[$value] ?? '';
}

View File

@ -214,22 +214,27 @@ class Install extends Command
$adminFile = ROOT_PATH . 'public' . DS . 'admin.php';
// 数据库配置文件
$dbConfigFile = APP_PATH . 'database.php';
$dbConfigText = @file_get_contents($dbConfigFile);
$envSampleFile = ROOT_PATH . '.env.sample';
$envFile = ROOT_PATH . '.env';
if (!file_exists($envFile)) {
if (!copy($envSampleFile, $envFile)) {
throw new Exception(__('Failed to copy %s to %s', '.env.sample', '.env'));
}
}
$envText = @file_get_contents($envFile);
$callback = function ($matches) use ($mysqlHostname, $mysqlHostport, $mysqlUsername, $mysqlPassword, $mysqlDatabase, $mysqlPrefix) {
$field = "mysql" . ucfirst($matches[1]);
$replace = $$field;
if ($matches[1] == 'hostport' && $mysqlHostport == 3306) {
$replace = '';
}
return "'{$matches[1]}'{$matches[2]}=>{$matches[3]}Env::get('database.{$matches[1]}', '{$replace}'),";
return "{$matches[1]} = {$replace}";
};
$dbConfigText = preg_replace_callback("/'(hostname|database|username|password|hostport|prefix)'(\s+)=>(\s+)Env::get\((.*)\)\,/", $callback, $dbConfigText);
$envText = preg_replace_callback("/(hostname|database|username|password|hostport|prefix)\s*=\s*(.*)/", $callback, $envText);
// 检测能否成功写入数据库配置
$result = @file_put_contents($dbConfigFile, $dbConfigText);
$result = @file_put_contents($envFile, $envText);
if (!$result) {
throw new Exception(__('The current permissions are insufficient to write the file %s', 'application/database.php'));
throw new Exception(__('The current permissions are insufficient to write the file %s', '.env'));
}
// 设置新的Token随机密钥key
@ -244,7 +249,7 @@ class Install extends Command
throw new Exception(__('The current permissions are insufficient to write the file %s', 'application/config.php'));
}
$avatar = request()->domain() . '/assets/img/avatar.png';
$avatar = '/assets/img/avatar.png';
// 变更默认管理员密码
$adminPassword = $adminPassword ? $adminPassword : Random::alnum(8);
$adminEmail = $adminEmail ? $adminEmail : "admin@admin.com";

View File

@ -21,7 +21,6 @@ use think\Validate;
*/
class Ajax extends Backend
{
protected $noNeedLogin = ['lang'];
protected $noNeedRight = ['*'];
protected $layout = '';
@ -207,7 +206,6 @@ class Ajax extends Backend
$type = $this->request->request("type");
switch ($type) {
case 'all':
// no break
case 'content':
//内容缓存
rmdirs(CACHE_PATH, false);
@ -215,18 +213,21 @@ class Ajax extends Backend
if ($type == 'content') {
break;
}
// no break
case 'template':
// 模板缓存
rmdirs(TEMP_PATH, false);
if ($type == 'template') {
break;
}
// no break
case 'addons':
// 插件缓存
Service::refresh();
if ($type == 'addons') {
break;
}
// no break
case 'browser':
// 浏览器缓存
// 只有生产环境下才修改
@ -323,5 +324,4 @@ class Ajax extends Backend
$response = Response::create($data, '', 200, $header);
return $response;
}
}

View File

@ -7,6 +7,7 @@ return [
'Mobile' => '手机',
'Email' => '邮箱',
'Password' => '密码',
'Mobile' => '手机号',
'Sign up' => '注 册',
'Sign in' => '登 录',
'Sign out' => '退 出',

View File

@ -30,6 +30,7 @@ return [
'You\'ve logged in, do not login again' => '你已经登录,无需重复登录',
'Username or password can not be empty' => '用户名密码不能为空',
'Username or password is incorrect' => '用户名或密码不正确',
'Username must be 3 to 30 characters' => '用户名只能由3-30位数字、字母、下划线组合',
'Username is incorrect' => '用户名不正确',
'Password is incorrect' => '密码不正确',
'Admin is forbidden' => '管理员已经被禁止登录',

View File

@ -1 +1 @@
<script src="__CDN__/assets/js/require{$Think.config.app_debug?'':'.min'}.js" data-main="__CDN__/assets/js/require-backend{$Think.config.app_debug?'':'.min'}.js?v={$site.version|htmlentities}"></script>
<script src="__CDN__/assets/js/require.min.js" data-main="__CDN__/assets/js/require-backend{$Think.config.app_debug?'':'.min'}.js?v={$site.version|htmlentities}"></script>

View File

@ -46,9 +46,9 @@
{/foreach}
</select>
</div>
<button type="button" id="faupload-local" class="btn btn-primary faupload" data-input-id="c-local" data-multiple="true" data-preview-id="p-local" data-url="{:url('ajax/upload')}"><i class="fa fa-upload"></i> {:__("Upload to local")}</button>
<button type="button" id="faupload-local" class="btn btn-primary faupload" data-input-id="c-local" data-multiple="true" data-preview-id="p-local" data-url="{:url('ajax/upload')}" data-cdnurl=""><i class="fa fa-upload"></i> {:__("Upload to local")}</button>
{if $config.upload.chunking}
<button type="button" id="faupload-local-chunking" class="btn btn-primary faupload" data-chunking="true" data-maxsize="1gb" data-input-id="c-local" data-multiple="true" data-preview-id="p-local" data-url="{:url('ajax/upload')}"><i class="fa fa-upload"></i> {:__("Upload to local by chunk")}</button>
<button type="button" id="faupload-local-chunking" class="btn btn-primary faupload" data-chunking="true" data-maxsize="1gb" data-input-id="c-local" data-multiple="true" data-preview-id="p-local" data-url="{:url('ajax/upload')}" data-cdnurl=""><i class="fa fa-upload"></i> {:__("Upload to local by chunk")}</button>
{/if}
</div>
</div>

View File

@ -19,10 +19,8 @@ class Log extends AbstractLogger
* @param array $context
*
* @return void
*
* @throws \Psr\Log\InvalidArgumentException
*/
public function log($level, $message, array $context = array())
public function log($level, $message, array $context = [])
{
\think\Log::write($message);
}

View File

@ -219,7 +219,7 @@ class Config extends Model
}
file_put_contents(
CONF_PATH . 'extra' . DS . 'site.php',
'<?php' . "\n\nreturn " . var_export_short($config) . ";\n"
'<?php' . "\n\nreturn " . var_export($config, true) . ";\n"
);
return true;
}

View File

@ -26,6 +26,10 @@ return [
* 是否支持批量上传
*/
'multiple' => false,
/**
* 上传超时时长这里仅用于JS上传超时控制
*/
'timeout' => 60000,
/**
* 是否支持分片上传
*/

View File

@ -11,11 +11,11 @@ class Http
/**
* 发送一个POST请求
* @param string $url 请求URL
* @param array $params 请求参数
* @param array $options 扩展参数
* @param array $params 请求参数
* @param array $options 扩展参数
* @return mixed|string
*/
public static function post($url, $params = [], $options = [])
public static function post(string $url, array $params = [], array $options = [])
{
$req = self::sendRequest($url, $params, 'POST', $options);
return $req['ret'] ? $req['msg'] : '';
@ -24,11 +24,11 @@ class Http
/**
* 发送一个GET请求
* @param string $url 请求URL
* @param array $params 请求参数
* @param array $options 扩展参数
* @param array $params 请求参数
* @param array $options 扩展参数
* @return mixed|string
*/
public static function get($url, $params = [], $options = [])
public static function get(string $url, array $params = [], array $options = [])
{
$req = self::sendRequest($url, $params, 'GET', $options);
return $req['ret'] ? $req['msg'] : '';
@ -42,7 +42,7 @@ class Http
* @param mixed $options CURL的参数
* @return array
*/
public static function sendRequest($url, $params = [], $method = 'POST', $options = [])
public static function sendRequest(string $url, $params = [], string $method = 'POST', $options = []): array
{
$method = strtoupper($method);
$protocol = substr($url, 0, 5);
@ -108,7 +108,7 @@ class Http
* @param string $method 请求的方法
* @return boolean TRUE
*/
public static function sendAsyncRequest($url, $params = [], $method = 'POST')
public static function sendAsyncRequest(string $url, $params = [], string $method = 'POST'): bool
{
$method = strtoupper($method);
$method = $method == 'POST' ? 'POST' : 'GET';
@ -157,10 +157,10 @@ class Http
/**
* 发送文件到客户端
* @param string $file
* @param bool $delaftersend
* @param bool $exitaftersend
* @param bool $deleteAfterSend
* @param bool $exitAfterSend
*/
public static function sendToBrowser($file, $delaftersend = true, $exitaftersend = true)
public static function sendToBrowser(string $file, bool $deleteAfterSend = true, bool $exitAfterSend = true)
{
if (file_exists($file) && is_readable($file)) {
header('Content-Description: File Transfer');
@ -174,10 +174,10 @@ class Http
ob_clean();
flush();
readfile($file);
if ($delaftersend) {
if ($deleteAfterSend) {
unlink($file);
}
if ($exitaftersend) {
if ($exitAfterSend) {
exit;
}
}

View File

@ -14,7 +14,7 @@ class Random
* @param int $len 长度
* @return string
*/
public static function alnum($len = 6)
public static function alnum(int $len = 6): string
{
return self::build('alnum', $len);
}
@ -25,7 +25,7 @@ class Random
* @param int $len 长度
* @return string
*/
public static function alpha($len = 6)
public static function alpha(int $len = 6): string
{
return self::build('alpha', $len);
}
@ -36,7 +36,7 @@ class Random
* @param int $len 长度
* @return string
*/
public static function numeric($len = 4)
public static function numeric(int $len = 4): string
{
return self::build('numeric', $len);
}
@ -47,7 +47,7 @@ class Random
* @param int $len 长度
* @return string
*/
public static function nozero($len = 4)
public static function nozero(int $len = 4): string
{
return self::build('nozero', $len);
}
@ -58,7 +58,7 @@ class Random
* @param int $len 长度
* @return string
*/
public static function build($type = 'alnum', $len = 8)
public static function build(string $type = 'alnum', int $len = 8): string
{
switch ($type) {
case 'alpha':
@ -93,7 +93,7 @@ class Random
* 获取全球唯一标识
* @return string
*/
public static function uuid()
public static function uuid(): string
{
return sprintf(
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',

View File

@ -325,7 +325,7 @@ class Tree
'@url' => $childdata || !isset($value['@url']) ? "javascript:;" : $value['@url'],
'@addtabs' => $childdata || !isset($value['@url']) ? "" : (stripos($value['@url'], "?") !== false ? "&" : "?") . "ref=addtabs",
'@caret' => ($childdata && (!isset($value['@badge']) || !$value['@badge']) ? '<i class="fa fa-angle-left"></i>' : ''),
'@badge' => isset($value['@badge']) ? $value['@badge'] : '',
'@badge' => $value['@badge'] ?? '',
'@class' => ($selected ? ' active' : '') . ($disabled ? ' disabled' : '') . ($childdata ? ' treeview' . (config('fastadmin.show_submenu') ? ' treeview-open' : '') : ''),
);
$str .= strtr($nstr, $value);
@ -422,7 +422,7 @@ class Tree
{
$arr = [];
foreach ($data as $k => $v) {
$childlist = isset($v['childlist']) ? $v['childlist'] : [];
$childlist = $v['childlist'] ?? [];
unset($v['childlist']);
$v[$field] = $v['spacer'] . ' ' . $v[$field];
$v['haschild'] = $childlist ? 1 : 0;

View File

@ -165,7 +165,10 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template', 'cookie']
title: __('Operate'),
table: table,
formatter: Controller.api.formatter.operate,
align: 'right'
align: 'right',
cellStyle: function (value, row, index) {
return {css: {'min-width': '158px'}};
}
},
]
],
@ -238,6 +241,27 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template', 'cookie']
Layer.close(index);
});
return false;
} else if (ret && ret.code === -3) {
//插件目录发现影响全局的文件
Layer.open({
content: Template("conflicttpl", ret.data),
shade: 0.8,
area: area,
title: __('Warning'),
btn: [__('Continue install'), __('Cancel')],
end: function () {
},
yes: function (index) {
up.removeFile(file);
file.force = true;
up.uploadFile(file);
Layer.close(index);
}
});
} else {
Layer.alert(ret.msg, {title: __('Warning'), icon: 0});
}
});

View File

@ -180,7 +180,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
var iconfunc = function () {
Layer.open({
type: 1,
area: ['99%', '98%'], //宽高
area: ['80%', '80%'], //宽高
content: Template('chooseicontpl', {iconlist: iconlist})
});
};
@ -192,8 +192,8 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
});
$(document).on('click', ".btn-search-icon", function () {
if (iconlist.length == 0) {
$.get(Config.site.cdnurl + "/assets/libs/font-awesome/less/variables.less", function (ret) {
var exp = /fa-var-(.*):/ig;
$.get(Config.site.cdnurl + "/assets/libs/font-awesome/css/font-awesome.css", function (ret) {
var exp = /fa-(.*):before/ig;
var result;
while ((result = exp.exec(ret)) != null) {
iconlist.push(result[1]);

View File

@ -167,7 +167,7 @@ define(['jquery', 'bootstrap', 'backend', 'form', 'table'], function ($, undefin
{
field: 'operate', title: __('Operate'), width: 85, events: {
'click .btn-chooseone': function (e, value, row, index) {
Fast.api.close({url: row.url, multiple: multiple});
Fast.api.close($.extend({multiple: multiple}, row));
},
}, formatter: function () {
return '<a href="javascript:;" class="btn btn-danger btn-chooseone btn-xs"><i class="fa fa-check"></i> ' + __('Choose') + '</a>';

View File

@ -208,7 +208,7 @@
} else {
value = process ? process(obj.val()) : obj.val();
}
if (removeempty && (value == '' || value == null || ($.isArray(value) && value.length == 0)) && !sym.match(/null/i)) {
if (removeempty && (value === '' || value == null || ($.isArray(value) && value.length === 0)) && !sym.match(/null/i)) {
return true;
}
@ -268,7 +268,7 @@
return "Common search";
},
formatCommonSubmitButton: function () {
return "Submit";
return "Search";
},
formatCommonResetButton: function () {
return "Reset";

View File

@ -102,6 +102,7 @@ define(['jquery', 'bootstrap', 'upload', 'validator', 'validator-lang'], functio
//绑定select元素事件
if ($(".selectpicker", form).length > 0) {
require(['bootstrap-select', 'bootstrap-select-lang'], function () {
$.fn.selectpicker.Constructor.BootstrapVersion = '3';
$('.selectpicker', form).selectpicker();
$(form).on("reset", function () {
setTimeout(function () {
@ -294,6 +295,9 @@ define(['jquery', 'bootstrap', 'upload', 'validator', 'validator-lang'], functio
var url = Config.upload.fullmode ? Fast.api.cdnurl(data.url) : data.url;
$("#" + input_id).val(url).trigger("change").trigger("validate");
}
// 触发选择文件自定义事件
button.trigger("fa.event.selectedfile", data);
}
});
return false;
@ -567,7 +571,9 @@ define(['jquery', 'bootstrap', 'upload', 'validator', 'validator-lang'], functio
}
};
// @formatter:on
var $disabledElements = form.find(':disabled').removeAttr('disabled');
var dataArr = form.serializeArray(), dataObj = {}, fieldName, fieldValue;
$disabledElements.attr('disabled', 'disabled');
$(dataArr).each(function (i, field) {
fieldName = field.name;
fieldValue = field.value;