更新:迁移cms基本信息,初始化超宇项目

virtual
zwesy 2020-11-25 09:07:06 +08:00
parent 10e948a980
commit 2024c80bbf
892 changed files with 123572 additions and 0 deletions

32
LICENSE.txt Normal file
View File

@ -0,0 +1,32 @@
ThinkPHP遵循Apache2开源协议发布并提供免费使用。
版权所有Copyright © 2006-2016 by ThinkPHP (http://thinkphp.cn)
All rights reserved。
ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
Apache Licence是著名的非盈利开源组织Apache采用的协议。
该协议和BSD类似鼓励代码共享和尊重原作者的著作权
允许代码修改,再作为开源或商业软件发布。需要满足
的条件:
1 需要给代码的用户一份Apache Licence
2 如果你修改了代码,需要在被修改的文件中说明;
3 在延伸的代码中(修改和有源代码衍生的代码中)需要
带有原来代码中的协议,商标,专利声明和其他原来作者规
定需要包含的说明;
4 如果再发布的产品中包含一个Notice文件则在Notice文
件中需要带有本协议内容。你可以在Notice中增加自己的
许可但不可以表现为对Apache Licence构成更改。
具体的协议参考http://www.apache.org/licenses/LICENSE-2.0
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

7
README.md Normal file
View File

@ -0,0 +1,7 @@
本CMS基于ThinkPHP 6.0.3开发
> 运行环境要求PHP7.1+。
> MySql版本使用的是5.7.21
> 富文本编辑器选择wangEditorv3.1.1 https://github.com/wangfupeng1988/wangEditor
> 上传插件选择filepond4.7.1 https://github.com/pqina/filepond
> 上传插件修改为使用layui组件库自带的

367
app/common.php Normal file
View File

@ -0,0 +1,367 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 流年 <liu21st@gmail.com>
// +----------------------------------------------------------------------
use think\exception\ClassNotFoundException;
// 应用公共文件
if (!function_exists('widget')) {
/**
* 渲染输出Widget
* @param string $name Widget名称
* @param array $data 传入的参数
* @return mixed
* milo 2019-05-08 从TP5.1代码中拿来修改的
*/
function widget($name, $data = [])
{
return action($name, $data, 'widget');
}
}
if (!function_exists('action')) {
/**
* 调用模块的操作方法 参数格式 [模块/控制器/]操作
* @param string $url 调用地址
* @param string|array $vars 调用参数 支持字符串和数组
* @param string $layer 要调用的控制层名称
* @param bool $appendSuffix 是否添加类名后缀
* @return mixed
* milo 2019-05-08 从TP5.1代码中拿来修改的
*/
function action($url, $vars = [], $layer = 'controller', $appendSuffix = false)
{
$info = pathinfo($url);
$action = $info['basename'];
$module = '.' != $info['dirname'] ? $info['dirname'] : request()->controller();
$class = controller($module, $layer);
if (is_scalar($vars)) {
if (strpos($vars, '=')) {
parse_str($vars, $vars);
} else {
$vars = [$vars];
}
}
return app()->invokeMethod([$class, $action . config('route.action_suffix')], $vars);
}
}
if (!function_exists('controller')) {
/**
* 实例化(分层)控制器 格式:[模块名/]控制器名
* @access public
* @param string $name 资源地址
* @param string $layer 控制层名称
* @param bool $appendSuffix 是否添加类名后缀
* @param string $empty 空控制器名称
* @return object
* @throws ClassNotFoundException
*
* milo 2019-05-08 从TP5.1代码中拿来修改的
*/
function controller($name, $layer = 'controller', $empty = '')
{
$class = parseClass($name, $layer);
if (class_exists($class)) {
return app()->make($class);
} elseif ($empty && class_exists($emptyClass = app()->parseClass($layer, $empty))) {
return app()->make($emptyClass);
}
throw new ClassNotFoundException('class not exists:' . $class, $class);
}
}
if (!function_exists('parseClass')) {
/**
* 解析模块和类名
* @access protected
* @param string $name 资源地址
* @param string $layer 验证层名称
* @param bool $appendSuffix 是否添加类名后缀
* @return array
*
* milo 2019-05-08 从TP5.1代码中拿来修改的
*/
function parseClass($name, $layer)
{
if (false !== strpos($name, '\\')) {
$class = $name;
} else {
if (strpos($name, '/')) {
$names = explode('/', $name, 2);
$name = $names[1];
}
$class = app()->parseClass($layer,$name);
}
return $class;
}
}
if (!function_exists('randomStr')) {
/**
* 获取随机字符串
* @param int $type 0:数字(默认)1全部2:小写字母;3:大写字母4字母
* @param int $len 字符串长度
* @return string
*/
function randomStr($type = 0,$len = 5)
{
$strPol = "0123456789";
if($type == 1) {
$strPol = "ABCDEFGHIJKLMOPQRSTUVWYZ0123456789abcdefghijklmopqrstuvwyz";
} elseif ($type == 2) {
$strPol = "abcdefghijklmopqrstuvwyz";
} elseif ($type == 3) {
$strPol = "ABCDEFGHIJKLMOPQRSTUVWYZ";
} elseif ($type == 4) {
$strPol = "ABCDEFGHIJKLMOPQRSTUVWYZabcdefghijklmopqrstuvwyz";
}
$max = strlen($strPol) - 1;
$str = '';
for ($i=0;$i<$len;$i++) {
$str .= $strPol[rand(0,$max)];
}
return $str;
}
}
if(!function_exists('isMobile')){
//判断访问终端是否为移动端
function isMobile()
{
// 如果有HTTP_X_WAP_PROFILE则一定是移动设备
if (isset($_SERVER['HTTP_X_WAP_PROFILE'])) {
return true;
}
// 如果via信息含有wap则一定是移动设备,部分服务商会屏蔽该信息
if (isset($_SERVER['HTTP_VIA'])) {
// 找不到为flase,否则为true
return stristr($_SERVER['HTTP_VIA'], "wap") ? true : false;
}
// 脑残法,判断手机发送的客户端标志,兼容性有待提高。其中'MicroMessenger'是电脑微信
if (isset($_SERVER['HTTP_USER_AGENT'])) {
$clientkeywords = ['nokia','sony','ericsson','mot','samsung','htc','sgh','lg','sharp','sie-','philips','panasonic','alcatel','lenovo','iphone','ipod','blackberry','meizu','android','netfront','symbian','ucweb','windowsce','palm','operamini','operamobi','openwave','nexusone','cldc','midp','wap','mobile','MicroMessenger'];
// 从HTTP_USER_AGENT中查找手机浏览器的关键字
if (preg_match("/(" . implode('|', $clientkeywords) . ")/i", strtolower($_SERVER['HTTP_USER_AGENT']))) {
return true;
}
}
// 协议法,因为有可能不准确,放到最后判断
if (isset ($_SERVER['HTTP_ACCEPT'])) {
// 如果只支持wml并且不支持html那一定是移动设备
// 如果支持wml和html但是wml在html之前则是移动设备
if ((strpos($_SERVER['HTTP_ACCEPT'], 'vnd.wap.wml') !== false) && (strpos($_SERVER['HTTP_ACCEPT'], 'text/html') === false || (strpos($_SERVER['HTTP_ACCEPT'], 'vnd.wap.wml') < strpos($_SERVER['HTTP_ACCEPT'], 'text/html')))) {
return true;
}
}
return false;
}
}
//根据栏目获取路径
if(!function_exists('getUri')){
function getUri($cate)
{
$url = '';
if(!empty($cate)){
if($cate['is_index']){
$url = '/';
}elseif(!empty($cate['url'])){
$url = $cate['url'];
}else{
$url = url($cate['template'].'/index', ['category_id' => $cate['id']]);
}
}
return $url;
}
}
//根据文件大小转换为文字
if(!function_exists('sizeToStr')){
function sizeToStr($size)
{
if(!is_numeric($size) || $size <= 0){
return '';
}
$size = $size / 1024;
if($size < 1024){
return sprintf("%.2fK", $size);
}
$size = $size / 1024;
if($size < 1024){
return sprintf("%.2fM", $size);
}
$size = $size / 1024;
return sprintf("%.2fG", $size);
}
}
//根据路径获取文件名
if(!function_exists('getKeyByPath')){
function getKeyByPath($path)
{
return substr($path, strrpos($path, '/')+1);
}
}
//富文本中提取图片路径
if(!function_exists('getImageUrlFromText')){
function getImageUrlFromText($content)
{
preg_match_all('/<img.*?src="(.*?)".*?>/is', $content, $imgs);
$data = [];
if(!empty($imgs) && !empty($imgs[1])){
foreach($imgs[1] as $img){
if(substr($img, 0, 4) != 'http'){
$key = getKeyByPath($img);
$data[$key] = $img;
}
}
}
return $data;
}
}
//富文本中提取视频路径
if(!function_exists('getVideoUrlFromText')){
function getVideoUrlFromText($content)
{
preg_match_all('/<video.*?src="(.*?)".*?>/is', $content, $videos);
$data = [];
if(!empty($videos) && !empty($videos[1])){
foreach($videos[1] as $video){
if(substr($video, 0, 4) != 'http'){
$key = getKeyByPath($video);
$data[$key] = $video;
}
}
}
return $data;
}
}
//获取目录下的所有文件
if(!function_exists('getAllFilesByPath')){
function getAllFilesByPath($path, $rootPath)
{
if(is_dir($path) && file_exists($path)){
$items = scandir($path);
$files = [];
foreach($items as $item){
if(substr($item, 0, 1) != '.' && strpos($item, '_') == false){
$itemPath = $path . '/' . $item;
if(is_file($itemPath)){
$size = filesize($itemPath);
$files[$item] = [
'path' => str_replace($rootPath, '/', $itemPath),
'realPath' => $itemPath,
'size' => $size,
'sizeStr' => sizeToStr($size),
'suffix' => strtolower(substr($item, strrpos($item, '.')+1)) //后缀
];
}elseif(is_dir($itemPath)){
$childFiles = getAllFilesByPath($itemPath, $rootPath);
if(!empty($childFiles)){
$files = array_merge($files, $childFiles);
}else{
rmdir($itemPath);
}
}
}
}
return $files;
}
return [];
}
}
//过滤get输入
if(!function_exists('getFilter')){
function getFilter($value){
$getFilter = "'|(and|or)\b.+?(>|<|=|in|like)|\/\*.+?\*\/|<\s*script\b|\bEXEC\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\s+(TABLE|DATABASE)";
$forArray = false;
if(is_array($value)){
$forFilter = implode($value);
$forArray = true;
}else{
$forFilter = $value;
}
if (preg_match("/".$getFilter."/is", $forFilter) == 1){
$value = $forArray ? [] : '';
}
return filterExp($value);
}
}
//过滤post录入
if(!function_exists($postFilter)){
function postFilter($value){
$postFilter = "\b(and|or)\b.{1,6}?(=|>|<|\bin\b|\blike\b)|\/\*.+?\*\/|<\s*script\b|\bEXEC\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\s+(TABLE|DATABASE)";
$forArray = false;
if(is_array($value)){
$forFilter = implode($value);
$forArray = true;
}else{
$forFilter = $value;
}
if (preg_match("/".$postFilter."/is", $forFilter) == 1){
$value = $forArray ? [] : '';
}
return filterExp($value);
}
}
//过滤cookie数据
if(!function_exists('cookieFilter')){
function cookieFilter($value){
$cookieFilter = "\b(and|or)\b.{1,6}?(=|>|<|\bin\b|\blike\b)|\/\*.+?\*\/|<\s*script\b|\bEXEC\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\s+(TABLE|DATABASE)";
$forArray = false;
if(is_array($value)){
$forFilter = implode($value);
$forArray = true;
}else{
$forFilter = $value;
}
if (preg_match("/".$cookieFilter."/is", $forFilter) == 1){
$value = $forArray ? [] : '';
}
return filterExp($value);
}
}
if(!function_exists('filterExp')){
function filterExp($value){
$filter = '/^EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT LIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN$/i';
$forArray = false;
if(is_array($value)){
$forFilter = implode($value);
$forArray = true;
}else{
$forFilter = $value;
}
if (preg_match($filter, $forFilter) == 1){
$value = $forArray ? [] : '';
}
return $value;
}
}
if(!function_exists('arrayHtmlFilter')){
function arrayHtmlFilter($arr) {
foreach ($arr as $k => $one) {
if(is_array($one)) {
$arr[$k] = arrayHtmlFilter($one);
} else {
$one = trim($one);
$arr[$k] = strip_tags($one);
}
}
return $arr;
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace app\controller;
use app\model\{Article as MArticle, Category};
class Article extends Base
{
//详情
public function detail($id=0)
{
if($id <= 0){
return $this->error('错误页面');
}
$article = MArticle::getById($id);
if(empty($article)){
return $this->error('无此文章');
}
MArticle::updateById($id, ['views' => $article['views'] + 1]);
$category = Category::getById($article['category_id']);
$prev = MArticle::getPrevArticleByIdAndCategoryId($id, $article['category_id']);
$next = MArticle::getNextArticleByIdAndCategoryId($id, $article['category_id']);
$keywords = $article['seo_keywords'] ? $article['seo_keywords'] : $this->system['seo_keywords'];
$description = $article['seo_description'] ? $article['seo_description'] : $this->system['seo_description'];
$this->setSeo($article['title'], $keywords, $description);
$this->data['article'] = $article;
$this->data['category'] = $category;
$this->data['categoryId'] = $category['id'];
$this->data['prev'] = $prev;
$this->data['next'] = $next;
return $this->view($category['template_detail'] ?? '');
}
//列表页
public function index()
{
$categoryId = input('param.category_id/d', 0);
if($categoryId <= 0){
return $this->error('错误页面');
}
$category = Category::getById($categoryId);
if(empty($category)){
return $this->error('错误页面');
}
$childCategory = Category::getChildrenByParentId($categoryId);
$description = $category['description'] ? $category['description'] : $this->system['seo_description'];
$this->setSeo($category['title'], $this->system['seo_keywords'], $description);
$this->data['items'] = MArticle::getListPageByCategory($categoryId, $category['number'] ? $category['number'] : 20);
$this->data['category'] = $category;
$this->data['categoryId'] = $categoryId;
return $this->view($category['template_list'] ?? '');
}
}

52
app/controller/Base.php Normal file
View File

@ -0,0 +1,52 @@
<?php
namespace app\controller;
use app\controller\BaseController;
use app\model\System;
/**
* 控制器基础类
*/
class Base extends BaseController
{
//需要向模板传递的值
protected $data = [];
//系统配置信息
protected $system = [];
// 初始化
protected function initialize()
{
$this->middleware = ['csrf'];
$this->system = System::getSystem();
$this->data['system'] = $this->system;
$this->setCsrfToken();
}
//设置SEO信息
protected function setSeo($title, $keywords, $description)
{
$this->data['seoTitle'] = $title;
$this->data['seoKeywords'] = $keywords;
$this->data['seoDescription'] = $description;
}
//设置默认SEO信息
protected function setDefaultSeo()
{
$this->data['seoTitle'] = $this->system['seo_title'];
$this->data['seoKeywords'] = $this->system['seo_keywords'];
$this->data['seoDescription'] = $this->system['seo_description'];
}
//模板
protected function view($template = '')
{
return view($template)->assign($this->data);
}
protected function setCsrfToken()
{
$this->data['_token'] = session('_token') ?? '';
}
}

View File

@ -0,0 +1,193 @@
<?php
declare (strict_types = 1);
namespace app\controller;
use think\{App, Validate};
use think\exception\ValidateException;
/**
* 控制器基础类
*/
abstract class BaseController
{
/**
* Request实例
* @var \think\Request
*/
protected $request;
/**
* 应用实例
* @var \think\App
*/
protected $app;
/**
* 是否批量验证
* @var bool
*/
protected $batchValidate = false;
/**
* 控制器中间件
* @var array
*/
protected $middleware = [];
/**
* 构造方法
* @access public
* @param App $app 应用对象
*/
public function __construct(App $app)
{
$this->app = $app;
$this->request = $this->app->request;
// 控制器初始化
$this->initialize();
}
// 初始化
protected function initialize()
{
}
/**
* 验证数据
* @access protected
* @param array $data 数据
* @param string|array $validate 验证器名或者验证规则数组
* @param array $message 提示信息
* @param bool $batch 是否批量验证
* @return array|string|true
* @throws ValidateException
*/
protected function validate(array $data, $validate, array $message = [], bool $batch = false)
{
if (is_array($validate)) {
$v = new Validate();
$v->rule($validate);
} else {
if (strpos($validate, '.')) {
// 支持场景
list($validate, $scene) = explode('.', $validate);
}
$class = false !== strpos($validate, '\\') ? $validate : $this->app->parseClass('validate', $validate);
$v = new $class();
if (!empty($scene)) {
$v->scene($scene);
}
}
$v->message($message);
// 是否批量验证
if ($batch || $this->batchValidate) {
$v->batch(true);
}
return $v->failException(true)->check($data);
}
/**
* 操作成功跳转的快捷方法
* @access protected
* @param mixed $msg 提示信息
* @param string $url 跳转的URL地址
* @param mixed $data 返回的数据
* @param integer $wait 跳转等待时间
* @param array $header 发送的Header信息
* @return void
*/
protected function success($msg = '', string $url = null, $data = '', int $wait = 3, array $header = [])
{
if (is_null($url) && isset($_SERVER["HTTP_REFERER"])) {
$url = $_SERVER["HTTP_REFERER"];
} elseif ($url) {
$url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : $this->app->route->buildUrl($url);
}
$result = [
'code' => 1,
'msg' => $msg,
'data' => $data,
'url' => $url,
'wait' => $wait,
];
return $this->redirect(url('error/jump',$result));
}
/**
* 操作错误跳转的快捷方法
* @access protected
* @param mixed $msg 提示信息
* @param string $url 跳转的URL地址
* @param mixed $data 返回的数据
* @param integer $wait 跳转等待时间
* @param array $header 发送的Header信息
* @return void
*/
protected function error($msg = '', string $url = null, $data = '', int $wait = 3)
{
if (is_null($url)) {
$referer = $_SERVER['HTTP_REFERER'] ?? null;
if (empty($referer)) {
$url = $this->request->isAjax() ? '' : '/';
} else {
$url = $this->request->isAjax() ? '' : 'javascript:history.back(-1);';
}
} elseif ($url) {
$url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : $this->app->route->buildUrl($url);
}
$result = [
'code' => 0,
'msg' => $msg,
'data' => $data,
'url' => $url,
'wait' => $wait,
];
return $this->redirect(url('error/jump', $result));
}
/**
* 返回封装后的API数据到客户端
* 以json格式抛出异常
* @access protected
* @param mixed $data 要返回的数据
* @param integer $code 返回的code
* @param mixed $msg 提示信息
* @param string $type 返回数据格式
* @param array $header 发送的Header信息
* @return void
*/
protected function json($code = 0, $msg = 'ok', $data= [])
{
$result = [
'code' => $code,
'msg' => $msg,
'time' => time(),
'data' => $data
];
return json($result);
}
/**
* URL重定向
* @access protected
* @param string $url 跳转的URL表达式
* @param array|integer $params 其它URL参数
* @param integer $code http code
* @param array $with 隐式传参
* @return void
*/
protected function redirect($url)
{
if(!is_string($url)){
$url = $url->__toString();
}
return redirect($url);
}
}

48
app/controller/Error.php Normal file
View File

@ -0,0 +1,48 @@
<?php
namespace app\controller;
class Error extends BaseController
{
public function __call($method, $args)
{
if(request()->isAjax()) {
return $this->json(404, 'error request!');
} else {
$referer = $_SERVER['HTTP_REFERER'] ?? null;
if (empty($referer)) {
$url = '/';
} else {
$domain = $this->request->domain();
$urlInfo = parse_url($referer);
$scheme = $urlInfo['scheme'] ?? '';
$requestSrc = '';
if (!empty($scheme)) {
$requestSrc = $scheme.'://'.($urlInfo['host'] ?? '');
}
if($domain != $requestSrc) {
$url = '/';
} else {
$url = 'javascript:history.back(-1);';
}
}
$result = [
'code' => 404,
'msg' => '无效请求! 没有找到相关资源',
'data' => [],
'url' => $url,
'wait' => 5,
];
return view('error/400')->assign($result);
}
}
public function jump()
{
$param = request()->param();
return view()->assign($param);
}
}

17
app/controller/Index.php Normal file
View File

@ -0,0 +1,17 @@
<?php
namespace app\controller;
use app\model\{Category, Block};
class Index extends Base
{
public function index()
{
$category = Category::getIndex();
$categoryId = $category['id'] ?? 0;
$this->data['categoryId'] = $categoryId;
$this->setSeo($this->system['seo_title'], $this->system['seo_keywords'], $this->system['seo_description']);
$this->data['blocks'] = Block::getByCategoryId($categoryId);
return $this->view();
}
}

25
app/controller/Page.php Normal file
View File

@ -0,0 +1,25 @@
<?php
namespace app\controller;
use app\model\{Category, Block};
class Page extends Base
{
public function index($categoryId)
{
$category = Category::getById($categoryId);
if ($category) {
$description = $category['description'] ? $category['description'] : $this->system['seo_description'];
$this->setSeo($category['title'], $this->system['seo_keywords'], $description);
} else {
return $this->error('错误页面');
}
$childCategory = Category::getChildrenByParentId($categoryId);
$this->data['categoryId'] = $categoryId;
$this->data['category'] = $category;
$this->data['childCategory'] = $childCategory;
$this->data['blocks'] = Block::getByCategoryId($categoryId);
return $this->view($category['template_detail']);
}
}

View File

@ -0,0 +1,217 @@
<?php
namespace app\controller\manager;
use app\model\{Article as MArticle, Category, System, Log};
use app\validate\Article as VArticle;
use think\exception\ValidateException;
/**
* 内容管理 - 文章管理
*/
class Article extends Base
{
//批量修改属性
public function attribute()
{
if($this->request->isPost()){
$ids = input('post.id/a');
if(empty($ids) || !is_array($ids)) {
return $this->json(2, '参数错误,请核对之后再操作!');
}
$data = [];
foreach(['top', 'hot', 'recommend'] as $key){
$val = input('post.'.$key, 0);
if(in_array($val, [1, 2])){
if($val == 1){
$data[$key] = 1;
}else{
$data[$key] = 0;
}
}
}
if(!empty($data)){
MArticle::whereIn('id', $ids)->update($data);
Log::write('article', 'attribute', '批量修改了文章属性涉及到的文章ID为' . implode(',', $ids));
}
return $this->json();
}
return $this->json(1, '非法请求!');
}
//批量删除
public function batchDel()
{
if ($this->request->isPost()) {
$ids = input('post.ids/a');
if(empty($ids) || !is_array($ids)) {
return $this->json(2, '参数错误,请核对之后再操作!');
}
$items = MArticle::getListByIds($ids);
if(!empty($items)){
$delIds = [];
foreach($items as $item){
$delIds[] = $item['id'];
}
MArticle::destroy($delIds);
Log::write('article', 'betchDel', '批量删除了文章涉及到的文章ID为' . implode(',', $delIds));
return $this->json();
}else{
return $this->json(3, '待删除文章列表为空');
}
}
return $this->json(1, '非法请求!');
}
//删除
public function del()
{
if ($this->request->isPost()) {
$id = input('post.id/d');
if(is_numeric($id) && $id > 0) {
$item = MArticle::getById($id);
if(!empty($item)){
MArticle::destroy($id);
Log::write('article', 'del', '删除文章ID' . $id . ',标题:' . $item['title']);
return $this->json();
}
return $this->json(3,'待删除文章不存在');
}
return $this->json(2, '参数错误,请核对之后再操作!');
}
return $this->json(1, '非法请求!');
}
//排序
public function sort()
{
if($this->request->isPost()){
$id = input('post.id/d');
$sort = input('post.sort');
$num = input('post.num/d', 1);
if($num <= 0){
$num = 1;
}
if(!in_array($sort, ['up', 'down'], true)){
return $this->json(2, '参数错误');
}
$item = MArticle::getById($id);
if(empty($item)){
return $this->json(3, '该文章信息不存在');
}
if($sort == 'up'){
$where = "category_id='{$item['category_id']}' and sort > {$item['sort']}";
$order = "sort asc";
}else{
$where = "category_id='{$item['category_id']}' and sort < {$item['sort']}";
$order = "sort desc";
}
$forSortItems = MArticle::getListByWhereAndOrder($where, $order, $num);
if(!empty($forSortItems)){
$updateData = [];
$forSortCount = count($forSortItems);
for($i = 0; $i < $forSortCount; $i++){
if($i == 0){
$updateData[] = [
'id' => $forSortItems[$i]['id'],
'sort' => $item['sort']
];
}else{
$updateData[] = [
'id' => $forSortItems[$i]['id'],
'sort' => $forSortItems[$i - 1]['sort']
];
}
}
$updateData[] = [
'id' => $item['id'],
'sort' => $forSortItems[$i - 1]['sort']
];
if(!empty($updateData)){
$model = new MArticle();
$model->saveAll($updateData);
$sortStr = $sort == 'up' ? '上移' : '下调';
Log::write('article', 'sort', "文章排序ID{$id} ,标题:{$item['title']}{$sortStr}{$num}");
return $this->json();
}
}
return $this->json(4, '无须调整排序!');
}
return $this->json(1, '非法请求!');
}
//编辑
public function edit()
{
if($this->request->isPost()){
$item = input('post.item/a');
$img = input('post.img');
$id = input('post.id/d');
$article = MArticle::getById($id);
if (empty($article)) {
return $this->json(1, '该文章不存在!');
}
if(!empty($img)){
$item['src'] = $img;
}
try {
validate(VArticle::class)->check($item);
$auth = session('auth');
$item['update_time'] = time();
$item['updated'] = $auth['userName'];
MArticle::updateById($id, $item);
Log::write('article', 'edit', "文章编辑ID{$id} ,标题:{$item['title']}");
return $this->json();
} catch (ValidateException $e) {
return $this->json(2, $e->getError());
}
}else{
$id = input('param.id');
$article = MArticle::getById($id);
$category = Category::getById($article['category_id']);
if($category['img_width'] && $category['img_height']){
$imgSize = $category['img_width'] . '像素 X ' . $category['img_height'] . '像素';
}else{
$imgSize = System::getArticleImageSize();
}
$this->data['item'] = $article;
$this->data['category'] = $category;
$this->data['imgSize'] = $imgSize;
return $this->view();
}
}
//添加
public function add()
{
if($this->request->isPost()){
$item = input('post.item/a');
$img = input('post.img');
if(!empty($img)){
$item['src'] = $img;
}
try {
validate(VArticle::class)->check($item);
$content = $item['content'] ?? '';
if(isset($item['content'])){
unset($item['content']);
}
$item['content'] = $content;
$article = MArticle::create($item);
Log::write('article', 'add', "文章新增ID{$article->id} ,标题:{$item['title']}");
return $this->json();
} catch (ValidateException $e) {
return $this->json(2, $e->getError());
}
}else{
$categoryId = input('param.category_id');
$category = Category::getById($categoryId);
if(count($category) > 0 && $category['img_width'] && $category['img_height']){
$imgSize = $category['img_width'] . '像素 X ' . $category['img_height'] . '像素';
}else{
$imgSize = System::getArticleImageSize();
}
$this->data['category'] = $category;
$this->data['imgSize'] = $imgSize;
return $this->view();
}
}
}

View File

@ -0,0 +1,153 @@
<?php
namespace app\controller\manager;
use think\facade\{Db, Config, Env};
use app\model\{Log as MLog};
use app\service\Tool;
class Backup extends Base
{
/**
* 因受mysql 配置参数net_buffer_length和max_allowed_packet大小的限制因此insert语句改为单条插入
* show VARIABLES like "%net_buffer_length%" 默认单条sql语句长度16k
* show VARIABLES like "%max_allowed_packet%" 默认单个sql文件最大容量
* sql 文件注释 -- + 空格之后填写注释内容
* 因根目录文件夹权限限制因此需要手动创建backup/data目录并赋予权限www用户组
*/
public function back()
{
ini_set('max_execution_time', 0);
ini_set("memory_limit",-1);
set_time_limit(0);
$path = Config::get('filesystem.disks.backup.root') . '/';
$dataBase = Env::get('database.database');
// 不要使用单引号,双引号中的变量可以解析,单引号就是绝对的字符串
$eol = "\r\n";
$eolB = "\r\n\r\n";
$info = '-- ------------------------------'.$eol;
$info .= '-- 日期: '.date('Y-m-d H:i:s',time()).$eol;
$info .= '-- MySQL --Database - '.$dataBase.$eol;
$info .= '-- ------------------------------'.$eol;
$info .= 'CREATE DATABASE IF NOT EXISTS `'.$dataBase.'` DEFAULT CHARACTER SET "utf8mb4" COLLATE "utf8mb4_general_ci";'.$eolB;
$info .= 'USE `'.$dataBase.'`;'.$eol;
if(is_dir($path)) {
if(!is_writable($path)) {
chmod($path, 0755);
}
} else {
mkdir($path, 0755, true);
}
$fileName = $path.$dataBase.'_'.date('YmdHis',time()).'.sql';
if(file_exists($fileName)) {
@unlink($fileName);
}
// 以追加的方式写入
file_put_contents($fileName, $info, FILE_APPEND);
$tables = Db::query('show tables');
foreach ($tables as $table) {
// 表结构
$tableName = $table['Tables_in_'.$dataBase];
$sqlTable = 'SHOW CREATE TABLE '.$tableName;
$res = Db::query($sqlTable);
$infoTable = '-- ------------------------------'.$eol;
$infoTable .= '-- Table structure for `'.$tableName.'`'.$eol;
$infoTable .= '-- ------------------------------'.$eolB;
$infoTable .= 'DROP TABLE IF EXISTS `'.$tableName.'`;'.$eolB;
$infoTable .= $res[0]['Create Table'].';'.$eolB;
// 表数据
$infoTable .= '-- ------------------------------'.$eol;
$infoTable .= '-- Data for the table `'.$tableName.'`'.$eol;
$infoTable .= '-- ------------------------------'.$eolB;
file_put_contents($fileName, $infoTable, FILE_APPEND);
$sqlData = 'select * from '.$tableName;
$data = Db::query($sqlData);
$count = count($data);
if ($count > 0) {
$dataStr = 'INSERT INTO `'.$tableName.'` VALUE ';
foreach ($data as $k => $item) {
$valStr = '(';
foreach ($item as $val) {
// 字符串转义
$val = addslashes($val);
$valStr .= '"'.$val.'", ';
}
// 去除最后的逗号和空格
$valStr = substr($valStr,0,strlen($valStr)-2);
// 限制单条sql语句在16K以内可根据mysql配置条件进行调整
$sqlLength = strlen($dataStr) + strlen($valStr);
if($sqlLength >= (1024 * 16 - 10)) {
$dataStr .= $valStr.');'.$eol;
file_put_contents($fileName, $dataStr, FILE_APPEND);
// 提前分段写入后需重置数据语句
if ($k <= ($count-1)) {
$dataStr = 'INSERT INTO `'.$tableName.'` VALUE ';
} else {
$dataStr = '';
}
} else {
if ($k < ($count-1)) {
$dataStr .= $valStr.'),'.$eol;
} else {
$dataStr .= $valStr.');'.$eolB;
}
}
}
file_put_contents($fileName, $dataStr, FILE_APPEND);
}
}
clearstatcache();
$backups = explode('/', $fileName);
$backupName = $backups[count($backups)-1];
$src = Config::get('filesystem.disks.backup.url') . '/';
return $this->json(0, '备份成功:' . $src . $backupName);
}
public function index()
{
$path = Config::get('filesystem.disks.backup.root') . '/';
$src = Config::get('filesystem.disks.backup.url') . '/';
$items = [];
if(is_dir($path)) {
if(!is_readable($path)) {
chmod($path,0755);
}
$files = scandir($path);
foreach ($files as $file) {
if($file != '.' && $file != '..') {
$ext = substr($file, -4);
if ($ext == '.sql') {
$creatTime = substr($file, -18, 14);
$items[] = [
'file' => $file,
'path' => $src . $file,
'time' =>is_numeric($creatTime) ? date('Y-m-d H:i:s', strtotime($creatTime)) : '',
];
}
}
}
}
clearstatcache();
$this->data['items'] = $items;
$this->data['backupPath'] = str_replace('\\','/', $path);
return $this->view();
}
public function del()
{
if (request()->isPost()) {
$filePath = input('post.id');
Tool::delFile($filePath);
MLog::write('backup', 'del', '删除了备份数据文件,文件路径为:' . $filePath);
return $this->json();
} else {
return $this->json(1, '非法请求');
}
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace app\controller\manager;
use app\controller\BaseController;
/**
* 控制器基础类
*/
class Base extends BaseController
{
protected $data = [];
protected function initialize()
{
$this->middleware = [
'csrf',
'auth' => [
'except' => ['manager.login/index']
]
];
$this->setCsrfToken();
$auth = session('auth');
$this->data['member'] = $auth;
$this->data['groupId'] = $auth['groupId'] ?? 0;
}
//变量赋值到模板
protected function view()
{
return view()->assign($this->data);
}
protected function error($msg = '', string $url = null, $data = '', int $wait = 3)
{
if (is_null($url)) {
$url = $this->request->isAjax() ? '' : 'javascript:history.back(-1);';
} elseif ($url) {
$url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : $this->app->route->buildUrl($url);
}
$result = [
'code' => 0,
'msg' => $msg,
'data' => $data,
'url' => $url,
'wait' => $wait,
];
return $this->redirect(url('manager.error/jump', $result));
}
protected function setCsrfToken()
{
$this->data['_token'] = session('_token') ?? '';
}
}

View File

@ -0,0 +1,235 @@
<?php
namespace app\controller\manager;
use app\model\{Category as MCategory, Model as MCModel, Log};
use app\validate\Category as VCategory;
use think\exception\ValidateException;
use think\facade\Cache;
class Category extends Base
{
//栏目列表
public function index()
{
$auth = session('auth');
$authCates = array_filter(explode(',', $auth['cates']));
if ($auth['groupId'] == 1) {
$cates = MCategory::getList();
} else {
$cates = MCategory::getList(true, $authCates);
}
$items = MCategory::getCates($cates);
$powerAdd = false;
$ruleNames = Cache::get('rule_names_'.$auth['groupId']);
if (is_array($ruleNames) && in_array('category/add', $ruleNames, true)) {
$powerAdd = true;
}
$this->data['items'] = $items;
$this->data['power_add'] = $powerAdd;
return $this->view();
}
//批量删除
public function batchDel()
{
if($this->request->isPost()){
$ids = input('post.ids/a');
if(is_array($ids)) {
$idsArr = $ids;
} else {
$idsArr = array_filter(explode(',', $ids));
}
if(count($idsArr) == 0) {
return $this->json(1, '无效请求,参数错误!');
}
if (MCategory::hasChildren($idsArr)) {
return $this->json(2, '需删除的栏目下存在下级栏目,不可删除。请检查核实后再操作!');
}
$categories = MCategory::getListByIds($idsArr);
if(!empty($categories)){
$hasIds = [];
foreach($categories as $cate){
$hasIds[] = $cate['id'];
}
MCategory::destroy($hasIds);
Log::write('category', 'betchDel', '批量删除了栏目涉及到的ID为' . implode(',', $hasIds));
return $this->json();
}
return $this->json(3, '删除失败!栏目不存在,请刷新页面后再试!');
}
return $this->json(1, '非法请求!');
}
/**
* 删除
*/
public function del()
{
if($this->request->isPost()){
$id = input('post.id/d');
if(is_numeric($id) && $id > 0) {
if(MCategory::hasChildren($id)){
return $this->json(4, '此栏目有下级栏目,不可删除。请检查核实后再操作!');
}
$cate = MCategory::getById($id);
if(!empty($cate)){
MCategory::destroy($id);
Log::write('category', 'del', '删除栏目ID' . $id . ',标题:' . $cate['title']);
return $this->json();
}
return $this->json(3, '删除失败!栏目不存在,请刷新页面后再试!');
} else {
return $this->json(2, '无效请求,参数错误!');
}
}
return $this->json(1, '非法请求!');
}
//排序
public function sort()
{
if($this->request->isPost()){
$id = input('post.id/d');
$sort = input('post.sort');
$num = input('post.num/d', 1);
if($num <= 0){
$num = 1;
}
if(!in_array($sort, ['up', 'down'], true)){
return $this->json(2, '参数错误');
}
$item = MCategory::getById($id);
if(empty($item)) {
return $this->json(3, '无此栏目!');
}
if($sort == 'up'){
$where = "parent_id='{$item['parent_id']}' and sort < {$item['sort']}";
$order = "sort desc";
}else{
$where = "parent_id='{$item['parent_id']}' and sort > {$item['sort']}";
$order = "sort asc";
}
$forSortItems = MCategory::getListByWhereAndOrder($where, $order, $num);
if(!empty($forSortItems)){
$updateData = [];
$forSortCount = count($forSortItems);
for($i = 0; $i < $forSortCount; $i++){
if($i == 0){
$updateData[] = [
'id' => $forSortItems[$i]['id'],
'sort' => $item['sort']
];
}else{
$updateData[] = [
'id' => $forSortItems[$i]['id'],
'sort' => $forSortItems[$i - 1]['sort']
];
}
}
$updateData[] = [
'id' => $item['id'],
'sort' => $forSortItems[$i - 1]['sort']
];
if(!empty($updateData)){
$model = new MCategory();
$model->saveAll($updateData);
$sortStr = $sort == 'up' ? '上移' : '下调';
Log::write('category', 'sort', "栏目排序ID{$id} ,标题:{$item['title']}{$sortStr}{$num}");
return $this->json();
}
}
return $this->json(4, '无须调整排序!');
}
return $this->json(1, '非法请求!');
}
/**
* 更新栏目数据
*/
public function edit()
{
if($this->request->isPost()){
$item = input('post.item/a');
$id = input('post.id');
$img = input('post.img');
if (count($item) > 0 && (is_numeric($id) === true && $id > 0)) {
try {
validate(VCategory::class)->check($item);
if(!empty($img)){
$item['src'] = $img;
}
MCategory::updateById($id, $item);
Log::write('category', 'edit', "栏目编辑ID{$id} ,标题:{$item['title']}");
return $this->json();
} catch (ValidateException $e) {
return $this->json(2, $e->getError());
}
}
return $this->json(1,'传入参数错误,请核对之后再操作!');
} else {
$id = input('param.id');
if (is_numeric($id) === true && $id > 0) {
$item = MCategory::getById($id);
if(empty($item)){
return $this->json(1,'参数错误,无此栏目!');
}
$this->data['item'] = $item;
$this->data['parent'] = MCategory::getById($item['parent_id']);
$this->data['models'] = MCModel::getList();
return $this->view();
}
return $this->json(1,'参数错误,请核对之后再操作!');
}
}
/**
* 栏目添加
*/
public function add()
{
if($this->request->isPost()){
$item = input('post.item/a');
$img = input('post.img');
if (is_array($item) === true && count($item) > 0) {
if (!empty($img)) {
$item['src'] = $img;
}
try {
validate(VCategory::class)->check($item);
if(!isset($item['number']) || $item['number'] <= 0){
$item['number'] = 20;
}
if($item['parent_id'] == 0){
$item['path'] = ',0,';
}else{
$parent = MCategory::getById($item['parent_id']);
if(empty($parent)){
$item['path'] = ',0,';
}else{
$item['path'] = $parent['path'] . $parent['id'] . ',';
}
}
$category = MCategory::create($item);
Log::write('category', 'add', "栏目新增ID{$category->id} ,标题:{$item['title']}");
return $this->json();
} catch (ValidateException $e) {
return $this->json(2,$e->getError());
}
}
return $this->json(1, '传入值错误,请核对之后再操作!');
} else {
$parentId = input('param.id')??input('param.parent_id');
if(empty($parentId)){
$parent = [];
}else{
$parent = MCategory::getById($parentId);
}
$this->data['parent'] = $parent;
$this->data['models'] = MCModel::getList();
return $this->view();
}
}
}

View File

@ -0,0 +1,173 @@
<?php
namespace app\controller\manager;
use app\model\{Category, Article, Block, Log, History};
class Content extends Base
{
//文章
public function article()
{
$categoryId = input('param.category_id');
$keyword = input('param.keyword');
$param = input('param.param/a', []);
$category = Category::getById($categoryId);
if(empty($category)){
return $this->error('无此栏目');
}
$list = Article::getList($categoryId, 20, $keyword, $param);
$this->data['list'] = $list;
$this->data['category'] = $category;
$this->data['keyword'] = $keyword;
$this->data['param'] = $param;
return $this->view();
}
//做页面跳转
public function index()
{
$items = Category::getList();
if(!empty($items)){
$items = Category::getCates($items);
}
if(!empty($items)){
$first = array_shift($items);
if(isset($first['children'])){
$childrenFirst = array_shift($first['children']);
$url = url('manager.content/'.$childrenFirst['manager'],['category_id' => $childrenFirst['id']]);
}else{
$url = url('manager.content/'.$first['manager'],['category_id' => $first['id']]);
}
if(!empty($url)){
return $this->redirect($url);
}
}else{
return $this->redirect(url('manager.category/add'));
}
}
//单页
public function page()
{
if($this->request->isAjax()){
$blocks = input('post.block/a'); //所有文本信息
$texts = input('post.text/a'); //所有富文本信息
$codes = input('post.code/a'); //所有代码信息
$categoryId = input('post.category_id/d');
$category = Category::getById($categoryId);
unset($_POST['block']);
unset($_POST['text']);
unset($_POST['file']);
unset($_POST['code']);
$imgs = []; //图片信息
$videos = []; //视频信息
$groups = []; //组图信息
$groupIds = input('post.groupIds/a', []);
foreach($_POST as $key => $val){
if(strpos($key, '_') !== FALSE){
$keys = explode('_',$key);
if($keys[1] == 'img'){ //图片
$imgs[$keys[2]] = $val;
}elseif($keys[1] == 'video'){ //视频
$videos[$keys[2]][$keys[0]] = $val;
}elseif($keys[1] == 'group'){ //组图
$groups[$keys[2]] = $val;
}
}
}
$data = [];
if(!empty($blocks)){
foreach($blocks as $key => $block){
$data[] = [
'id' => $key,
'value' => $block
];
}
}
if(!empty($texts)){
foreach($texts as $key => $text){
$data[] = [
'id' => $key,
'value' => $text
];
}
}
if(!empty($codes)){
foreach($codes as $key => $code){
$data[] = [
'id' => $key,
'value' => $code
];
}
}
if(!empty($imgs)){
foreach($imgs as $key => $img){
$data[] = [
'id' => $key,
'value' => $img
];
}
}
if(!empty($videos)){
foreach($videos as $key => $video){
$data[] = [
'id' => $key,
'img' => $video['img'],
'value' => $video['video']
];
}
}
if(!empty($groupIds)){
foreach($groupIds as $key => $groupId){
$group = $groups[$groupId] ?? [];
$data[] = [
'id' => $groupId,
'value' => json_encode($group)
];
}
}
$block = new Block;
$block->saveAll($data);
Log::write('content', 'page', "单页编辑栏目ID{$category['id']} ,标题:{$category['title']}");
return $this->json();
}else{
$categoryId = input('param.category_id');
$children = Category::getChildrenByParentId($categoryId);
if(empty($children)){
$category = Category::getById($categoryId);
}else{
$child = Category::getChildrenByParentId($children[0]['id']);
if(empty($child)){
$category = $children[0];
}else{
$category = $child[0];
}
}
if(empty($category)){
return $this->redirect(url('manager.content/index'));
}
$blocks = Block::getByCategoryId($category['id']);
$this->data['categoryId'] = $categoryId;
$this->data['category'] = $category;
$this->data['blocks'] = $blocks;
$this->data['groupId'] = session('auth.groupId');
$this->data['types'] = Block::getTypes();
return $this->view();
}
}
// 发展历程
public function history()
{
$categoryId = input('param.category_id/d', 0);
$category = Category::getById($categoryId);
if(empty($category)){
return $this->redirect(url('manager.content/index'));
}
$this->data['categoryId'] = $categoryId;
$this->data['category'] = $category;
$this->data['items'] = History::getPaginateList($categoryId, 20, false);
return $this->view();
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace app\controller\manager;
class Error
{
public function jump()
{
$param = request()->param();
return view()->assign($param);
}
}

View File

@ -0,0 +1,142 @@
<?php
namespace app\controller\manager;
use app\model\{File as MFile, Article, Block, Category, Link, Slide, Log};
use app\service\Tool;
class File extends Base
{
//删除磁盘上的文件
public function delPath()
{
if ($this->request->isPost()) {
$paths = input('post.paths/a');
if(!empty($paths)){
foreach($paths as $path){
Tool::delFile($path);
}
Log::write('file', 'delPath', '批量删除了磁盘文件,涉及到的文件路径为:' . implode(',', $paths));
return $this->json();
}
return $this->json(2, '待删除文件列表为空');
}
return $this->json(1, '非法请求!');
}
//删除文件记录
public function del()
{
if ($this->request->isPost()) {
$ids = input('post.ids/a');
if(empty($ids) || !is_array($ids)) {
return $this->json(2, '参数错误,请核对之后再操作!');
}
$items = MFile::getListByIds($ids);
if(!empty($items)){
$delIds = [];
foreach($items as $item){
$delIds[] = $item['id'];
if($item['type'] == MFile::IMG){
Tool::delFile($item['src'], 1);
}else{
Tool::delFile($item['src']);
}
}
MFile::destroy($delIds);
Log::write('file', 'del', '批量删除了文件涉及到的文件ID为' . implode(',', $delIds));
return $this->json();
}else{
return $this->json(3, '待删除文件列表为空');
}
}
return $this->json(1, '非法请求!');
}
/**
* 未使用文件列表,
* 1. 遍历数据库中使用的图片视频及文件路径
* 2. 遍历上传目录中的文件
* 3. 数据对比,找出存在目录中的文件&不在数据库中的文件
* 4. 页面上显示查找出来的文件
*/
public function unuse()
{
$filesInUse = $this->getAllFilesInUse(); //数据库中在使用的文件
$rootPath = app()->getRootPath();
$uploadPath = $rootPath . 'storage';
$uploadedFiles = getAllFilesByPath($uploadPath, $rootPath); //磁盘上上传的文件
$files = MFile::getAll();
$dbUploadedFiles = []; //数据库中上传的文件
foreach($files as $file){
$src = trim($file['src']);
if(!empty($src)){
$key = getKeyByPath($src);
$dbUploadedFiles[$key] = $file;
}
}
$uploadedNotInUseFiles = array_diff_key($uploadedFiles, $filesInUse); //磁盘上上传未使用的文件
$dbUploadedNotInUseFiles = array_diff_key($dbUploadedFiles, $filesInUse); //数据库中上传未使用的文件
$bothNotInUseFiles = array_intersect_key($uploadedNotInUseFiles, $dbUploadedNotInUseFiles); //磁盘和数据库中,两边都未使用
$this->data['uploadedNotInUseFiles'] = $uploadedNotInUseFiles;
$this->data['dbUploadedNotInUseFiles'] = $dbUploadedNotInUseFiles;
$this->data['bothNotInUseFilesKey'] = array_keys($bothNotInUseFiles);
return $this->view();
}
//获取所有在使用的文件
private function getAllFilesInUse()
{
$files = [];
$blockFiles = Block::getFilesInUse();
if(!empty($blockFiles)){
$files = array_merge($files, $blockFiles);
}
$slideFiles = Slide::getFilesInUse();
if(!empty($slideFiles)){
$files = array_merge($files, $slideFiles);
}
$linkFiles = Link::getFilesInUse();
if(!empty($linkFiles)){
$files = array_merge($files, $linkFiles);
}
$categoryFiles = Category::getFilesInUse();
if(!empty($categoryFiles)){
$files = array_merge($files, $categoryFiles);
}
$articleFiles = Article::getFilesInUse();
if(!empty($articleFiles)){
$files = array_merge($files, $articleFiles);
}
return $files;
}
//ajax获取文件列表
public function ajaxList()
{
if($this->request->isAjax()){
$page = input('param.page/d', 1);
$size = input('param.size/d', 20);
if(!is_integer($page) || $page < 1){
$page = 1;
}
if (!is_integer($size) || $size < 1) {
$size = 20;
}
$type = input('param.type', '');
if(!in_array($type, array_keys(MFile::getTypes()))){
$type = '';
}
$items = MFile::getList($type, $page, $size);
return $this->json(0, 'ok', $items);
}
return $this->json(1, '无此操作');
}
//列表
public function index()
{
$items = MFile::getListPage();
$this->data['items'] = $items;
$this->data['types'] = MFile::getTypes();
return $this->view();
}
}

View File

@ -0,0 +1,141 @@
<?php
namespace app\controller\manager;
use app\model\{AuthGroup, AuthRule, Log};
use app\validate\AuthGroup as VAuthGroup;
use think\exception\ValidateException;
/**
* 角色管理控制器
*/
class Group extends Base
{
/**
* 角色、分组删除
*/
public function del()
{
if ($this->request->isPost()) {
$id = input('post.id/d');
if (is_numeric($id) === true && $id > 0) {
$item = AuthGroup::getById($id);
if(!empty($item)){
AuthGroup::destroy($id);
Log::write('group', 'del', '删除角色ID' . $id . ',名称:' . $item['title']);
return $this->json();
}
}
return $this->json(2, '传入参数错误,请核对之后再操作!');
}
return $this->json(1, '非法请求!');
}
/**
* 角色、分组权限分配
*/
public function rule()
{
if($this->request->isPost()){
$rules = input('post.rules/a');
$groupId = input('post.group_id/d');
if (is_array($rules) && (is_numeric($groupId) === true && $groupId > 0)) {
$group = AuthGroup::getById($groupId);
if(empty($group)){
return $this->json(2, '无此角色信息,请核对之后再操作!');
}
AuthGroup::updateRules($groupId, $rules);
// 重置该角色对应的权限缓存
AuthGroup::resetGroupRulesCache($groupId);
Log::write('group', 'rule', '角色分配权限ID' . $groupId . ',名称:' . $group['title']);
return $this->json();
}else{
return $this->json(3, '传入参数错误,请核对之后再操作!');
}
} else {
$groupId = input('param.group_id/d');
$group = AuthGroup::getById($groupId);
if(!empty($group)){
$rules = AuthRule::getListTree();
$this->data['group_id'] = $groupId;
$this->data['group'] = $group;
$this->data['rules'] = $rules;
return $this->view();
}else{
return $this->json(1, '无此角色信息,请核对之后再操作!');
}
}
}
/**
* 角色、分组添加
* @param int $status 1:正常0禁止
*/
public function add()
{
if($this->request->isPost()){
$title = trim(input('post.title'));
$status = input('post.status/d');
if (!empty($title) && (is_numeric($status) === true) && ($status == 1 || $status == 0)) {
$item = [
'title' => $title,
'status' => $status
];
try {
validate(VAuthGroup::class)->check($item);
$group = AuthGroup::create($item);
Log::write('group', 'add', "角色新增ID{$group->id} ,标题:{$group->title}");
return $this->json();
} catch (ValidateException $e) {
return $this->json(2, $e->getError());
}
}
return $this->json(1, '传入参数错误,请核对之后再操作!');
}else{
return $this->view();
}
}
/**
* 角色、分组编辑
*/
public function edit()
{
if($this->request->isPost()){
$title = trim(input('post.title'));
$status = input('post.status/d');
$id = input('post.id/d');
if (!empty($title) && ($status == 1 || $status == 0) && (is_numeric($id) === true && $id > 0)) {
$item = [
'title' => $title,
'status' => $status
];
try {
validate(VAuthGroup::class)->check($item);
AuthGroup::updateById($id, $item);
Log::write('group', 'edit', "角色编辑ID{$id} ,标题:{$item['title']}");
return $this->json();
} catch (ValidateException $e) {
return $this->json(2, $e->getError());
}
}
return $this->json(1, '传入参数错误,请核对之后再操作!');
}else{
$id = input('param.id/d');
if (is_numeric($id) === true && $id > 0) {
$item = AuthGroup::getById($id);
$this->data['item'] = $item;
return $this->view();
}
return $this->json(1, '传入参数错误,请核对之后再操作!');
}
}
/**
* 所有角色分组信息
* @return void
*/
public function index()
{
$list = AuthGroup::select()->toArray();
$this->data['list'] = $list;
return $this->view();
}
}

View File

@ -0,0 +1,345 @@
<?php
namespace app\controller\manager;
use app\model\{HistoryInfo as MHistoryInfo, History as MHistory, Category as MCategory, Log as MLog, System};
use think\exception\ValidateException;
use app\validate\{History as VHistory, HistoryInfo as VHistoryInfo};
use think\facade\Db;
/**
* 发展历程
* Class History
* @package app\controller\manager
*/
class History extends Base
{
public function add()
{
if(request()->isPost()) {
$params = input('post.item/a', []);
$params = arrayHtmlFilter($params);
try {
validate(VHistory::class)->check($params);
$data = [
'title' => $params['title'],
'visible' => $params['visible'],
'category_id' => $params['category_id'],
];
$newItem = MHistory::create($data);
MLog::write('history', 'add', '新增发展历程ID:'.$newItem->id);
} catch (ValidateException $e) {
return $this->json(1, $e->getError());
}
return $this->json();
} else {
$categoryId = input('param.category_id/d', 0);
$category = MCategory::getById($categoryId);
$this->data['category'] = $category;
return $this->view();
}
}
public function edit()
{
$id = input('param.id/d', 0);
$item = MHistory::getById($id);
if(count($item) == 0) {
return $this->json(1, '该历程信息不存在');
}
if(request()->isPost()) {
$params = input('post.item/a', []);
$params = arrayHtmlFilter($params);
try {
validate(VHistory::class)->check($params);
$data = [
'title' => $params['title'],
'visible' => $params['visible'],
];
MHistory::updateById($id, $data);
MLog::write('history', 'edit', '修改发展历程ID:'.$id);
} catch (ValidateException $e) {
return $this->json(2, $e->getError());
}
return $this->json();
} else {
$this->data['item'] = $item;
return $this->view();
}
}
public function sort()
{
if(request()->isPost()) {
$id = input('post.id/d');
$sort = input('post.sort');
$num = input('post.num/d', 1);
if($num <= 0){
$num = 1;
}
if(!in_array($sort, ['up', 'down'], true)){
return $this->json(2, '参数错误');
}
$item = MHistory::getById($id);
if(empty($item)){
return $this->json(3, '该历程信息不存在');
}
if($sort == 'up'){
$where = "category_id='{$item['category_id']}' and sort < {$item['sort']}";
$order = "sort desc";
}else{
$where = "category_id='{$item['category_id']}' and sort > {$item['sort']}";
$order = "sort asc";
}
$forSortItems = MHistory::getListByWhereAndOrder($where, $order, $num);
if(!empty($forSortItems)){
$updateData = [];
$forSortCount = count($forSortItems);
for($i = 0; $i < $forSortCount; $i++){
if($i == 0){
$updateData[] = [
'id' => $forSortItems[$i]['id'],
'sort' => $item['sort']
];
}else{
$updateData[] = [
'id' => $forSortItems[$i]['id'],
'sort' => $forSortItems[$i - 1]['sort']
];
}
}
$updateData[] = [
'id' => $item['id'],
'sort' => $forSortItems[$i - 1]['sort']
];
if(!empty($updateData)){
$model = new MHistory();
$model->saveAll($updateData);
$sortStr = $sort == 'up' ? '上移' : '下调';
MLog::write('history', 'sort', "发展历程排序ID{$id} {$sortStr}{$num}");
return $this->json();
}
}
return $this->json(4, '无须调整排序!');
}
return $this->json(1, '无此操作');
}
// 删除历程和历程相关的事例
public function del()
{
if(request()->isPost()) {
$historyId = input('param.id/d', 0);
$item = MHistory::getById($historyId);
if(count($item) == 0) {
return $this->json(2, '该历程信息不存在');
}
Db::startTrans();
try {
MHistory::destroy($historyId);
$hasInfo = MHistoryInfo::countByHistoryId($historyId);
if($hasInfo > 0) {
MHistoryInfo::delByHistoryId($historyId);
}
MLog::write('history','del', '删除历程ID:'.$historyId);
Db::commit();
} catch (\Exception $e) {
Db::rollback();
return $this->json(3, '删除失败,'.$e->getMessage());
}
return $this->json();
}
return $this->json(1, '无此操作');
}
public function info()
{
$historyId = input('param.history_id/d', 0);
$history = MHistory::getById($historyId);
$infoItems = [];
$categoryId = $history['category_id'] ?? 0;
if(count($history) > 0) {
$infoItems = MHistoryInfo::getByHistoryId($historyId);
}
$this->data['history'] = $history;
$this->data['categoryId'] = $categoryId;
$this->data['items'] = $infoItems;
return $this->view();
}
// 新增发展历程详情
public function addInfo()
{
$historyId = input('param.history_id/d', 0);
$history = MHistory::getById($historyId);
if(count($history) == 0) {
return $this->json(1, '该历程信息不存在');
}
if(request()->isPost()) {
$params = input('post.item/a', []);
$params = arrayHtmlFilter($params);
$imgs = input('post.img/a');
if (!empty($imgs) && is_array($imgs)) {
$imgs = json_encode($imgs);
} else {
$imgs = '';
}
try {
validate(VHistoryInfo::class)->check($params);
$data = [
'title' => $params['title'],
'visible' => $params['visible'],
'history_id' => $historyId,
'imgs' => $imgs,
];
$newItem = MHistoryInfo::create($data);
MLog::write('history', 'addInfo', '新增发展历程事例ID:'.$newItem->id);
} catch (ValidateException $e) {
return $this->json(2, $e->getError());
}
return $this->json();
} else {
$imgSize = '';
$category = MCategory::getById($history['category_id']);
if(count($category) > 0 && $category['img_width'] && $category['img_height']){
$imgSize = $category['img_width'] . '像素 X ' . $category['img_height'] . '像素';
}
$this->data['historyId'] = $historyId;
$this->data['history'] = $history;
$this->data['imgSize'] = $imgSize;
return $this->view();
}
}
// 编辑发展历程详情
public function editInfo()
{
$id = input('param.id/d', 0);
$item = MHistoryInfo::getById($id);
if(count($item) == 0) {
return $this->json(1, '该历程事例信息不存在');
}
if(request()->isPost()) {
$params = input('post.item/a', []);
$params = arrayHtmlFilter($params);
$imgs = input('post.img/a');
if (!empty($imgs) && is_array($imgs)) {
$imgs = json_encode($imgs);
} else {
$imgs = '';
}
try {
validate(VHistoryInfo::class)->check($params);
$data = [
'title' => $params['title'],
'visible' => $params['visible'],
'imgs' => $imgs,
];
MHistoryInfo::updateById($id, $data);
MLog::write('history', 'editInfo', '修改发展历程事例ID:'.$id);
} catch (ValidateException $e) {
return $this->json(2, $e->getError());
}
return $this->json();
} else {
$history = MHistory::getById($item['history_id']);
$imgSize = '';
if(count($history) > 0) {
$category = MCategory::getById($history['category_id']);
if(count($category) > 0 && $category['img_width'] && $category['img_height']){
$imgSize = $category['img_width'] . '像素 X ' . $category['img_height'] . '像素';
}
}
$this->data['item'] = $item;
$this->data['imgSize'] = $imgSize;
return $this->view();
}
}
public function sortInfo()
{
if(request()->isPost()) {
$id = input('post.id/d');
$sort = input('post.sort');
$num = input('post.num/d', 1);
if($num <= 0){
$num = 1;
}
if(!in_array($sort, ['up', 'down'], true)){
return $this->json(2, '参数错误');
}
$item = MHistoryInfo::getById($id);
if(empty($item)){
return $this->json(3, '该历程事例信息不存在');
}
if($sort == 'up'){
$where = "history_id='{$item['history_id']}' and sort < {$item['sort']}";
$order = "sort desc";
}else{
$where = "history_id='{$item['history_id']}' and sort > {$item['sort']}";
$order = "sort asc";
}
$forSortItems = MHistoryInfo::getListByWhereAndOrder($where, $order, $num);
if(!empty($forSortItems)){
$updateData = [];
$forSortCount = count($forSortItems);
for($i = 0; $i < $forSortCount; $i++){
if($i == 0){
$updateData[] = [
'id' => $forSortItems[$i]['id'],
'sort' => $item['sort']
];
}else{
$updateData[] = [
'id' => $forSortItems[$i]['id'],
'sort' => $forSortItems[$i - 1]['sort']
];
}
}
$updateData[] = [
'id' => $item['id'],
'sort' => $forSortItems[$i - 1]['sort']
];
if(!empty($updateData)){
$model = new MHistoryInfo();
$model->saveAll($updateData);
$sortStr = $sort == 'up' ? '上移' : '下调';
MLog::write('history', 'sortInfo', "发展历程事例排序ID{$id} {$sortStr}{$num}");
return $this->json();
}
}
return $this->json(4, '无须调整排序!');
}
return $this->json(1, '无此操作');
}
public function delInfo()
{
if(request()->isPost()) {
$infoIds = [];
$ids = input('post.ids', []);
$id = input('post.id', 0);
if(!empty($ids)) {
if(is_array($ids)) {
$infoIds = $ids;
} else {
$infoIds = explode(',', $ids);
}
} elseif($id > 0) {
$infoIds[] = $id;
}
if(count($infoIds) > 0) {
MHistoryInfo::destroy($infoIds);
MLog::write('history','delInfo', '删除历程事例IDs:'.implode(',', $infoIds));
return $this->json();
}
return $this->json(2, '参数错误');
}
return $this->json(1, '无此操作');
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace app\controller\manager;
class Index extends Base
{
//后台首页
public function index()
{
return $this->redirect(url('manager.safe/index'));
}
}

View File

@ -0,0 +1,177 @@
<?php
namespace app\controller\manager;
use app\model\{Link as MLink, System, Log};
use app\validate\Link as VLink;
use think\exception\ValidateException;
class Link extends Base
{
//批量删除
public function batchDel()
{
if ($this->request->isPost()) {
$ids = input('post.ids/a');
if(empty($ids) || !is_array($ids)) {
return $this->json(2, '参数错误,请核对之后再操作!');
}
$items = MLink::getListByIds($ids);
if(!empty($items)){
$delIds = [];
foreach($items as $item){
$delIds[] = $item['id'];
}
MLink::destroy($delIds);
Log::write('link', 'betchDel', '批量删除了友情链接涉及到的ID为' . implode(',', $delIds));
return $this->json();
}else{
return $this->json(3, '待删除友情链接为空');
}
}
return $this->json(1, '非法请求!');
}
//删除
public function del()
{
if ($this->request->isPost()) {
$id = input('post.id/d');
if(is_numeric($id) && $id > 0) {
$item = MLink::getById($id);
if(!empty($item)){
MLink::destroy($id);
Log::write('link', 'del', '删除友情链接ID' . $id . ',标题:' . $item['title']);
return $this->json();
}
return $this->json(3, '待删除友情链接不存在');
}
return $this->json(2, '参数错误,请核对之后再操作!');
}
return $this->json(1, '非法请求!');
}
//排序
public function sort()
{
if($this->request->isPost()){
$id = input('post.id/d');
$sort = input('post.sort');
$num = input('post.num/d', 1);
if($num <= 0){
$num = 1;
}
if(!in_array($sort, ['up', 'down'], true)){
return $this->json(2, '参数错误');
}
$item = MLink::getById($id);
if(empty($item)){
return $this->json(3, '该友情链接信息不存在!');
}
if($sort == 'up'){
$where = "sort < {$item['sort']}";
$order = "sort desc";
}else{
$where = "sort > {$item['sort']}";
$order = "sort asc";
}
$forSortItems = MLink::getListByWhereAndOrder($where, $order, $num);
if(!empty($forSortItems)){
$updateData = [];
$forSortCount = count($forSortItems);
for($i = 0; $i < $forSortCount; $i++){
if($i == 0){
$updateData[] = [
'id' => $forSortItems[$i]['id'],
'sort' => $item['sort']
];
}else{
$updateData[] = [
'id' => $forSortItems[$i]['id'],
'sort' => $forSortItems[$i - 1]['sort']
];
}
}
$updateData[] = [
'id' => $item['id'],
'sort' => $forSortItems[$i - 1]['sort']
];
if(!empty($updateData)){
$model = new MLink();
$model->saveAll($updateData);
$sortStr = $sort == 'up' ? '上移' : '下调';
Log::write('link', 'sort', "友情链接排序ID{$id} ,标题:{$item['title']}{$sortStr}{$num}");
return $this->json();
}
}
return $this->json(4, '无须调整排序!');
}
return $this->json(1, '非法请求!');
}
//编辑
public function edit()
{
if($this->request->isPost()){
$item = input('post.item/a');
$id = input('post.id/d');
$img = input('post.img');
if(is_numeric($id) && $id > 0) {
$link = MLink::getById($id);
if(empty($link)) {
return $this->json(2, '该友情链接信息不存在!');
}
if(!empty($img)){
$item['src'] = $img;
}
try {
validate(VLink::class)->check($item);
MLink::updateById($id, $item);
Log::write('link', 'edit', "友情链接编辑ID{$id} ,标题:{$item['title']}");
return $this->json();
} catch (ValidateException $e) {
return $this->json(3, $e->getError());
}
}
return $this->json(1, '参数错误,请核对之后再操作!');
} else {
$id = input('param.id/d');
$item = MLink::getById($id);
$imgSize = System::getLinkImageSize();
$this->data['item'] = $item;
$this->data['img_size'] = $imgSize;
return $this->view();
}
}
//添加
public function add()
{
if($this->request->isPost()){
$item = input('post.item/a');
$img = input('post.img');
if(!empty($img)){
$item['src'] = $img;
}
try {
validate(VLink::class)->check($item);
$link = MLink::create($item);
Log::write('link', 'add', "友情链接新增ID{$link->id} ,标题:{$item['title']}");
return $this->json();
} catch (ValidateException $e) {
return $this->json(2, $e->getError());
}
} else {
$imgSize = System::getLinkImageSize();
$this->data['img_size'] = $imgSize;
return $this->view();
}
}
public function index()
{
$items = MLink::getList();
$this->data['items'] = $items;
return $this->view();
}
}

View File

@ -0,0 +1,78 @@
<?php
namespace app\controller\manager;
use app\model\{Member, AuthRule, LoginLog};
use app\controller\BaseController;
class Login extends BaseController
{
protected $middleware = ['csrf'];
/**
* user lgoin
* use ajax post push
*
* @return void | JSON
*/
public function index()
{
if(request()->isPost()){
$username = trim(input('param.username'));
$password = trim(input('param.password'));
$loginUrl = url('manager.login/index');
if(empty($username) || empty($password)){
session('loginError','用户名和密码不能为空');
return $this->redirect($loginUrl);
}
$member = Member::getByUserName($username);
if(empty($member)){
session('loginError','用户名错误');
return $this->redirect($loginUrl);
}
if($member['password'] != md5($password)){
session('loginError','用户密码错误');
return $this->redirect($loginUrl);
}
$rulesList = AuthRule::userRolesList($member['group_id']);
$rulesIdStr = '';
if (!empty($rulesList)) {
$rulesId = $rulesList['allRulesId'];
$rulesIdStr = implode(',', $rulesId);
}
$authSession = [
'userId' => $member['id'],
'userName' => $member['username'],
'groupId' => $member['group_id'],
'rules' => $rulesIdStr,
'cates' => $member['cates']
];
//记录最后登陆时间
$ip = request()->ip();
$time = time();
Member::updateById($member['id'], [
'login_time' => $time,
'login_ip' => $ip
]);
LoginLog::create([
'member_id' => $member['id'],
'name' => $member['username'],
'ip' => $ip,
'create_time' => $time
]);
session('auth', $authSession);
return redirect(url('manager.index/index'));
}
$viewData = [];
if(session('?loginError')) {
$viewData['error'] = session('loginError');
}
session('loginError', null);
$viewData['_token'] = $this->request->csrfToken;
return view()->assign($viewData);
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace app\controller\manager;
use app\controller\BaseController;
class Logout extends BaseController
{
public function index()
{
session(null);
return redirect(url('manager.login/index'));
}
}

View File

@ -0,0 +1,181 @@
<?php
namespace app\controller\manager;
use app\model\{Category, AuthGroup, Member as MMember, Log};
use Exception;
use think\facade\Db;
class Member extends Base
{
/**
* 删除管理用户
*/
public function del()
{
if ($this->request->isPost()) {
$id = input('post.id/d');
if (is_numeric($id) === true && $id > 0) {
$item = MMember::getByID($id);
if(!empty($item)){
MMember::destroy($id);
Log::write('member', 'del', "管理员删除ID{$id}, 管理员:{$item['username']}");
return $this->json();
}
}
return $this->json(2, '参数错误,请核对之后再操作!');
}
return $this->json(1, '非法请求!');
}
/**
* 修改管理用户信息
* 由于try语法中抛出的异常类型与$this->json()抛出的异常类型不一致,因此需要利用$errorMsg 来判断返回情况
*/
public function edit()
{
if($this->request->isPost()){
$id = input('post.id/d');
$username = trim(input('post.username'));
$password = trim(input('post.password'));
$groupId = input('post.group_id/d');
if ((is_numeric($id) === true && $id > 0) && ((is_numeric($groupId) === true && $groupId > 0) && !empty($username))) {
$member = MMember::getByUserName($username);
if(!empty($member) && $member['id'] != $id){
return $this->json(2, '该用户名已被使用!');
}
$errorMsg = '';
Db::startTrans();
try {
$member = MMember::getById($id);
$item = [
'username' => $username,
'group_id' => $groupId
];
//角色权限重新赋值
$group = AuthGroup::getById($groupId);
$item['rules'] = $group['rules'];
if(!empty($password)){
$item['password'] = md5($password);
}
MMember::updateById($id, $item);
Log::write('member', 'edit', "管理员编辑ID{$id}, 管理员:{$item['username']}");
Db::commit();
} catch (Exception $e) {
Db::rollback();
$errorMsg = '用户信息修改失败!'.$e->getMessage();
}
if (empty($errorMsg)) {
return $this->json();
}
return $this->json(3, $errorMsg);
}
return $this->json(1, '参数错误,请核对之后再操作!');
}else{
$id = input('param.id/d');
if (is_numeric($id) === true && $id > 0) {
$member = MMember::getByID($id);
$item = [
'id' => $member['id'],
'username' => $member['username'],
'group_id' => $member['group_id']
];
$auth = session('auth');
$groups = AuthGroup::getListById($auth['groupId']);
$this->data['groups'] = $groups;
$this->data['item'] = $item;
return $this->view();
}
return $this->json(1, '参数错误,请核对之后再操作!');
}
}
/**
* 新增管理用户
*/
public function add()
{
if($this->request->isPost()){
$groupId = input('post.group_id/d');
$username = trim(input('post.username'));
$password = trim(input('post.password'));
if ((is_numeric($groupId) === true && $groupId > 0) && ($username != "" && $password != "")) {
$member = MMember::getByUserName($username);
if(!empty($member)){
return $this->json(2, '该用户名已被使用!');
}
$group = AuthGroup::getById($groupId);
$newMember = MMember::create([
'username' => $username,
'group_id' => $groupId,
'password' => md5($password),
'rules' => $group['rules'],
'cates' => '',
'login_time' => 0,
]);
Log::write('member', 'add', "管理员新增ID{$newMember->id}, 管理员:{$newMember['username']}");
return $this->json();
}
return $this->json(1, '参数错误,请核对之后再操作!');
}
$auth = session('auth');
$groups = AuthGroup::getListById($auth['groupId']);
$this->data['groups'] = $groups;
return $this->view();
}
/**
* 栏目菜单分配
*/
public function menuAlloter()
{
if(request()->isPost()) {
$cates = input('post.cates/a');
$id = input('post.id/d');
if (is_array($cates) && (is_numeric($id) === true && $id > 0)) {
$member = MMember::getById($id);
if(empty($member)){
return $this->json(2, '无此用户信息,请核对之后再操作!');
}
MMember::updateCates($id, $cates);
Log::write('member', 'menuAlloter', "管理员栏目分配ID{$id}, 管理员:{$member['username']}");
return $this->json();
}else{
return $this->json(3, '传入参数错误,请核对之后再操作!');
}
} else {
$id = input('param.id/d');
if (is_numeric($id) && $id > 0) {
$member = MMember::getById($id);
if (empty($member)) {
return $this->json(2, '该管理员信息不存在,请核对之后再操作!');
}
$cates = Category::getListTree(false);
$memberCates = array_filter(explode(',', $member['cates']));
$this->data['id'] = $id;
$this->data['member'] = $member;
$this->data['memberCates'] = $memberCates;
$this->data['cates'] = $cates;
return $this->view();
}
return $this->json(1, '参数错误,请核对之后再操作!',$id);
}
}
/**
* 所有用户列表
*/
public function index()
{
$auth = session('auth');
if ($auth['groupId'] == 1) {
$items = MMember::getList(40);
} else {
$items = MMember::getListByGroup($auth['groupId'], 40);
}
$this->data['items'] = $items;
return $this->view();
}
}

View File

@ -0,0 +1,184 @@
<?php
namespace app\controller\manager;
use app\model\{Model as MModel, Log};
use app\validate\Model as VModel;
use think\exception\ValidateException;
class Model extends Base
{
//批量删除模型
public function batchDel()
{
if($this->request->isPost()){
$ids = input('post.ids/a');
if(is_array($ids)) {
$idsArr = $ids;
} else {
$idsArr = array_filter(explode(',', $ids));
}
if(count($idsArr) == 0) {
return $this->json(1, '无效请求,参数错误!');
}
$items = MModel::getListByIds($idsArr);
if(!empty($items)){
$delIds = [];
foreach($items as $item){
$delIds[] = $item['id'];
}
MModel::destroy($delIds);
Log::write('model', 'batchDel', "模型批量删除ID" . implode(',', $ids));
return $this->json();
}
return $this->json(2, '无效请求,参数错误!');
}
return $this->json(1, '非法请求!');
}
//删除单个模型
public function del()
{
if($this->request->isPost()){
$id = input('post.id/d');
if(is_numeric($id) && $id > 0) {
$item = MModel::getById($id);
if(!empty($item)) {
MModel::destroy($id);
Log::write('model', 'del', "模型删除ID{$id}, 标题:{$item['name']}");
return $this->json();
}
return $this->json(3, '删除失败!该模型不存在,请刷新页面后再试!');
}
return $this->json(2, '无效请求,参数错误!');
}
return $this->json(1, '非法请求!');
}
//编辑模型
public function edit()
{
if($this->request->isPost()){
$item = [];
$item['name'] = input('post.name');
$item['template'] = input('post.template');
$item['manager'] = input('post.manager');
$id = input('post.id/d');
if(is_numeric($id) && $id > 0) {
$model = MModel::getById($id);
if(empty($model)){
return $this->json(2, '无此模型数据!');
}
try {
validate(VModel::class)->check($item);
MModel::updateById($id, $item);
Log::write('model', 'edit', "模型编辑ID{$id}, 标题:{$item['name']}");
return $this->json();
} catch (ValidateException $e) {
return $this->json(3, $e->getError());
}
}
return $this->json(1, '无效请求,参数错误!');
}else{
$id = input('param.id/d');
if (is_numeric($id) && $id > 0) {
$item = MModel::getById($id);
$this->data['item'] = $item;
return $this->view();
}
return $this->json(1,'传入参数错误,请核对之后再操作!');
}
}
/**
* 添加模型
*/
public function add()
{
if($this->request->isPost()){
$item = [];
$item['name'] = input('post.name');
$item['template'] = input('post.template');
$item['manager'] = input('post.manager');
try {
validate(VModel::class)->check($item);
$model = MModel::create($item);
Log::write('model', 'add', "模型新增ID{$model->id}, 标题:{$item['name']}");
return $this->json();
} catch (ValidateException $e) {
return $this->json(2, $e->getError());
}
}else {
return $this->view();
}
}
/**
* 模型列表
*/
public function index()
{
$items = MModel::getList();
$this->data['items'] = $items;
return $this->view();
}
/**
* 排序
*/
public function sort()
{
if($this->request->isPost()){
$id = input('post.id/d');
$sort = input('post.sort');
$num = input('post.num/d', 1);
if($num <= 0){
$num = 1;
}
if(!in_array($sort, ['up', 'down'], true)){
return $this->json(2, '参数错误');
}
$item = MModel::getById($id);
if(empty($item)) {
return $this->json(3, '无此模型!');
}
if($sort == 'up'){
$where = "sort < {$item['sort']}";
$order = "sort desc";
}else{
$where = "sort > {$item['sort']}";
$order = "sort asc";
}
$forSortItems = MModel::getListByWhereAndOrder($where, $order, $num);
if(!empty($forSortItems)){
$updateData = [];
$forSortCount = count($forSortItems);
for($i = 0; $i < $forSortCount; $i++){
if($i == 0){
$updateData[] = [
'id' => $forSortItems[$i]['id'],
'sort' => $item['sort']
];
}else{
$updateData[] = [
'id' => $forSortItems[$i]['id'],
'sort' => $forSortItems[$i - 1]['sort']
];
}
}
$updateData[] = [
'id' => $item['id'],
'sort' => $forSortItems[$i - 1]['sort']
];
if(!empty($updateData)){
$model = new MModel();
$model->saveAll($updateData);
$sortStr = $sort == 'up' ? '上移' : '下调';
Log::write('model', 'sort', "模型排序ID{$id} ,标题:{$item['name']}{$sortStr}{$num}");
return $this->json();
}
}
return $this->json(4, '无须调整排序!');
}
return $this->json(1, '非法请求!');
}
}

View File

@ -0,0 +1,461 @@
<?php
namespace app\controller\manager;
use app\model\{Category, Block, Log};
use app\validate\Block as VBlock;
use think\exception\ValidateException;
class Page extends Base
{
//源码,代码
public function code()
{
if($this->request->isPost()){
$item = input('post.item/a');
$categoryId = input('post.category_id/d');
$id = input('post.id/d');
try{
validate(VBlock::class)->check($item);
if(empty($item['value'])){
return $this->json(2, '内容不可为空!');
}
$block = Block::getByKeyword($item['keyword'], $categoryId);
if($id){
if(!empty($block) && $block['id'] != $id){
return $this->json(3, '键值已存在,请更改键值');
}
Block::updateById($id, $item);
Log::write('page', 'code', "单页代码编辑ID{$id}, 键值:{$item['keyword']}");
}else{
if($categoryId <= 0){
return $this->json(4, '栏目参数错误!');
}
if(!empty($block)){
return $this->json(3, '键值已存在,请更改键值');
}
$item['category_id'] = $categoryId;
$item['type'] = Block::CODE;
$block = Block::create($item);
Log::write('page', 'code', "单页代码新增ID{$block->id}, 键值:{$item['keyword']}");
}
return $this->json();
} catch (ValidateException $e){
return $this->json(1, $e->getError());
}
}else{
$id = input('param.id/d');
if($id <= 0){ //添加
$categoryId = input('param.category_id/d');
$category = Category::getById($categoryId);
if(empty($category)){
$url = url('manager.content/index')->__toString();
return $this->error('无此栏目', $url);
}
}else{ //修改
$item = Block::getById($id);
if(empty($item)){
return $this->error('无此代码块!');
}
$categoryId = $item['category_id'];
$this->data['item'] = $item;
}
$this->data['categoryId'] = $categoryId;
return $this->view();
}
}
//排序
public function sort()
{
if($this->request->isPost()){
$id = input('post.id/d');
$sort = input('post.sort');
$num = input('post.num/d', 1);
if($num <= 0){
$num = 1;
}
if(!in_array($sort, ['up', 'down'], true)){
return $this->json(2, '参数错误');
}
$item = Block::getById($id);
if(empty($item)){
return $this->json(3, '无此块信息');
}
if($sort == 'up'){
$where = "category_id='{$item['category_id']}' and sort < {$item['sort']}";
$order = "sort desc";
}else{
$where = "category_id='{$item['category_id']}' and sort > {$item['sort']}";
$order = "sort asc";
}
$forSortItems = Block::getListByWhereAndOrder($where, $order, $num);
if(!empty($forSortItems)){
$updateData = [];
$forSortCount = count($forSortItems);
for($i = 0; $i < $forSortCount; $i++){
if($i == 0){
$updateData[] = [
'id' => $forSortItems[$i]['id'],
'sort' => $item['sort']
];
}else{
$updateData[] = [
'id' => $forSortItems[$i]['id'],
'sort' => $forSortItems[$i - 1]['sort']
];
}
}
$updateData[] = [
'id' => $item['id'],
'sort' => $forSortItems[$i - 1]['sort']
];
if(!empty($updateData)){
$model = new Block();
$model->saveAll($updateData);
$sortStr = $sort == 'up' ? '上移' : '下调';
Log::write('page', 'sort', "单页区块排序ID{$id} ,键值:{$item['keyword']}{$sortStr}{$num}");
return $this->json();
}
}
return $this->json(4, '无须调整排序!');
}
return $this->error('无此操作');
}
//删除
public function delblock()
{
if ($this->request->isAjax()) {
$id = input('post.id/d');
$item = Block::getById($id);
if(!empty($item)){
Block::destroy($id);
Log::write('page', 'delblock', "单页区块删除ID{$id} ,键值:{$item['keyword']}");
return $this->json();
}
return $this->json(1, 'fail');
}
return $this->error('无此操作');
}
//图片
public function img()
{
if($this->request->isPost()){
$item = input('post.item/a');
$img = trim(input('post.img'));
$categoryId = input('post.category_id/d');
if (!empty($img) && $img == 'null') {
$img = '';
}
$id = input('post.id/d');
try{
validate(VBlock::class)->check($item);
$block = Block::getByKeyword($item['keyword'], $categoryId);
if($id){
if(!empty($block) && $block['id'] != $id){
return $this->json(4, '键值已存在,请更改键值');
}
if(!empty($img)){
$item['value'] = $img;
}
Block::updateById($id, $item);
Log::write('page', 'img', "单页图片编辑ID{$id} ,键值:{$item['keyword']}");
}else{
if(!empty($block)){
return $this->json(4, '键值已存在,请更改键值');
}
if($categoryId <= 0){
return $this->json(2, '栏目参数错误!!');
}
if(empty($img)){
return $this->json(3, '图片不可为空');
}
$item['value'] = $img;
$item['type'] = Block::IMG;
$item['category_id'] = $categoryId;
$block = Block::create($item);
Log::write('page', 'img', "单页图片新增ID{$block->id} ,键值:{$item['keyword']}");
}
return $this->json();
} catch (ValidateException $e){
return $this->json(1, $e->getError());
}
}else{
$id = input('param.id/d');
if($id <= 0){ //添加
$categoryId = input('param.category_id/d');
$category = Category::getById($categoryId);
if(empty($category)){
$url = url('manager.content/index')->__toString();
return $this->error('无此栏目', $url);
}
}else{ //修改
$item = Block::getById($id);
if(empty($item)){
return $this->error('无此图片!');
}
$categoryId = $item['category_id'];
$this->data['item'] = $item;
}
if(isset($item) && $item['width'] && $item['height']){
$imgSize = $item['width'].'px X '.$item['height'].'px';
}else{
$imgSize = '';
}
$this->data['categoryId'] = $categoryId;
$this->data['imgSize'] = $imgSize;
$this->data['groupId'] = session('auth.groupId');
return $this->view();
}
}
//文字块
public function block()
{
if($this->request->isPost()){
$item = input('post.item/a');
$categoryId = input('post.category_id/d');
$id = input('post.id/d');
try{
validate(VBlock::class)->check($item);
if(empty($item['value'])){
return $this->json(1, '内容不可为空!');
}
$block = Block::getByKeyword($item['keyword'], $categoryId);
if($id){
if(!empty($block) && $block['id'] != $id){
return $this->json(4, '键值已存在,请更改键值');
}
Block::updateById($id, $item);
Log::write('page', 'block', "单页文字块编辑ID{$id} ,键值:{$item['keyword']}");
}else{
if($categoryId <= 0){
return $this->json(2, '栏目参数错误!');
}
if(!empty($block)){
return $this->json(4, '键值已存在,请更改键值');
}
$item['category_id'] = $categoryId;
$block = Block::create($item);
Log::write('page', 'block', "单页文字块新增ID{$block->id} ,键值:{$item['keyword']}");
}
return $this->json();
} catch (ValidateException $e){
return $this->json(1, $e->getError());
}
}else{
$id = input('param.id/d');
if($id <= 0){ //添加
$categoryId = input('param.category_id/d');
$category = Category::getById($categoryId);
if(empty($category)){
$url = url('manager.content/index')->__toString();
return $this->error('无此栏目', $url);
}
}else{ //修改
$item = Block::getById($id);
if(empty($item)){
return $this->error('无此文字块!');
}
$categoryId = $item['category_id'];
$this->data['item'] = $item;
}
$this->data['categoryId'] = $categoryId;
return $this->view();
}
}
//富文本内容
public function text()
{
if($this->request->isPost()){
$item = input('post.item/a');
$categoryId = input('post.category_id/d');
try{
validate(VBlock::class)->check($item);
if(empty($item['value'])){
return $this->json(1, '内容不可为空!');
}
$block = Block::getByKeyword($item['keyword'], $categoryId);
$id = input('post.id/d');
if($id){
if(!empty($block) && $block['id'] != $id){
return $this->json(4, '键值已存在,请更改');
}
Block::updateById($id, $item);
Log::write('page', 'text', "单页富文本编辑ID{$id} ,键值:{$item['keyword']}");
}else{
if($categoryId <= 0){
return $this->json(2, '栏目参数错误!');
}
if(!empty($block)){
return $this->json(4, '键值已存在,请更改键值');
}
$item['category_id'] = $categoryId;
$item['type'] = Block::TEXT;
$block = Block::create($item);
Log::write('page', 'text', "单页富文本新增ID{$block->id} ,键值:{$item['keyword']}");
}
return $this->json();
} catch (ValidateException $e){
return $this->json(1, $e->getError());
}
}else{
$id = input('param.id/d');
if($id <= 0){ //添加
$categoryId = input('param.category_id/d');
$category = Category::getById($categoryId);
if(empty($category)){
$url = url('manager.content/index')->__toString();
return $this->error('无此栏目', $url);
}
}else{ //修改
$item = Block::getById($id);
if(empty($item)){
return $this->error('无此富文本!');
}
$categoryId = $item['category_id'];
$this->data['item'] = $item;
}
$this->data['categoryId'] = $categoryId;
return $this->view();
}
}
//组图
public function group()
{
if($this->request->isPost()){
$item = input('post.item/a');
$imgs = input('post.img/a');
$categoryId = input('post.category_id/d');
if (!empty($imgs) && is_array($imgs)) {
$item['value'] = json_encode($imgs);
} else {
$item['value'] = '';
}
try{
validate(VBlock::class)->check($item);
$block = Block::getByKeyword($item['keyword'], $categoryId);
$id = input('post.id/d');
if($id){
if(!empty($block) && $block['id'] != $id){
return $this->json(4, '键值已存在,请更改');
}
Block::updateById($id, $item);
Log::write('page', 'group', "单页组图编辑ID{$id} ,键值:{$item['keyword']}");
}else{
if($categoryId <= 0){
return $this->json(2, '栏目参数错误!');
}
if(!empty($block)){
return $this->json(4, '键值已存在,请更改键值');
}
$item['category_id'] = $categoryId;
$item['type'] = Block::GROUP;
$block = Block::create($item);
Log::write('page', 'group', "单页组图新增ID{$block->id} ,键值:{$item['keyword']}");
}
return $this->json();
} catch (ValidateException $e){
return $this->json(1, $e->getError());
}
}else{
$id = input('param.id/d');
if($id <= 0){ //添加
$categoryId = input('param.category_id/d');
$category = Category::getById($categoryId);
if(empty($category)){
$url = url('manager.content/index')->__toString();
return $this->error('无此栏目', $url);
}
}else{ //修改
$item = Block::getById($id);
if(empty($item)){
return $this->error('无此组图!');
}
$categoryId = $item['category_id'];
$this->data['item'] = $item;
}
$this->data['categoryId'] = $categoryId;
if(isset($item) && $item['width'] && $item['height']){
$imgSize = $item['width'].'px X '.$item['height'].'px';
}else{
$imgSize = '';
}
$this->data['imgSize'] = $imgSize;
$this->data['groupId'] = session('auth.groupId');
return $this->view();
}
}
//视频
public function video()
{
if($this->request->isPost()){
$item = input('post.item/a');
$img = trim(input('post.img'));
$video = trim(input('post.video'));
$categoryId = input('post.category_id/d');
try{
validate(VBlock::class)->check($item);
$block = Block::getByKeyword($item['keyword'], $categoryId);
$id = input('post.id/d');
if($id){
if(!empty($block) && $block['id'] != $id){
return $this->json(4, '键值已存在,请更改');
}
if(!empty($img)){
$item['img'] = $img;
}
if(!empty($video)){
$item['value'] = $video;
}
Block::updateById($id, $item);
Log::write('page', 'video', "单页视频编辑ID{$id} ,键值:{$item['keyword']}");
}else{
if($categoryId <= 0){
return $this->json(2, '栏目参数错误!');
}
if(!empty($block)){
return $this->json(3, '键值已存在,请更改键值');
}
if(empty($video)){
return $this->json(3, '视频不可为空');
}
$item['category_id'] = $categoryId;
$item['type'] = Block::VIDEO;
$item['value'] = $video;
$item['img'] = $img;
$block = Block::create($item);
Log::write('page', 'video', "单页视频新增ID{$block->id} ,键值:{$item['keyword']}");
}
return $this->json();
} catch (ValidateException $e){
return $this->json(1, $e->getError());
}
}else{
$id = input('param.id/d');
if($id <= 0){ //添加
$categoryId = input('param.category_id/d');
$category = Category::getById($categoryId);
if(empty($category)){
$url = url('manager.content/index')->__toString();
return $this->error('无此栏目', $url);
}
}else{ //修改
$item = Block::getById($id);
if(empty($item)){
return $this->error('无此视频!');
}
$categoryId = $item['category_id'];
$this->data['item'] = $item;
}
$this->data['categoryId'] = $categoryId;
if(isset($item) && $item['width'] && $item['height']){
$imgSize = $item['width'].'px X '.$item['height'].'px';
}else{
$imgSize = '';
}
$this->data['imgSize'] = $imgSize;
$this->data['groupId'] = session('auth.groupId');
return $this->view();
}
}
}

View File

@ -0,0 +1,180 @@
<?php
namespace app\controller\manager;
use app\model\{AuthRule, AuthGroup, Log};
use app\validate\AuthRule as VAuthRule;
use think\exception\ValidateException;
class Rule extends Base
{
/**
* 权限排序
* 暂不允许父级变更
*
* @return void
*/
public function sort()
{
if ($this->request->isAjax()) {
$id = input('post.id');
$sort = input('post.sort');
$num = input('post.num/d', 1);
if($num <= 0){
$num = 1;
}
if(!in_array($sort, ['up', 'down'], true)){
return $this->json(2, '参数错误');
}
$item = AuthRule::getById($id);
if(empty($item)){
return $this->json(3, '权限不存在');
}
if($sort == 'up'){
$where = "parent_id = {$item['parent_id']} and sort < {$item['sort']}";
$order = "sort desc";
}else{
$where = "parent_id = {$item['parent_id']} and sort > {$item['sort']}";
$order = "sort asc";
}
$forSortItems = AuthRule::getListByWhereAndOrder($where, $order, $num);
if(!empty($forSortItems)){
$updateData = [];
$forSortCount = count($forSortItems);
for($i = 0; $i < $forSortCount; $i++){
if($i == 0){
$updateData[] = [
'id' => $forSortItems[$i]['id'],
'sort' => $item['sort']
];
}else{
$updateData[] = [
'id' => $forSortItems[$i]['id'],
'sort' => $forSortItems[$i - 1]['sort']
];
}
}
$updateData[] = [
'id' => $item['id'],
'sort' => $forSortItems[$i - 1]['sort']
];
if(!empty($updateData)){
$model = new AuthRule();
$model->saveAll($updateData);
$sortStr = $sort == 'up' ? '上移' : '下调';
Log::write('rule', 'sort', "权限排序ID{$id} ,标题:{$item['title']}{$sortStr}{$num}");
return $this->json();
}
}
return $this->json(4, '无须调整排序!');
}
return $this->json(1, '非法请求!');
}
/**
* 权限删除
*/
public function del()
{
if ($this->request->isAjax()) {
$id = input('post.id/d');
$item = AuthRule::getById($id);
if(empty($item)){
return $this->json(1, '无此权限');
}
$children = AuthRule::getListByParentId($id);
if(!empty($children)){
return $this->json(2, '当前权限有下级权限,不可删除');
}
AuthRule::destroy($id);
AuthGroup::resetGroupRulesCache();
Log::write('rule', 'del', "权限删除ID{$id}, 标题:{$item['title']}");
return $this->json();
}
return $this->json(1, '非法请求!');
}
/**
* 权限修改
*/
public function edit()
{
if($this->request->isPost()){
$item = input('post.item/a');
$id = input('post.id');
$rule = AuthRule::getById($id);
if(empty($rule)){
return $this->json(1, '请选择正确的权限');
}
$rule2 = AuthRule::getByName($item['name']);
if(!empty($rule2) && $rule2['id'] != $id){
return $this->json(2, '已存在相同权限['.$item['name'].']');
}
try {
validate(VAuthRule::class)->check($item);
AuthRule::updateById($id, $item);
AuthGroup::resetGroupRulesCache();
Log::write('rule', 'edit', "权限编辑ID{$id}, 标题:{$item['title']}");
return $this->json();
} catch (ValidateException $e) {
return $this->json(3, $e->getError());
}
}
$id = input('param.id/d');
$rule = AuthRule::getById($id);
if(empty($rule)){
return $this->json(1,'无此权限信息,请核对之后再操作!');
}else{
$this->data['item'] = $rule;
if($rule['parent_id'] > 0){
$parent = AuthRule::getById($rule['parent_id']);
$this->data['parent'] = $parent;
}
return $this->view();
}
}
/**
* 权限添加
*/
public function add()
{
if($this->request->isPost()){
$item = input('post.item/a');
try {
validate(VAuthRule::class)->check($item);
$rule = AuthRule::getByName($item['name']);
if(!empty($rule)){
return $this->json(1, '已存在相同权限');
}
$rule = AuthRule::create($item);
//基本权限的话需要重置所有已有角色权限缓存
if ($item['is_base'] > 0) {
AuthGroup::resetGroupRulesCache();
} else {
AuthGroup::resetGroupRulesCache(1);
}
Log::write('rule', 'add', "权限新增ID{$rule->id}, 标题:{$item['title']}");
return $this->json();
} catch (ValidateException $e) {
return $this->json(2, $e->getError());
}
}
$parentId = input('param.parent_id/d',0);
if($parentId > 0){
$parent = AuthRule::getById($parentId);
$this->data['parent'] = $parent;
}
$this->data['parentId'] = $parentId;
return $this->view();
}
/**
* 权限列表(全部)
*/
public function index()
{
$list = AuthRule::getListTree();
$this->data['items'] = $list;
return $this->view();
}
}

View File

@ -0,0 +1,63 @@
<?php
namespace app\controller\manager;
use app\model\{Member, Log};
class Safe extends Base
{
/**
* 安全设置
* @return Safe
*/
public function index()
{
$auth = session('auth');
if($this->request->isPost()){
if ($auth) {
$authId = $auth['userId'];
$oldPassword = trim(input('post.password_old'));
$password = trim(input('post.password'));
$passwordAgain = trim(input('post.password_again'));
$name = trim(input('post.name'));
$user = Member::getByID($authId);
if (empty($user)) {
return $this->json(1, '登录失效,请重新登录后再试!');
}
if (empty($name)) {
return $this->json(2, '用户名不能为空!');
}
$hasUser = Member::getByUserName($name);
if (!empty($hasUser) && $hasUser['id'] != $authId) {
return $this->json(3, '该用户名已被其他用户使用,请更换!');
}
if (empty($password) || empty($oldPassword)) {
return $this->json(4, '用户密码不能为空!');
}
if ($password != $passwordAgain) {
return $this->json(5, '新密码两次输入不一致!');
}
if (mb_strlen($password) < 6 || mb_strlen($password) > 30) {
return $this->json(6, '新密码长度格式不正确请输入6~30位密码');
}
if ($user['password'] != md5($oldPassword)) {
return $this->json(7,'原密码不正确');
}
$data['password'] = md5($password);
Member::updateById($authId, $data);
Log::write('safe', 'index', "安全设置ID{$authId}, 管理员:{$name}");
session('auth', null);
//cache('rules_'.$authId, null); //当前看代码,这个是无用代码;先注释掉,如果在使用过程中不会用到,再删除。
cache('group_rules_'.$authId, null);
cache('rule_names_'.$authId, null);
return $this->json(0, '修改成功,请重新登录!');
} else {
return $this->json(1, '登录失效,请重新登录后再试!');
}
}else{
$this->data['item'] = $auth;
return $this->view();
}
}
}

View File

@ -0,0 +1,191 @@
<?php
namespace app\controller\manager;
use app\model\{Slide as MSlide, System, Log};
use app\validate\Slide as VSlide;
use think\exception\ValidateException;
class Slide extends Base
{
//批量删除
public function batchDel()
{
if ($this->request->isPost()) {
$ids = input('post.ids/a');
if(empty($ids) || !is_array($ids)) {
return $this->json(2, '参数错误,请核对之后再操作!');
}
$items = MSlide::getListByIds($ids);
if(!empty($items)){
$delIds = [];
foreach($items as $item){
$delIds[] = $item['id'];
}
MSlide::destroy($delIds);
Log::write('link', 'betchDel', '批量删除了友情链接涉及到的ID为' . implode(',', $delIds));
return $this->json();
}else{
return $this->json(3, '待删除友情链接为空');
}
}
return $this->json(1, '非法请求!');
}
//可以删除一个可以批量删除TODO 需要调整
public function del()
{
if ($this->request->isPost()) {
$id = input('post.id/d');
if(is_numeric($id) && $id > 0) {
$item = MSlide::getById($id);
if(!empty($item)){
MSlide::destroy($id);
Log::write('link', 'del', '删除轮播图ID' . $id . ',标题:' . $item['title']);
return $this->json();
}
return $this->json(3, '待删除轮播图不存在');
}
return $this->json(2, '参数错误,请核对之后再操作!');
}
return $this->json(1, '非法请求!');
}
//排序
public function sort()
{
if($this->request->isPost()){
$id = input('post.id/d');
$sort = input('post.sort');
$num = input('post.num/d', 1);
if($num <= 0){
$num = 1;
}
if(!in_array($sort, ['up', 'down'], true)){
return $this->json(2, '参数错误');
}
$item = MSlide::getById($id);
if(empty($item)){
return $this->json(3, '无此轮播图');
}
if($sort == 'up'){
$where = "sort < {$item['sort']}";
$order = "sort desc";
}else{
$where = "sort > {$item['sort']}";
$order = "sort asc";
}
$forSortItems = MSlide::getListByWhereAndOrder($where, $order, $num);
if(!empty($forSortItems)){
$updateData = [];
$forSortCount = count($forSortItems);
for($i = 0; $i < $forSortCount; $i++){
if($i == 0){
$updateData[] = [
'id' => $forSortItems[$i]['id'],
'sort' => $item['sort']
];
}else{
$updateData[] = [
'id' => $forSortItems[$i]['id'],
'sort' => $forSortItems[$i - 1]['sort']
];
}
}
$updateData[] = [
'id' => $item['id'],
'sort' => $forSortItems[$i - 1]['sort']
];
if(!empty($updateData)){
$model = new MSlide();
$model->saveAll($updateData);
$sortStr = $sort == 'up' ? '上移' : '下调';
Log::write('slide', 'sort', "轮播图排序ID{$id} ,标题:{$item['title']}{$sortStr}{$num}");
return $this->json();
}
}
return $this->json(4, '无须调整排序!');
}
return $this->json(1, '非法请求!');
}
//编辑
public function edit()
{
if($this->request->isPost()){
$item = input('post.item');
$img = input('post.img_pc');
$imgMobile = input('post.img_mobile');
$id = input('post.id/d');
if(is_numeric($id) && $id > 0) {
$slide = MSlide::getById($id);
if(empty($slide)) {
return $this->json(2, 'id参数错误,没有相关轮播图信息记录!');
}
if(!empty($imgMobile)){
$item['src_mobile'] = $imgMobile;
}
if(!empty($img)){
$item['src'] = $img;
}
try {
validate(VSlide::class)->check($item);
MSlide::updateById($id, $item);
Log::write('slide', 'edit', "轮播图编辑ID{$id} ,标题:{$item['title']}");
return $this->json();
} catch (ValidateException $e) {
return $this->json(3, $e->getError());
}
}
return $this->json(1, '参数错误,请核对之后再操作!');
}else{
$id = input('param.id/d');
$item = MSlide::getById($id);
$imgSize = System::getSlideImageSize();
$this->data['item'] = $item;
$this->data['img_size'] = $imgSize;
return $this->view();
}
}
//添加
public function add()
{
if($this->request->isPost()){
$item = input('post.item');
$img = input('post.img_pc');
$imgMobile = input('post.img_mobile');
if (empty($item)) {
return $this->json(1, '参数错误,请核对之后再操作!');
}
if(!empty($img)){
$item['src'] = $img;
}
if(!empty($imgMobile)){
$item['src_mobile'] = $imgMobile;
}
try {
validate(VSlide::class)->check($item);
$slide = MSlide::create($item);
Log::write('slide', 'add', "轮播图新增ID{$slide->id} ,标题:{$item['title']}");
return $this->json();
} catch (ValidateException $e) {
return $this->json(2,$e->getError());
}
}else{
$this->data['img_size'] = System::getSlideImageSize();
return $this->view();
}
}
/**
* 列表
* 暂定只有首页轮播图,后期可以根据需求进行分组管理
* @return Slide
*/
public function index()
{
$items = MSlide::getList();
$this->data['items'] = $items;
return $this->view();
}
}

View File

@ -0,0 +1,63 @@
<?php
namespace app\controller\manager;
use app\service\Tool;
use app\model\{System as MSystem, Log};
use app\service\Image;
use think\facade\Cache;
class System extends Base
{
/**
* 获取当前系统设置
*
* @return void
*/
public function index()
{
if ($this->request->isPost()) {
$item = input('post.item/a');
$img = input('post.img');
$system = MSystem::getSystem();
if (empty($system)) {
if(!empty($img)){
$item['mark_img'] = $img;
}
$system = MSystem::create($item);
Log::write('system', 'index', "系统设置ID{$system->id}");
} else {
if (!empty($img)) {
Image::delImg($system['mark_img']);
$item['mark_img'] = $img;
}
MSystem::update($item, ['id' => $system['id']]);
Log::write('system', 'index', "系统设置ID{$system['id']}");
}
return $this->json();
} else {
$item = MSystem::getSystem();
$positions = Image::getMarkPosition();
$this->data['item'] = $item;
$this->data['positions'] = $positions;
return $this->view();
}
}
public function other()
{
return $this->view();
}
public function clearCache()
{
Cache::clear();
$cachePath = app()->getRuntimePath().'cache';
$tempPath = app()->getRuntimePath().'temp';
Tool::removeByPath($cachePath);
Tool::removeByPath($tempPath);
clearstatcache();
return $this->json();
}
}

View File

@ -0,0 +1,139 @@
<?php
namespace app\controller\manager;
use app\service\Image;
use app\model\{System, File};
use app\validate\Upload as VUpload;
use think\facade\{Filesystem, Config, Lang};
use think\Image as TImage;
use app\controller\BaseController;
class Upload extends BaseController
{
private $isCompress = true;
private $validate;
private $uploadPath;
private $uploadPathIsWritable = 0;
public function __construct()
{
$system = System::getSystem();
if (!empty($system)) {
$this->isCompress = $system['compress']??true;
}
$this->validate = new VUpload();
$this->uploadPath = Config::get('filesystem.disks.local.url');
if(is_writable(app()->getRootPath() . 'public' . $this->uploadPath)){
$this->uploadPathIsWritable = 1;
}
}
//视频上传
public function video()
{
if(!$this->uploadPathIsWritable){
return $this->json(1, '上传文件夹需要写入权限');
}
$video = request()->file('video');
if($this->validate->checkVideo($video)){
$src = Filesystem::disk('video')->putFile(date('Ymd'), $video, 'uniqid');
$src = $this->uploadPath . '/' . $src;
$return['src'] = $src;
File::add($video, $src, 'video'); //加入上传文件表
return $this->json(0, 'ok', $return);
}else{
$errorMsg = Lang::get($this->validate->getError());
return $this->json(1, $errorMsg);
}
}
//文件上传(通用)
public function file()
{
$file = request()->file('file');
if($this->validate->checkFile($file)){
try{
if(!$this->uploadPathIsWritable){
throw new \Exception('上传文件夹需要写入权限');
}
$src = Filesystem::putFile(date('Ymd'), $file, 'uniqid');
$src = $this->uploadPath . '/' . $src;
$return['src'] = $src;
$return['name'] = $file->getOriginalName();
File::add($file, $src, 'file'); //加入上传文件表
} catch (\Exception $e) {
return $this->json(1, $e->getMessage());
}
return $this->json(0,'ok',$return);
}else{
$errorMsg = Lang::get($this->validate->getError());
return $this->json(1, $errorMsg);
}
}
//图片上传(通用)
public function image()
{
$image = request()->file('image');
if($this->validate->checkImage($image)){
try{
if(!$this->uploadPathIsWritable){
throw new \Exception('上传文件夹需要写入权限');
}
$src = Filesystem::putFile(date('Ymd'), $image, 'uniqid');
$src = $this->uploadPath . '/' . $src;
$suffix = strtolower($image->getOriginalExtension());
if($suffix == 'gif'){
$return['thumb_src'] = $src; //TODO获取GIF缩略图
}else{
$return['thumb_src'] = Image::getThumb($src, 100, 100, TImage::THUMB_SCALING); //上传返回缩略图宽度为100
}
$return['src'] = $src;
if($this->isCompress){
Image::resize($src);
}
File::add($image, $src); //加入上传文件表
} catch (\Exception $e) {
return $this->json(1, $e->getMessage());
}
return $this->json(0, 'ok', $return);
}else{
$errorMsg = Lang::get($this->validate->getError());
return $this->json(1, $errorMsg);
}
}
//富文本编辑器商城图片
public function wangImage()
{
$imageArr = request()->file('wang_img'); // 该方式前端js上传方法中字段名称必须以数组形式传参 如 wang_img[] = 值
$errno = 0;
$data = [];
if(!$this->uploadPathIsWritable){
$errno = 1;
$data[] = '上传文件夹需要写入权限';
}else{
foreach ($imageArr as $image) {
if($this->validate->checkImage($image)){
$src = Filesystem::putFile(date('Ymd'), $image, 'uniqid');
$src = $this->uploadPath . '/' . $src;
$data[] = $src;
if($this->isCompress){
Image::resize($src);
}
File::add($image, $src); //加入上传文件表
}else{
$errno = 1;
$data = [];
$data[] = Lang::get($this->validate->getError());
break;
}
}
}
$return['errno'] = $errno;
$return['data'] = $data;
return json($return);
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace app\controller\manager;
/**
* 微信公众号配置
* Class Wechat
* @package app\controller\manager
*/
class Wechat extends Base
{
// 公众号设置
public function base()
{
// TODO
return 'TODO ...';
}
// 公众号设置
public function menu()
{
// TODO
return 'TODO ...';
}
}

17
app/event.php Normal file
View File

@ -0,0 +1,17 @@
<?php
// 事件定义文件
return [
'bind' => [
],
'listen' => [
'AppInit' => [],
'HttpRun' => [],
'HttpEnd' => [],
'LogLevel' => [],
'LogWrite' => [],
],
'subscribe' => [
],
];

12
app/middleware.php Normal file
View File

@ -0,0 +1,12 @@
<?php
// 全局中间件定义文件
return [
// 全局请求缓存
// \think\middleware\CheckRequestCache::class,
// 多语言加载
//\think\middleware\LoadLangPack::class,
// Session初始化
\think\middleware\SessionInit::class,
//csrf_token 全局校验
\app\middleware\Csrf::class,
];

65
app/middleware/Auth.php Normal file
View File

@ -0,0 +1,65 @@
<?php
namespace app\middleware;
use Closure;
use app\model\AuthRule;
use think\facade\Cache;
class Auth
{
public function handle($request, Closure $next)
{
$auth = session('auth');
if(!$auth){
return redirect(url('manager.login/index'));
}
// 角色权限
$rules = Cache::get('group_rules_'.$auth['groupId']);
$ruleNames = Cache::get('rule_names_'.$auth['groupId']);
//如果是超级管理员,不用验证权限,给予所有权限
if(empty($rules)){
$ruleNames = [];
if($auth['groupId'] == 1){
$rules = AuthRule::getListTree(0);
}else{
// 角色权限 + 基本权限
$rules = AuthRule::getAuthListByRuleIDs($auth['groupId']);
}
foreach($rules as &$rule){
if(!stripos($rule['name'],'/')){
$rule['name'] = $rule['name'].'/index';
}
$ruleNames[] = strtolower($rule['name']);
if(isset($rule['children']) && !empty($rule['children'])){
foreach($rule['children'] as &$child){
if(!stripos($child['name'],'/')){
$child['name'] = $child['name'].'/index';
}
$ruleNames[] = strtolower($child['name']);
}
}
}
// 对角色赋予权限缓存,角色权限更新时需要同步更新缓存
Cache::set('group_rules_'.$auth['groupId'], $rules);
Cache::set('rule_names_'.$auth['groupId'], $ruleNames);
}
if($auth['groupId'] == 1){
return $next($request);
}
$controller = strtolower(request()->controller());
$controller = str_replace('manager.', '', $controller);
$action = request()->action();
$name = strtolower($controller.'/'.$action);
if(!empty($ruleNames) && in_array($name, $ruleNames, true)){
return $next($request);
}
if(request()->isAjax()){
return json(['code' => 1,'msg' => '没有权限']);
}else{
exit('无操作权限') ;
}
}
}

79
app/middleware/Csrf.php Normal file
View File

@ -0,0 +1,79 @@
<?php
namespace app\middleware;
use Closure;
/**
* CSRF校验
*/
class Csrf
{
public function handle($request, Closure $next)
{
$csrfToken = session('_token');
if (empty($csrfToken)) {
$csrfToken = randomStr(1, 32);
session('_token', $csrfToken);
}
$paramToken = input('param._token', '');
if (!empty($paramToken)) {
if ($paramToken != $csrfToken) {
return $this->csrfError($request);
}
} elseif($request->isPost() || $request->isAjax()) {
if (empty($paramToken) || $paramToken != $csrfToken) {
return $this->csrfError($request);
}
//HTTP_REFERER 认证
$referer = $_SERVER['HTTP_REFERER'] ?? null;
if(!empty($referer)) {
$domain = $request->domain();
$urlInfo = parse_url($referer);
$scheme = $urlInfo['scheme'] ?? '';
$requestSrc = '';
if (!empty($scheme)) {
$requestSrc = $scheme.'://'.($urlInfo['host'] ?? '');
}
if($domain != $requestSrc) {
return $this->csrfError($request);
}
}
}
$request->csrfToken = $csrfToken;
return $next($request);
}
protected function csrfError($request, $msg = '非法请求, 用户身份认证失败!')
{
if($request->isAjax()) {
return json(['code'=>401, 'msg'=>$msg],200);
} else {
$referer = $_SERVER['HTTP_REFERER'] ?? null;
if (empty($referer)) {
$url = '/';
} else {
$domain = $request->domain();
$urlInfo = parse_url($referer);
$scheme = $urlInfo['scheme'] ?? '';
$requestSrc = '';
if (!empty($scheme)) {
$requestSrc = $scheme.'://'.($urlInfo['host'] ?? '');
}
if($domain != $requestSrc) {
$url = '/';
} else {
$url = 'javascript:history.back(-1);';
}
}
$errorData = [
'code'=> 401,
'msg' => $msg,
'data' => [],
'wait' => 5,
'url' => $url
];
return view('error/400', $errorData);
// 返回401视图 response type has html、json、jsonp、xml、file、view、redirect
}
}
}

155
app/model/Article.php Normal file
View File

@ -0,0 +1,155 @@
<?php
namespace app\model;
class Article extends Base
{
//获取最高访问的文章列表
public static function getMostVisited($limit = 5)
{
if($limit <= 0){
$limit = 5;
}
return self::order('views', 'desc')
->limit($limit)
->select()
->toArray();
}
//获取栏目下最新记录
public static function getLatestByCategory($categoryId, $limit = 5)
{
if(empty($categoryId)){
return [];
}
if($limit <= 0){
$limit = 5;
}
return self::where('category_id', $categoryId)
->order('create_time', 'desc')
->limit($limit)
->select()
->toArray();
}
//根据文章ID和栏目ID获取下一篇文章
public static function getNextArticleByIdAndCategoryId($id, $categoryId)
{
return self::where('id','<',$id)
->where('category_id', $categoryId)
->where('status', 1)
->order('sort desc')
->findOrEmpty()
->toArray();
}
//根据文章ID和栏目ID获取上一篇文章
public static function getPrevArticleByIdAndCategoryId($id, $categoryId)
{
return self::where('id','>',$id)
->where('category_id',$categoryId)
->where('status', 1)
->order('sort asc')
->findOrEmpty()
->toArray();
}
//根据栏目ID获取文章列表
public static function getListByCategory($categoryId, $limit = 10)
{
return self::where('category_id', $categoryId)
->where('status', 1)
->order("sort desc")
->limit($limit)
->select()
->toArray();
}
//根据栏目ID获取文章分页列表
public static function getListPageByCategory($categoryId, $per = 20, $keyword = '')
{
$where = [
['category_id', '=', $categoryId],
['status', '=', 1],
];
$param['category_id'] = $categoryId;
if($keyword!=''){
$where[] = ['title', 'like', '%'.$keyword.'%'];
$param['keyword'] = $keyword;
}
$paginate = [
'list_rows' => $per,
'query' => $param
];
return self::where($where)
->order("sort desc")
->paginate($paginate,false);
}
public static function onAfterInsert($article)
{
$article->sort = $article->id;
$article->create_time = $article->update_time = time();
$auth = session('auth');
$article->created = $article->updated = $auth['userName'];
$article->save();
}
/**
* 获取文章列表
* @param $categoryId 分类ID
* @param int $per 每页数量
* @param string $keyword 关键词
* @param array $param 文章类型:置顶、热门、推荐 ['top','hot','recommend']
* @param int $status 文章状态,-1表示不限制
* @return \think\Paginator
* @throws \think\db\exception\DbException
*/
public static function getList($categoryId, $per = 20, $keyword = '', $param = [], $status = -1)
{
$whereMap = [];
$pageParam = [];
if(is_numeric($categoryId) && $categoryId > 0) {
$whereMap[] = ['category_id', '=', $categoryId];
$pageParam['category_id'] = $categoryId;
}
if(!empty($keyword)){
$whereMap[] = ['title', 'like', '%'.$keyword.'%'];
$pageParam['keyword'] = $keyword;
}
if (is_array($param) && count($param) > 0) {
$pageParam['param'] = $param;
foreach ($param as $vo) {
if(in_array($vo, ['top','hot','recommend'], true)) {
$whereMap[] = ["{$vo}", '=', 1];
}
}
}
$paginate = [
'list_rows' => $per,
'query' => $pageParam
];
return self::when(count($whereMap) > 0, function($query) use ($whereMap) {
$query->where($whereMap);
})
->order("sort desc")
->paginate($paginate,false);
}
//获取文章涉及到的图片
public static function getFilesInUse()
{
$items = self::select()->toArray();
$data = [];
foreach($items as $item){
$src = trim($item['src']);
if(!empty($src)){
$key = getKeyByPath($src);
$data[$key] = $src;
}
$imgs = getImageUrlFromText($item['content']);
if(!empty($imgs)){
$data = array_merge($data, $imgs);
}
$videos = getVideoUrlFromText($item['content']);
if(!empty($videos)){
$data = array_merge($data, $videos);
}
}
return $data;
}
}

54
app/model/AuthGroup.php Normal file
View File

@ -0,0 +1,54 @@
<?php
namespace app\model;
use think\facade\Cache;
class AuthGroup extends Base
{
public static function updateRules($groupId, $rules)
{
$rules = implode(',', $rules);
$data = ['rules' => $rules];
self::updateById($groupId, $data);
Member::where('group_id', $groupId)
->update($data);
}
//根据ID获取角色列表ID为1是超级管理员
public static function getListById($groupId = 1)
{
if($groupId < 1){
return [];
}
$group = self::getById($groupId);
if(empty($group)){
return [];
}
if($groupId == 1){
return self::select()
->toArray();
}else{
return self::where('id','<>','1')
->select()
->toArray();
}
}
/**
* 重置角色权限缓存
* @param int $groupId 指定重置的角色ID若不指定则重置所有角色
*/
public static function resetGroupRulesCache($groupId = 0)
{
if(is_numeric($groupId) && $groupId > 0) {
Cache::set('group_rules_'.$groupId, null);
Cache::set('rule_names_'.$groupId, null);
} else {
$groupIds = self::column('id');
foreach ($groupIds as $id){
Cache::set('group_rules_'.$id, null);
Cache::set('rule_names_'.$id, null);
}
}
}
}

271
app/model/AuthRule.php Normal file
View File

@ -0,0 +1,271 @@
<?php
namespace app\model;
class AuthRule extends Base
{
//根据权限IDs获取权限列表
public static function getListByRuleIDs($ruleIds)
{
$items = self::wherein('id',$ruleIds)
//->where('status', 1)
->order('sort', 'asc')
->select()
->toArray();
return self::getChildren($items);
}
//角色权限 + 基本权限
public static function getAuthListByRuleIDs($groupId)
{
$order = ['sort'=>'asc'];
if ($groupId == 1) {
$items = self::order($order)->select()->toArray();
} else {
$group = AuthGroup::getById($groupId);
$rulesArr = [];
if ($group && !empty($group['rules'])) {
$rulesArr = array_filter(explode(',', $group['rules']));
}
if (count($rulesArr) >0) {
$items = self::wherein('id', $rulesArr)->whereOr('is_base', 1)->order($order)->select()->toArray();
} else {
$items = self::where('is_base', 1)->order($order)->select()->toArray();
}
}
$levelItems = self::getChildren($items);
$levelIds = self::getChildrenIds($items);
// 独立写入不在连续分层中的权限,连续分层权限可用于菜单显示
foreach ($items as $item) {
if(!in_array($item['id'], $levelIds)) {
$levelItems[] = $item;
}
}
return $levelItems;
}
//根据上级ID获取下级权限列表
public static function getListTree($forMenu = 0)
{
$items = self::getList($forMenu);
return self::getChildren($items);
}
//根据上级ID获取下级权限列表
public static function getListByParentId($parentId)
{
return self::where('parent_id', $parentId)
->order('sort', 'asc')
->select()
->toArray();
}
/**
* @param array $items
* @param integer $parent_id
* @return array
*/
public static function getChildren($items, $parentId = 0)
{
$data = [];
foreach($items as $item){
if($item['parent_id'] == $parentId){
$childern = self::getChildren($items, $item['id']);
if(!empty($childern)){
$item['hasChildren'] = true;
$item['children'] = $childern;
}
$data[] = $item;
}
}
return $data;
}
public static function getChildrenIds($items, $parentId = 0)
{
$data = [];
foreach($items as $item){
if($item['parent_id'] == $parentId){
$childrenIds = self::getChildrenIds($items, $item['id']);
if(!empty($childrenIds)){
$data = array_merge($data, $childrenIds);
}
$data[] = $item['id'];
}
}
return $data;
}
/**
* @param integer $forMenu 大于0表示获取可显示的权限
* @return void
*/
private static function getList($forMenu = 0)
{
if($forMenu){
return self::where('status', 1)->order('sort', 'asc')->select()->toArray();
}else{
return self::order('sort', 'asc')->select()->toArray();
}
}
//获取可显示权限
public static function getTopListForDisplay()
{
return self::where('status', 1)
->where('parent_id', 0)
->order('sort', 'asc')
->select()
->toArray();
}
//根据两个名字获取权限
public static function getByTwoName($name, $otherName)
{
return self::whereOr('name',$name)
->whereOr('name',$otherName)
->findOrEmpty()
->toArray();
}
//根据name获取权限
public static function getByName($name)
{
return self::where('name', $name)
->findOrEmpty()
->toArray();
}
public static function onAfterInsert($rule)
{
$rule->sort = $rule->id;
$rule->save();
}
/**
* 该情况适合无限级菜单分组显示 当前系统仅支持2级权限因此推荐使用getListTree 方法)
* @param int $forMenu 是否只获取可显示菜单权限
*/
public static function getListTreeSort($forMenu = 0)
{
$items = self::getList($forMenu);
return self::groupSort($items, 0, 1, true, true);
}
/**
* 分组排序(不拆分为子集)
*
* @param $items 数据源
* @param integer $pid
* @param integer $level
* @param bool $clear 是否释放局部变量(外部调用时必须先释放,避免数据被累加;内部不用,需要累加)
* @param bool $isArr 传入的$items是否为数组默认否数据集
* @param string $levelSpare 分层符
* @return void
*/
public static function groupSort($items,$pid = 0, $level = 1, $clear = true, $isArr = false, $levelSpare = '_')
{
static $data = [];
if ($clear) {
$data = [];
static $data = [];
}
if(!empty($levelSpare) && $level > 1) {
$levelSpare = str_repeat($levelSpare, $level-1);
}
if (count($items) > 0) {
if ($isArr) {
foreach ($items as $key => $item) {
if ($item['parent_id'] == $pid) {
$item['level'] = $level;
$item['title'] = $levelSpare.$item['title'];
$data[] = $item;
self::groupSort($items, $item['id'], $level+1, false, $isArr);
}
}
} else {
foreach ($items as $key => $item) {
if ($item->parent_id == $pid) {
$item->level = $level;
$item->title = $levelSpare.$item->title;
$data[] = $item;
self::groupSort($items, $item->id, $level+1, false, $isArr);
}
}
}
}
return $data;
}
/**
* 树形排序 (拆分子集)
*
* @param Collection $items 数据集(非数组)
* @param integer $pid
* @param integer $level
* @return void
*/
public static function treeSort($items,$pid = 0, $level = 1)
{
$data = [];
if (count($items) > 0) {
foreach ($items as $key => $item) {
if ($item->parent_id == $pid) {
$item->level = $level;
$children = self::treeSort($items, $item->id, $level+1);
$item->children = $children;
$data[] = $item;
}
}
}
return $data;
}
/**
* 查询用户权限(登陆时更具角色更新为最新权限)
* 可显示权限和所有权限 角色权限 + 基本权限
*
* @param boolean $groupId 角色ID,超级管理员(对应group_id = 1)
* @return void
*/
public static function userRolesList($groupId = 1)
{
$userRoles = [];
$items = null;
$order = ['sort'=>'asc'];
if ($groupId == 1) {
$items = self::order($order)->select();
} else {
$group = AuthGroup::getById($groupId);
$rulesArr = [];
if ($group && !empty($group['rules'])) {
$rulesArr = array_filter(explode(',', $group['rules']));
}
if (count($rulesArr) >0) {
$items = self::wherein('id', $rulesArr)->whereOr('is_base', 1)->order($order)->select();
} else {
$items = self::where('is_base', 1)->order($order)->select();
}
}
if (empty($items) || $items->isEmpty()) {
return $userRoles;
}
$allRulesId = [];
$displayRules = [];
$displayRulesId = [];
foreach ($items as $key => $item) {
$allRulesId[] = $item->id;
if ($item->status > 0) {
$displayRules[] = $item;
$displayRulesId[] = $item->id;
}
}
$userRoles['allRules'] = $items;
$userRoles['allRulesId'] = $allRulesId;
$userRoles['displayRules'] = $displayRules;
$userRoles['displayRulesId'] = $displayRulesId;
return $userRoles;
}
}

41
app/model/Base.php Normal file
View File

@ -0,0 +1,41 @@
<?php
namespace app\model;
use think\Model;
class Base extends Model
{
protected $autoWriteTimestamp = false;
//根据Id列表获取列表
public static function getListByIds($ids)
{
if(count($ids) == 0 || empty($ids)) {
return [];
}
return self::where('id', 'in', $ids)->select()->toArray();
}
//根据ID获取单条数据
public static function getById($id)
{
if($id <= 0){
return [];
}
return self::where('id', $id)->findOrEmpty()->toArray();
}
//根据ID更新数据
public static function updateById($id, $data)
{
return self::where('id', $id)->update($data);
}
//根据where条件和排序获取记录
public static function getListByWhereAndOrder($where, $order, $limit = 1)
{
return self::where($where)
->order($order)
->limit($limit)
->select()
->toArray();
}
}

113
app/model/Block.php Normal file
View File

@ -0,0 +1,113 @@
<?php
namespace app\model;
class Block extends Base
{
const BLOCK = 1;
const IMG = 2;
const TEXT = 3;
const GROUP = 4;
const VIDEO = 5;
const CODE = 6;
//获取类型键值对
public static function getTypes()
{
return [
'1' => 'block',
'2' => 'img',
'3' => 'text',
'4' => 'group',
'5' => 'video',
'6' => 'code'
];
}
//根据键值和类目获取数据
public static function getByKeyword($keyword, $categoryId)
{
return self::where('keyword', $keyword)->where('category_id', $categoryId)->findOrEmpty()->toArray();
}
//根据栏目ID获取块列表
public static function getByCategoryId($categoryId)
{
if($categoryId <= 0){
return [];
}
$category = Category::getById($categoryId);
if(empty($category)){
return [];
}
$items = self::where('category_id', $categoryId)
->order('sort asc')
->select()
->toArray();
if(empty($items)){
return [];
}
$data = [];
foreach($items as $item){
$data[$item['keyword']] = $item;
}
return $data;
}
public static function onAfterInsert($item)
{
$item->sort = $item->id;
$item->save();
}
//获取在使用中的文件
public static function getFilesInUse()
{
$items = self::whereIn('type', [self::IMG, self::GROUP, self::VIDEO, self::TEXT])
->select()
->toArray();
$data = [];
foreach($items as $item){
if($item['type'] == self::IMG){
$file = trim($item['value']);
if(!empty($file)){
$key = getKeyByPath($file);
$data[$key] = $file;
}
}elseif($item['type'] == self::GROUP){
$val = trim($item['value']);
if (!empty($val) && $val != '[]' && $val != 'null') {
$files = json_decode($val, true);
foreach($files as $file){
$src = trim($file['src']);
if(!empty($src)){
$key = getKeyByPath($src);
$data[$key] = $src;
}
}
}
}elseif($item['type'] == self::VIDEO){
$video = trim($item['value']);
if(!empty($video)){
$key = getKeyByPath($video);
$data[$key] = $video;
}
$img = trim($item['img']);
if(!empty($img)){
$key = getKeyByPath($img);
$data[$key] = $img;
}
}elseif($item['type'] == self::TEXT){
$imgs = getImageUrlFromText($item['value']);
if(!empty($imgs)){
$data = array_merge($data, $imgs);
}
$videos = getVideoUrlFromText($item['value']);
if(!empty($videos)){
$data = array_merge($data, $videos);
}
}
}
return $data;
}
}

188
app/model/Category.php Normal file
View File

@ -0,0 +1,188 @@
<?php
namespace app\model;
class Category extends Base
{
//获取首页栏目ID
public static function getIndex()
{
return self::where('is_index', 1)->findOrEmpty()->toArray();
}
//根据上级ID和语言获取下级栏目
public static function getChildrenByParentId($parentId)
{
if($parentId <= 0){
return [];
}
$category = self::getById($parentId);
if(empty($category)){
return [];
}
return self::alias('c')
->leftJoin('model m', 'c.model_id=m.id')
->field('c.*, m.manager, m.template, m.name as modelName')
->where('c.parent_id', $parentId)
->order('c.sort','asc')
->select()
->toArray();
}
//重写方法
public static function getById($categoryId)
{
return self::alias('c')
->leftJoin('model m', 'c.model_id = m.id')
->where('c.id', $categoryId)
->field('c.*, m.template')
->findOrEmpty()
->toArray();
}
//查看是否有下级栏目
public static function hasChildren($categoryId)
{
if (is_array($categoryId)) {
$count = self::where('parent_id', 'in', $categoryId)->count();
} else {
$count = self::where(['parent_id'=>$categoryId])->count();
}
return $count ? true : false;
}
//获取前台菜单
public static function getListForFrontMenu()
{
$items = self::alias('c')
->leftJoin('model m','c.model_id=m.id')
->field('c.*, m.manager, m.template')
->where('c.state', 1)
->order('is_index desc, sort asc')
->select()
->toArray();
return self::getCates($items);
}
/**
* 获取栏目
* @param bool $limit 是否限制查询
* @param array $cates 需要限制查询的栏目IDs
*/
public static function getList($limit = false, $cates = [])
{
if ($limit && empty($cates)) {
return [];
}
return self::alias('c')
->leftJoin('model m', 'c.model_id=m.id')
->field('c.*, m.manager, m.name as modelName')
->when($limit, function($query) use($cates) {
$query->whereIn('c.id', $cates);
})
->order('sort','asc')
->select()
->toArray();
}
//获取栏目分级列表
public static function getListTree($isMenu = false)
{
if ($isMenu) {
$items = self::where('c.state', 1) ->order('sort','asc')->select()->toArray();
} else {
$items = self::order('sort','asc')->select()->toArray();
}
return self::getCates($items);
}
//获取递归栏目
public static function getCates($cates,$parentId=0)
{
$menus = [];
foreach($cates as $cate){
if($cate['parent_id'] == $parentId){
$children = self::getCates($cates,$cate['id']);
if(!empty($children)){
$cate['children'] = $children;
}
$menus[] = $cate;
}
}
return $menus;
}
public static function onAfterInsert($category)
{
$category->sort = $category->id;
$category->save();
}
//递归获取上级栏目名称
public static function getCatesCrumbs($currentId = 0)
{
$crumbs = [];
$category = self::getById($currentId);
if($category['parent_id'] == 0){
$cateCrumbs[] = $category['title'];
}else{
$categoryIds = explode(',', trim($category['path'], ','));
$categories = self::where('id', 'in', $categoryIds)->column('*', 'id');
foreach($categoryIds as $id){
if(isset($categories[$id])){
$crumbs[] = $categories[$id]['title'];
}
}
}
return $crumbs;
}
//获取栏目中涉及到的文件
public static function getFilesInUse()
{
$items = self::select()->toArray();
$data = [];
foreach($items as $item){
$src = trim($item['src']);
if(!empty($src)){
$key = getKeyByPath($src);
$data[$key] = $src;
}
}
return $data;
}
//当前分类的最高级分类Id
public static function firstGradeById($id)
{
$res = 0;
$item = self::getById($id);
if ($item) {
$res = $id;
if ($item['parent_id'] > 0) {
$items = self::select()->toArray();
$first = self::getFirstGrade($items, $item['parent_id']);
if (!empty($first)) {
$res = $first['id'];
}
}
}
return $res;
}
public static function getFirstGrade($items, $parentId)
{
$data = [];
foreach ($items as $key=> $item) {
if($item['id'] == $parentId) {
if ($item['parent_id'] > 0) {
unset($items[$key]);
$parent = self::getFirstGrade($items, $item['parent_id']);
if (!empty($parent)) {
$data = $parent;
} else {
$data = $item;
}
} else {
$data = $item;
}
}
}
return $data;
}
}

93
app/model/File.php Normal file
View File

@ -0,0 +1,93 @@
<?php
namespace app\model;
use think\Image;
class File extends Base
{
const IMG = 'img';
const VIDEO = 'video';
const FILE = 'file';
//获取文件类型
public static function getTypes()
{
return [
'img' => '图片',
'video' => '视频',
'file' => '文件'
];
}
//获取文件列表
public static function getList($type = '', $page = 1, $per = 20)
{
$limit = ($page - 1) * $per;
if($type != ''){
if(!in_array($type, array_keys(self::getTypes()))){
return [];
}
$items = self::where('type', $type)
->order('id desc');
}else{
$items = self::order('id desc');
}
$items = $items->limit($limit, $per)->select()->toArray();
foreach($items as &$item){
$item['sizeStr'] = sizeToStr($item['size']);
}
return $items;
}
//获取分页列表
public static function getListPage($type = '', $per = 20)
{
if($type != ''){
if(!in_array($type, array_keys(self::getTypes()))){
return [];
}
return self::where('type', $type)
->order('id desc')
->paginate([
'list_rows' => $per,
'query' => [
'type' => $type
]
], false);
}else{
return self::order('id desc')
->paginate([
'list_rows' => $per
], false);
}
}
//添加,$w_h图片尺寸大小单位像素只对type=img时有效
public static function add($file, $src, $type = 'img')
{
$realPath = app()->getRootPath() . ltrim($src,'/');
if(is_file($realPath) && $type == 'img'){
$img = Image::open($realPath);
list($w,$h) = $img->size();
$w_h = $w . 'px * ' . $h . 'px';
}else{
$w_h = '';
}
return self::create([
'type' => $type,
'name' => $file->getOriginalName(),
'src' => $src,
'size' => $file->getSize(),
'suffix' => $file->getOriginalExtension(),
'mime_type' => $file->getOriginalMime(),
'create_time' => time(),
'w_h' => $w_h
]);
}
//获取所有记录
public static function getAll()
{
return self::select()->toArray();
}
}

45
app/model/History.php Normal file
View File

@ -0,0 +1,45 @@
<?php
namespace app\model;
class History extends Base
{
public static function onAfterInsert($item)
{
$item->sort = $item->id;
$item->save();
}
public static function getPaginateList($categoryId, $per = 20, $isSample = false)
{
$paginate = [
'list_rows' => $per
];
$items = self::where('category_id', $categoryId)->order('sort', 'asc')->paginate($paginate, $isSample);
if(!$items->isEmpty()) {
$ids = $items->column('id');
$infoList = HistoryInfo::getByHistoryIds($ids);
foreach ($items as $k => $item) {
$items[$k]['info'] = $infoList[$item['id']] ?? [];
}
}
return $items;
}
public static function getByCategoryId($categoryId, $onlyVisible = false)
{
$items = self::where('category_id', $categoryId)
->when($onlyVisible, function($query){
$query->where('visible', 1);
})
->order('sort', 'asc')
->select();
if(!$items->isEmpty()) {
$ids = $items->column('id');
$infoList = HistoryInfo::getByHistoryIds($ids, $onlyVisible);
foreach ($items as $k => $item) {
$items[$k]['info'] = $infoList[$item['id']] ?? [];
}
}
return $items->toArray();
}
}

52
app/model/HistoryInfo.php Normal file
View File

@ -0,0 +1,52 @@
<?php
namespace app\model;
class HistoryInfo extends Base
{
public static function onAfterInsert($item)
{
$item->sort = $item->id;
$item->save();
}
public static function getByHistoryIds($historyIds = [], $onlyVisible = false)
{
if(!is_array($historyIds) || count($historyIds) == 0) {
return [];
}
$list = self::whereIn('history_id', $historyIds)
->when($onlyVisible, function ($query) {
$query->where('visible', 1);
})
->order(['history_id'=>'asc','sort'=>'asc'])
->select()
->toArray();
$data = [];
foreach ($list as $item) {
$item['img_list'] = [];
if(!empty($item['imgs'])) {
$item['img_list'] = array_filter(explode(',', $item['imgs']));
}
$data[$item['history_id']][] = $item;
}
return $data;
}
public static function countByHistoryId($historyId)
{
return self::where('history_id', $historyId)->count();
}
public static function delByHistoryId($historyId)
{
return self::where('history_id', $historyId)->delete();
}
public static function getByHistoryId($historyId)
{
if($historyId <= 0) {
return [];
}
return self::where('history_id', $historyId)->order(['sort'=>'asc'])->select()->toArray();
}
}

39
app/model/Link.php Normal file
View File

@ -0,0 +1,39 @@
<?php
namespace app\model;
class Link extends Base
{
public static function delByIds($ids)
{
return self::whereIn('id', $ids)->delete();
}
//获取友情链接
public static function getList()
{
return self::order('sort asc')
->select()
->toArray();
}
public static function onAfterInsert($item)
{
$item->sort = $item->id;
$item->save();
}
//获取友情链接涉及到的文件
public static function getFilesInUse()
{
$items = self::select()->toArray();
$data = [];
foreach($items as $item){
$src = trim($item['src']);
if(!empty($src)){
$key = getKeyByPath($src);
$data[$key] = $src;
}
}
return $data;
}
}

20
app/model/Log.php Normal file
View File

@ -0,0 +1,20 @@
<?php
namespace app\model;
class Log extends Base
{
//记录操作日志
public static function write($controller, $action, $content)
{
$auth = session('auth');
return self::create([
'member_id' => $auth['userId'],
'name' => $auth['userName'],
'ip' => request()->ip(),
'create_time' => time(),
'controller' => $controller,
'action' => $action,
'content' => $content
]);
}
}

6
app/model/LoginLog.php Normal file
View File

@ -0,0 +1,6 @@
<?php
namespace app\model;
class LoginLog extends Base
{
}

57
app/model/Member.php Normal file
View File

@ -0,0 +1,57 @@
<?php
namespace app\model;
class Member extends Base
{
public static function getList($limit = 40)
{
$items = self::alias('m')
->leftjoin('auth_group g','g.id=m.group_id')
->field('m.id,m.username,m.login_time,m.group_id,g.title')
->order('m.id', 'asc')
->paginate($limit);
return $items;
}
/**
* 根据角色分组返回用户
* @param int $groupId 角色分组ID
* @param int $limit 每页数量
*/
public static function getListByGroup($groupId, $limit = 40)
{
$items = self::alias('m')
->leftjoin('auth_group g','g.id=m.group_id')
->field('m.id,m.username,m.login_time,m.group_id,g.title')
->where('m.group_id', '=', $groupId)
->order('m.id', 'asc')
->paginate($limit);
return $items;
}
//根据用户名获取管理账号
public static function getByUserName($username)
{
return self::where('username', trim($username))
->findOrEmpty()
->toArray();
}
//根据ID获取管理账户和相关权限
public static function getMemberAndRulesByID($memberId)
{
return self::alias('m')
->join('auth_group g', 'm.group_id = g.id', 'LEFT')
->field('m.group_id,g.rules')
->where('m.id', $memberId)
->findOrEmpty()
->toArray();
}
public static function updateCates($id, $cates)
{
$cates = implode(',', $cates);
$data = ['cates' => $cates];
self::updateById($id, $data);
}
}

14
app/model/Message.php Normal file
View File

@ -0,0 +1,14 @@
<?php
namespace app\model;
class Message extends Base
{
/**
* 获取留言列表
*/
public static function getList($per = 20)
{
return self::order("create_time desc")
->paginate($per);
}
}

18
app/model/Model.php Normal file
View File

@ -0,0 +1,18 @@
<?php
namespace app\model;
class Model extends Base
{
//获取模型列表
public static function getList()
{
return self::order('sort asc')
->select()
->toArray();
}
public static function onAfterInsert($model)
{
$model->sort = $model->id;
$model->save();
}
}

43
app/model/Slide.php Normal file
View File

@ -0,0 +1,43 @@
<?php
namespace app\model;
class Slide extends Base
{
public static function delByIds($ids)
{
return self::whereIn('id', $ids)->delete();
}
//获取幻灯片列表
public static function getList()
{
return self::order("sort asc")
->select()
->toArray();
}
public static function onAfterInsert($slide)
{
$slide->sort = $slide->id;
$slide->save();
}
//获取轮播图涉及到的文件
public static function getFilesInUse()
{
$items = self::select()->toArray();
$data = [];
foreach($items as $item){
$src = trim($item['src']);
if(!empty($src)){
$key = getKeyByPath($src);
$data[$key] = $src;
}
$mobileSrc = trim($item['src_mobile']);
if(!empty($mobileSrc)){
$key = getKeyByPath($mobileSrc);
$data[$key] = $mobileSrc;
}
}
return $data;
}
}

54
app/model/System.php Normal file
View File

@ -0,0 +1,54 @@
<?php
namespace app\model;
class System extends Base
{
//获取友情链接上传图片尺寸
public static function getLinkImageSize()
{
$system = self::getSystem();
if(!empty($system['link_w']) && !empty($system['link_h'])){
$linkSize = $system['link_w'] . '像素 X ' . $system['link_h'] . '像素';
}else{
$linkSize = '';
}
return $linkSize;
}
//获取幻灯片上传尺寸
public static function getSlideImageSize()
{
$system = self::getSystem();
if(!empty($system['slide_w']) && !empty($system['slide_h'])){
$slideSize = $system['slide_w'] . '像素 X ' . $system['slide_h'] . '像素';
}else{
$slideSize = '';
}
if(!empty($system['slide_mobile_w']) && !empty($system['slide_mobile_h'])){
$slideMobileSize = $system['slide_mobile_w'] . '像素 X ' . $system['slide_mobile_h'] . '像素';
}else{
$slideMobileSize = '';
}
return [
'slide_size' => $slideSize,
'slide_mobile_size' => $slideMobileSize
];
}
//获取文章图片尺寸
public static function getArticleImageSize()
{
$system = self::getSystem();
if(!empty($system['article_w']) && !empty($system['article_h'])){
$articleSize = $system['article_w'] . '像素 X ' . $system['article_h'] . '像素';
}else{
$articleSize = '';
}
return $articleSize;
}
//获取系统配置信息
public static function getSystem()
{
return self::order('id asc')
->findOrEmpty()
->toArray();
}
}

17
app/service/File.php Normal file
View File

@ -0,0 +1,17 @@
<?php
namespace app\service;
use think\file\UploadedFile;
class File
{
//上传文件移动到上传文件夹
public static function move(UploadedFile $file)
{
$upload_path = 'uploads/' . date('Ymd');
$path = app()->getRootPath() . $upload_path;
$filename = uniqid() . '.' . $file->extension();
$upload_filename = '/' . $upload_path . '/' . $filename;
return [$file->move($path, $filename), $file, $upload_filename];
}
}

131
app/service/Image.php Normal file
View File

@ -0,0 +1,131 @@
<?php
namespace app\service;
use think\Image as TImage;
use app\model\System;
class Image extends File
{
/**
* 对图片进行重置大小并对宽度大于max宽度的等比缩放为宽度为1920
* milo
* 2019-10-24修改
*/
public static function resize($src)
{
$max = 1920;
$realPath = app()->getRootPath() . 'public/' . ltrim($src,'/');
if(is_file($realPath)){
$img = TImage::open($realPath);
list($img_w,$img_h) = $img->size();
if($max > 0 && $img_w > $max){
$img->thumb($max, $max * ($img_h / $img_w))->save($realPath);
}
}
}
/**
* 添加水印
* milo
* 2018-01-17
*/
public static function mark($src)
{
$rootPath = app()->getRootPath();
if(!empty($src)){
$system = System::getSystem();
$realPath = $rootPath . 'public/' . ltrim($src, '/');
if(is_file($realPath)){
if($system['is_mark']){
$mark = $rootPath . ltrim($system['mark_img'], '/');
if(is_file($mark)){
$mark_position = $system['mark_position']??5;
$mark_opacity = $system['mark_opacity']??50;
$img = TImage::Open($realPath);
$img->water($mark,$mark_position,$mark_opacity)->save($realPath);
}
}
}
}
}
//获取水印位置键值对
public static function getMarkPosition()
{
return [
"1" => '上左',
"2" => '上中',
"3" => '上右',
"4" => '中左',
"5" => '正中',
"6" => '中右',
"7" => '下左',
"8" => '下中',
"9" => '下右'
];
}
/**
* 删除图片
* milo
* 2018-01-15
*/
public static function delImg($src)
{
if(!empty(trim($src))){
$realPath = app()->getRootPath() . 'public/' . ltrim($src, '/');
if (file_exists($realPath)) {
$info = pathinfo($realPath);
$source = $info['dirname'] . DIRECTORY_SEPARATOR . $info['filename'] . '*.' . $info['extension'];
foreach(glob($source) as $filename){
if(is_file($filename)){
unlink($filename);
}
}
clearstatcache();// 清除缓存
}
}
}
/**
* 获取缩略图
* milo
* 2019-10-24修改
* 避免跨平台出错,目录分隔符全部转换为'/'
* app()->getRuntimePath() = app()->getRootPath().'runtime/当前应用模块api/'
*/
public static function getThumb($src,$width=0,$height=0,$type = TImage::THUMB_CENTER)
{
if(empty($src)){
return '';
}
$rootPath = app()->getRootPath();
$realPath = $rootPath . 'public/' . ltrim($src, '/');
$realPath = str_replace('\\', '/', $realPath);
if(!file_exists($realPath)){
return '';
}
$info = pathinfo($src);
if($width <= 0 && $height <= 0){ //高宽都小于或等于0则返回原图片
return $src;
}
$image = TImage::open($realPath);
list($imageWidth, $imageHeight) = $image->size();
if($width <= 0){
$width = floor($height * ($imageWidth / $imageHeight));
}elseif($height <= 0){
$height = floor($width * ($imageHeight / $imageWidth));
}
if($width >= $imageWidth || $height >= $imageHeight){
return $src;
}
$thumbName = $info['dirname']. DIRECTORY_SEPARATOR .$info['filename'].'_'.$width.'_'.$height.'.'.$info['extension'];
$realThumbName = $rootPath . 'public/' . ltrim($thumbName, '/');
$realThumbName = str_replace('\\', '/', $realThumbName);
if(!file_exists($realThumbName)){
$image = TImage::open($realPath);
$image->thumb($width, $height, $type)->save($realThumbName);
}
return str_replace('\\', '/', $thumbName);
}
}

47
app/service/Tool.php Normal file
View File

@ -0,0 +1,47 @@
<?php
namespace app\service;
class Tool
{
//删除文件
public static function delFile($path,$isImg = 0)
{
if(!empty(trim($path))){
$realPath = app()->getRootPath() . ltrim($path, '/');
if (file_exists($realPath)) {
$info = pathinfo($realPath);
$source = $info['dirname'] . DIRECTORY_SEPARATOR . $info['filename'] . '*.' . $info['extension'];
foreach(glob($source) as $filename){
if(is_file($filename)){
unlink($filename);
}
}
clearstatcache();// 清除缓存
}
}
}
/**
* 删除目录下的所有文件和子目录
* 调用完毕后请用clearstatcache()清理缓存
*/
public static function removeByPath($path)
{
if(is_dir($path)) {
if($handle = @opendir($path)) {
while (($file = readdir($handle)) !== false){
if($file != '.' && $file != '..') {
$child = $path.'/'.$file;
is_dir($child) ? self::removeByPath($child) : @unlink($child);
}
}
}
closedir($handle);
} elseif (is_file($path)) {
@unlink($path);
} else {
return false;
}
return true;
}
}

16
app/validate/Article.php Normal file
View File

@ -0,0 +1,16 @@
<?php
namespace app\validate;
use think\Validate;
class Article extends Validate
{
protected $rule = [
'title' => 'require',
'link' => 'url'
];
protected $message = [
'title.require' => '标题必须',
'link.url' => '请填写有效的网址'
];
}

View File

@ -0,0 +1,17 @@
<?php
namespace app\validate;
use think\Validate;
class AuthGroup extends Validate
{
protected $rule = [
'title' => 'require',
'status' => 'require|number',
];
protected $message = [
'title.require' => '角色名称不能为空',
'status.require' => '角色状态必须设置',
'status.number' => '角色状态参数值只能为数字类型',
];
}

19
app/validate/AuthRule.php Normal file
View File

@ -0,0 +1,19 @@
<?php
namespace app\validate;
use think\Validate;
class AuthRule extends Validate
{
protected $rule = [
'title' => 'require',
'name' => 'require',
'status' =>'require|number',
];
protected $message = [
'title.require' => '名称必须',
'name.require'=> '标识必须',
'status.require'=> '显示状态必须传值',
'status.number'=> '显示状态传值只能为数字表示',
];
}

16
app/validate/Block.php Normal file
View File

@ -0,0 +1,16 @@
<?php
namespace app\validate;
use think\Validate;
class Block extends Validate
{
protected $rule = [
'title' => 'require',
'keyword' => 'require',
];
protected $message = [
'title.require' => '名称必须',
'keyword.require' => '键值必须'
];
}

18
app/validate/Category.php Normal file
View File

@ -0,0 +1,18 @@
<?php
namespace app\validate;
use think\Validate;
class Category extends Validate
{
protected $rule = [
'title' => 'require',
'model_id' => 'require|number|min:1',
];
protected $message = [
'title.require' => '栏目名称必须',
'model_id.require' => '栏目模型必须',
'model_id.number' => '栏目模型格式要正确',
'model_id.min' => '请选择正确的栏目模型'
];
}

19
app/validate/History.php Normal file
View File

@ -0,0 +1,19 @@
<?php
namespace app\validate;
use think\Validate;
class History extends Validate
{
protected $rule = [
'title' => 'require|length:1,60',
'visible' => 'require|in:0,1',
];
protected $message = [
'title.require' => '标题不能为空',
'name.length' => '标题长度限制为60个字符以内',
'visible.require' => '历程状态必须设置',
'visible.in' => '状态参数错误',
];
}

View File

@ -0,0 +1,19 @@
<?php
namespace app\validate;
use think\Validate;
class HistoryInfo extends Validate
{
protected $rule = [
'title' => 'require|length:1,200',
'visible' => 'require|in:0,1',
];
protected $message = [
'title.require' => '标题不能为空',
'name.length' => '标题长度限制为200个字符以内',
'visible.require' => '历程事例状态必须设置',
'visible.in' => '状态参数错误',
];
}

16
app/validate/Link.php Normal file
View File

@ -0,0 +1,16 @@
<?php
namespace app\validate;
use think\Validate;
class Link extends Validate
{
protected $rule = [
'title' => 'require',
'url' => 'url',
];
protected $message = [
'title.require' => '名称必须',
'url.url' => '请填写有效的网址,以http://或https://开头'
];
}

17
app/validate/Member.php Normal file
View File

@ -0,0 +1,17 @@
<?php
namespace app\validate;
use think\Validate;
class Member extends Validate
{
protected $rule = [
'group_id' => 'require|number',
'username' => 'require',
];
protected $message = [
'group_id.require' => '所属角色不能为空!',
'group_id.number' => '用户角色信息数据格式不正确!',
'username.require' => '用户姓名不能为空!',
];
}

16
app/validate/Model.php Normal file
View File

@ -0,0 +1,16 @@
<?php
namespace app\validate;
use think\Validate;
class Model extends Validate
{
protected $rule = [
'name' => 'require',
'manager' => 'require',
];
protected $message = [
'name.require' => '名称必须',
'manager.require' => '后台管理必须'
];
}

16
app/validate/Slide.php Normal file
View File

@ -0,0 +1,16 @@
<?php
namespace app\validate;
use think\Validate;
class Slide extends Validate
{
protected $rule = [
'title' => 'require',
'url' => 'url',
];
protected $message = [
'title.require' => '标题必须',
'url.url' => '请填写有效的网址,以http://或https://开头'
];
}

60
app/validate/Upload.php Normal file
View File

@ -0,0 +1,60 @@
<?php
namespace app\validate;
use think\Validate;
use app\model\System;
use think\Lang;
class Upload extends Validate
{
protected $rule = [];
protected $message = [];
protected $system;
public function __construct()
{
$this->system = System::getSystem();
$this->lang = new Lang;
}
//验证图片上传
public function checkImage($image)
{
$ext = str_replace('', ',', $this->system['img_type']);
$size = $this->system['img_size'] * 1024 * 1024;
$this->rule = [
'image' => [
'fileExt' => $ext,
'fileSize' => (int)$size
]
];
return $this->check(['image' => $image]);
}
//验证视频上传
public function checkVideo($video)
{
$ext = str_replace('', ',', $this->system['video_type']);
$size = $this->system['video_size'] * 1024 * 1024;
$this->rule = [
'video' => [
'fileExt' => $ext,
'fileSize' => (int)$size
]
];
return $this->check(['video' => $video]);
}
//验证文件上传
public function checkFile($file)
{
$ext = str_replace('', ',', $this->system['file_type']);
$size = $this->system['file_size'] * 1024 * 1024;
$this->rule = [
'file' => [
'fileExt' => $ext,
'fileSize' => (int)$size
]
];
return $this->check(['file' => $file]);
}
}

17
app/widget/Crumbs.php Normal file
View File

@ -0,0 +1,17 @@
<?php
namespace app\widget;
use app\model\Category;
use think\facade\View;
class Crumbs
{
public function index($categoryId)
{
$data = [
'crumbs' => Category::getCatesCrumbs($categoryId)
];
return View::assign($data)->fetch('public/crumbs');
}
}

25
app/widget/Menu.php Normal file
View File

@ -0,0 +1,25 @@
<?php
namespace app\widget;
use think\facade\{View, Cache};
use app\model\Category;
class Menu
{
public function index($categoryId)
{
$menus = Cache::get('rules');
if(empty($menus)){
$menus = Category::getListForFrontMenu();
}
$currentFirstId = 0;
if (is_numeric($categoryId) && $categoryId > 0) {
$currentFirstId = Category::firstGradeById($categoryId);
}
$data = [
'categoryId' => $categoryId,
'menus' => $menus,
];
return View::assign($data)->fetch('public/menu');
}
}

16
app/widget/Slide.php Normal file
View File

@ -0,0 +1,16 @@
<?php
namespace app\widget;
use think\facade\View;
use app\model\Slide as WSlide;
class Slide
{
public function index()
{
$data = [
'slides' => WSlide::getList(),
];
return View::assign($data)->fetch('public/slide');
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace app\widget\manager;
use app\model\{Category, AuthRule};
use think\facade\View;
class Crumbs
{
public function index()
{
$request = request();
$controller = strtolower($request->controller());
$action = strtolower($request->action());
$controller = str_replace('manager.', '', $controller);
$name = $controller . '/' . $action;
if($action == 'index'){
$rule = AuthRule::getByTwoName($name, $controller);
}else{
$rule = AuthRule::getByName($name);
}
$parent = [];
if(!empty($rule) && $rule['parent_id']){
$parent = AuthRule::getById($rule['parent_id']);
}
$cateCrumbs = [];
$isContent = false;
if($controller == 'content') {
$isContent = true;
$categoryId = $request->param('category_id', 0);
if (is_numeric($categoryId) && $categoryId > 0) {
$cateCrumbs = Category::getCatesCrumbs($categoryId);
}
}
$data = [
'rule' => $rule,
'parent' => $parent,
'isContent' => $isContent,
'cateCrumbs' => $cateCrumbs
];
return View::assign($data)->fetch('manager/widget/crumbs');
}
}

View File

@ -0,0 +1,90 @@
<?php
namespace app\widget\manager;
use think\facade\{View, Cache};
use app\model\Category;
class Menu
{
/**
* 左侧菜单
* milo
* 2018-01-06
*/
public function left($categoryId = 0)
{
$auth = session('auth');
$authCates = array_filter(explode(',', $auth['cates']));
$rules = Cache::get('group_rules_'.$auth['groupId']);
$menuRules = $this->getMenuRules($rules);
$current = strtolower(request()->controller());
$current = str_replace('manager.', '', $current);
$currentAction = strtolower($current.'/'.request()->action());
// message 留言管理是否集成在内容管理中,后期开发中根据情况调整
if(in_array($current, ['article', 'product', 'page'], true)){
$current = 'content';
}
if($auth['groupId'] == 1) {
$menus = $this->getMenus(Category::getList(false));
} else {
$menus = $this->getMenus(Category::getList(true, $authCates));
}
$data = [
'rules' => $menuRules,
'categoryId' => $categoryId,
'menus' => $menus,
'current' => $current,
'currentAction' => $currentAction
];
return View::assign($data)->fetch('manager/widget/left');
}
/**
* 过滤出权限菜单
* @param $rules
* @return array
*/
private function getMenuRules($rules)
{
$menuRules = [];
if (!empty($rules)) {
foreach ($rules as $rule) {
$hasChildren = $rule['hasChildren'] ?? false;
if ($hasChildren) {
$rule['children'] = $this->getMenuRules($rule['children']);
if(count($rule['children']) > 0) {
$rule['status'] = 1;
}
}
if($rule['status'] > 0) {
$menuRules[] = $rule;
}
}
}
return $menuRules;
}
/**
* 内容栏目菜单
* @param $cates
* @param int $parent_id
* @return array
*/
private function getMenus($cates,$parentId=0)
{
$menus = [];
foreach($cates as $cate){
if($cate['parent_id'] == $parentId && $cate['state'] == 1){
$children = $this->getMenus($cates,$cate['id']);
if(!empty($children)){
$cate['children'] = $children;
}
if(!empty($cate['children']) || !empty($cate['manager'])){
$menus[] = $cate;
}
}
}
return $menus;
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace app\widget\manager;
use think\facade\View;
class Upload
{
//视频
public function video($src = '', $append = '')
{
$data = [
'src' => $src,
'append' => $append
];
return View::assign($data)->fetch('manager/widget/video');
}
//图片layui自带上传控件,若同一页面内徐亚加载多层本上传控件则需要传不同的$append来区分控件ID
public function image($src = '', $append = '', $imgSize = 0, $thumb = 0)
{
$data = [
'src' => $src,
'append' => $append,
'imgSize' => $imgSize,
'thumb' => $thumb,
];
return View::assign($data)->fetch('manager/widget/image');
}
//上传文件,目前在文章中添加附件
public function files($files = [], $num = 10, $append = '')
{
if(!empty($files) && $files == 'null') {
$files = [];
}
$data = [
'files' => $files,
'append' => $append,
'num' => $num,
];
return View::assign($data)->fetch('manager/widget/files');
}
/**
* 水印图片上传
* milo
* 2018-01-13
*/
public function mark($src = '')
{
return View::assign(['src' => $src])->fetch('manager/widget/mark');
}
/**
* layui组图上传
* milo
*/
public function multi($imgs = [], $num = 10, $append = '', $imgSize = '')
{
if(!empty($imgs) && $imgs == 'null') {
$imgs = [];
}
$data = [
'imgs' => $imgs,
'append' => $append,
'imgSize' => $imgSize,
'num' => $num
];
return View::assign($data)->fetch('manager/widget/multi');
}
}

26
build.example.php Normal file
View File

@ -0,0 +1,26 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
/**
* php think build 自动生成应用的目录结构的定义示例
*/
return [
// 需要自动创建的文件
'__file__' => [],
// 需要自动创建的目录
'__dir__' => ['controller', 'model', 'view'],
// 需要自动创建的控制器
'controller' => ['Index'],
// 需要自动创建的模型
'model' => ['User'],
// 需要自动创建的模板
'view' => ['index/index'],
];

46
composer.json Normal file
View File

@ -0,0 +1,46 @@
{
"name": "topthink/think",
"description": "the new thinkphp framework",
"type": "project",
"keywords": [
"framework",
"thinkphp",
"ORM"
],
"homepage": "http://thinkphp.cn/",
"license": "Apache-2.0",
"authors": [
{
"name": "liu21st",
"email": "liu21st@gmail.com"
}
],
"require": {
"php": ">=7.1.0",
"topthink/framework": "^6.0.0",
"topthink/think-orm": "^2.0",
"topthink/think-view": "^1.0",
"topthink/think-image": "^1.0"
},
"require-dev": {
"symfony/var-dumper": "^4.2",
"topthink/think-trace":"^1.0"
},
"autoload": {
"psr-4": {
"app\\": "app"
},
"psr-0": {
"": "extend/"
}
},
"config": {
"preferred-install": "dist"
},
"scripts": {
"post-autoload-dump": [
"@php think service:discover",
"@php think vendor:publish"
]
}
}

1038
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

40
config/app.php Normal file
View File

@ -0,0 +1,40 @@
<?php
use think\facade\Env;
return [
// 应用地址
'app_host' => Env::get('app.host', ''),
// 应用的命名空间
'app_namespace' => '',
// 是否启用路由
'with_route' => true,
// 是否启用事件
'with_event' => true,
// 自动多应用模式
'auto_multi_app' => true,
// 应用映射(自动多应用模式有效)
'app_map' => [],
// 域名绑定(自动多应用模式有效)
'domain_bind' => [],
// 禁止URL访问的应用列表自动多应用模式有效
'deny_app_list' => [],
// 默认应用
'default_app' => 'index',
// 默认时区
'default_timezone' => 'Asia/Shanghai',
// 异常页面的模板文件
'exception_tmpl' => app()->getThinkPath() . 'tpl/think_exception.tpl',
/*
'http_exception_template' => [
400 => '',
500 => app()->getThinkPath() . 'tpl/think_exception.tpl',
],
*/
// 错误显示信息,非调试模式有效
'error_message' => '页面错误!请稍后再试~',
// 显示错误信息
'show_error_msg' => true,
];

30
config/cache.php Normal file
View File

@ -0,0 +1,30 @@
<?php
use think\facade\Env;
// +----------------------------------------------------------------------
// | 缓存设置
// +----------------------------------------------------------------------
return [
// 默认缓存驱动
'default' => Env::get('cache.driver', 'file'),
// 缓存连接方式配置
'stores' => [
'file' => [
// 驱动方式
'type' => 'File',
// 缓存保存目录
'path' => '',
// 缓存前缀
'prefix' => '',
// 缓存有效期 0表示永久缓存
'expire' => 0,
// 缓存标签前缀
'tag_prefix' => 'tag:',
// 序列化机制 例如 ['serialize', 'unserialize']
'serialize' => [],
],
// 更多的缓存连接
],
];

9
config/console.php Normal file
View File

@ -0,0 +1,9 @@
<?php
// +----------------------------------------------------------------------
// | 控制台配置
// +----------------------------------------------------------------------
return [
// 指令定义
'commands' => [
],
];

18
config/cookie.php Normal file
View File

@ -0,0 +1,18 @@
<?php
// +----------------------------------------------------------------------
// | Cookie设置
// +----------------------------------------------------------------------
return [
// cookie 保存时间
'expire' => 0,
// cookie 保存路径
'path' => '/',
// cookie 有效域名
'domain' => '',
// cookie 启用安全传输
'secure' => false,
// httponly设置
'httponly' => false,
// 是否使用 setcookie
'setcookie' => true,
];

63
config/database.php Normal file
View File

@ -0,0 +1,63 @@
<?php
use think\facade\Env;
return [
// 默认使用的数据库连接配置
'default' => Env::get('database.driver', 'mysql'),
// 自定义时间查询规则
'time_query_rule' => [],
// 自动写入时间戳字段
// true为自动识别类型 false关闭
// 字符串则明确指定时间字段类型 支持 int timestamp datetime date
'auto_timestamp' => true,
// 时间字段取出后的默认时间格式
'datetime_format' => 'Y-m-d H:i:s',
// 数据库连接配置信息
'connections' => [
'mysql' => [
// 数据库类型
'type' => Env::get('database.type', 'mysql'),
// 服务器地址
'hostname' => Env::get('database.hostname', '127.0.0.1'),
// 数据库名
'database' => Env::get('database.database', ''),
// 用户名
'username' => Env::get('database.username', 'root'),
// 密码
'password' => Env::get('database.password', ''),
// 端口
'hostport' => Env::get('database.hostport', '3306'),
// 数据库连接参数
'params' => [],
// 数据库编码默认采用utf8
'charset' => Env::get('database.charset', 'utf8'),
// 数据库表前缀
'prefix' => Env::get('database.prefix', ''),
// 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
'deploy' => 0,
// 数据库读写是否分离 主从式有效
'rw_separate' => false,
// 读写分离后 主服务器数量
'master_num' => 1,
// 指定从服务器序号
'slave_no' => '',
// 是否严格检查字段是否存在
'fields_strict' => true,
// 是否需要断线重连
'break_reconnect' => false,
// 监听SQL
'trigger_sql' => true,
// 开启字段缓存
'fields_cache' => false,
// 字段缓存路径
'schema_cache_path' => app()->getRuntimePath() . 'schema' . DIRECTORY_SEPARATOR,
],
// 更多的数据库配置信息
],
];

42
config/filesystem.php Normal file
View File

@ -0,0 +1,42 @@
<?php
use think\facade\Env;
return [
// 默认磁盘
'default' => Env::get('filesystem.driver', 'local'),
// 磁盘列表
'disks' => [
'local' => [
// 磁盘类型
'type' => 'local',
// 磁盘路径
'root' => app()->getRootPath() . 'public/storage',
// 磁盘路径对应的外部URL路径
'url' => '/storage',
// 可见性
'visibility' => 'public',
],
'video' => [
// 磁盘类型
'type' => 'local',
// 磁盘路径
'root' => app()->getRootPath() . 'public/storage/videos',
// 磁盘路径对应的外部URL路径
'url' => '/storage/videos',
// 可见性
'visibility' => 'public',
],
'backup' => [
// 磁盘类型
'type' => 'local',
// 磁盘路径
'root' => app()->getRootPath() . 'public/storage/backup',
// 磁盘路径对应的外部URL路径
'url' => '/storage/backup',
// 可见性
'visibility' => 'public',
],
// 更多的磁盘配置信息
],
];

27
config/lang.php Normal file
View File

@ -0,0 +1,27 @@
<?php
// +----------------------------------------------------------------------
// | 多语言设置
// +----------------------------------------------------------------------
use think\facade\Env;
return [
// 默认语言
'default_lang' => Env::get('lang.default_lang', 'zh-cn'),
// 允许的语言列表
'allow_lang_list' => ['zh-cn'],
// 多语言自动侦测变量名
'detect_var' => 'lang',
// 是否使用Cookie记录
'use_cookie' => true,
// 多语言cookie变量
'cookie_var' => 'think_lang',
// 扩展语言包
'extend_list' => [],
// Accept-Language转义为对应语言包名称
'accept_language' => [
'zh-hans-cn' => 'zh-cn',
],
// 是否支持语言分组
'allow_group' => false,
];

46
config/log.php Normal file
View File

@ -0,0 +1,46 @@
<?php
use think\facade\Env;
// +----------------------------------------------------------------------
// | 日志设置
// +----------------------------------------------------------------------
return [
// 默认日志记录通道
'default' => Env::get('log.channel', 'file'),
// 日志记录级别
'level' => [],
// 日志类型记录的通道 ['error'=>'email',...]
'type_channel' => [],
// 关闭全局日志写入
'close' => false,
// 全局日志处理 支持闭包
'processor' => null,
// 日志通道列表
'channels' => [
'file' => [
// 日志记录方式
'type' => 'File',
// 日志保存目录
'path' => '',
// 单文件日志写入
'single' => false,
// 独立日志级别
'apart_level' => [],
// 最大日志文件数量
'max_files' => 0,
// 使用JSON格式记录
'json' => false,
// 日志处理
'processor' => null,
// 关闭通道日志写入
'close' => false,
// 日志输出格式化
'format' => '[%s][%s] %s',
// 是否实时写入
'realtime_write' => false,
],
// 其它日志通道配置
],
];

11
config/middleware.php Normal file
View File

@ -0,0 +1,11 @@
<?php
// 中间件配置
return [
// 别名或分组
'alias' => [
'auth' => app\middleware\Auth::class,
'csrf' => app\middleware\Csrf::class,
],
// 优先级设置,此数组中的中间件会按照数组中的顺序优先执行
'priority' => [],
];

51
config/route.php Normal file
View File

@ -0,0 +1,51 @@
<?php
// +----------------------------------------------------------------------
// | 路由设置
// +----------------------------------------------------------------------
return [
// pathinfo分隔符
'pathinfo_depr' => '/',
// URL伪静态后缀
'url_html_suffix' => 'html',
// URL普通方式参数 用于自动生成
'url_common_param' => true,
// 是否开启路由延迟解析
'url_lazy_route' => false,
// 是否强制使用路由
'url_route_must' => false,
// 合并路由规则
'route_rule_merge' => false,
// 路由是否完全匹配
'route_complete_match' => false,
// 是否开启路由缓存
'route_check_cache' => false,
// 路由缓存连接参数
'route_cache_option' => [],
// 路由缓存Key
'route_check_cache_key' => '',
// 访问控制器层名称
'controller_layer' => 'controller',
// 空控制器名
'empty_controller' => 'Error',
// 是否使用控制器后缀
'controller_suffix' => false,
// 默认的路由变量规则
'default_route_pattern' => '[\w\.]+',
// 是否开启请求缓存 true自动缓存 支持设置请求缓存规则
'request_cache' => false,
// 请求缓存有效期
'request_cache_expire' => null,
// 全局请求缓存排除规则
'request_cache_except' => [],
// 默认控制器名
'default_controller' => 'Index',
// 默认操作名
'default_action' => 'index',
// 操作方法后缀
'action_suffix' => '',
// 默认JSONP格式返回的处理方法
'default_jsonp_handler' => 'jsonpReturn',
// 默认JSONP处理方法
'var_jsonp_handler' => 'callback',
];

19
config/session.php Normal file
View File

@ -0,0 +1,19 @@
<?php
// +----------------------------------------------------------------------
// | 会话设置
// +----------------------------------------------------------------------
return [
// session name
'name' => 'PHPSESSID',
// SESSION_ID的提交变量,解决flash上传跨域
'var_session_id' => '',
// 驱动方式 支持file cache
'type' => 'file',
// 存储连接标识 当type使用cache的时候有效
'store' => null,
// 过期时间(秒)
'expire' => 3600 * 2,
// 前缀
'prefix' => '',
];

10
config/trace.php Normal file
View File

@ -0,0 +1,10 @@
<?php
// +----------------------------------------------------------------------
// | Trace设置 开启调试模式后有效
// +----------------------------------------------------------------------
return [
// 内置Html和Console两种方式 支持扩展
'type' => 'Html',
// 读取的日志通道名
'channel' => '',
];

33
config/view.php Normal file
View File

@ -0,0 +1,33 @@
<?php
// +----------------------------------------------------------------------
// | 模板设置
// +----------------------------------------------------------------------
return [
// 模板引擎类型使用Think
'type' => 'Think',
// 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 3 保持操作方法
'auto_rule' => 1,
// 模板目录名
'view_dir_name' => 'view',
// 模板后缀
'view_suffix' => 'html',
// 模板文件名分隔符
'view_depr' => DIRECTORY_SEPARATOR,
// 模板引擎普通标签开始标记
'tpl_begin' => '{',
// 模板引擎普通标签结束标记
'tpl_end' => '}',
// 标签库标签开始标记
'taglib_begin' => '{',
// 标签库标签结束标记
'taglib_end' => '}',
'tpl_replace_string' => [
'__STATIC__' => '/static',
'__MANAGER__' => '/static/manager',
'__COMMON__' => '/static/common',
'__JS__' => '/static/js',
'__CSS__' => '/static/css',
'__IMG__' => '/static/image'
],
];

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

5
package-lock.json generated Normal file
View File

@ -0,0 +1,5 @@
{
"name": "cms",
"version": "1.0.0",
"lockfileVersion": 1
}

16
package.json Normal file
View File

@ -0,0 +1,16 @@
{
"name": "cms",
"version": "1.0.0",
"description": "本CMS基于ThinkPHP 6.0开发",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "https://gitee.com/dxtc/cms.git"
},
"keywords": [],
"author": "",
"license": "ISC"
}

24
public/index.php Normal file
View File

@ -0,0 +1,24 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
// [ 应用入口文件 ]
namespace think;
header("Access-Control-Allow-Origin:*");
header('Access-Control-Allow-Methods:POST');
header('Access-Control-Allow-Headers:x-requested-with, content-type');
require dirname(__DIR__) . '/vendor/autoload.php';
// 执行HTTP应用并响应
$http = (new App())->http;
$response = $http->run();
$response->send();
$http->end($response);

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,349 @@
/* BASICS */
.CodeMirror {
/* Set height, width, borders, and global font properties here */
font-family: monospace;
height: 300px;
color: black;
direction: ltr;
}
/* PADDING */
.CodeMirror-lines {
padding: 4px 0; /* Vertical padding around content */
}
.CodeMirror pre.CodeMirror-line,
.CodeMirror pre.CodeMirror-line-like {
padding: 0 4px; /* Horizontal padding of content */
}
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
background-color: white; /* The little square between H and V scrollbars */
}
/* GUTTER */
.CodeMirror-gutters {
border-right: 1px solid #ddd;
background-color: #f7f7f7;
white-space: nowrap;
}
.CodeMirror-linenumbers {}
.CodeMirror-linenumber {
padding: 0 3px 0 5px;
min-width: 20px;
text-align: right;
color: #999;
white-space: nowrap;
}
.CodeMirror-guttermarker { color: black; }
.CodeMirror-guttermarker-subtle { color: #999; }
/* CURSOR */
.CodeMirror-cursor {
border-left: 1px solid black;
border-right: none;
width: 0;
}
/* Shown when moving in bi-directional text */
.CodeMirror div.CodeMirror-secondarycursor {
border-left: 1px solid silver;
}
.cm-fat-cursor .CodeMirror-cursor {
width: auto;
border: 0 !important;
background: #7e7;
}
.cm-fat-cursor div.CodeMirror-cursors {
z-index: 1;
}
.cm-fat-cursor-mark {
background-color: rgba(20, 255, 20, 0.5);
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
}
.cm-animate-fat-cursor {
width: auto;
border: 0;
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
background-color: #7e7;
}
@-moz-keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
@-webkit-keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
@keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
/* Can style cursor different in overwrite (non-insert) mode */
.CodeMirror-overwrite .CodeMirror-cursor {}
.cm-tab { display: inline-block; text-decoration: inherit; }
.CodeMirror-rulers {
position: absolute;
left: 0; right: 0; top: -50px; bottom: 0;
overflow: hidden;
}
.CodeMirror-ruler {
border-left: 1px solid #ccc;
top: 0; bottom: 0;
position: absolute;
}
/* DEFAULT THEME */
.cm-s-default .cm-header {color: blue;}
.cm-s-default .cm-quote {color: #090;}
.cm-negative {color: #d44;}
.cm-positive {color: #292;}
.cm-header, .cm-strong {font-weight: bold;}
.cm-em {font-style: italic;}
.cm-link {text-decoration: underline;}
.cm-strikethrough {text-decoration: line-through;}
.cm-s-default .cm-keyword {color: #708;}
.cm-s-default .cm-atom {color: #219;}
.cm-s-default .cm-number {color: #164;}
.cm-s-default .cm-def {color: #00f;}
.cm-s-default .cm-variable,
.cm-s-default .cm-punctuation,
.cm-s-default .cm-property,
.cm-s-default .cm-operator {}
.cm-s-default .cm-variable-2 {color: #05a;}
.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
.cm-s-default .cm-comment {color: #a50;}
.cm-s-default .cm-string {color: #a11;}
.cm-s-default .cm-string-2 {color: #f50;}
.cm-s-default .cm-meta {color: #555;}
.cm-s-default .cm-qualifier {color: #555;}
.cm-s-default .cm-builtin {color: #30a;}
.cm-s-default .cm-bracket {color: #997;}
.cm-s-default .cm-tag {color: #170;}
.cm-s-default .cm-attribute {color: #00c;}
.cm-s-default .cm-hr {color: #999;}
.cm-s-default .cm-link {color: #00c;}
.cm-s-default .cm-error {color: #f00;}
.cm-invalidchar {color: #f00;}
.CodeMirror-composing { border-bottom: 2px solid; }
/* Default styles for common addons */
div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
.CodeMirror-activeline-background {background: #e8f2ff;}
/* STOP */
/* The rest of this file contains styles related to the mechanics of
the editor. You probably shouldn't touch them. */
.CodeMirror {
position: relative;
overflow: hidden;
background: white;
}
.CodeMirror-scroll {
overflow: scroll !important; /* Things will break if this is overridden */
/* 50px is the magic margin used to hide the element's real scrollbars */
/* See overflow: hidden in .CodeMirror */
margin-bottom: -50px; margin-right: -50px;
padding-bottom: 50px;
height: 100%;
outline: none; /* Prevent dragging from highlighting the element */
position: relative;
}
.CodeMirror-sizer {
position: relative;
border-right: 50px solid transparent;
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
before actual scrolling happens, thus preventing shaking and
flickering artifacts. */
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
position: absolute;
z-index: 6;
display: none;
}
.CodeMirror-vscrollbar {
right: 0; top: 0;
overflow-x: hidden;
overflow-y: scroll;
}
.CodeMirror-hscrollbar {
bottom: 0; left: 0;
overflow-y: hidden;
overflow-x: scroll;
}
.CodeMirror-scrollbar-filler {
right: 0; bottom: 0;
}
.CodeMirror-gutter-filler {
left: 0; bottom: 0;
}
.CodeMirror-gutters {
position: absolute; left: 0; top: 0;
min-height: 100%;
z-index: 3;
}
.CodeMirror-gutter {
white-space: normal;
height: 100%;
display: inline-block;
vertical-align: top;
margin-bottom: -50px;
}
.CodeMirror-gutter-wrapper {
position: absolute;
z-index: 4;
background: none !important;
border: none !important;
}
.CodeMirror-gutter-background {
position: absolute;
top: 0; bottom: 0;
z-index: 4;
}
.CodeMirror-gutter-elt {
position: absolute;
cursor: default;
z-index: 4;
}
.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
.CodeMirror-lines {
cursor: text;
min-height: 1px; /* prevents collapsing before first draw */
}
.CodeMirror pre.CodeMirror-line,
.CodeMirror pre.CodeMirror-line-like {
/* Reset some styles that the rest of the page might have set */
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
border-width: 0;
background: transparent;
font-family: inherit;
font-size: inherit;
margin: 0;
white-space: pre;
word-wrap: normal;
line-height: inherit;
color: inherit;
z-index: 2;
position: relative;
overflow: visible;
-webkit-tap-highlight-color: transparent;
-webkit-font-variant-ligatures: contextual;
font-variant-ligatures: contextual;
}
.CodeMirror-wrap pre.CodeMirror-line,
.CodeMirror-wrap pre.CodeMirror-line-like {
word-wrap: break-word;
white-space: pre-wrap;
word-break: normal;
}
.CodeMirror-linebackground {
position: absolute;
left: 0; right: 0; top: 0; bottom: 0;
z-index: 0;
}
.CodeMirror-linewidget {
position: relative;
z-index: 2;
padding: 0.1px; /* Force widget margins to stay inside of the container */
}
.CodeMirror-widget {}
.CodeMirror-rtl pre { direction: rtl; }
.CodeMirror-code {
outline: none;
}
/* Force content-box sizing for the elements where we expect it */
.CodeMirror-scroll,
.CodeMirror-sizer,
.CodeMirror-gutter,
.CodeMirror-gutters,
.CodeMirror-linenumber {
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.CodeMirror-measure {
position: absolute;
width: 100%;
height: 0;
overflow: hidden;
visibility: hidden;
}
.CodeMirror-cursor {
position: absolute;
pointer-events: none;
}
.CodeMirror-measure pre { position: static; }
div.CodeMirror-cursors {
visibility: hidden;
position: relative;
z-index: 3;
}
div.CodeMirror-dragcursors {
visibility: visible;
}
.CodeMirror-focused div.CodeMirror-cursors {
visibility: visible;
}
.CodeMirror-selected { background: #d9d9d9; }
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
.CodeMirror-crosshair { cursor: crosshair; }
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
.cm-searching {
background-color: #ffa;
background-color: rgba(255, 255, 0, .4);
}
/* Used to force a border model for a node */
.cm-force-border { padding-right: .1px; }
@media print {
/* Hide the cursor when printing */
.CodeMirror div.CodeMirror-cursors {
visibility: hidden;
}
}
/* See issue #2901 */
.cm-tab-wrap-hack:after { content: ''; }
/* Help users use markselection to safely style text background */
span.CodeMirror-selectedtext { background: none; }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,860 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineMode("css", function(config, parserConfig) {
var inline = parserConfig.inline
if (!parserConfig.propertyKeywords) parserConfig = CodeMirror.resolveMode("text/css");
var indentUnit = config.indentUnit,
tokenHooks = parserConfig.tokenHooks,
documentTypes = parserConfig.documentTypes || {},
mediaTypes = parserConfig.mediaTypes || {},
mediaFeatures = parserConfig.mediaFeatures || {},
mediaValueKeywords = parserConfig.mediaValueKeywords || {},
propertyKeywords = parserConfig.propertyKeywords || {},
nonStandardPropertyKeywords = parserConfig.nonStandardPropertyKeywords || {},
fontProperties = parserConfig.fontProperties || {},
counterDescriptors = parserConfig.counterDescriptors || {},
colorKeywords = parserConfig.colorKeywords || {},
valueKeywords = parserConfig.valueKeywords || {},
allowNested = parserConfig.allowNested,
lineComment = parserConfig.lineComment,
supportsAtComponent = parserConfig.supportsAtComponent === true;
var type, override;
function ret(style, tp) { type = tp; return style; }
// Tokenizers
function tokenBase(stream, state) {
var ch = stream.next();
if (tokenHooks[ch]) {
var result = tokenHooks[ch](stream, state);
if (result !== false) return result;
}
if (ch == "@") {
stream.eatWhile(/[\w\\\-]/);
return ret("def", stream.current());
} else if (ch == "=" || (ch == "~" || ch == "|") && stream.eat("=")) {
return ret(null, "compare");
} else if (ch == "\"" || ch == "'") {
state.tokenize = tokenString(ch);
return state.tokenize(stream, state);
} else if (ch == "#") {
stream.eatWhile(/[\w\\\-]/);
return ret("atom", "hash");
} else if (ch == "!") {
stream.match(/^\s*\w*/);
return ret("keyword", "important");
} else if (/\d/.test(ch) || ch == "." && stream.eat(/\d/)) {
stream.eatWhile(/[\w.%]/);
return ret("number", "unit");
} else if (ch === "-") {
if (/[\d.]/.test(stream.peek())) {
stream.eatWhile(/[\w.%]/);
return ret("number", "unit");
} else if (stream.match(/^-[\w\\\-]*/)) {
stream.eatWhile(/[\w\\\-]/);
if (stream.match(/^\s*:/, false))
return ret("variable-2", "variable-definition");
return ret("variable-2", "variable");
} else if (stream.match(/^\w+-/)) {
return ret("meta", "meta");
}
} else if (/[,+>*\/]/.test(ch)) {
return ret(null, "select-op");
} else if (ch == "." && stream.match(/^-?[_a-z][_a-z0-9-]*/i)) {
return ret("qualifier", "qualifier");
} else if (/[:;{}\[\]\(\)]/.test(ch)) {
return ret(null, ch);
} else if (stream.match(/[\w-.]+(?=\()/)) {
if (/^(url(-prefix)?|domain|regexp)$/.test(stream.current().toLowerCase())) {
state.tokenize = tokenParenthesized;
}
return ret("variable callee", "variable");
} else if (/[\w\\\-]/.test(ch)) {
stream.eatWhile(/[\w\\\-]/);
return ret("property", "word");
} else {
return ret(null, null);
}
}
function tokenString(quote) {
return function(stream, state) {
var escaped = false, ch;
while ((ch = stream.next()) != null) {
if (ch == quote && !escaped) {
if (quote == ")") stream.backUp(1);
break;
}
escaped = !escaped && ch == "\\";
}
if (ch == quote || !escaped && quote != ")") state.tokenize = null;
return ret("string", "string");
};
}
function tokenParenthesized(stream, state) {
stream.next(); // Must be '('
if (!stream.match(/\s*[\"\')]/, false))
state.tokenize = tokenString(")");
else
state.tokenize = null;
return ret(null, "(");
}
// Context management
function Context(type, indent, prev) {
this.type = type;
this.indent = indent;
this.prev = prev;
}
function pushContext(state, stream, type, indent) {
state.context = new Context(type, stream.indentation() + (indent === false ? 0 : indentUnit), state.context);
return type;
}
function popContext(state) {
if (state.context.prev)
state.context = state.context.prev;
return state.context.type;
}
function pass(type, stream, state) {
return states[state.context.type](type, stream, state);
}
function popAndPass(type, stream, state, n) {
for (var i = n || 1; i > 0; i--)
state.context = state.context.prev;
return pass(type, stream, state);
}
// Parser
function wordAsValue(stream) {
var word = stream.current().toLowerCase();
if (valueKeywords.hasOwnProperty(word))
override = "atom";
else if (colorKeywords.hasOwnProperty(word))
override = "keyword";
else
override = "variable";
}
var states = {};
states.top = function(type, stream, state) {
if (type == "{") {
return pushContext(state, stream, "block");
} else if (type == "}" && state.context.prev) {
return popContext(state);
} else if (supportsAtComponent && /@component/i.test(type)) {
return pushContext(state, stream, "atComponentBlock");
} else if (/^@(-moz-)?document$/i.test(type)) {
return pushContext(state, stream, "documentTypes");
} else if (/^@(media|supports|(-moz-)?document|import)$/i.test(type)) {
return pushContext(state, stream, "atBlock");
} else if (/^@(font-face|counter-style)/i.test(type)) {
state.stateArg = type;
return "restricted_atBlock_before";
} else if (/^@(-(moz|ms|o|webkit)-)?keyframes$/i.test(type)) {
return "keyframes";
} else if (type && type.charAt(0) == "@") {
return pushContext(state, stream, "at");
} else if (type == "hash") {
override = "builtin";
} else if (type == "word") {
override = "tag";
} else if (type == "variable-definition") {
return "maybeprop";
} else if (type == "interpolation") {
return pushContext(state, stream, "interpolation");
} else if (type == ":") {
return "pseudo";
} else if (allowNested && type == "(") {
return pushContext(state, stream, "parens");
}
return state.context.type;
};
states.block = function(type, stream, state) {
if (type == "word") {
var word = stream.current().toLowerCase();
if (propertyKeywords.hasOwnProperty(word)) {
override = "property";
return "maybeprop";
} else if (nonStandardPropertyKeywords.hasOwnProperty(word)) {
override = "string-2";
return "maybeprop";
} else if (allowNested) {
override = stream.match(/^\s*:(?:\s|$)/, false) ? "property" : "tag";
return "block";
} else {
override += " error";
return "maybeprop";
}
} else if (type == "meta") {
return "block";
} else if (!allowNested && (type == "hash" || type == "qualifier")) {
override = "error";
return "block";
} else {
return states.top(type, stream, state);
}
};
states.maybeprop = function(type, stream, state) {
if (type == ":") return pushContext(state, stream, "prop");
return pass(type, stream, state);
};
states.prop = function(type, stream, state) {
if (type == ";") return popContext(state);
if (type == "{" && allowNested) return pushContext(state, stream, "propBlock");
if (type == "}" || type == "{") return popAndPass(type, stream, state);
if (type == "(") return pushContext(state, stream, "parens");
if (type == "hash" && !/^#([0-9a-fA-f]{3,4}|[0-9a-fA-f]{6}|[0-9a-fA-f]{8})$/.test(stream.current())) {
override += " error";
} else if (type == "word") {
wordAsValue(stream);
} else if (type == "interpolation") {
return pushContext(state, stream, "interpolation");
}
return "prop";
};
states.propBlock = function(type, _stream, state) {
if (type == "}") return popContext(state);
if (type == "word") { override = "property"; return "maybeprop"; }
return state.context.type;
};
states.parens = function(type, stream, state) {
if (type == "{" || type == "}") return popAndPass(type, stream, state);
if (type == ")") return popContext(state);
if (type == "(") return pushContext(state, stream, "parens");
if (type == "interpolation") return pushContext(state, stream, "interpolation");
if (type == "word") wordAsValue(stream);
return "parens";
};
states.pseudo = function(type, stream, state) {
if (type == "meta") return "pseudo";
if (type == "word") {
override = "variable-3";
return state.context.type;
}
return pass(type, stream, state);
};
states.documentTypes = function(type, stream, state) {
if (type == "word" && documentTypes.hasOwnProperty(stream.current())) {
override = "tag";
return state.context.type;
} else {
return states.atBlock(type, stream, state);
}
};
states.atBlock = function(type, stream, state) {
if (type == "(") return pushContext(state, stream, "atBlock_parens");
if (type == "}" || type == ";") return popAndPass(type, stream, state);
if (type == "{") return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top");
if (type == "interpolation") return pushContext(state, stream, "interpolation");
if (type == "word") {
var word = stream.current().toLowerCase();
if (word == "only" || word == "not" || word == "and" || word == "or")
override = "keyword";
else if (mediaTypes.hasOwnProperty(word))
override = "attribute";
else if (mediaFeatures.hasOwnProperty(word))
override = "property";
else if (mediaValueKeywords.hasOwnProperty(word))
override = "keyword";
else if (propertyKeywords.hasOwnProperty(word))
override = "property";
else if (nonStandardPropertyKeywords.hasOwnProperty(word))
override = "string-2";
else if (valueKeywords.hasOwnProperty(word))
override = "atom";
else if (colorKeywords.hasOwnProperty(word))
override = "keyword";
else
override = "error";
}
return state.context.type;
};
states.atComponentBlock = function(type, stream, state) {
if (type == "}")
return popAndPass(type, stream, state);
if (type == "{")
return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top", false);
if (type == "word")
override = "error";
return state.context.type;
};
states.atBlock_parens = function(type, stream, state) {
if (type == ")") return popContext(state);
if (type == "{" || type == "}") return popAndPass(type, stream, state, 2);
return states.atBlock(type, stream, state);
};
states.restricted_atBlock_before = function(type, stream, state) {
if (type == "{")
return pushContext(state, stream, "restricted_atBlock");
if (type == "word" && state.stateArg == "@counter-style") {
override = "variable";
return "restricted_atBlock_before";
}
return pass(type, stream, state);
};
states.restricted_atBlock = function(type, stream, state) {
if (type == "}") {
state.stateArg = null;
return popContext(state);
}
if (type == "word") {
if ((state.stateArg == "@font-face" && !fontProperties.hasOwnProperty(stream.current().toLowerCase())) ||
(state.stateArg == "@counter-style" && !counterDescriptors.hasOwnProperty(stream.current().toLowerCase())))
override = "error";
else
override = "property";
return "maybeprop";
}
return "restricted_atBlock";
};
states.keyframes = function(type, stream, state) {
if (type == "word") { override = "variable"; return "keyframes"; }
if (type == "{") return pushContext(state, stream, "top");
return pass(type, stream, state);
};
states.at = function(type, stream, state) {
if (type == ";") return popContext(state);
if (type == "{" || type == "}") return popAndPass(type, stream, state);
if (type == "word") override = "tag";
else if (type == "hash") override = "builtin";
return "at";
};
states.interpolation = function(type, stream, state) {
if (type == "}") return popContext(state);
if (type == "{" || type == ";") return popAndPass(type, stream, state);
if (type == "word") override = "variable";
else if (type != "variable" && type != "(" && type != ")") override = "error";
return "interpolation";
};
return {
startState: function(base) {
return {tokenize: null,
state: inline ? "block" : "top",
stateArg: null,
context: new Context(inline ? "block" : "top", base || 0, null)};
},
token: function(stream, state) {
if (!state.tokenize && stream.eatSpace()) return null;
var style = (state.tokenize || tokenBase)(stream, state);
if (style && typeof style == "object") {
type = style[1];
style = style[0];
}
override = style;
if (type != "comment")
state.state = states[state.state](type, stream, state);
return override;
},
indent: function(state, textAfter) {
var cx = state.context, ch = textAfter && textAfter.charAt(0);
var indent = cx.indent;
if (cx.type == "prop" && (ch == "}" || ch == ")")) cx = cx.prev;
if (cx.prev) {
if (ch == "}" && (cx.type == "block" || cx.type == "top" ||
cx.type == "interpolation" || cx.type == "restricted_atBlock")) {
// Resume indentation from parent context.
cx = cx.prev;
indent = cx.indent;
} else if (ch == ")" && (cx.type == "parens" || cx.type == "atBlock_parens") ||
ch == "{" && (cx.type == "at" || cx.type == "atBlock")) {
// Dedent relative to current context.
indent = Math.max(0, cx.indent - indentUnit);
}
}
return indent;
},
electricChars: "}",
blockCommentStart: "/*",
blockCommentEnd: "*/",
blockCommentContinue: " * ",
lineComment: lineComment,
fold: "brace"
};
});
function keySet(array) {
var keys = {};
for (var i = 0; i < array.length; ++i) {
keys[array[i].toLowerCase()] = true;
}
return keys;
}
var documentTypes_ = [
"domain", "regexp", "url", "url-prefix"
], documentTypes = keySet(documentTypes_);
var mediaTypes_ = [
"all", "aural", "braille", "handheld", "print", "projection", "screen",
"tty", "tv", "embossed"
], mediaTypes = keySet(mediaTypes_);
var mediaFeatures_ = [
"width", "min-width", "max-width", "height", "min-height", "max-height",
"device-width", "min-device-width", "max-device-width", "device-height",
"min-device-height", "max-device-height", "aspect-ratio",
"min-aspect-ratio", "max-aspect-ratio", "device-aspect-ratio",
"min-device-aspect-ratio", "max-device-aspect-ratio", "color", "min-color",
"max-color", "color-index", "min-color-index", "max-color-index",
"monochrome", "min-monochrome", "max-monochrome", "resolution",
"min-resolution", "max-resolution", "scan", "grid", "orientation",
"device-pixel-ratio", "min-device-pixel-ratio", "max-device-pixel-ratio",
"pointer", "any-pointer", "hover", "any-hover"
], mediaFeatures = keySet(mediaFeatures_);
var mediaValueKeywords_ = [
"landscape", "portrait", "none", "coarse", "fine", "on-demand", "hover",
"interlace", "progressive"
], mediaValueKeywords = keySet(mediaValueKeywords_);
var propertyKeywords_ = [
"align-content", "align-items", "align-self", "alignment-adjust",
"alignment-baseline", "anchor-point", "animation", "animation-delay",
"animation-direction", "animation-duration", "animation-fill-mode",
"animation-iteration-count", "animation-name", "animation-play-state",
"animation-timing-function", "appearance", "azimuth", "backdrop-filter",
"backface-visibility", "background", "background-attachment",
"background-blend-mode", "background-clip", "background-color",
"background-image", "background-origin", "background-position",
"background-position-x", "background-position-y", "background-repeat",
"background-size", "baseline-shift", "binding", "bleed", "block-size",
"bookmark-label", "bookmark-level", "bookmark-state", "bookmark-target",
"border", "border-bottom", "border-bottom-color", "border-bottom-left-radius",
"border-bottom-right-radius", "border-bottom-style", "border-bottom-width",
"border-collapse", "border-color", "border-image", "border-image-outset",
"border-image-repeat", "border-image-slice", "border-image-source",
"border-image-width", "border-left", "border-left-color", "border-left-style",
"border-left-width", "border-radius", "border-right", "border-right-color",
"border-right-style", "border-right-width", "border-spacing", "border-style",
"border-top", "border-top-color", "border-top-left-radius",
"border-top-right-radius", "border-top-style", "border-top-width",
"border-width", "bottom", "box-decoration-break", "box-shadow", "box-sizing",
"break-after", "break-before", "break-inside", "caption-side", "caret-color",
"clear", "clip", "color", "color-profile", "column-count", "column-fill",
"column-gap", "column-rule", "column-rule-color", "column-rule-style",
"column-rule-width", "column-span", "column-width", "columns", "contain",
"content", "counter-increment", "counter-reset", "crop", "cue", "cue-after",
"cue-before", "cursor", "direction", "display", "dominant-baseline",
"drop-initial-after-adjust", "drop-initial-after-align",
"drop-initial-before-adjust", "drop-initial-before-align", "drop-initial-size",
"drop-initial-value", "elevation", "empty-cells", "fit", "fit-position",
"flex", "flex-basis", "flex-direction", "flex-flow", "flex-grow",
"flex-shrink", "flex-wrap", "float", "float-offset", "flow-from", "flow-into",
"font", "font-family", "font-feature-settings", "font-kerning",
"font-language-override", "font-optical-sizing", "font-size",
"font-size-adjust", "font-stretch", "font-style", "font-synthesis",
"font-variant", "font-variant-alternates", "font-variant-caps",
"font-variant-east-asian", "font-variant-ligatures", "font-variant-numeric",
"font-variant-position", "font-variation-settings", "font-weight", "gap",
"grid", "grid-area", "grid-auto-columns", "grid-auto-flow", "grid-auto-rows",
"grid-column", "grid-column-end", "grid-column-gap", "grid-column-start",
"grid-gap", "grid-row", "grid-row-end", "grid-row-gap", "grid-row-start",
"grid-template", "grid-template-areas", "grid-template-columns",
"grid-template-rows", "hanging-punctuation", "height", "hyphens", "icon",
"image-orientation", "image-rendering", "image-resolution", "inline-box-align",
"inset", "inset-block", "inset-block-end", "inset-block-start", "inset-inline",
"inset-inline-end", "inset-inline-start", "isolation", "justify-content",
"justify-items", "justify-self", "left", "letter-spacing", "line-break",
"line-height", "line-height-step", "line-stacking", "line-stacking-ruby",
"line-stacking-shift", "line-stacking-strategy", "list-style",
"list-style-image", "list-style-position", "list-style-type", "margin",
"margin-bottom", "margin-left", "margin-right", "margin-top", "marks",
"marquee-direction", "marquee-loop", "marquee-play-count", "marquee-speed",
"marquee-style", "max-block-size", "max-height", "max-inline-size",
"max-width", "min-block-size", "min-height", "min-inline-size", "min-width",
"mix-blend-mode", "move-to", "nav-down", "nav-index", "nav-left", "nav-right",
"nav-up", "object-fit", "object-position", "offset", "offset-anchor",
"offset-distance", "offset-path", "offset-position", "offset-rotate",
"opacity", "order", "orphans", "outline", "outline-color", "outline-offset",
"outline-style", "outline-width", "overflow", "overflow-style",
"overflow-wrap", "overflow-x", "overflow-y", "padding", "padding-bottom",
"padding-left", "padding-right", "padding-top", "page", "page-break-after",
"page-break-before", "page-break-inside", "page-policy", "pause",
"pause-after", "pause-before", "perspective", "perspective-origin", "pitch",
"pitch-range", "place-content", "place-items", "place-self", "play-during",
"position", "presentation-level", "punctuation-trim", "quotes",
"region-break-after", "region-break-before", "region-break-inside",
"region-fragment", "rendering-intent", "resize", "rest", "rest-after",
"rest-before", "richness", "right", "rotate", "rotation", "rotation-point",
"row-gap", "ruby-align", "ruby-overhang", "ruby-position", "ruby-span",
"scale", "scroll-behavior", "scroll-margin", "scroll-margin-block",
"scroll-margin-block-end", "scroll-margin-block-start", "scroll-margin-bottom",
"scroll-margin-inline", "scroll-margin-inline-end",
"scroll-margin-inline-start", "scroll-margin-left", "scroll-margin-right",
"scroll-margin-top", "scroll-padding", "scroll-padding-block",
"scroll-padding-block-end", "scroll-padding-block-start",
"scroll-padding-bottom", "scroll-padding-inline", "scroll-padding-inline-end",
"scroll-padding-inline-start", "scroll-padding-left", "scroll-padding-right",
"scroll-padding-top", "scroll-snap-align", "scroll-snap-type",
"shape-image-threshold", "shape-inside", "shape-margin", "shape-outside",
"size", "speak", "speak-as", "speak-header", "speak-numeral",
"speak-punctuation", "speech-rate", "stress", "string-set", "tab-size",
"table-layout", "target", "target-name", "target-new", "target-position",
"text-align", "text-align-last", "text-combine-upright", "text-decoration",
"text-decoration-color", "text-decoration-line", "text-decoration-skip",
"text-decoration-skip-ink", "text-decoration-style", "text-emphasis",
"text-emphasis-color", "text-emphasis-position", "text-emphasis-style",
"text-height", "text-indent", "text-justify", "text-orientation",
"text-outline", "text-overflow", "text-rendering", "text-shadow",
"text-size-adjust", "text-space-collapse", "text-transform",
"text-underline-position", "text-wrap", "top", "transform", "transform-origin",
"transform-style", "transition", "transition-delay", "transition-duration",
"transition-property", "transition-timing-function", "translate",
"unicode-bidi", "user-select", "vertical-align", "visibility", "voice-balance",
"voice-duration", "voice-family", "voice-pitch", "voice-range", "voice-rate",
"voice-stress", "voice-volume", "volume", "white-space", "widows", "width",
"will-change", "word-break", "word-spacing", "word-wrap", "writing-mode", "z-index",
// SVG-specific
"clip-path", "clip-rule", "mask", "enable-background", "filter", "flood-color",
"flood-opacity", "lighting-color", "stop-color", "stop-opacity", "pointer-events",
"color-interpolation", "color-interpolation-filters",
"color-rendering", "fill", "fill-opacity", "fill-rule", "image-rendering",
"marker", "marker-end", "marker-mid", "marker-start", "shape-rendering", "stroke",
"stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin",
"stroke-miterlimit", "stroke-opacity", "stroke-width", "text-rendering",
"baseline-shift", "dominant-baseline", "glyph-orientation-horizontal",
"glyph-orientation-vertical", "text-anchor", "writing-mode"
], propertyKeywords = keySet(propertyKeywords_);
var nonStandardPropertyKeywords_ = [
"border-block", "border-block-color", "border-block-end",
"border-block-end-color", "border-block-end-style", "border-block-end-width",
"border-block-start", "border-block-start-color", "border-block-start-style",
"border-block-start-width", "border-block-style", "border-block-width",
"border-inline", "border-inline-color", "border-inline-end",
"border-inline-end-color", "border-inline-end-style",
"border-inline-end-width", "border-inline-start", "border-inline-start-color",
"border-inline-start-style", "border-inline-start-width",
"border-inline-style", "border-inline-width", "margin-block",
"margin-block-end", "margin-block-start", "margin-inline", "margin-inline-end",
"margin-inline-start", "padding-block", "padding-block-end",
"padding-block-start", "padding-inline", "padding-inline-end",
"padding-inline-start", "scroll-snap-stop", "scrollbar-3d-light-color",
"scrollbar-arrow-color", "scrollbar-base-color", "scrollbar-dark-shadow-color",
"scrollbar-face-color", "scrollbar-highlight-color", "scrollbar-shadow-color",
"scrollbar-track-color", "searchfield-cancel-button", "searchfield-decoration",
"searchfield-results-button", "searchfield-results-decoration", "shape-inside", "zoom"
], nonStandardPropertyKeywords = keySet(nonStandardPropertyKeywords_);
var fontProperties_ = [
"font-display", "font-family", "src", "unicode-range", "font-variant",
"font-feature-settings", "font-stretch", "font-weight", "font-style"
], fontProperties = keySet(fontProperties_);
var counterDescriptors_ = [
"additive-symbols", "fallback", "negative", "pad", "prefix", "range",
"speak-as", "suffix", "symbols", "system"
], counterDescriptors = keySet(counterDescriptors_);
var colorKeywords_ = [
"aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige",
"bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown",
"burlywood", "cadetblue", "chartreuse", "chocolate", "coral", "cornflowerblue",
"cornsilk", "crimson", "cyan", "darkblue", "darkcyan", "darkgoldenrod",
"darkgray", "darkgreen", "darkkhaki", "darkmagenta", "darkolivegreen",
"darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen",
"darkslateblue", "darkslategray", "darkturquoise", "darkviolet",
"deeppink", "deepskyblue", "dimgray", "dodgerblue", "firebrick",
"floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite",
"gold", "goldenrod", "gray", "grey", "green", "greenyellow", "honeydew",
"hotpink", "indianred", "indigo", "ivory", "khaki", "lavender",
"lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral",
"lightcyan", "lightgoldenrodyellow", "lightgray", "lightgreen", "lightpink",
"lightsalmon", "lightseagreen", "lightskyblue", "lightslategray",
"lightsteelblue", "lightyellow", "lime", "limegreen", "linen", "magenta",
"maroon", "mediumaquamarine", "mediumblue", "mediumorchid", "mediumpurple",
"mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise",
"mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin",
"navajowhite", "navy", "oldlace", "olive", "olivedrab", "orange", "orangered",
"orchid", "palegoldenrod", "palegreen", "paleturquoise", "palevioletred",
"papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue",
"purple", "rebeccapurple", "red", "rosybrown", "royalblue", "saddlebrown",
"salmon", "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue",
"slateblue", "slategray", "snow", "springgreen", "steelblue", "tan",
"teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white",
"whitesmoke", "yellow", "yellowgreen"
], colorKeywords = keySet(colorKeywords_);
var valueKeywords_ = [
"above", "absolute", "activeborder", "additive", "activecaption", "afar",
"after-white-space", "ahead", "alias", "all", "all-scroll", "alphabetic", "alternate",
"always", "amharic", "amharic-abegede", "antialiased", "appworkspace",
"arabic-indic", "armenian", "asterisks", "attr", "auto", "auto-flow", "avoid", "avoid-column", "avoid-page",
"avoid-region", "background", "backwards", "baseline", "below", "bidi-override", "binary",
"bengali", "blink", "block", "block-axis", "bold", "bolder", "border", "border-box",
"both", "bottom", "break", "break-all", "break-word", "bullets", "button", "button-bevel",
"buttonface", "buttonhighlight", "buttonshadow", "buttontext", "calc", "cambodian",
"capitalize", "caps-lock-indicator", "caption", "captiontext", "caret",
"cell", "center", "checkbox", "circle", "cjk-decimal", "cjk-earthly-branch",
"cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote",
"col-resize", "collapse", "color", "color-burn", "color-dodge", "column", "column-reverse",
"compact", "condensed", "contain", "content", "contents",
"content-box", "context-menu", "continuous", "copy", "counter", "counters", "cover", "crop",
"cross", "crosshair", "currentcolor", "cursive", "cyclic", "darken", "dashed", "decimal",
"decimal-leading-zero", "default", "default-button", "dense", "destination-atop",
"destination-in", "destination-out", "destination-over", "devanagari", "difference",
"disc", "discard", "disclosure-closed", "disclosure-open", "document",
"dot-dash", "dot-dot-dash",
"dotted", "double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out",
"element", "ellipse", "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede",
"ethiopic-abegede-am-et", "ethiopic-abegede-gez", "ethiopic-abegede-ti-er",
"ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er",
"ethiopic-halehame-aa-et", "ethiopic-halehame-am-et",
"ethiopic-halehame-gez", "ethiopic-halehame-om-et",
"ethiopic-halehame-sid-et", "ethiopic-halehame-so-et",
"ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et", "ethiopic-halehame-tig",
"ethiopic-numeric", "ew-resize", "exclusion", "expanded", "extends", "extra-condensed",
"extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "flex", "flex-end", "flex-start", "footnotes",
"forwards", "from", "geometricPrecision", "georgian", "graytext", "grid", "groove",
"gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hard-light", "hebrew",
"help", "hidden", "hide", "higher", "highlight", "highlighttext",
"hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "hue", "icon", "ignore",
"inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite",
"infobackground", "infotext", "inherit", "initial", "inline", "inline-axis",
"inline-block", "inline-flex", "inline-grid", "inline-table", "inset", "inside", "intrinsic", "invert",
"italic", "japanese-formal", "japanese-informal", "justify", "kannada",
"katakana", "katakana-iroha", "keep-all", "khmer",
"korean-hangul-formal", "korean-hanja-formal", "korean-hanja-informal",
"landscape", "lao", "large", "larger", "left", "level", "lighter", "lighten",
"line-through", "linear", "linear-gradient", "lines", "list-item", "listbox", "listitem",
"local", "logical", "loud", "lower", "lower-alpha", "lower-armenian",
"lower-greek", "lower-hexadecimal", "lower-latin", "lower-norwegian",
"lower-roman", "lowercase", "ltr", "luminosity", "malayalam", "match", "matrix", "matrix3d",
"media-controls-background", "media-current-time-display",
"media-fullscreen-button", "media-mute-button", "media-play-button",
"media-return-to-realtime-button", "media-rewind-button",
"media-seek-back-button", "media-seek-forward-button", "media-slider",
"media-sliderthumb", "media-time-remaining-display", "media-volume-slider",
"media-volume-slider-container", "media-volume-sliderthumb", "medium",
"menu", "menulist", "menulist-button", "menulist-text",
"menulist-textfield", "menutext", "message-box", "middle", "min-intrinsic",
"mix", "mongolian", "monospace", "move", "multiple", "multiply", "myanmar", "n-resize",
"narrower", "ne-resize", "nesw-resize", "no-close-quote", "no-drop",
"no-open-quote", "no-repeat", "none", "normal", "not-allowed", "nowrap",
"ns-resize", "numbers", "numeric", "nw-resize", "nwse-resize", "oblique", "octal", "opacity", "open-quote",
"optimizeLegibility", "optimizeSpeed", "oriya", "oromo", "outset",
"outside", "outside-shape", "overlay", "overline", "padding", "padding-box",
"painted", "page", "paused", "persian", "perspective", "plus-darker", "plus-lighter",
"pointer", "polygon", "portrait", "pre", "pre-line", "pre-wrap", "preserve-3d",
"progress", "push-button", "radial-gradient", "radio", "read-only",
"read-write", "read-write-plaintext-only", "rectangle", "region",
"relative", "repeat", "repeating-linear-gradient",
"repeating-radial-gradient", "repeat-x", "repeat-y", "reset", "reverse",
"rgb", "rgba", "ridge", "right", "rotate", "rotate3d", "rotateX", "rotateY",
"rotateZ", "round", "row", "row-resize", "row-reverse", "rtl", "run-in", "running",
"s-resize", "sans-serif", "saturation", "scale", "scale3d", "scaleX", "scaleY", "scaleZ", "screen",
"scroll", "scrollbar", "scroll-position", "se-resize", "searchfield",
"searchfield-cancel-button", "searchfield-decoration",
"searchfield-results-button", "searchfield-results-decoration", "self-start", "self-end",
"semi-condensed", "semi-expanded", "separate", "serif", "show", "sidama",
"simp-chinese-formal", "simp-chinese-informal", "single",
"skew", "skewX", "skewY", "skip-white-space", "slide", "slider-horizontal",
"slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow",
"small", "small-caps", "small-caption", "smaller", "soft-light", "solid", "somali",
"source-atop", "source-in", "source-out", "source-over", "space", "space-around", "space-between", "space-evenly", "spell-out", "square",
"square-button", "start", "static", "status-bar", "stretch", "stroke", "sub",
"subpixel-antialiased", "super", "sw-resize", "symbolic", "symbols", "system-ui", "table",
"table-caption", "table-cell", "table-column", "table-column-group",
"table-footer-group", "table-header-group", "table-row", "table-row-group",
"tamil",
"telugu", "text", "text-bottom", "text-top", "textarea", "textfield", "thai",
"thick", "thin", "threeddarkshadow", "threedface", "threedhighlight",
"threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er",
"tigrinya-er-abegede", "tigrinya-et", "tigrinya-et-abegede", "to", "top",
"trad-chinese-formal", "trad-chinese-informal", "transform",
"translate", "translate3d", "translateX", "translateY", "translateZ",
"transparent", "ultra-condensed", "ultra-expanded", "underline", "unset", "up",
"upper-alpha", "upper-armenian", "upper-greek", "upper-hexadecimal",
"upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url",
"var", "vertical", "vertical-text", "visible", "visibleFill", "visiblePainted",
"visibleStroke", "visual", "w-resize", "wait", "wave", "wider",
"window", "windowframe", "windowtext", "words", "wrap", "wrap-reverse", "x-large", "x-small", "xor",
"xx-large", "xx-small"
], valueKeywords = keySet(valueKeywords_);
var allWords = documentTypes_.concat(mediaTypes_).concat(mediaFeatures_).concat(mediaValueKeywords_)
.concat(propertyKeywords_).concat(nonStandardPropertyKeywords_).concat(colorKeywords_)
.concat(valueKeywords_);
CodeMirror.registerHelper("hintWords", "css", allWords);
function tokenCComment(stream, state) {
var maybeEnd = false, ch;
while ((ch = stream.next()) != null) {
if (maybeEnd && ch == "/") {
state.tokenize = null;
break;
}
maybeEnd = (ch == "*");
}
return ["comment", "comment"];
}
CodeMirror.defineMIME("text/css", {
documentTypes: documentTypes,
mediaTypes: mediaTypes,
mediaFeatures: mediaFeatures,
mediaValueKeywords: mediaValueKeywords,
propertyKeywords: propertyKeywords,
nonStandardPropertyKeywords: nonStandardPropertyKeywords,
fontProperties: fontProperties,
counterDescriptors: counterDescriptors,
colorKeywords: colorKeywords,
valueKeywords: valueKeywords,
tokenHooks: {
"/": function(stream, state) {
if (!stream.eat("*")) return false;
state.tokenize = tokenCComment;
return tokenCComment(stream, state);
}
},
name: "css"
});
CodeMirror.defineMIME("text/x-scss", {
mediaTypes: mediaTypes,
mediaFeatures: mediaFeatures,
mediaValueKeywords: mediaValueKeywords,
propertyKeywords: propertyKeywords,
nonStandardPropertyKeywords: nonStandardPropertyKeywords,
colorKeywords: colorKeywords,
valueKeywords: valueKeywords,
fontProperties: fontProperties,
allowNested: true,
lineComment: "//",
tokenHooks: {
"/": function(stream, state) {
if (stream.eat("/")) {
stream.skipToEnd();
return ["comment", "comment"];
} else if (stream.eat("*")) {
state.tokenize = tokenCComment;
return tokenCComment(stream, state);
} else {
return ["operator", "operator"];
}
},
":": function(stream) {
if (stream.match(/\s*\{/, false))
return [null, null]
return false;
},
"$": function(stream) {
stream.match(/^[\w-]+/);
if (stream.match(/^\s*:/, false))
return ["variable-2", "variable-definition"];
return ["variable-2", "variable"];
},
"#": function(stream) {
if (!stream.eat("{")) return false;
return [null, "interpolation"];
}
},
name: "css",
helperType: "scss"
});
CodeMirror.defineMIME("text/x-less", {
mediaTypes: mediaTypes,
mediaFeatures: mediaFeatures,
mediaValueKeywords: mediaValueKeywords,
propertyKeywords: propertyKeywords,
nonStandardPropertyKeywords: nonStandardPropertyKeywords,
colorKeywords: colorKeywords,
valueKeywords: valueKeywords,
fontProperties: fontProperties,
allowNested: true,
lineComment: "//",
tokenHooks: {
"/": function(stream, state) {
if (stream.eat("/")) {
stream.skipToEnd();
return ["comment", "comment"];
} else if (stream.eat("*")) {
state.tokenize = tokenCComment;
return tokenCComment(stream, state);
} else {
return ["operator", "operator"];
}
},
"@": function(stream) {
if (stream.eat("{")) return [null, "interpolation"];
if (stream.match(/^(charset|document|font-face|import|(-(moz|ms|o|webkit)-)?keyframes|media|namespace|page|supports)\b/i, false)) return false;
stream.eatWhile(/[\w\\\-]/);
if (stream.match(/^\s*:/, false))
return ["variable-2", "variable-definition"];
return ["variable-2", "variable"];
},
"&": function() {
return ["atom", "atom"];
}
},
name: "css",
helperType: "less"
});
CodeMirror.defineMIME("text/x-gss", {
documentTypes: documentTypes,
mediaTypes: mediaTypes,
mediaFeatures: mediaFeatures,
propertyKeywords: propertyKeywords,
nonStandardPropertyKeywords: nonStandardPropertyKeywords,
fontProperties: fontProperties,
counterDescriptors: counterDescriptors,
colorKeywords: colorKeywords,
valueKeywords: valueKeywords,
supportsAtComponent: true,
tokenHooks: {
"/": function(stream, state) {
if (!stream.eat("*")) return false;
state.tokenize = tokenCComment;
return tokenCComment(stream, state);
}
},
name: "css",
helperType: "gss"
});
});

View File

@ -0,0 +1,152 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("../xml/xml"), require("../javascript/javascript"), require("../css/css"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "../xml/xml", "../javascript/javascript", "../css/css"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
var defaultTags = {
script: [
["lang", /(javascript|babel)/i, "javascript"],
["type", /^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^module$|^$/i, "javascript"],
["type", /./, "text/plain"],
[null, null, "javascript"]
],
style: [
["lang", /^css$/i, "css"],
["type", /^(text\/)?(x-)?(stylesheet|css)$/i, "css"],
["type", /./, "text/plain"],
[null, null, "css"]
]
};
function maybeBackup(stream, pat, style) {
var cur = stream.current(), close = cur.search(pat);
if (close > -1) {
stream.backUp(cur.length - close);
} else if (cur.match(/<\/?$/)) {
stream.backUp(cur.length);
if (!stream.match(pat, false)) stream.match(cur);
}
return style;
}
var attrRegexpCache = {};
function getAttrRegexp(attr) {
var regexp = attrRegexpCache[attr];
if (regexp) return regexp;
return attrRegexpCache[attr] = new RegExp("\\s+" + attr + "\\s*=\\s*('|\")?([^'\"]+)('|\")?\\s*");
}
function getAttrValue(text, attr) {
var match = text.match(getAttrRegexp(attr))
return match ? /^\s*(.*?)\s*$/.exec(match[2])[1] : ""
}
function getTagRegexp(tagName, anchored) {
return new RegExp((anchored ? "^" : "") + "<\/\s*" + tagName + "\s*>", "i");
}
function addTags(from, to) {
for (var tag in from) {
var dest = to[tag] || (to[tag] = []);
var source = from[tag];
for (var i = source.length - 1; i >= 0; i--)
dest.unshift(source[i])
}
}
function findMatchingMode(tagInfo, tagText) {
for (var i = 0; i < tagInfo.length; i++) {
var spec = tagInfo[i];
if (!spec[0] || spec[1].test(getAttrValue(tagText, spec[0]))) return spec[2];
}
}
CodeMirror.defineMode("htmlmixed", function (config, parserConfig) {
var htmlMode = CodeMirror.getMode(config, {
name: "xml",
htmlMode: true,
multilineTagIndentFactor: parserConfig.multilineTagIndentFactor,
multilineTagIndentPastTag: parserConfig.multilineTagIndentPastTag
});
var tags = {};
var configTags = parserConfig && parserConfig.tags, configScript = parserConfig && parserConfig.scriptTypes;
addTags(defaultTags, tags);
if (configTags) addTags(configTags, tags);
if (configScript) for (var i = configScript.length - 1; i >= 0; i--)
tags.script.unshift(["type", configScript[i].matches, configScript[i].mode])
function html(stream, state) {
var style = htmlMode.token(stream, state.htmlState), tag = /\btag\b/.test(style), tagName
if (tag && !/[<>\s\/]/.test(stream.current()) &&
(tagName = state.htmlState.tagName && state.htmlState.tagName.toLowerCase()) &&
tags.hasOwnProperty(tagName)) {
state.inTag = tagName + " "
} else if (state.inTag && tag && />$/.test(stream.current())) {
var inTag = /^([\S]+) (.*)/.exec(state.inTag)
state.inTag = null
var modeSpec = stream.current() == ">" && findMatchingMode(tags[inTag[1]], inTag[2])
var mode = CodeMirror.getMode(config, modeSpec)
var endTagA = getTagRegexp(inTag[1], true), endTag = getTagRegexp(inTag[1], false);
state.token = function (stream, state) {
if (stream.match(endTagA, false)) {
state.token = html;
state.localState = state.localMode = null;
return null;
}
return maybeBackup(stream, endTag, state.localMode.token(stream, state.localState));
};
state.localMode = mode;
state.localState = CodeMirror.startState(mode, htmlMode.indent(state.htmlState, "", ""));
} else if (state.inTag) {
state.inTag += stream.current()
if (stream.eol()) state.inTag += " "
}
return style;
};
return {
startState: function () {
var state = CodeMirror.startState(htmlMode);
return {token: html, inTag: null, localMode: null, localState: null, htmlState: state};
},
copyState: function (state) {
var local;
if (state.localState) {
local = CodeMirror.copyState(state.localMode, state.localState);
}
return {token: state.token, inTag: state.inTag,
localMode: state.localMode, localState: local,
htmlState: CodeMirror.copyState(htmlMode, state.htmlState)};
},
token: function (stream, state) {
return state.token(stream, state);
},
indent: function (state, textAfter, line) {
if (!state.localMode || /^\s*<\//.test(textAfter))
return htmlMode.indent(state.htmlState, textAfter, line);
else if (state.localMode.indent)
return state.localMode.indent(state.localState, textAfter, line);
else
return CodeMirror.Pass;
},
innerMode: function (state) {
return {state: state.localState || state.htmlState, mode: state.localMode || htmlMode};
}
};
}, "xml", "javascript", "css");
CodeMirror.defineMIME("text/html", "htmlmixed");
});

View File

@ -0,0 +1,933 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineMode("javascript", function(config, parserConfig) {
var indentUnit = config.indentUnit;
var statementIndent = parserConfig.statementIndent;
var jsonldMode = parserConfig.jsonld;
var jsonMode = parserConfig.json || jsonldMode;
var isTS = parserConfig.typescript;
var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;
// Tokenizer
var keywords = function(){
function kw(type) {return {type: type, style: "keyword"};}
var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"), D = kw("keyword d");
var operator = kw("operator"), atom = {type: "atom", style: "atom"};
return {
"if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
"return": D, "break": D, "continue": D, "new": kw("new"), "delete": C, "void": C, "throw": C,
"debugger": kw("debugger"), "var": kw("var"), "const": kw("var"), "let": kw("var"),
"function": kw("function"), "catch": kw("catch"),
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
"in": operator, "typeof": operator, "instanceof": operator,
"true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
"this": kw("this"), "class": kw("class"), "super": kw("atom"),
"yield": C, "export": kw("export"), "import": kw("import"), "extends": C,
"await": C
};
}();
var isOperatorChar = /[+\-*&%=<>!?|~^@]/;
var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;
function readRegexp(stream) {
var escaped = false, next, inSet = false;
while ((next = stream.next()) != null) {
if (!escaped) {
if (next == "/" && !inSet) return;
if (next == "[") inSet = true;
else if (inSet && next == "]") inSet = false;
}
escaped = !escaped && next == "\\";
}
}
// Used as scratch variables to communicate multiple values without
// consing up tons of objects.
var type, content;
function ret(tp, style, cont) {
type = tp; content = cont;
return style;
}
function tokenBase(stream, state) {
var ch = stream.next();
if (ch == '"' || ch == "'") {
state.tokenize = tokenString(ch);
return state.tokenize(stream, state);
} else if (ch == "." && stream.match(/^\d[\d_]*(?:[eE][+\-]?[\d_]+)?/)) {
return ret("number", "number");
} else if (ch == "." && stream.match("..")) {
return ret("spread", "meta");
} else if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
return ret(ch);
} else if (ch == "=" && stream.eat(">")) {
return ret("=>", "operator");
} else if (ch == "0" && stream.match(/^(?:x[\dA-Fa-f_]+|o[0-7_]+|b[01_]+)n?/)) {
return ret("number", "number");
} else if (/\d/.test(ch)) {
stream.match(/^[\d_]*(?:n|(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)?/);
return ret("number", "number");
} else if (ch == "/") {
if (stream.eat("*")) {
state.tokenize = tokenComment;
return tokenComment(stream, state);
} else if (stream.eat("/")) {
stream.skipToEnd();
return ret("comment", "comment");
} else if (expressionAllowed(stream, state, 1)) {
readRegexp(stream);
stream.match(/^\b(([gimyus])(?![gimyus]*\2))+\b/);
return ret("regexp", "string-2");
} else {
stream.eat("=");
return ret("operator", "operator", stream.current());
}
} else if (ch == "`") {
state.tokenize = tokenQuasi;
return tokenQuasi(stream, state);
} else if (ch == "#" && stream.peek() == "!") {
stream.skipToEnd();
return ret("meta", "meta");
} else if (ch == "#" && stream.eatWhile(wordRE)) {
return ret("variable", "property")
} else if (ch == "<" && stream.match("!--") || ch == "-" && stream.match("->")) {
stream.skipToEnd()
return ret("comment", "comment")
} else if (isOperatorChar.test(ch)) {
if (ch != ">" || !state.lexical || state.lexical.type != ">") {
if (stream.eat("=")) {
if (ch == "!" || ch == "=") stream.eat("=")
} else if (/[<>*+\-]/.test(ch)) {
stream.eat(ch)
if (ch == ">") stream.eat(ch)
}
}
if (ch == "?" && stream.eat(".")) return ret(".")
return ret("operator", "operator", stream.current());
} else if (wordRE.test(ch)) {
stream.eatWhile(wordRE);
var word = stream.current()
if (state.lastType != ".") {
if (keywords.propertyIsEnumerable(word)) {
var kw = keywords[word]
return ret(kw.type, kw.style, word)
}
if (word == "async" && stream.match(/^(\s|\/\*.*?\*\/)*[\[\(\w]/, false))
return ret("async", "keyword", word)
}
return ret("variable", "variable", word)
}
}
function tokenString(quote) {
return function(stream, state) {
var escaped = false, next;
if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){
state.tokenize = tokenBase;
return ret("jsonld-keyword", "meta");
}
while ((next = stream.next()) != null) {
if (next == quote && !escaped) break;
escaped = !escaped && next == "\\";
}
if (!escaped) state.tokenize = tokenBase;
return ret("string", "string");
};
}
function tokenComment(stream, state) {
var maybeEnd = false, ch;
while (ch = stream.next()) {
if (ch == "/" && maybeEnd) {
state.tokenize = tokenBase;
break;
}
maybeEnd = (ch == "*");
}
return ret("comment", "comment");
}
function tokenQuasi(stream, state) {
var escaped = false, next;
while ((next = stream.next()) != null) {
if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) {
state.tokenize = tokenBase;
break;
}
escaped = !escaped && next == "\\";
}
return ret("quasi", "string-2", stream.current());
}
var brackets = "([{}])";
// This is a crude lookahead trick to try and notice that we're
// parsing the argument patterns for a fat-arrow function before we
// actually hit the arrow token. It only works if the arrow is on
// the same line as the arguments and there's no strange noise
// (comments) in between. Fallback is to only notice when we hit the
// arrow, and not declare the arguments as locals for the arrow
// body.
function findFatArrow(stream, state) {
if (state.fatArrowAt) state.fatArrowAt = null;
var arrow = stream.string.indexOf("=>", stream.start);
if (arrow < 0) return;
if (isTS) { // Try to skip TypeScript return type declarations after the arguments
var m = /:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(stream.string.slice(stream.start, arrow))
if (m) arrow = m.index
}
var depth = 0, sawSomething = false;
for (var pos = arrow - 1; pos >= 0; --pos) {
var ch = stream.string.charAt(pos);
var bracket = brackets.indexOf(ch);
if (bracket >= 0 && bracket < 3) {
if (!depth) { ++pos; break; }
if (--depth == 0) { if (ch == "(") sawSomething = true; break; }
} else if (bracket >= 3 && bracket < 6) {
++depth;
} else if (wordRE.test(ch)) {
sawSomething = true;
} else if (/["'\/`]/.test(ch)) {
for (;; --pos) {
if (pos == 0) return
var next = stream.string.charAt(pos - 1)
if (next == ch && stream.string.charAt(pos - 2) != "\\") { pos--; break }
}
} else if (sawSomething && !depth) {
++pos;
break;
}
}
if (sawSomething && !depth) state.fatArrowAt = pos;
}
// Parser
var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "jsonld-keyword": true};
function JSLexical(indented, column, type, align, prev, info) {
this.indented = indented;
this.column = column;
this.type = type;
this.prev = prev;
this.info = info;
if (align != null) this.align = align;
}
function inScope(state, varname) {
for (var v = state.localVars; v; v = v.next)
if (v.name == varname) return true;
for (var cx = state.context; cx; cx = cx.prev) {
for (var v = cx.vars; v; v = v.next)
if (v.name == varname) return true;
}
}
function parseJS(state, style, type, content, stream) {
var cc = state.cc;
// Communicate our context to the combinators.
// (Less wasteful than consing up a hundred closures on every call.)
cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style;
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = true;
while(true) {
var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
if (combinator(type, content)) {
while(cc.length && cc[cc.length - 1].lex)
cc.pop()();
if (cx.marked) return cx.marked;
if (type == "variable" && inScope(state, content)) return "variable-2";
return style;
}
}
}
// Combinator utils
var cx = {state: null, column: null, marked: null, cc: null};
function pass() {
for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
}
function cont() {
pass.apply(null, arguments);
return true;
}
function inList(name, list) {
for (var v = list; v; v = v.next) if (v.name == name) return true
return false;
}
function register(varname) {
var state = cx.state;
cx.marked = "def";
if (state.context) {
if (state.lexical.info == "var" && state.context && state.context.block) {
// FIXME function decls are also not block scoped
var newContext = registerVarScoped(varname, state.context)
if (newContext != null) {
state.context = newContext
return
}
} else if (!inList(varname, state.localVars)) {
state.localVars = new Var(varname, state.localVars)
return
}
}
// Fall through means this is global
if (parserConfig.globalVars && !inList(varname, state.globalVars))
state.globalVars = new Var(varname, state.globalVars)
}
function registerVarScoped(varname, context) {
if (!context) {
return null
} else if (context.block) {
var inner = registerVarScoped(varname, context.prev)
if (!inner) return null
if (inner == context.prev) return context
return new Context(inner, context.vars, true)
} else if (inList(varname, context.vars)) {
return context
} else {
return new Context(context.prev, new Var(varname, context.vars), false)
}
}
function isModifier(name) {
return name == "public" || name == "private" || name == "protected" || name == "abstract" || name == "readonly"
}
// Combinators
function Context(prev, vars, block) { this.prev = prev; this.vars = vars; this.block = block }
function Var(name, next) { this.name = name; this.next = next }
var defaultVars = new Var("this", new Var("arguments", null))
function pushcontext() {
cx.state.context = new Context(cx.state.context, cx.state.localVars, false)
cx.state.localVars = defaultVars
}
function pushblockcontext() {
cx.state.context = new Context(cx.state.context, cx.state.localVars, true)
cx.state.localVars = null
}
function popcontext() {
cx.state.localVars = cx.state.context.vars
cx.state.context = cx.state.context.prev
}
popcontext.lex = true
function pushlex(type, info) {
var result = function() {
var state = cx.state, indent = state.indented;
if (state.lexical.type == "stat") indent = state.lexical.indented;
else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev)
indent = outer.indented;
state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
};
result.lex = true;
return result;
}
function poplex() {
var state = cx.state;
if (state.lexical.prev) {
if (state.lexical.type == ")")
state.indented = state.lexical.indented;
state.lexical = state.lexical.prev;
}
}
poplex.lex = true;
function expect(wanted) {
function exp(type) {
if (type == wanted) return cont();
else if (wanted == ";" || type == "}" || type == ")" || type == "]") return pass();
else return cont(exp);
};
return exp;
}
function statement(type, value) {
if (type == "var") return cont(pushlex("vardef", value), vardef, expect(";"), poplex);
if (type == "keyword a") return cont(pushlex("form"), parenExpr, statement, poplex);
if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
if (type == "keyword d") return cx.stream.match(/^\s*$/, false) ? cont() : cont(pushlex("stat"), maybeexpression, expect(";"), poplex);
if (type == "debugger") return cont(expect(";"));
if (type == "{") return cont(pushlex("}"), pushblockcontext, block, poplex, popcontext);
if (type == ";") return cont();
if (type == "if") {
if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
cx.state.cc.pop()();
return cont(pushlex("form"), parenExpr, statement, poplex, maybeelse);
}
if (type == "function") return cont(functiondef);
if (type == "for") return cont(pushlex("form"), forspec, statement, poplex);
if (type == "class" || (isTS && value == "interface")) {
cx.marked = "keyword"
return cont(pushlex("form", type == "class" ? type : value), className, poplex)
}
if (type == "variable") {
if (isTS && value == "declare") {
cx.marked = "keyword"
return cont(statement)
} else if (isTS && (value == "module" || value == "enum" || value == "type") && cx.stream.match(/^\s*\w/, false)) {
cx.marked = "keyword"
if (value == "enum") return cont(enumdef);
else if (value == "type") return cont(typename, expect("operator"), typeexpr, expect(";"));
else return cont(pushlex("form"), pattern, expect("{"), pushlex("}"), block, poplex, poplex)
} else if (isTS && value == "namespace") {
cx.marked = "keyword"
return cont(pushlex("form"), expression, statement, poplex)
} else if (isTS && value == "abstract") {
cx.marked = "keyword"
return cont(statement)
} else {
return cont(pushlex("stat"), maybelabel);
}
}
if (type == "switch") return cont(pushlex("form"), parenExpr, expect("{"), pushlex("}", "switch"), pushblockcontext,
block, poplex, poplex, popcontext);
if (type == "case") return cont(expression, expect(":"));
if (type == "default") return cont(expect(":"));
if (type == "catch") return cont(pushlex("form"), pushcontext, maybeCatchBinding, statement, poplex, popcontext);
if (type == "export") return cont(pushlex("stat"), afterExport, poplex);
if (type == "import") return cont(pushlex("stat"), afterImport, poplex);
if (type == "async") return cont(statement)
if (value == "@") return cont(expression, statement)
return pass(pushlex("stat"), expression, expect(";"), poplex);
}
function maybeCatchBinding(type) {
if (type == "(") return cont(funarg, expect(")"))
}
function expression(type, value) {
return expressionInner(type, value, false);
}
function expressionNoComma(type, value) {
return expressionInner(type, value, true);
}
function parenExpr(type) {
if (type != "(") return pass()
return cont(pushlex(")"), maybeexpression, expect(")"), poplex)
}
function expressionInner(type, value, noComma) {
if (cx.state.fatArrowAt == cx.stream.start) {
var body = noComma ? arrowBodyNoComma : arrowBody;
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, expect("=>"), body, popcontext);
else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
}
var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
if (type == "function") return cont(functiondef, maybeop);
if (type == "class" || (isTS && value == "interface")) { cx.marked = "keyword"; return cont(pushlex("form"), classExpression, poplex); }
if (type == "keyword c" || type == "async") return cont(noComma ? expressionNoComma : expression);
if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop);
if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
if (type == "{") return contCommasep(objprop, "}", null, maybeop);
if (type == "quasi") return pass(quasi, maybeop);
if (type == "new") return cont(maybeTarget(noComma));
if (type == "import") return cont(expression);
return cont();
}
function maybeexpression(type) {
if (type.match(/[;\}\)\],]/)) return pass();
return pass(expression);
}
function maybeoperatorComma(type, value) {
if (type == ",") return cont(maybeexpression);
return maybeoperatorNoComma(type, value, false);
}
function maybeoperatorNoComma(type, value, noComma) {
var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
var expr = noComma == false ? expression : expressionNoComma;
if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
if (type == "operator") {
if (/\+\+|--/.test(value) || isTS && value == "!") return cont(me);
if (isTS && value == "<" && cx.stream.match(/^([^<>]|<[^<>]*>)*>\s*\(/, false))
return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, me);
if (value == "?") return cont(expression, expect(":"), expr);
return cont(expr);
}
if (type == "quasi") { return pass(quasi, me); }
if (type == ";") return;
if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
if (type == ".") return cont(property, me);
if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
if (isTS && value == "as") { cx.marked = "keyword"; return cont(typeexpr, me) }
if (type == "regexp") {
cx.state.lastType = cx.marked = "operator"
cx.stream.backUp(cx.stream.pos - cx.stream.start - 1)
return cont(expr)
}
}
function quasi(type, value) {
if (type != "quasi") return pass();
if (value.slice(value.length - 2) != "${") return cont(quasi);
return cont(expression, continueQuasi);
}
function continueQuasi(type) {
if (type == "}") {
cx.marked = "string-2";
cx.state.tokenize = tokenQuasi;
return cont(quasi);
}
}
function arrowBody(type) {
findFatArrow(cx.stream, cx.state);
return pass(type == "{" ? statement : expression);
}
function arrowBodyNoComma(type) {
findFatArrow(cx.stream, cx.state);
return pass(type == "{" ? statement : expressionNoComma);
}
function maybeTarget(noComma) {
return function(type) {
if (type == ".") return cont(noComma ? targetNoComma : target);
else if (type == "variable" && isTS) return cont(maybeTypeArgs, noComma ? maybeoperatorNoComma : maybeoperatorComma)
else return pass(noComma ? expressionNoComma : expression);
};
}
function target(_, value) {
if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorComma); }
}
function targetNoComma(_, value) {
if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorNoComma); }
}
function maybelabel(type) {
if (type == ":") return cont(poplex, statement);
return pass(maybeoperatorComma, expect(";"), poplex);
}
function property(type) {
if (type == "variable") {cx.marked = "property"; return cont();}
}
function objprop(type, value) {
if (type == "async") {
cx.marked = "property";
return cont(objprop);
} else if (type == "variable" || cx.style == "keyword") {
cx.marked = "property";
if (value == "get" || value == "set") return cont(getterSetter);
var m // Work around fat-arrow-detection complication for detecting typescript typed arrow params
if (isTS && cx.state.fatArrowAt == cx.stream.start && (m = cx.stream.match(/^\s*:\s*/, false)))
cx.state.fatArrowAt = cx.stream.pos + m[0].length
return cont(afterprop);
} else if (type == "number" || type == "string") {
cx.marked = jsonldMode ? "property" : (cx.style + " property");
return cont(afterprop);
} else if (type == "jsonld-keyword") {
return cont(afterprop);
} else if (isTS && isModifier(value)) {
cx.marked = "keyword"
return cont(objprop)
} else if (type == "[") {
return cont(expression, maybetype, expect("]"), afterprop);
} else if (type == "spread") {
return cont(expressionNoComma, afterprop);
} else if (value == "*") {
cx.marked = "keyword";
return cont(objprop);
} else if (type == ":") {
return pass(afterprop)
}
}
function getterSetter(type) {
if (type != "variable") return pass(afterprop);
cx.marked = "property";
return cont(functiondef);
}
function afterprop(type) {
if (type == ":") return cont(expressionNoComma);
if (type == "(") return pass(functiondef);
}
function commasep(what, end, sep) {
function proceed(type, value) {
if (sep ? sep.indexOf(type) > -1 : type == ",") {
var lex = cx.state.lexical;
if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
return cont(function(type, value) {
if (type == end || value == end) return pass()
return pass(what)
}, proceed);
}
if (type == end || value == end) return cont();
if (sep && sep.indexOf(";") > -1) return pass(what)
return cont(expect(end));
}
return function(type, value) {
if (type == end || value == end) return cont();
return pass(what, proceed);
};
}
function contCommasep(what, end, info) {
for (var i = 3; i < arguments.length; i++)
cx.cc.push(arguments[i]);
return cont(pushlex(end, info), commasep(what, end), poplex);
}
function block(type) {
if (type == "}") return cont();
return pass(statement, block);
}
function maybetype(type, value) {
if (isTS) {
if (type == ":") return cont(typeexpr);
if (value == "?") return cont(maybetype);
}
}
function maybetypeOrIn(type, value) {
if (isTS && (type == ":" || value == "in")) return cont(typeexpr)
}
function mayberettype(type) {
if (isTS && type == ":") {
if (cx.stream.match(/^\s*\w+\s+is\b/, false)) return cont(expression, isKW, typeexpr)
else return cont(typeexpr)
}
}
function isKW(_, value) {
if (value == "is") {
cx.marked = "keyword"
return cont()
}
}
function typeexpr(type, value) {
if (value == "keyof" || value == "typeof" || value == "infer") {
cx.marked = "keyword"
return cont(value == "typeof" ? expressionNoComma : typeexpr)
}
if (type == "variable" || value == "void") {
cx.marked = "type"
return cont(afterType)
}
if (value == "|" || value == "&") return cont(typeexpr)
if (type == "string" || type == "number" || type == "atom") return cont(afterType);
if (type == "[") return cont(pushlex("]"), commasep(typeexpr, "]", ","), poplex, afterType)
if (type == "{") return cont(pushlex("}"), commasep(typeprop, "}", ",;"), poplex, afterType)
if (type == "(") return cont(commasep(typearg, ")"), maybeReturnType, afterType)
if (type == "<") return cont(commasep(typeexpr, ">"), typeexpr)
}
function maybeReturnType(type) {
if (type == "=>") return cont(typeexpr)
}
function typeprop(type, value) {
if (type == "variable" || cx.style == "keyword") {
cx.marked = "property"
return cont(typeprop)
} else if (value == "?" || type == "number" || type == "string") {
return cont(typeprop)
} else if (type == ":") {
return cont(typeexpr)
} else if (type == "[") {
return cont(expect("variable"), maybetypeOrIn, expect("]"), typeprop)
} else if (type == "(") {
return pass(functiondecl, typeprop)
}
}
function typearg(type, value) {
if (type == "variable" && cx.stream.match(/^\s*[?:]/, false) || value == "?") return cont(typearg)
if (type == ":") return cont(typeexpr)
if (type == "spread") return cont(typearg)
return pass(typeexpr)
}
function afterType(type, value) {
if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
if (value == "|" || type == "." || value == "&") return cont(typeexpr)
if (type == "[") return cont(typeexpr, expect("]"), afterType)
if (value == "extends" || value == "implements") { cx.marked = "keyword"; return cont(typeexpr) }
if (value == "?") return cont(typeexpr, expect(":"), typeexpr)
}
function maybeTypeArgs(_, value) {
if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
}
function typeparam() {
return pass(typeexpr, maybeTypeDefault)
}
function maybeTypeDefault(_, value) {
if (value == "=") return cont(typeexpr)
}
function vardef(_, value) {
if (value == "enum") {cx.marked = "keyword"; return cont(enumdef)}
return pass(pattern, maybetype, maybeAssign, vardefCont);
}
function pattern(type, value) {
if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(pattern) }
if (type == "variable") { register(value); return cont(); }
if (type == "spread") return cont(pattern);
if (type == "[") return contCommasep(eltpattern, "]");
if (type == "{") return contCommasep(proppattern, "}");
}
function proppattern(type, value) {
if (type == "variable" && !cx.stream.match(/^\s*:/, false)) {
register(value);
return cont(maybeAssign);
}
if (type == "variable") cx.marked = "property";
if (type == "spread") return cont(pattern);
if (type == "}") return pass();
if (type == "[") return cont(expression, expect(']'), expect(':'), proppattern);
return cont(expect(":"), pattern, maybeAssign);
}
function eltpattern() {
return pass(pattern, maybeAssign)
}
function maybeAssign(_type, value) {
if (value == "=") return cont(expressionNoComma);
}
function vardefCont(type) {
if (type == ",") return cont(vardef);
}
function maybeelse(type, value) {
if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex);
}
function forspec(type, value) {
if (value == "await") return cont(forspec);
if (type == "(") return cont(pushlex(")"), forspec1, poplex);
}
function forspec1(type) {
if (type == "var") return cont(vardef, forspec2);
if (type == "variable") return cont(forspec2);
return pass(forspec2)
}
function forspec2(type, value) {
if (type == ")") return cont()
if (type == ";") return cont(forspec2)
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression, forspec2) }
return pass(expression, forspec2)
}
function functiondef(type, value) {
if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
if (type == "variable") {register(value); return cont(functiondef);}
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, statement, popcontext);
if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondef)
}
function functiondecl(type, value) {
if (value == "*") {cx.marked = "keyword"; return cont(functiondecl);}
if (type == "variable") {register(value); return cont(functiondecl);}
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, popcontext);
if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondecl)
}
function typename(type, value) {
if (type == "keyword" || type == "variable") {
cx.marked = "type"
return cont(typename)
} else if (value == "<") {
return cont(pushlex(">"), commasep(typeparam, ">"), poplex)
}
}
function funarg(type, value) {
if (value == "@") cont(expression, funarg)
if (type == "spread") return cont(funarg);
if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(funarg); }
if (isTS && type == "this") return cont(maybetype, maybeAssign)
return pass(pattern, maybetype, maybeAssign);
}
function classExpression(type, value) {
// Class expressions may have an optional name.
if (type == "variable") return className(type, value);
return classNameAfter(type, value);
}
function className(type, value) {
if (type == "variable") {register(value); return cont(classNameAfter);}
}
function classNameAfter(type, value) {
if (value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, classNameAfter)
if (value == "extends" || value == "implements" || (isTS && type == ",")) {
if (value == "implements") cx.marked = "keyword";
return cont(isTS ? typeexpr : expression, classNameAfter);
}
if (type == "{") return cont(pushlex("}"), classBody, poplex);
}
function classBody(type, value) {
if (type == "async" ||
(type == "variable" &&
(value == "static" || value == "get" || value == "set" || (isTS && isModifier(value))) &&
cx.stream.match(/^\s+[\w$\xa1-\uffff]/, false))) {
cx.marked = "keyword";
return cont(classBody);
}
if (type == "variable" || cx.style == "keyword") {
cx.marked = "property";
return cont(classfield, classBody);
}
if (type == "number" || type == "string") return cont(classfield, classBody);
if (type == "[")
return cont(expression, maybetype, expect("]"), classfield, classBody)
if (value == "*") {
cx.marked = "keyword";
return cont(classBody);
}
if (isTS && type == "(") return pass(functiondecl, classBody)
if (type == ";" || type == ",") return cont(classBody);
if (type == "}") return cont();
if (value == "@") return cont(expression, classBody)
}
function classfield(type, value) {
if (value == "?") return cont(classfield)
if (type == ":") return cont(typeexpr, maybeAssign)
if (value == "=") return cont(expressionNoComma)
var context = cx.state.lexical.prev, isInterface = context && context.info == "interface"
return pass(isInterface ? functiondecl : functiondef)
}
function afterExport(type, value) {
if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
if (type == "{") return cont(commasep(exportField, "}"), maybeFrom, expect(";"));
return pass(statement);
}
function exportField(type, value) {
if (value == "as") { cx.marked = "keyword"; return cont(expect("variable")); }
if (type == "variable") return pass(expressionNoComma, exportField);
}
function afterImport(type) {
if (type == "string") return cont();
if (type == "(") return pass(expression);
return pass(importSpec, maybeMoreImports, maybeFrom);
}
function importSpec(type, value) {
if (type == "{") return contCommasep(importSpec, "}");
if (type == "variable") register(value);
if (value == "*") cx.marked = "keyword";
return cont(maybeAs);
}
function maybeMoreImports(type) {
if (type == ",") return cont(importSpec, maybeMoreImports)
}
function maybeAs(_type, value) {
if (value == "as") { cx.marked = "keyword"; return cont(importSpec); }
}
function maybeFrom(_type, value) {
if (value == "from") { cx.marked = "keyword"; return cont(expression); }
}
function arrayLiteral(type) {
if (type == "]") return cont();
return pass(commasep(expressionNoComma, "]"));
}
function enumdef() {
return pass(pushlex("form"), pattern, expect("{"), pushlex("}"), commasep(enummember, "}"), poplex, poplex)
}
function enummember() {
return pass(pattern, maybeAssign);
}
function isContinuedStatement(state, textAfter) {
return state.lastType == "operator" || state.lastType == "," ||
isOperatorChar.test(textAfter.charAt(0)) ||
/[,.]/.test(textAfter.charAt(0));
}
function expressionAllowed(stream, state, backUp) {
return state.tokenize == tokenBase &&
/^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(state.lastType) ||
(state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0))))
}
// Interface
return {
startState: function(basecolumn) {
var state = {
tokenize: tokenBase,
lastType: "sof",
cc: [],
lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
localVars: parserConfig.localVars,
context: parserConfig.localVars && new Context(null, null, false),
indented: basecolumn || 0
};
if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
state.globalVars = parserConfig.globalVars;
return state;
},
token: function(stream, state) {
if (stream.sol()) {
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = false;
state.indented = stream.indentation();
findFatArrow(stream, state);
}
if (state.tokenize != tokenComment && stream.eatSpace()) return null;
var style = state.tokenize(stream, state);
if (type == "comment") return style;
state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
return parseJS(state, style, type, content, stream);
},
indent: function(state, textAfter) {
if (state.tokenize == tokenComment) return CodeMirror.Pass;
if (state.tokenize != tokenBase) return 0;
var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, top
// Kludge to prevent 'maybelse' from blocking lexical scope pops
if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {
var c = state.cc[i];
if (c == poplex) lexical = lexical.prev;
else if (c != maybeelse) break;
}
while ((lexical.type == "stat" || lexical.type == "form") &&
(firstChar == "}" || ((top = state.cc[state.cc.length - 1]) &&
(top == maybeoperatorComma || top == maybeoperatorNoComma) &&
!/^[,\.=+\-*:?[\(]/.test(textAfter))))
lexical = lexical.prev;
if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
lexical = lexical.prev;
var type = lexical.type, closing = firstChar == type;
if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info.length + 1 : 0);
else if (type == "form" && firstChar == "{") return lexical.indented;
else if (type == "form") return lexical.indented + indentUnit;
else if (type == "stat")
return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0);
else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
else if (lexical.align) return lexical.column + (closing ? 0 : 1);
else return lexical.indented + (closing ? 0 : indentUnit);
},
electricInput: /^\s*(?:case .*?:|default:|\{|\})$/,
blockCommentStart: jsonMode ? null : "/*",
blockCommentEnd: jsonMode ? null : "*/",
blockCommentContinue: jsonMode ? null : " * ",
lineComment: jsonMode ? null : "//",
fold: "brace",
closeBrackets: "()[]{}''\"\"``",
helperType: jsonMode ? "json" : "javascript",
jsonldMode: jsonldMode,
jsonMode: jsonMode,
expressionAllowed: expressionAllowed,
skipExpression: function(state) {
var top = state.cc[state.cc.length - 1]
if (top == expression || top == expressionNoComma) state.cc.pop()
}
};
});
CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/);
CodeMirror.defineMIME("text/javascript", "javascript");
CodeMirror.defineMIME("text/ecmascript", "javascript");
CodeMirror.defineMIME("application/javascript", "javascript");
CodeMirror.defineMIME("application/x-javascript", "javascript");
CodeMirror.defineMIME("application/ecmascript", "javascript");
CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true});
CodeMirror.defineMIME("application/ld+json", {name: "javascript", jsonld: true});
CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
});

View File

@ -0,0 +1,413 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
var htmlConfig = {
autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true,
'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true,
'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true,
'track': true, 'wbr': true, 'menuitem': true},
implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true,
'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true,
'th': true, 'tr': true},
contextGrabbers: {
'dd': {'dd': true, 'dt': true},
'dt': {'dd': true, 'dt': true},
'li': {'li': true},
'option': {'option': true, 'optgroup': true},
'optgroup': {'optgroup': true},
'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true,
'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true,
'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true,
'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true,
'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true},
'rp': {'rp': true, 'rt': true},
'rt': {'rp': true, 'rt': true},
'tbody': {'tbody': true, 'tfoot': true},
'td': {'td': true, 'th': true},
'tfoot': {'tbody': true},
'th': {'td': true, 'th': true},
'thead': {'tbody': true, 'tfoot': true},
'tr': {'tr': true}
},
doNotIndent: {"pre": true},
allowUnquoted: true,
allowMissing: true,
caseFold: true
}
var xmlConfig = {
autoSelfClosers: {},
implicitlyClosed: {},
contextGrabbers: {},
doNotIndent: {},
allowUnquoted: false,
allowMissing: false,
allowMissingTagName: false,
caseFold: false
}
CodeMirror.defineMode("xml", function(editorConf, config_) {
var indentUnit = editorConf.indentUnit
var config = {}
var defaults = config_.htmlMode ? htmlConfig : xmlConfig
for (var prop in defaults) config[prop] = defaults[prop]
for (var prop in config_) config[prop] = config_[prop]
// Return variables for tokenizers
var type, setStyle;
function inText(stream, state) {
function chain(parser) {
state.tokenize = parser;
return parser(stream, state);
}
var ch = stream.next();
if (ch == "<") {
if (stream.eat("!")) {
if (stream.eat("[")) {
if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>"));
else return null;
} else if (stream.match("--")) {
return chain(inBlock("comment", "-->"));
} else if (stream.match("DOCTYPE", true, true)) {
stream.eatWhile(/[\w\._\-]/);
return chain(doctype(1));
} else {
return null;
}
} else if (stream.eat("?")) {
stream.eatWhile(/[\w\._\-]/);
state.tokenize = inBlock("meta", "?>");
return "meta";
} else {
type = stream.eat("/") ? "closeTag" : "openTag";
state.tokenize = inTag;
return "tag bracket";
}
} else if (ch == "&") {
var ok;
if (stream.eat("#")) {
if (stream.eat("x")) {
ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";");
} else {
ok = stream.eatWhile(/[\d]/) && stream.eat(";");
}
} else {
ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";");
}
return ok ? "atom" : "error";
} else {
stream.eatWhile(/[^&<]/);
return null;
}
}
inText.isInText = true;
function inTag(stream, state) {
var ch = stream.next();
if (ch == ">" || (ch == "/" && stream.eat(">"))) {
state.tokenize = inText;
type = ch == ">" ? "endTag" : "selfcloseTag";
return "tag bracket";
} else if (ch == "=") {
type = "equals";
return null;
} else if (ch == "<") {
state.tokenize = inText;
state.state = baseState;
state.tagName = state.tagStart = null;
var next = state.tokenize(stream, state);
return next ? next + " tag error" : "tag error";
} else if (/[\'\"]/.test(ch)) {
state.tokenize = inAttribute(ch);
state.stringStartCol = stream.column();
return state.tokenize(stream, state);
} else {
stream.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/);
return "word";
}
}
function inAttribute(quote) {
var closure = function(stream, state) {
while (!stream.eol()) {
if (stream.next() == quote) {
state.tokenize = inTag;
break;
}
}
return "string";
};
closure.isInAttribute = true;
return closure;
}
function inBlock(style, terminator) {
return function(stream, state) {
while (!stream.eol()) {
if (stream.match(terminator)) {
state.tokenize = inText;
break;
}
stream.next();
}
return style;
}
}
function doctype(depth) {
return function(stream, state) {
var ch;
while ((ch = stream.next()) != null) {
if (ch == "<") {
state.tokenize = doctype(depth + 1);
return state.tokenize(stream, state);
} else if (ch == ">") {
if (depth == 1) {
state.tokenize = inText;
break;
} else {
state.tokenize = doctype(depth - 1);
return state.tokenize(stream, state);
}
}
}
return "meta";
};
}
function Context(state, tagName, startOfLine) {
this.prev = state.context;
this.tagName = tagName;
this.indent = state.indented;
this.startOfLine = startOfLine;
if (config.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent))
this.noIndent = true;
}
function popContext(state) {
if (state.context) state.context = state.context.prev;
}
function maybePopContext(state, nextTagName) {
var parentTagName;
while (true) {
if (!state.context) {
return;
}
parentTagName = state.context.tagName;
if (!config.contextGrabbers.hasOwnProperty(parentTagName) ||
!config.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
return;
}
popContext(state);
}
}
function baseState(type, stream, state) {
if (type == "openTag") {
state.tagStart = stream.column();
return tagNameState;
} else if (type == "closeTag") {
return closeTagNameState;
} else {
return baseState;
}
}
function tagNameState(type, stream, state) {
if (type == "word") {
state.tagName = stream.current();
setStyle = "tag";
return attrState;
} else if (config.allowMissingTagName && type == "endTag") {
setStyle = "tag bracket";
return attrState(type, stream, state);
} else {
setStyle = "error";
return tagNameState;
}
}
function closeTagNameState(type, stream, state) {
if (type == "word") {
var tagName = stream.current();
if (state.context && state.context.tagName != tagName &&
config.implicitlyClosed.hasOwnProperty(state.context.tagName))
popContext(state);
if ((state.context && state.context.tagName == tagName) || config.matchClosing === false) {
setStyle = "tag";
return closeState;
} else {
setStyle = "tag error";
return closeStateErr;
}
} else if (config.allowMissingTagName && type == "endTag") {
setStyle = "tag bracket";
return closeState(type, stream, state);
} else {
setStyle = "error";
return closeStateErr;
}
}
function closeState(type, _stream, state) {
if (type != "endTag") {
setStyle = "error";
return closeState;
}
popContext(state);
return baseState;
}
function closeStateErr(type, stream, state) {
setStyle = "error";
return closeState(type, stream, state);
}
function attrState(type, _stream, state) {
if (type == "word") {
setStyle = "attribute";
return attrEqState;
} else if (type == "endTag" || type == "selfcloseTag") {
var tagName = state.tagName, tagStart = state.tagStart;
state.tagName = state.tagStart = null;
if (type == "selfcloseTag" ||
config.autoSelfClosers.hasOwnProperty(tagName)) {
maybePopContext(state, tagName);
} else {
maybePopContext(state, tagName);
state.context = new Context(state, tagName, tagStart == state.indented);
}
return baseState;
}
setStyle = "error";
return attrState;
}
function attrEqState(type, stream, state) {
if (type == "equals") return attrValueState;
if (!config.allowMissing) setStyle = "error";
return attrState(type, stream, state);
}
function attrValueState(type, stream, state) {
if (type == "string") return attrContinuedState;
if (type == "word" && config.allowUnquoted) {setStyle = "string"; return attrState;}
setStyle = "error";
return attrState(type, stream, state);
}
function attrContinuedState(type, stream, state) {
if (type == "string") return attrContinuedState;
return attrState(type, stream, state);
}
return {
startState: function(baseIndent) {
var state = {tokenize: inText,
state: baseState,
indented: baseIndent || 0,
tagName: null, tagStart: null,
context: null}
if (baseIndent != null) state.baseIndent = baseIndent
return state
},
token: function(stream, state) {
if (!state.tagName && stream.sol())
state.indented = stream.indentation();
if (stream.eatSpace()) return null;
type = null;
var style = state.tokenize(stream, state);
if ((style || type) && style != "comment") {
setStyle = null;
state.state = state.state(type || style, stream, state);
if (setStyle)
style = setStyle == "error" ? style + " error" : setStyle;
}
return style;
},
indent: function(state, textAfter, fullLine) {
var context = state.context;
// Indent multi-line strings (e.g. css).
if (state.tokenize.isInAttribute) {
if (state.tagStart == state.indented)
return state.stringStartCol + 1;
else
return state.indented + indentUnit;
}
if (context && context.noIndent) return CodeMirror.Pass;
if (state.tokenize != inTag && state.tokenize != inText)
return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0;
// Indent the starts of attribute names.
if (state.tagName) {
if (config.multilineTagIndentPastTag !== false)
return state.tagStart + state.tagName.length + 2;
else
return state.tagStart + indentUnit * (config.multilineTagIndentFactor || 1);
}
if (config.alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0;
var tagAfter = textAfter && /^<(\/)?([\w_:\.-]*)/.exec(textAfter);
if (tagAfter && tagAfter[1]) { // Closing tag spotted
while (context) {
if (context.tagName == tagAfter[2]) {
context = context.prev;
break;
} else if (config.implicitlyClosed.hasOwnProperty(context.tagName)) {
context = context.prev;
} else {
break;
}
}
} else if (tagAfter) { // Opening tag spotted
while (context) {
var grabbers = config.contextGrabbers[context.tagName];
if (grabbers && grabbers.hasOwnProperty(tagAfter[2]))
context = context.prev;
else
break;
}
}
while (context && context.prev && !context.startOfLine)
context = context.prev;
if (context) return context.indent + indentUnit;
else return state.baseIndent || 0;
},
electricInput: /<\/[\s\w:]+>$/,
blockCommentStart: "<!--",
blockCommentEnd: "-->",
configuration: config.htmlMode ? "html" : "xml",
helperType: config.htmlMode ? "html" : "xml",
skipAttribute: function(state) {
if (state.state == attrValueState)
state.state = attrState
},
xmlCurrentTag: function(state) {
return state.tagName ? {name: state.tagName, close: state.type == "closeTag"} : null
},
xmlCurrentContext: function(state) {
var context = []
for (var cx = state.context; cx; cx = cx.prev)
if (cx.tagName) context.push(cx.tagName)
return context.reverse()
}
};
});
CodeMirror.defineMIME("text/xml", "xml");
CodeMirror.defineMIME("application/xml", "xml");
if (!CodeMirror.mimeModes.hasOwnProperty("text/html"))
CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true});
});

Some files were not shown because too many files have changed in this diff Show More