From 5d523feaa51701cf180d3d27af677eee7b3d7edf Mon Sep 17 00:00:00 2001 From: Anyon Date: Mon, 17 Aug 2020 15:33:06 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=A8=A1=E5=9D=97=E7=AE=A1?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/module/change/2020.08.03.00.md | 2 + app/admin/{ver.php => module/version.php} | 13 +- app/wechat/module/change/2020.08.03.00.md | 2 + .../module/database/2020.08.03.00_install.sql | 131 ++ .../module/database/2020.08.03.00_unset.sql | 6 + .../module/database/2020.08.03.00_update.sql | 1 + app/wechat/{ver.php => module/version.php} | 9 - composer.lock | 9 +- config/database.php | 2 +- vendor/composer/autoload_classmap.php | 1 + vendor/composer/autoload_static.php | 1 + vendor/composer/installed.json | 9 +- vendor/services.php | 2 +- vendor/zoujingli/think-library/composer.json | 1 + .../think-library/src/command/Queue.php | 8 +- .../think-library/src/extend/Parsedown.php | 1504 +++++++++++++++++ .../src/service/ModuleService.php | 12 +- 17 files changed, 1674 insertions(+), 39 deletions(-) create mode 100644 app/admin/module/change/2020.08.03.00.md rename app/admin/{ver.php => module/version.php} (70%) create mode 100644 app/wechat/module/change/2020.08.03.00.md create mode 100644 app/wechat/module/database/2020.08.03.00_install.sql create mode 100644 app/wechat/module/database/2020.08.03.00_unset.sql create mode 100644 app/wechat/module/database/2020.08.03.00_update.sql rename app/wechat/{ver.php => module/version.php} (81%) create mode 100644 vendor/zoujingli/think-library/src/extend/Parsedown.php diff --git a/app/admin/module/change/2020.08.03.00.md b/app/admin/module/change/2020.08.03.00.md new file mode 100644 index 000000000..d594c7fec --- /dev/null +++ b/app/admin/module/change/2020.08.03.00.md @@ -0,0 +1,2 @@ +# 系统模块初始化成功 +* 这次更新了许多内容哦 \ No newline at end of file diff --git a/app/admin/ver.php b/app/admin/module/version.php similarity index 70% rename from app/admin/ver.php rename to app/admin/module/version.php index 651dfbf37..0697ad8d8 100644 --- a/app/admin/ver.php +++ b/app/admin/module/version.php @@ -13,20 +13,9 @@ // | github 代码仓库:https://github.com/zoujingli/ThinkAdmin // +---------------------------------------------------------------------- -// 模块配置文件 return [ 'name' => 'admin', 'author' => 'Anyon', - 'version' => '2020.08.05.00', + 'version' => '2020.08.03.00', 'content' => 'ThinkAdmin 系统基础模块', - 'changes' => [ - '2020.08.05.00' => [ - 'content' => '优化系统模块管理', - 'database' => ['select version()'], - ], - '2020.08.03.00' => [ - 'content' => '系统模块初始化提交', - 'database' => ['select version()'], - ], - ], ]; \ No newline at end of file diff --git a/app/wechat/module/change/2020.08.03.00.md b/app/wechat/module/change/2020.08.03.00.md new file mode 100644 index 000000000..4ae5bfdba --- /dev/null +++ b/app/wechat/module/change/2020.08.03.00.md @@ -0,0 +1,2 @@ +# 微信模块初始化成功 +* 这次更新了许多内容哦 \ No newline at end of file diff --git a/app/wechat/module/database/2020.08.03.00_install.sql b/app/wechat/module/database/2020.08.03.00_install.sql new file mode 100644 index 000000000..de64207f6 --- /dev/null +++ b/app/wechat/module/database/2020.08.03.00_install.sql @@ -0,0 +1,131 @@ +-- ---------------------------- +-- Table structure for wechat_fans +-- ---------------------------- +DROP TABLE IF EXISTS `_PREFIX_wechat_fans`; +CREATE TABLE `_PREFIX_wechat_fans` ( + `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `appid` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '公众号APPID', + `unionid` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '粉丝unionid', + `openid` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '粉丝openid', + `tagid_list` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '粉丝标签id', + `is_black` tinyint(1) UNSIGNED NULL DEFAULT 0 COMMENT '是否为黑名单状态', + `subscribe` tinyint(1) UNSIGNED NULL DEFAULT 0 COMMENT '关注状态(0未关注,1已关注)', + `nickname` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户昵称', + `sex` tinyint(1) UNSIGNED NULL DEFAULT 0 COMMENT '用户性别(1男性,2女性,0未知)', + `country` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户所在国家', + `province` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户所在省份', + `city` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户所在城市', + `language` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户的语言(zh_CN)', + `headimgurl` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户头像', + `subscribe_time` bigint(20) UNSIGNED NULL DEFAULT 0 COMMENT '关注时间', + `subscribe_at` datetime NULL DEFAULT NULL COMMENT '关注时间', + `remark` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '备注', + `subscribe_scene` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '扫码关注场景', + `qr_scene` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '二维码场景值', + `qr_scene_str` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '二维码场景内容', + `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `index_wechat_fans_openid`(`openid`) USING BTREE, + INDEX `index_wechat_fans_unionid`(`unionid`) USING BTREE, + INDEX `index_wechat_fans_is_back`(`is_black`) USING BTREE, + INDEX `index_wechat_fans_subscribe`(`subscribe`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信-粉丝'; + +-- ---------------------------- +-- Table structure for wechat_fans_tags +-- ---------------------------- +DROP TABLE IF EXISTS `_PREFIX_wechat_fans_tags`; +CREATE TABLE `_PREFIX_wechat_fans_tags` ( + `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '标签ID', + `appid` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '公众号APPID', + `name` varchar(35) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标签名称', + `count` bigint(20) UNSIGNED NULL DEFAULT 0 COMMENT '总数', + `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建日期', + INDEX `index_wechat_fans_tags_id`(`id`) USING BTREE, + INDEX `index_wechat_fans_tags_appid`(`appid`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信-标签'; + +-- ---------------------------- +-- Table structure for wechat_keys +-- ---------------------------- +DROP TABLE IF EXISTS `_PREFIX_wechat_keys`; +CREATE TABLE `_PREFIX_wechat_keys` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `appid` char(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '公众号APPID', + `type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '类型(text,image,news)', + `keys` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '关键字', + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '文本内容', + `image_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '图片链接', + `voice_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '语音链接', + `music_title` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '音乐标题', + `music_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '音乐链接', + `music_image` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '缩略图片', + `music_desc` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '音乐描述', + `video_title` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '视频标题', + `video_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '视频URL', + `video_desc` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '视频描述', + `news_id` bigint(20) UNSIGNED NULL DEFAULT NULL COMMENT '图文ID', + `sort` bigint(20) UNSIGNED NULL DEFAULT 0 COMMENT '排序字段', + `status` tinyint(1) UNSIGNED NULL DEFAULT 1 COMMENT '状态(0禁用,1启用)', + `create_by` bigint(20) UNSIGNED NULL DEFAULT 0 COMMENT '创建人', + `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `index_wechat_keys_appid`(`appid`) USING BTREE, + INDEX `index_wechat_keys_type`(`type`) USING BTREE, + INDEX `index_wechat_keys_keys`(`keys`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信-关键字'; + +-- ---------------------------- +-- Table structure for wechat_media +-- ---------------------------- +DROP TABLE IF EXISTS `_PREFIX_wechat_media`; +CREATE TABLE `_PREFIX_wechat_media` ( + `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `appid` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '公众号ID', + `md5` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '文件md5', + `type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '媒体类型', + `media_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '永久素材MediaID', + `local_url` varchar(300) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '本地文件链接', + `media_url` varchar(300) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '远程图片链接', + `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `index_wechat_media_appid`(`appid`) USING BTREE, + INDEX `index_wechat_media_md5`(`md5`) USING BTREE, + INDEX `index_wechat_media_type`(`type`) USING BTREE, + INDEX `index_wechat_media_media_id`(`media_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信-素材'; + +-- ---------------------------- +-- Table structure for wechat_news +-- ---------------------------- +DROP TABLE IF EXISTS `_PREFIX_wechat_news`; +CREATE TABLE `_PREFIX_wechat_news` ( + `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `media_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '永久素材MediaID', + `local_url` varchar(300) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '永久素材外网URL', + `article_id` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '关联图文ID(用英文逗号做分割)', + `is_deleted` tinyint(1) UNSIGNED NULL DEFAULT 0 COMMENT '删除状态(0未删除,1已删除)', + `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建人', + PRIMARY KEY (`id`) USING BTREE, + INDEX `index_wechat_news_artcle_id`(`article_id`) USING BTREE, + INDEX `index_wechat_news_media_id`(`media_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信-图文'; + +-- ---------------------------- +-- Table structure for wechat_news_article +-- ---------------------------- +DROP TABLE IF EXISTS `_PREFIX_wechat_news_article`; +CREATE TABLE `_PREFIX_wechat_news_article` ( + `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '素材标题', + `local_url` varchar(300) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '永久素材显示URL', + `show_cover_pic` tinyint(4) UNSIGNED NULL DEFAULT 0 COMMENT '显示封面(0不显示,1显示)', + `author` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '文章作者', + `digest` varchar(300) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '摘要内容', + `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '图文内容', + `content_source_url` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '原文地址', + `read_num` bigint(20) UNSIGNED NULL DEFAULT 0 COMMENT '阅读数量', + `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信-文章'; \ No newline at end of file diff --git a/app/wechat/module/database/2020.08.03.00_unset.sql b/app/wechat/module/database/2020.08.03.00_unset.sql new file mode 100644 index 000000000..1d9e3dd3f --- /dev/null +++ b/app/wechat/module/database/2020.08.03.00_unset.sql @@ -0,0 +1,6 @@ +DROP TABLE IF EXISTS `_PREFIX_wechat_fans`; +DROP TABLE IF EXISTS `_PREFIX_wechat_fans_tags`; +DROP TABLE IF EXISTS `_PREFIX_wechat_keys`; +DROP TABLE IF EXISTS `_PREFIX_wechat_media`; +DROP TABLE IF EXISTS `_PREFIX_wechat_news`; +DROP TABLE IF EXISTS `_PREFIX_wechat_news_article`; \ No newline at end of file diff --git a/app/wechat/module/database/2020.08.03.00_update.sql b/app/wechat/module/database/2020.08.03.00_update.sql new file mode 100644 index 000000000..b3d9bbc7f --- /dev/null +++ b/app/wechat/module/database/2020.08.03.00_update.sql @@ -0,0 +1 @@ + 'wechat', 'author' => 'Anyon', 'version' => '2020.08.03.01', 'content' => 'ThinkAdmin 微信基础模块', - 'changes' => [ - '2020.08.03.00' => [ - 'content' => '模块初始化提交', - 'database' => [ - 'select version()', - ], - ], - ], ]; \ No newline at end of file diff --git a/composer.lock b/composer.lock index 862510038..3f9d0b6e7 100644 --- a/composer.lock +++ b/composer.lock @@ -937,12 +937,12 @@ "source": { "type": "git", "url": "https://github.com/zoujingli/ThinkLibrary.git", - "reference": "08aff82bffe0e4b6356373c65ddf7c2d521598a5" + "reference": "822d461c4f23e5ab3cd3489cd740a7171706e367" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zoujingli/ThinkLibrary/zipball/08aff82bffe0e4b6356373c65ddf7c2d521598a5", - "reference": "08aff82bffe0e4b6356373c65ddf7c2d521598a5", + "url": "https://api.github.com/repos/zoujingli/ThinkLibrary/zipball/822d461c4f23e5ab3cd3489cd740a7171706e367", + "reference": "822d461c4f23e5ab3cd3489cd740a7171706e367", "shasum": "", "mirrors": [ { @@ -956,6 +956,7 @@ "ext-gd": "*", "ext-iconv": "*", "ext-json": "*", + "ext-mbstring": "*", "topthink/framework": "^6.0" }, "type": "library", @@ -986,7 +987,7 @@ ], "description": "ThinkPHP v6.0 Development Library", "homepage": "http://thinkadmin.top", - "time": "2020-08-14T09:41:49+00:00" + "time": "2020-08-17T07:02:36+00:00" }, { "name": "zoujingli/wechat-developer", diff --git a/config/database.php b/config/database.php index a7c3df48d..a3bcc8a16 100644 --- a/config/database.php +++ b/config/database.php @@ -28,7 +28,7 @@ return [ // 数据库类型 'type' => 'mysql', // 服务器地址 - 'hostname' => '127.0.0.1', + 'hostname' => 'server.cuci.cc', // 数据库名 'database' => 'admin_v6', // 用户名 diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index 8c50a3f7f..a846263da 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -298,6 +298,7 @@ return array( 'think\\admin\\service\\CaptchaService' => $vendorDir . '/zoujingli/think-library/src/service/CaptchaService.php', 'think\\admin\\service\\ExpressService' => $vendorDir . '/zoujingli/think-library/src/service/ExpressService.php', 'think\\admin\\service\\InstallService' => $vendorDir . '/zoujingli/think-library/src/service/InstallService.php', + 'think\\admin\\service\\MarkdownService' => $vendorDir . '/zoujingli/think-library/src/service/MarkdownService.php', 'think\\admin\\service\\MenuService' => $vendorDir . '/zoujingli/think-library/src/service/MenuService.php', 'think\\admin\\service\\MessageService' => $vendorDir . '/zoujingli/think-library/src/service/MessageService.php', 'think\\admin\\service\\ModuleService' => $vendorDir . '/zoujingli/think-library/src/service/ModuleService.php', diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index fc47d0280..482b5dee5 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -431,6 +431,7 @@ class ComposerStaticInitb911c14a0826c73d9f097343fd33a252 'think\\admin\\service\\CaptchaService' => __DIR__ . '/..' . '/zoujingli/think-library/src/service/CaptchaService.php', 'think\\admin\\service\\ExpressService' => __DIR__ . '/..' . '/zoujingli/think-library/src/service/ExpressService.php', 'think\\admin\\service\\InstallService' => __DIR__ . '/..' . '/zoujingli/think-library/src/service/InstallService.php', + 'think\\admin\\service\\MarkdownService' => __DIR__ . '/..' . '/zoujingli/think-library/src/service/MarkdownService.php', 'think\\admin\\service\\MenuService' => __DIR__ . '/..' . '/zoujingli/think-library/src/service/MenuService.php', 'think\\admin\\service\\MessageService' => __DIR__ . '/..' . '/zoujingli/think-library/src/service/MessageService.php', 'think\\admin\\service\\ModuleService' => __DIR__ . '/..' . '/zoujingli/think-library/src/service/ModuleService.php', diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index 4673200a5..f45475293 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -963,12 +963,12 @@ "source": { "type": "git", "url": "https://github.com/zoujingli/ThinkLibrary.git", - "reference": "08aff82bffe0e4b6356373c65ddf7c2d521598a5" + "reference": "822d461c4f23e5ab3cd3489cd740a7171706e367" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zoujingli/ThinkLibrary/zipball/08aff82bffe0e4b6356373c65ddf7c2d521598a5", - "reference": "08aff82bffe0e4b6356373c65ddf7c2d521598a5", + "url": "https://api.github.com/repos/zoujingli/ThinkLibrary/zipball/822d461c4f23e5ab3cd3489cd740a7171706e367", + "reference": "822d461c4f23e5ab3cd3489cd740a7171706e367", "shasum": "", "mirrors": [ { @@ -982,9 +982,10 @@ "ext-gd": "*", "ext-iconv": "*", "ext-json": "*", + "ext-mbstring": "*", "topthink/framework": "^6.0" }, - "time": "2020-08-14T09:41:49+00:00", + "time": "2020-08-17T07:02:36+00:00", "type": "library", "extra": { "think": { diff --git a/vendor/services.php b/vendor/services.php index 0f79115a8..c13f3c074 100644 --- a/vendor/services.php +++ b/vendor/services.php @@ -1,5 +1,5 @@ 'think\\admin\\Library', diff --git a/vendor/zoujingli/think-library/composer.json b/vendor/zoujingli/think-library/composer.json index a01e4cf91..6d3ab8ba0 100644 --- a/vendor/zoujingli/think-library/composer.json +++ b/vendor/zoujingli/think-library/composer.json @@ -15,6 +15,7 @@ "ext-curl": "*", "ext-json": "*", "ext-iconv": "*", + "ext-mbstring": "*", "topthink/framework": "^6.0" }, "autoload": { diff --git a/vendor/zoujingli/think-library/src/command/Queue.php b/vendor/zoujingli/think-library/src/command/Queue.php index 44e8b72cf..b7e7fe3cd 100644 --- a/vendor/zoujingli/think-library/src/command/Queue.php +++ b/vendor/zoujingli/think-library/src/command/Queue.php @@ -94,7 +94,7 @@ class Queue extends Command $host = $this->input->getOption('host') ?: '127.0.0.1'; $root = $this->app->getRootPath() . 'public' . DIRECTORY_SEPARATOR; $command = "php -S {$host}:{$port} -t {$root} {$root}router.php"; - $this->output->highlight("># {$command}"); + $this->output->comment("># {$command}"); if (count($result = $this->process->query($command)) > 0) { if ($this->process->iswin()) $this->process->exec("start http://{$host}:{$port}"); $this->output->writeln(">> WebServer process already exist for pid {$result[0]['pid']}"); @@ -116,7 +116,7 @@ class Queue extends Command { $root = $this->app->getRootPath() . 'public' . DIRECTORY_SEPARATOR; if (count($result = $this->process->query("-t {$root} {$root}router.php")) > 0) { - $this->output->highlight("># {$result[0]['cmd']}"); + $this->output->comment("># {$result[0]['cmd']}"); $this->output->writeln(">> WebServer process {$result[0]['pid']} running"); } else { $this->output->writeln(">> The WebServer process is not running"); @@ -144,7 +144,7 @@ class Queue extends Command { $this->app->db->name($this->table)->count(); $command = $this->process->think('xadmin:queue listen'); - $this->output->highlight("># {$command}"); + $this->output->comment("># {$command}"); if (count($result = $this->process->query($command)) > 0) { $this->output->writeln(">> Asynchronous daemons already exist for pid {$result[0]['pid']}"); } else { @@ -234,7 +234,7 @@ class Queue extends Command [$start, $where] = [microtime(true), [['status', '=', 1], ['exec_time', '<=', time()]]]; foreach ($this->app->db->name($this->table)->where($where)->order('exec_time asc')->select()->toArray() as $vo) try { $command = $this->process->think("xadmin:queue dorun {$vo['code']} -"); - $this->output->highlight("># {$command}"); + $this->output->comment("># {$command}"); if (count($this->process->query($command)) > 0) { $this->output->writeln(">> Already in progress -> [{$vo['code']}] {$vo['title']}"); } else { diff --git a/vendor/zoujingli/think-library/src/extend/Parsedown.php b/vendor/zoujingli/think-library/src/extend/Parsedown.php new file mode 100644 index 000000000..07d321232 --- /dev/null +++ b/vendor/zoujingli/think-library/src/extend/Parsedown.php @@ -0,0 +1,1504 @@ +DefinitionData = []; + # standardize line breaks + $text = str_replace(["\r\n", "\r"], "\n", $text); + # remove surrounding line breaks + $text = trim($text, "\n"); + # split text into lines + $lines = explode("\n", $text); + # iterate through lines to identify blocks + $markup = $this->lines($lines); + # trim line breaks + $markup = trim($markup, "\n"); + return $markup; + } + + # + # Setters + # + + function setBreaksEnabled($breaksEnabled) + { + $this->breaksEnabled = $breaksEnabled; + return $this; + } + + protected $breaksEnabled; + + function setMarkupEscaped($markupEscaped) + { + $this->markupEscaped = $markupEscaped; + return $this; + } + + protected $markupEscaped; + + function setUrlsLinked($urlsLinked) + { + $this->urlsLinked = $urlsLinked; + return $this; + } + + protected $urlsLinked = true; + + function setSafeMode($safeMode) + { + $this->safeMode = (bool)$safeMode; + return $this; + } + + protected $safeMode; + + protected $safeLinksWhitelist = [ + 'http://', + 'https://', + 'ftp://', + 'ftps://', + 'mailto:', + 'data:image/png;base64,', + 'data:image/gif;base64,', + 'data:image/jpeg;base64,', + 'irc:', + 'ircs:', + 'git:', + 'ssh:', + 'news:', + 'steam:', + ]; + + # + # Lines + # + + protected $BlockTypes = [ + '#' => ['Header'], + '*' => ['Rule', 'List'], + '+' => ['List'], + '-' => ['SetextHeader', 'Table', 'Rule', 'List'], + '0' => ['List'], + '1' => ['List'], + '2' => ['List'], + '3' => ['List'], + '4' => ['List'], + '5' => ['List'], + '6' => ['List'], + '7' => ['List'], + '8' => ['List'], + '9' => ['List'], + ':' => ['Table'], + '<' => ['Comment', 'Markup'], + '=' => ['SetextHeader'], + '>' => ['Quote'], + '[' => ['Reference'], + '_' => ['Rule'], + '`' => ['FencedCode'], + '|' => ['Table'], + '~' => ['FencedCode'], + ]; + + # ~ + + protected $unmarkedBlockTypes = ['Code']; + + # + # Blocks + # + + protected function lines(array $lines) + { + $CurrentBlock = null; + foreach ($lines as $line) { + if (chop($line) === '') { + if (isset($CurrentBlock)) { + $CurrentBlock['interrupted'] = true; + } + continue; + } + if (strpos($line, "\t") !== false) { + $parts = explode("\t", $line); + $line = $parts[0]; + unset($parts[0]); + foreach ($parts as $part) { + $shortage = 4 - mb_strlen($line, 'utf-8') % 4; + $line .= str_repeat(' ', $shortage); + $line .= $part; + } + } + $indent = 0; + while (isset($line[$indent]) and $line[$indent] === ' ') { + $indent++; + } + $text = $indent > 0 ? substr($line, $indent) : $line; + $Line = ['body' => $line, 'indent' => $indent, 'text' => $text]; + if (isset($CurrentBlock['continuable'])) { + $Block = $this->{'block' . $CurrentBlock['type'] . 'Continue'}($Line, $CurrentBlock); + if (isset($Block)) { + $CurrentBlock = $Block; + continue; + } else { + if ($this->isBlockCompletable($CurrentBlock['type'])) { + $CurrentBlock = $this->{'block' . $CurrentBlock['type'] . 'Complete'}($CurrentBlock); + } + } + } + $marker = $text[0]; + $blockTypes = $this->unmarkedBlockTypes; + if (isset($this->BlockTypes[$marker])) { + foreach ($this->BlockTypes[$marker] as $blockType) { + $blockTypes [] = $blockType; + } + } + foreach ($blockTypes as $blockType) { + $Block = $this->{'block' . $blockType}($Line, $CurrentBlock); + if (isset($Block)) { + $Block['type'] = $blockType; + if (!isset($Block['identified'])) { + $Blocks [] = $CurrentBlock; + $Block['identified'] = true; + } + if ($this->isBlockContinuable($blockType)) { + $Block['continuable'] = true; + } + $CurrentBlock = $Block; + continue 2; + } + } + + # ~ + + if (isset($CurrentBlock) and !isset($CurrentBlock['type']) and !isset($CurrentBlock['interrupted'])) { + $CurrentBlock['element']['text'] .= "\n" . $text; + } else { + $Blocks [] = $CurrentBlock; + $CurrentBlock = $this->paragraph($Line); + $CurrentBlock['identified'] = true; + } + } + + if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type'])) { + $CurrentBlock = $this->{'block' . $CurrentBlock['type'] . 'Complete'}($CurrentBlock); + } + + $Blocks [] = $CurrentBlock; + + unset($Blocks[0]); + + # ~ + + $markup = ''; + + foreach ($Blocks as $Block) { + if (isset($Block['hidden'])) { + continue; + } + + $markup .= "\n"; + $markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']); + } + + $markup .= "\n"; + + # ~ + + return $markup; + } + + protected function isBlockContinuable($Type) + { + return method_exists($this, 'block' . $Type . 'Continue'); + } + + protected function isBlockCompletable($Type) + { + return method_exists($this, 'block' . $Type . 'Complete'); + } + + # + # Code + + protected function blockCode($Line, $Block = null) + { + if (isset($Block) and !isset($Block['type']) and !isset($Block['interrupted'])) { + return; + } + + if ($Line['indent'] >= 4) { + $text = substr($Line['body'], 4); + + $Block = [ + 'element' => [ + 'name' => 'pre', + 'handler' => 'element', + 'text' => [ + 'name' => 'code', + 'text' => $text, + ], + ], + ]; + + return $Block; + } + } + + protected function blockCodeContinue($Line, $Block) + { + if ($Line['indent'] >= 4) { + if (isset($Block['interrupted'])) { + $Block['element']['text']['text'] .= "\n"; + + unset($Block['interrupted']); + } + + $Block['element']['text']['text'] .= "\n"; + + $text = substr($Line['body'], 4); + + $Block['element']['text']['text'] .= $text; + + return $Block; + } + } + + protected function blockCodeComplete($Block) + { + $text = $Block['element']['text']['text']; + + $Block['element']['text']['text'] = $text; + + return $Block; + } + + # + # Comment + + protected function blockComment($Line) + { + if ($this->markupEscaped or $this->safeMode) { + return; + } + + if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!') { + $Block = [ + 'markup' => $Line['body'], + ]; + + if (preg_match('/-->$/', $Line['text'])) { + $Block['closed'] = true; + } + + return $Block; + } + } + + protected function blockCommentContinue($Line, array $Block) + { + if (isset($Block['closed'])) { + return; + } + + $Block['markup'] .= "\n" . $Line['body']; + + if (preg_match('/-->$/', $Line['text'])) { + $Block['closed'] = true; + } + + return $Block; + } + + # + # Fenced Code + + protected function blockFencedCode($Line) + { + if (preg_match('/^[' . $Line['text'][0] . ']{3,}[ ]*([^`]+)?[ ]*$/', $Line['text'], $matches)) { + $Element = [ + 'name' => 'code', + 'text' => '', + ]; + + if (isset($matches[1])) { + /** + * https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes + * Every HTML element may have a class attribute specified. + * The attribute, if specified, must have a value that is a set + * of space-separated tokens representing the various classes + * that the element belongs to. + * [...] + * The space characters, for the purposes of this specification, + * are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab), + * U+000A LINE FEED (LF), U+000C FORM FEED (FF), and + * U+000D CARRIAGE RETURN (CR). + */ + $language = substr($matches[1], 0, strcspn($matches[1], " \t\n\f\r")); + + $class = 'language-' . $language; + + $Element['attributes'] = [ + 'class' => $class, + ]; + } + + $Block = [ + 'char' => $Line['text'][0], + 'element' => [ + 'name' => 'pre', + 'handler' => 'element', + 'text' => $Element, + ], + ]; + + return $Block; + } + } + + protected function blockFencedCodeContinue($Line, $Block) + { + if (isset($Block['complete'])) { + return; + } + + if (isset($Block['interrupted'])) { + $Block['element']['text']['text'] .= "\n"; + + unset($Block['interrupted']); + } + + if (preg_match('/^' . $Block['char'] . '{3,}[ ]*$/', $Line['text'])) { + $Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1); + + $Block['complete'] = true; + + return $Block; + } + + $Block['element']['text']['text'] .= "\n" . $Line['body']; + + return $Block; + } + + protected function blockFencedCodeComplete($Block) + { + $text = $Block['element']['text']['text']; + + $Block['element']['text']['text'] = $text; + + return $Block; + } + + # + # Header + + protected function blockHeader($Line) + { + if (isset($Line['text'][1])) { + $level = 1; + + while (isset($Line['text'][$level]) and $Line['text'][$level] === '#') { + $level++; + } + + if ($level > 6) { + return; + } + + $text = trim($Line['text'], '# '); + + $Block = [ + 'element' => [ + 'name' => 'h' . min(6, $level), + 'text' => $text, + 'handler' => 'line', + ], + ]; + + return $Block; + } + } + + # + # List + + protected function blockList($Line) + { + [$name, $pattern] = $Line['text'][0] <= '-' ? ['ul', '[*+-]'] : ['ol', '[0-9]+[.]']; + + if (preg_match('/^(' . $pattern . '[ ]+)(.*)/', $Line['text'], $matches)) { + $Block = [ + 'indent' => $Line['indent'], + 'pattern' => $pattern, + 'element' => [ + 'name' => $name, + 'handler' => 'elements', + ], + ]; + + if ($name === 'ol') { + $listStart = stristr($matches[0], '.', true); + + if ($listStart !== '1') { + $Block['element']['attributes'] = ['start' => $listStart]; + } + } + + $Block['li'] = [ + 'name' => 'li', + 'handler' => 'li', + 'text' => [ + $matches[2], + ], + ]; + + $Block['element']['text'] [] = &$Block['li']; + + return $Block; + } + } + + protected function blockListContinue($Line, array $Block) + { + if ($Block['indent'] === $Line['indent'] and preg_match('/^' . $Block['pattern'] . '(?:[ ]+(.*)|$)/', $Line['text'], $matches)) { + if (isset($Block['interrupted'])) { + $Block['li']['text'] [] = ''; + + $Block['loose'] = true; + + unset($Block['interrupted']); + } + + unset($Block['li']); + + $text = isset($matches[1]) ? $matches[1] : ''; + + $Block['li'] = [ + 'name' => 'li', + 'handler' => 'li', + 'text' => [ + $text, + ], + ]; + + $Block['element']['text'] [] = &$Block['li']; + + return $Block; + } + + if ($Line['text'][0] === '[' and $this->blockReference($Line)) { + return $Block; + } + + if (!isset($Block['interrupted'])) { + $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']); + + $Block['li']['text'] [] = $text; + + return $Block; + } + + if ($Line['indent'] > 0) { + $Block['li']['text'] [] = ''; + + $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']); + + $Block['li']['text'] [] = $text; + + unset($Block['interrupted']); + + return $Block; + } + } + + protected function blockListComplete(array $Block) + { + if (isset($Block['loose'])) { + foreach ($Block['element']['text'] as &$li) { + if (end($li['text']) !== '') { + $li['text'] [] = ''; + } + } + } + + return $Block; + } + + # + # Quote + + protected function blockQuote($Line) + { + if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) { + $Block = [ + 'element' => [ + 'name' => 'blockquote', + 'handler' => 'lines', + 'text' => (array)$matches[1], + ], + ]; + + return $Block; + } + } + + protected function blockQuoteContinue($Line, array $Block) + { + if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) { + if (isset($Block['interrupted'])) { + $Block['element']['text'] [] = ''; + + unset($Block['interrupted']); + } + + $Block['element']['text'] [] = $matches[1]; + + return $Block; + } + + if (!isset($Block['interrupted'])) { + $Block['element']['text'] [] = $Line['text']; + + return $Block; + } + } + + # + # Rule + + protected function blockRule($Line) + { + if (preg_match('/^([' . $Line['text'][0] . '])([ ]*\1){2,}[ ]*$/', $Line['text'])) { + $Block = [ + 'element' => [ + 'name' => 'hr', + ], + ]; + + return $Block; + } + } + + # + # Setext + + protected function blockSetextHeader($Line, array $Block = null) + { + if (!isset($Block) or isset($Block['type']) or isset($Block['interrupted'])) { + return; + } + + if (chop($Line['text'], $Line['text'][0]) === '') { + $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2'; + + return $Block; + } + } + + # + # Markup + + protected function blockMarkup($Line) + { + if ($this->markupEscaped or $this->safeMode) { + return; + } + + if (preg_match('/^<(\w[\w-]*)(?:[ ]*' . $this->regexHtmlAttribute . ')*[ ]*(\/)?>/', $Line['text'], $matches)) { + $element = strtolower($matches[1]); + + if (in_array($element, $this->textLevelElements)) { + return; + } + + $Block = [ + 'name' => $matches[1], + 'depth' => 0, + 'markup' => $Line['text'], + ]; + + $length = strlen($matches[0]); + + $remainder = substr($Line['text'], $length); + + if (trim($remainder) === '') { + if (isset($matches[2]) or in_array($matches[1], $this->voidElements)) { + $Block['closed'] = true; + + $Block['void'] = true; + } + } else { + if (isset($matches[2]) or in_array($matches[1], $this->voidElements)) { + return; + } + + if (preg_match('/<\/' . $matches[1] . '>[ ]*$/i', $remainder)) { + $Block['closed'] = true; + } + } + + return $Block; + } + } + + protected function blockMarkupContinue($Line, array $Block) + { + if (isset($Block['closed'])) { + return; + } + + if (preg_match('/^<' . $Block['name'] . '(?:[ ]*' . $this->regexHtmlAttribute . ')*[ ]*>/i', $Line['text'])) # open + { + $Block['depth']++; + } + + if (preg_match('/(.*?)<\/' . $Block['name'] . '>[ ]*$/i', $Line['text'], $matches)) # close + { + if ($Block['depth'] > 0) { + $Block['depth']--; + } else { + $Block['closed'] = true; + } + } + + if (isset($Block['interrupted'])) { + $Block['markup'] .= "\n"; + + unset($Block['interrupted']); + } + + $Block['markup'] .= "\n" . $Line['body']; + + return $Block; + } + + # + # Reference + + protected function blockReference($Line) + { + if (preg_match('/^\[(.+?)\]:[ ]*?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches)) { + $id = strtolower($matches[1]); + + $Data = [ + 'url' => $matches[2], + 'title' => null, + ]; + + if (isset($matches[3])) { + $Data['title'] = $matches[3]; + } + + $this->DefinitionData['Reference'][$id] = $Data; + + $Block = [ + 'hidden' => true, + ]; + + return $Block; + } + } + + # + # Table + + protected function blockTable($Line, array $Block = null) + { + if (!isset($Block) or isset($Block['type']) or isset($Block['interrupted'])) { + return; + } + + if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '') { + $alignments = []; + + $divider = $Line['text']; + + $divider = trim($divider); + $divider = trim($divider, '|'); + + $dividerCells = explode('|', $divider); + + foreach ($dividerCells as $dividerCell) { + $dividerCell = trim($dividerCell); + + if ($dividerCell === '') { + continue; + } + + $alignment = null; + + if ($dividerCell[0] === ':') { + $alignment = 'left'; + } + + if (substr($dividerCell, -1) === ':') { + $alignment = $alignment === 'left' ? 'center' : 'right'; + } + + $alignments [] = $alignment; + } + + # ~ + + $HeaderElements = []; + + $header = $Block['element']['text']; + + $header = trim($header); + $header = trim($header, '|'); + + $headerCells = explode('|', $header); + + foreach ($headerCells as $index => $headerCell) { + $headerCell = trim($headerCell); + + $HeaderElement = [ + 'name' => 'th', + 'text' => $headerCell, + 'handler' => 'line', + ]; + + if (isset($alignments[$index])) { + $alignment = $alignments[$index]; + + $HeaderElement['attributes'] = [ + 'style' => 'text-align: ' . $alignment . ';', + ]; + } + + $HeaderElements [] = $HeaderElement; + } + + # ~ + + $Block = [ + 'alignments' => $alignments, + 'identified' => true, + 'element' => [ + 'name' => 'table', + 'handler' => 'elements', + ], + ]; + + $Block['element']['text'] [] = [ + 'name' => 'thead', + 'handler' => 'elements', + ]; + + $Block['element']['text'] [] = [ + 'name' => 'tbody', + 'handler' => 'elements', + 'text' => [], + ]; + + $Block['element']['text'][0]['text'] [] = [ + 'name' => 'tr', + 'handler' => 'elements', + 'text' => $HeaderElements, + ]; + + return $Block; + } + } + + protected function blockTableContinue($Line, array $Block) + { + if (isset($Block['interrupted'])) { + return; + } + + if ($Line['text'][0] === '|' or strpos($Line['text'], '|')) { + $Elements = []; + + $row = $Line['text']; + + $row = trim($row); + $row = trim($row, '|'); + + preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches); + + foreach ($matches[0] as $index => $cell) { + $cell = trim($cell); + + $Element = [ + 'name' => 'td', + 'handler' => 'line', + 'text' => $cell, + ]; + + if (isset($Block['alignments'][$index])) { + $Element['attributes'] = [ + 'style' => 'text-align: ' . $Block['alignments'][$index] . ';', + ]; + } + + $Elements [] = $Element; + } + + $Element = [ + 'name' => 'tr', + 'handler' => 'elements', + 'text' => $Elements, + ]; + + $Block['element']['text'][1]['text'] [] = $Element; + + return $Block; + } + } + + # + # ~ + # + + protected function paragraph($Line) + { + $Block = [ + 'element' => [ + 'name' => 'p', + 'text' => $Line['text'], + 'handler' => 'line', + ], + ]; + + return $Block; + } + + # + # Inline Elements + # + + protected $InlineTypes = [ + '"' => ['SpecialCharacter'], + '!' => ['Image'], + '&' => ['SpecialCharacter'], + '*' => ['Emphasis'], + ':' => ['Url'], + '<' => ['UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'], + '>' => ['SpecialCharacter'], + '[' => ['Link'], + '_' => ['Emphasis'], + '`' => ['Code'], + '~' => ['Strikethrough'], + '\\' => ['EscapeSequence'], + ]; + + # ~ + + protected $inlineMarkerList = '!"*_&[:<>`~\\'; + + # + # ~ + # + + public function line($text, $nonNestables = []) + { + $markup = ''; + + # $excerpt is based on the first occurrence of a marker + + while ($excerpt = strpbrk($text, $this->inlineMarkerList)) { + $marker = $excerpt[0]; + + $markerPosition = strpos($text, $marker); + + $Excerpt = ['text' => $excerpt, 'context' => $text]; + + foreach ($this->InlineTypes[$marker] as $inlineType) { + # check to see if the current inline type is nestable in the current context + + if (!empty($nonNestables) and in_array($inlineType, $nonNestables)) { + continue; + } + + $Inline = $this->{'inline' . $inlineType}($Excerpt); + + if (!isset($Inline)) { + continue; + } + + # makes sure that the inline belongs to "our" marker + + if (isset($Inline['position']) and $Inline['position'] > $markerPosition) { + continue; + } + + # sets a default inline position + + if (!isset($Inline['position'])) { + $Inline['position'] = $markerPosition; + } + + # cause the new element to 'inherit' our non nestables + + foreach ($nonNestables as $non_nestable) { + $Inline['element']['nonNestables'][] = $non_nestable; + } + + # the text that comes before the inline + $unmarkedText = substr($text, 0, $Inline['position']); + + # compile the unmarked text + $markup .= $this->unmarkedText($unmarkedText); + + # compile the inline + $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']); + + # remove the examined text + $text = substr($text, $Inline['position'] + $Inline['extent']); + + continue 2; + } + + # the marker does not belong to an inline + + $unmarkedText = substr($text, 0, $markerPosition + 1); + + $markup .= $this->unmarkedText($unmarkedText); + + $text = substr($text, $markerPosition + 1); + } + + $markup .= $this->unmarkedText($text); + + return $markup; + } + + # + # ~ + # + + protected function inlineCode($Excerpt) + { + $marker = $Excerpt['text'][0]; + + if (preg_match('/^(' . $marker . '+)[ ]*(.+?)[ ]*(? strlen($matches[0]), + 'element' => [ + 'name' => 'code', + 'text' => $text, + ], + ]; + } + } + + protected function inlineEmailTag($Excerpt) + { + if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches)) { + $url = $matches[1]; + + if (!isset($matches[2])) { + $url = 'mailto:' . $url; + } + + return [ + 'extent' => strlen($matches[0]), + 'element' => [ + 'name' => 'a', + 'text' => $matches[1], + 'attributes' => [ + 'href' => $url, + ], + ], + ]; + } + } + + protected function inlineEmphasis($Excerpt) + { + if (!isset($Excerpt['text'][1])) { + return; + } + + $marker = $Excerpt['text'][0]; + + if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches)) { + $emphasis = 'strong'; + } elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches)) { + $emphasis = 'em'; + } else { + return; + } + + return [ + 'extent' => strlen($matches[0]), + 'element' => [ + 'name' => $emphasis, + 'handler' => 'line', + 'text' => $matches[1], + ], + ]; + } + + protected function inlineEscapeSequence($Excerpt) + { + if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters)) { + return [ + 'markup' => $Excerpt['text'][1], + 'extent' => 2, + ]; + } + } + + protected function inlineImage($Excerpt) + { + if (!isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[') { + return; + } + + $Excerpt['text'] = substr($Excerpt['text'], 1); + + $Link = $this->inlineLink($Excerpt); + + if ($Link === null) { + return; + } + + $Inline = [ + 'extent' => $Link['extent'] + 1, + 'element' => [ + 'name' => 'img', + 'attributes' => [ + 'src' => $Link['element']['attributes']['href'], + 'alt' => $Link['element']['text'], + ], + ], + ]; + + $Inline['element']['attributes'] += $Link['element']['attributes']; + + unset($Inline['element']['attributes']['href']); + + return $Inline; + } + + protected function inlineLink($Excerpt) + { + $Element = [ + 'name' => 'a', + 'handler' => 'line', + 'nonNestables' => ['Url', 'Link'], + 'text' => null, + 'attributes' => [ + 'href' => null, + 'title' => null, + ], + ]; + + $extent = 0; + + $remainder = $Excerpt['text']; + + if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches)) { + $Element['text'] = $matches[1]; + + $extent += strlen($matches[0]); + + $remainder = substr($remainder, $extent); + } else { + return; + } + + if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches)) { + $Element['attributes']['href'] = $matches[1]; + + if (isset($matches[2])) { + $Element['attributes']['title'] = substr($matches[2], 1, -1); + } + + $extent += strlen($matches[0]); + } else { + if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches)) { + $definition = strlen($matches[1]) ? $matches[1] : $Element['text']; + $definition = strtolower($definition); + + $extent += strlen($matches[0]); + } else { + $definition = strtolower($Element['text']); + } + + if (!isset($this->DefinitionData['Reference'][$definition])) { + return; + } + + $Definition = $this->DefinitionData['Reference'][$definition]; + + $Element['attributes']['href'] = $Definition['url']; + $Element['attributes']['title'] = $Definition['title']; + } + + return [ + 'extent' => $extent, + 'element' => $Element, + ]; + } + + protected function inlineMarkup($Excerpt) + { + if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false) { + return; + } + + if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*[ ]*>/s', $Excerpt['text'], $matches)) { + return [ + 'markup' => $matches[0], + 'extent' => strlen($matches[0]), + ]; + } + + if ($Excerpt['text'][1] === '!' and preg_match('/^/s', $Excerpt['text'], $matches)) { + return [ + 'markup' => $matches[0], + 'extent' => strlen($matches[0]), + ]; + } + + if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*(?:[ ]*' . $this->regexHtmlAttribute . ')*[ ]*\/?>/s', $Excerpt['text'], $matches)) { + return [ + 'markup' => $matches[0], + 'extent' => strlen($matches[0]), + ]; + } + } + + protected function inlineSpecialCharacter($Excerpt) + { + if ($Excerpt['text'][0] === '&' and !preg_match('/^&#?\w+;/', $Excerpt['text'])) { + return [ + 'markup' => '&', + 'extent' => 1, + ]; + } + + $SpecialCharacter = ['>' => 'gt', '<' => 'lt', '"' => 'quot']; + + if (isset($SpecialCharacter[$Excerpt['text'][0]])) { + return [ + 'markup' => '&' . $SpecialCharacter[$Excerpt['text'][0]] . ';', + 'extent' => 1, + ]; + } + } + + protected function inlineStrikethrough($Excerpt) + { + if (!isset($Excerpt['text'][1])) { + return; + } + + if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches)) { + return [ + 'extent' => strlen($matches[0]), + 'element' => [ + 'name' => 'del', + 'text' => $matches[1], + 'handler' => 'line', + ], + ]; + } + } + + protected function inlineUrl($Excerpt) + { + if ($this->urlsLinked !== true or !isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/') { + return; + } + + if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)) { + $url = $matches[0][0]; + + $Inline = [ + 'extent' => strlen($matches[0][0]), + 'position' => $matches[0][1], + 'element' => [ + 'name' => 'a', + 'text' => $url, + 'attributes' => [ + 'href' => $url, + ], + ], + ]; + + return $Inline; + } + } + + protected function inlineUrlTag($Excerpt) + { + if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches)) { + $url = $matches[1]; + + return [ + 'extent' => strlen($matches[0]), + 'element' => [ + 'name' => 'a', + 'text' => $url, + 'attributes' => [ + 'href' => $url, + ], + ], + ]; + } + } + + # ~ + + protected function unmarkedText($text) + { + if ($this->breaksEnabled) { + $text = preg_replace('/[ ]*\n/', "
\n", $text); + } else { + $text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "
\n", $text); + $text = str_replace(" \n", "\n", $text); + } + + return $text; + } + + # + # Handlers + # + + protected function element(array $Element) + { + if ($this->safeMode) { + $Element = $this->sanitiseElement($Element); + } + + $markup = '<' . $Element['name']; + + if (isset($Element['attributes'])) { + foreach ($Element['attributes'] as $name => $value) { + if ($value === null) { + continue; + } + + $markup .= ' ' . $name . '="' . self::escape($value) . '"'; + } + } + + $permitRawHtml = false; + + if (isset($Element['text'])) { + $text = $Element['text']; + } + // very strongly consider an alternative if you're writing an + // extension + elseif (isset($Element['rawHtml'])) { + $text = $Element['rawHtml']; + $allowRawHtmlInSafeMode = isset($Element['allowRawHtmlInSafeMode']) && $Element['allowRawHtmlInSafeMode']; + $permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode; + } + + if (isset($text)) { + $markup .= '>'; + + if (!isset($Element['nonNestables'])) { + $Element['nonNestables'] = []; + } + + if (isset($Element['handler'])) { + $markup .= $this->{$Element['handler']}($text, $Element['nonNestables']); + } elseif (!$permitRawHtml) { + $markup .= self::escape($text, true); + } else { + $markup .= $text; + } + + $markup .= ''; + } else { + $markup .= ' />'; + } + + return $markup; + } + + protected function elements(array $Elements) + { + $markup = ''; + + foreach ($Elements as $Element) { + $markup .= "\n" . $this->element($Element); + } + + $markup .= "\n"; + + return $markup; + } + + # ~ + + protected function li($lines) + { + $markup = $this->lines($lines); + + $trimmedMarkup = trim($markup); + + if (!in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '

') { + $markup = $trimmedMarkup; + $markup = substr($markup, 3); + + $position = strpos($markup, "

"); + + $markup = substr_replace($markup, '', $position, 4); + } + + return $markup; + } + + # + # Deprecated Methods + # + + function parse($text) + { + $markup = $this->text($text); + + return $markup; + } + + protected function sanitiseElement(array $Element) + { + static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/'; + static $safeUrlNameToAtt = [ + 'a' => 'href', + 'img' => 'src', + ]; + + if (isset($safeUrlNameToAtt[$Element['name']])) { + $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]); + } + + if (!empty($Element['attributes'])) { + foreach ($Element['attributes'] as $att => $val) { + # filter out badly parsed attribute + if (!preg_match($goodAttribute, $att)) { + unset($Element['attributes'][$att]); + } # dump onevent attribute + elseif (self::striAtStart($att, 'on')) { + unset($Element['attributes'][$att]); + } + } + } + + return $Element; + } + + protected function filterUnsafeUrlInAttribute(array $Element, $attribute) + { + foreach ($this->safeLinksWhitelist as $scheme) { + if (self::striAtStart($Element['attributes'][$attribute], $scheme)) { + return $Element; + } + } + + $Element['attributes'][$attribute] = str_replace(':', '%3A', $Element['attributes'][$attribute]); + + return $Element; + } + + # + # Static Methods + # + + protected static function escape($text, $allowQuotes = false) + { + return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8'); + } + + protected static function striAtStart($string, $needle) + { + $len = strlen($needle); + + if ($len > strlen($string)) { + return false; + } else { + return strtolower(substr($string, 0, $len)) === strtolower($needle); + } + } + + static function instance($name = 'default') + { + if (isset(self::$instances[$name])) { + return self::$instances[$name]; + } + + $instance = new static(); + + self::$instances[$name] = $instance; + + return $instance; + } + + private static $instances = []; + + # + # Fields + # + + protected $DefinitionData; + + # + # Read-Only + + protected $specialCharacters = [ + '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', + ]; + + protected $StrongRegex = [ + '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s', + '_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us', + ]; + + protected $EmRegex = [ + '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s', + '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us', + ]; + + protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?'; + + protected $voidElements = ['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source']; + + protected $textLevelElements = [ + 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont', + 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing', + 'i', 'rp', 'del', 'code', 'strike', 'marquee', + 'q', 'rt', 'ins', 'font', 'strong', + 's', 'tt', 'kbd', 'mark', + 'u', 'xm', 'sub', 'nobr', + 'sup', 'ruby', + 'var', 'span', + 'wbr', 'time', + ]; +} \ No newline at end of file diff --git a/vendor/zoujingli/think-library/src/service/ModuleService.php b/vendor/zoujingli/think-library/src/service/ModuleService.php index 670f747ba..15d8f2a82 100644 --- a/vendor/zoujingli/think-library/src/service/ModuleService.php +++ b/vendor/zoujingli/think-library/src/service/ModuleService.php @@ -16,6 +16,7 @@ namespace think\admin\service; use think\admin\extend\HttpExtend; +use think\admin\extend\Parsedown; use think\admin\Service; /** @@ -104,9 +105,12 @@ class ModuleService extends Service public function getModules(array $data = []): array { foreach (NodeService::instance()->getModules() as $name) { - if (is_array($version = $this->getModuleVersion($name))) { - if (preg_match('|^\d{4}\.\d{2}\.\d{2}\.\d{2}$|', $version['version'])) { - $data[$name] = $version; + if (is_array($vars = $this->getModuleVersion($name)) && isset($vars['version'])) { + if (preg_match('|^\d{4}\.\d{2}\.\d{2}\.\d{2}$|', $vars['version'])) { + $data[$name] = $vars; + foreach (glob("{$this->app->getBasePath()}{$name}/module/change/*.md") as $file) { + $data[$name]['change'][pathinfo($file, PATHINFO_FILENAME)] = Parsedown::instance()->parse(file_get_contents($file)); + } } } } @@ -153,7 +157,7 @@ class ModuleService extends Service */ private function getModuleVersion($name) { - $file = $this->app->getBasePath() . $name . DIRECTORY_SEPARATOR . 'ver.php'; + $file = "{$this->app->getBasePath()}{$name}/module/version.php"; if (file_exists($file) && is_file($file) && is_array($vars = @include $file)) { return isset($vars['name']) && isset($vars['version']) ? $vars : null; } else {