Browse Source

更新了一些使用问题和样式问题

tushan 10 months ago
parent
commit
6b7adfcbd2
100 changed files with 16253 additions and 16350 deletions
  1. 25 25
      Dockerfile
  2. 32 32
      LICENSE.txt
  3. 73 73
      README.md
  4. 25 25
      app/AppService.php
  5. 143 143
      app/BaseController.php
  6. 62 62
      app/ExceptionHandle.php
  7. 32 32
      app/PluginsBase.php
  8. 17 17
      app/Request.php
  9. 65 65
      app/common.php
  10. 209 209
      app/controller/Admin.php
  11. 389 389
      app/controller/Api.php
  12. 28 28
      app/controller/Card.php
  13. 59 59
      app/controller/Config.php
  14. 88 88
      app/controller/Index.php
  15. 134 134
      app/controller/Link.php
  16. 282 282
      app/controller/LinkStore.php
  17. 110 110
      app/controller/Note.php
  18. 102 102
      app/controller/SearchEngine.php
  19. 71 71
      app/controller/Setting.php
  20. 55 55
      app/controller/Tabbar.php
  21. 338 338
      app/controller/User.php
  22. 368 368
      app/controller/admin/Index.php
  23. 17 17
      app/event.php
  24. 10 10
      app/middleware.php
  25. 135 135
      app/model/CardModel.php
  26. 18 18
      app/model/ConfigModel.php
  27. 18 18
      app/model/HistoryModel.php
  28. 14 14
      app/model/LinkFolderModel.php
  29. 51 51
      app/model/LinkModel.php
  30. 24 24
      app/model/LinkStoreModel.php
  31. 12 12
      app/model/NoteModel.php
  32. 10 10
      app/model/SearchEngineModel.php
  33. 47 47
      app/model/SettingModel.php
  34. 18 18
      app/model/TabbarModel.php
  35. 11 11
      app/model/TokenModel.php
  36. 49 49
      app/model/UserModel.php
  37. 12 12
      app/model/UserSearchEngineModel.php
  38. 10 10
      app/provider.php
  39. 9 9
      app/service.php
  40. 37 37
      app/view/cardNotFound.html
  41. 20 20
      auto_install.json
  42. 67 67
      composer.json
  43. 1982 1982
      composer.lock
  44. 124 124
      config/500.html
  45. 34 34
      config/app.php
  46. 39 39
      config/cache.php
  47. 12 12
      config/console.php
  48. 20 20
      config/cookie.php
  49. 68 68
      config/database.php
  50. 24 24
      config/filesystem.php
  51. 27 27
      config/lang.php
  52. 45 45
      config/log.php
  53. 8 8
      config/middleware.php
  54. 45 45
      config/route.php
  55. 19 19
      config/session.php
  56. 10 10
      config/trace.php
  57. 25 25
      config/view.php
  58. 0 1
      defaultData.sql
  59. 28 28
      docker/default.conf
  60. 109 109
      docker/nginx.conf
  61. 1947 1947
      docker/php.ini
  62. 1862 1862
      docker/redis.conf
  63. 440 440
      docker/www.conf
  64. 18 18
      extend/Axios.php
  65. 27 27
      extend/Mail.php
  66. 73 73
      extend/NetworkSpeedMonitor.php
  67. 47 47
      extend/PluginStaticSystem.php
  68. 175 175
      extend/PluginsInstall.php
  69. 177 177
      extend/Upgrade2.php
  70. 433 433
      install.sql
  71. 108 108
      nginx.conf
  72. 4 4
      nginx.rewrite
  73. 0 79
      public/404.html
  74. 0 17
      public/dist/index.html
  75. 15 15
      public/index.php
  76. 429 429
      public/install.php
  77. 2 2
      public/robots.txt
  78. 19 19
      public/router.php
  79. BIN
      public/static/aliyun.png
  80. 53 53
      public/static/css/card.css
  81. 751 751
      public/static/defaultTab.json
  82. 92 92
      public/static/js/card.js
  83. 1 1
      public/static/js/jquery.min.js
  84. 1 1
      public/static/searchEngine/360.svg
  85. 1 1
      public/static/searchEngine/baidu.svg
  86. 1 1
      public/static/searchEngine/bing.svg
  87. 1 1
      public/static/searchEngine/google.svg
  88. 1 1
      public/static/searchEngine/sougou.svg
  89. BIN
      public/static/wallpaper/.DS_Store
  90. 42 42
      route/app.php
  91. 8 8
      think
  92. BIN
      vendor/autoload.php
  93. 1609 1609
      vendor/composer/ClassLoader.php
  94. 27 27
      vendor/guzzlehttp/guzzle/LICENSE
  95. 94 94
      vendor/guzzlehttp/guzzle/README.md
  96. 1253 1253
      vendor/guzzlehttp/guzzle/UPGRADING.md
  97. 103 103
      vendor/guzzlehttp/guzzle/composer.json
  98. 28 28
      vendor/guzzlehttp/guzzle/src/BodySummarizer.php
  99. 13 13
      vendor/guzzlehttp/guzzle/src/BodySummarizerInterface.php
  100. 483 483
      vendor/guzzlehttp/guzzle/src/Client.php

+ 25 - 25
Dockerfile

@@ -1,26 +1,26 @@
-FROM alpine:3.13
-LABEL describe="tushan-mtab"
-LABEL author ="tushan<admin@mcecy.com>"
-
-WORKDIR /
-
-COPY ./docker/install.sh /install.sh
-COPY ./docker/start.sh /start.sh
-COPY ./docker/nginx.conf /nginx.conf
-COPY ./docker/default.conf /default.conf
-COPY ./docker/www.conf /www.conf
-COPY ./docker/redis.conf /opt/redis.conf
-COPY ./docker/php.ini /php.ini
-
-COPY . /www
-
-RUN chmod +x /install.sh && chmod +x /start.sh && /bin/sh /install.sh && rm /install.sh
-
-
-EXPOSE 80
-
-CMD ["/bin/bash","/start.sh"]
-
-#构建全平台 docker buildx create --name mybuilder --driver docker-container --use
-#构建全平台 docker buildx build --no-cache --platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6,linux/amd64/v3 -t itushan/mtab --push .
+FROM alpine:3.13
+LABEL describe="tushan-mtab"
+LABEL author ="tushan<admin@mcecy.com>"
+
+WORKDIR /
+
+COPY ./docker/install.sh /install.sh
+COPY ./docker/start.sh /start.sh
+COPY ./docker/nginx.conf /nginx.conf
+COPY ./docker/default.conf /default.conf
+COPY ./docker/www.conf /www.conf
+COPY ./docker/redis.conf /opt/redis.conf
+COPY ./docker/php.ini /php.ini
+
+COPY . /www
+
+RUN chmod +x /install.sh && chmod +x /start.sh && /bin/sh /install.sh && rm /install.sh
+
+
+EXPOSE 80
+
+CMD ["/bin/bash","/start.sh"]
+
+#构建全平台 docker buildx create --name mybuilder --driver docker-container --use
+#构建全平台 docker buildx build --no-cache --platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6,linux/amd64/v3 -t itushan/mtab --push .
 #构建本地镜像 docker build -t itushan/mtab .

+ 32 - 32
LICENSE.txt

@@ -1,32 +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.
+
+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.

+ 73 - 73
README.md

@@ -1,73 +1,73 @@
-# mTab新标签页
-
-![logo](https://raw.githubusercontent.com/tsxcw/imagesHouse/itushan/mTabReadme/192.png)
-
-### [mTab书签官网](https://mtab.cc) | [安装文档](https://mtab.cc/document.html)  | [作者Blog](https://blog.mcecy.com) | QQ群:694155153
-
-![](https://raw.githubusercontent.com/tsxcw/imagesHouse/itushan/mTabReadme/1.png?x-image-process=image/resize,m_lfit,w_900)
-
-
-### 主要有以下特点
-
-跨设备同步:不再为了在不同设备上找不到书签或笔记而苦恼。Mtab书签让你的收藏网址和重要笔记在所有设备上同步。
-
-跨浏览器支持:Mtab书签支持所有主流浏览器。Chrome、Firefox、Edge、Safari,无论你的选择是什么,都能在一应俱全的工具箱中找到你的书签和笔记。
-
-多功能一体:Mtab书签不仅仅是一个书签工具,它还提供了一个实用的记事本功能,让你随时随地记录想法、灵感和待办事项。此外,它还内置了一些在线小工具,解决您的日常工作问题。
-
-私有部署:如果部你对数据安全性有更高要求,Mtab书签也支持私有部署。你可以将它部署在自己的服务器上,完全掌控你的数据,不受任何干扰。
-
-免费无广告:Mtab书签坚守“免费无广告”的原则,为用户提供清爽的使用体验,没有任何干扰。
-
-Mtab书签的界面设计美观简洁,操作简单直观,让你可以专注于你的网络活动,而不是应用本身。它是你高效、无忧的网络生活的理想伴侣。
-高效流畅的操作体验:超级简约却强大的操作逻辑,没有繁琐的操作流程即可处理复杂的事情。
-
-## Demo演示站
-
-#### **[演示站Demo入口](https://demo.mtab.cc)**
-
-演示账号:admin
-
-演示密码:123456
-
-
-## Docker部署方式
-
-镜像: itushan/mtab
-
-视频教程: https://www.bilibili.com/video/BV1ee411B7fY/
-
-部署命令: docker run -itd --name mtab -p 9200:80 -v /opt/mtab:/app itushan/mtab
-
-命令解释: 其中 9200 可改为你服务器的其他端口。 /opt/mtab 可改为是你服务器的目录挂载路径,容器内目录和端口必须是 80 和 /app,--name为自定义容器名称。
-
-可视化部署: 群晖等其他管理面板请拉取 itushan/mtab 镜像。服务器端口请自己填写,容器请填写 80 ,服务器目录请填写自己想挂载的目录,容器部分请填写 /app。
-
-程序数据库安装: 部署完docker后访问您设置的端口,然后填写一些数据库配置后点击 安装 按钮即可等待安装完成, 注意的是容器部署下数据库地址请不要填写127.0.0.1,因为容器内127.0.0.1不指向宿主机网络。
-
-最后事项: 最后如果要使用外网访问,为了安全请使用Nginx反向代理或者CDN来代理您创建时填写的端口,并且配置SSL证书启用HTTPS,纯内网环境请随意啦。
-
-### docker-compose.yml
-
-在你想安装的目录创建docker-compose.yml,然后安装的目录执行`docker-compose  up -d `即可
-
-```yml
-version: '3'
-services:
-  mtabServer:
-    image: itushan/mtab
-    container_name: mtabServer
-    user: "${USER_ID}:${GROUP_ID}"
-    ports:
-      - "9200:80"
-    volumes:
-      - ./:/app
-    restart: always
-```
-## 预览图
-
-![](https://raw.githubusercontent.com/tsxcw/imagesHouse/itushan/mTabReadme/1.png)
-<img src="https://raw.githubusercontent.com/tsxcw/imagesHouse/itushan/mTabReadme/2.png" width="50%"><img src="https://raw.githubusercontent.com/tsxcw/imagesHouse/itushan/mTabReadme/3.png" width="50%">
-<img src="https://raw.githubusercontent.com/tsxcw/imagesHouse/itushan/mTabReadme/4.png" width="33.3%"><img src="https://raw.githubusercontent.com/tsxcw/imagesHouse/itushan/mTabReadme/5.png" width="33.3%"><img src="https://raw.githubusercontent.com/tsxcw/imagesHouse/itushan/mTabReadme/6.png" width="33.3%">
-<img src="https://raw.githubusercontent.com/tsxcw/imagesHouse/itushan/mTabReadme/8.png" width="50%"><img src="https://raw.githubusercontent.com/tsxcw/imagesHouse/itushan/mTabReadme/7.png" width="50%">
-
+# mTab新标签页
+
+![logo](https://raw.githubusercontent.com/tsxcw/imagesHouse/itushan/mTabReadme/192.png)
+
+### [mTab书签官网](https://mtab.cc) | [安装文档](https://mtab.cc/document.html)  | [作者Blog](https://blog.mcecy.com) | QQ群:694155153
+
+![](https://raw.githubusercontent.com/tsxcw/imagesHouse/itushan/mTabReadme/1.png?x-image-process=image/resize,m_lfit,w_900)
+
+
+### 主要有以下特点
+
+跨设备同步:不再为了在不同设备上找不到书签或笔记而苦恼。Mtab书签让你的收藏网址和重要笔记在所有设备上同步。
+
+跨浏览器支持:Mtab书签支持所有主流浏览器。Chrome、Firefox、Edge、Safari,无论你的选择是什么,都能在一应俱全的工具箱中找到你的书签和笔记。
+
+多功能一体:Mtab书签不仅仅是一个书签工具,它还提供了一个实用的记事本功能,让你随时随地记录想法、灵感和待办事项。此外,它还内置了一些在线小工具,解决您的日常工作问题。
+
+私有部署:如果部你对数据安全性有更高要求,Mtab书签也支持私有部署。你可以将它部署在自己的服务器上,完全掌控你的数据,不受任何干扰。
+
+免费无广告:Mtab书签坚守“免费无广告”的原则,为用户提供清爽的使用体验,没有任何干扰。
+
+Mtab书签的界面设计美观简洁,操作简单直观,让你可以专注于你的网络活动,而不是应用本身。它是你高效、无忧的网络生活的理想伴侣。
+高效流畅的操作体验:超级简约却强大的操作逻辑,没有繁琐的操作流程即可处理复杂的事情。
+
+## Demo演示站
+
+#### **[演示站Demo入口](https://demo.mtab.cc)**
+
+演示账号:admin
+
+演示密码:123456
+
+
+## Docker部署方式
+
+镜像: itushan/mtab
+
+视频教程: https://www.bilibili.com/video/BV1ee411B7fY/
+
+部署命令: docker run -itd --name mtab -p 9200:80 -v /opt/mtab:/app itushan/mtab
+
+命令解释: 其中 9200 可改为你服务器的其他端口。 /opt/mtab 可改为是你服务器的目录挂载路径,容器内目录和端口必须是 80 和 /app,--name为自定义容器名称。
+
+可视化部署: 群晖等其他管理面板请拉取 itushan/mtab 镜像。服务器端口请自己填写,容器请填写 80 ,服务器目录请填写自己想挂载的目录,容器部分请填写 /app。
+
+程序数据库安装: 部署完docker后访问您设置的端口,然后填写一些数据库配置后点击 安装 按钮即可等待安装完成, 注意的是容器部署下数据库地址请不要填写127.0.0.1,因为容器内127.0.0.1不指向宿主机网络。
+
+最后事项: 最后如果要使用外网访问,为了安全请使用Nginx反向代理或者CDN来代理您创建时填写的端口,并且配置SSL证书启用HTTPS,纯内网环境请随意啦。
+
+### docker-compose.yml
+
+在你想安装的目录创建docker-compose.yml,然后安装的目录执行`docker-compose  up -d `即可
+
+```yml
+version: '3'
+services:
+  mtabServer:
+    image: itushan/mtab
+    container_name: mtabServer
+    user: "${USER_ID}:${GROUP_ID}"
+    ports:
+      - "9200:80"
+    volumes:
+      - ./:/app
+    restart: always
+```
+## 预览图
+
+![](https://raw.githubusercontent.com/tsxcw/imagesHouse/itushan/mTabReadme/1.png)
+<img src="https://raw.githubusercontent.com/tsxcw/imagesHouse/itushan/mTabReadme/2.png" width="50%"><img src="https://raw.githubusercontent.com/tsxcw/imagesHouse/itushan/mTabReadme/3.png" width="50%">
+<img src="https://raw.githubusercontent.com/tsxcw/imagesHouse/itushan/mTabReadme/4.png" width="33.3%"><img src="https://raw.githubusercontent.com/tsxcw/imagesHouse/itushan/mTabReadme/5.png" width="33.3%"><img src="https://raw.githubusercontent.com/tsxcw/imagesHouse/itushan/mTabReadme/6.png" width="33.3%">
+<img src="https://raw.githubusercontent.com/tsxcw/imagesHouse/itushan/mTabReadme/8.png" width="50%"><img src="https://raw.githubusercontent.com/tsxcw/imagesHouse/itushan/mTabReadme/7.png" width="50%">
+

+ 25 - 25
app/AppService.php

@@ -1,25 +1,25 @@
-<?php
-declare (strict_types=1);
-namespace app;
-
-use think\Service;
-
-/**
- * 应用服务类
- */
-class AppService extends Service
-{
-    public function register()
-    {
-        // 服务注册
-        if (!file_exists(public_path() . 'installed.lock')) {//如果没有安装的就提示安装
-            header("Location:/install.php");
-            exit();
-        }
-    }
-
-    public function boot()
-    {
-        // 服务启动
-    }
-}
+<?php
+declare (strict_types=1);
+namespace app;
+
+use think\Service;
+
+/**
+ * 应用服务类
+ */
+class AppService extends Service
+{
+    public function register()
+    {
+        // 服务注册
+        if (!file_exists(public_path() . 'installed.lock')) {//如果没有安装的就提示安装
+            header("Location:/install.php");
+            exit();
+        }
+    }
+
+    public function boot()
+    {
+        // 服务启动
+    }
+}

+ 143 - 143
app/BaseController.php

@@ -1,143 +1,143 @@
-<?php
-/*
- * @description: 
- * @Date: 2022-09-26 17:52:37
- * @LastEditTime: 2022-09-26 20:28:17
- */
-
-declare(strict_types=1);
-
-namespace app;
-
-use app\model\SettingModel;
-use app\model\TokenModel;
-use app\model\UserModel;
-
-use think\App;
-use think\db\exception\DataNotFoundException;
-use think\db\exception\DbException;
-use think\db\exception\ModelNotFoundException;
-use think\Exception;
-use think\facade\Config;
-use think\Model;
-
-/**
- * 控制器基础类
- */
-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 应用对象
-     */
-    private $SettingConfig = false;
-    public $auth = false;
-
-    public function __construct(App $app)
-    {
-
-        $this->app = $app;
-        $this->request = $this->app->request;
-        // 控制器初始化
-        $this->initialize();
-    }
-
-    // 初始化
-    protected function initialize()
-    {
-        if ($this->systemSetting('authCode', env('authCode', false), true)) {
-            $this->auth = true;
-        }
-        if ($this->systemSetting("app_debug", '0') === '1') {
-            $this->app->debug(true);
-            Config::set([
-                'show_error_msg' => true,
-                'exception_tmpl' => app()->getThinkPath() . 'tpl/think_exception.tpl'
-            ], 'app');
-        }
-    }
-
-    //系统设置项
-    protected function systemSetting($key = false, $def = false, $emptyReplace = false)
-    {
-        if ($this->SettingConfig === false) {
-            $this->SettingConfig = SettingModel::Config();
-        }
-        if ($key) {
-            if (isset($this->SettingConfig[$key])) {
-                if ($emptyReplace && empty($this->SettingConfig[$key])) {
-                    return $def;
-                }
-                return $this->SettingConfig[$key];
-            }
-            return $def;
-        }
-        return $this->SettingConfig;
-    }
-
-    /**
-     * @description :用户信息获取
-     * @param false $must 是否强制验证,true则强制验证程序退出
-     * @return TokenModel|array|bool|mixed|Model|void
-     * @throws DataNotFoundException
-     * @throws DbException
-     * @throws ModelNotFoundException
-     */
-    protected function getUser(bool $must = false)
-    {
-        return UserModel::getUser($must);
-    }
-
-    //admin认证
-    protected function getAdmin()
-    {
-        $user = $this->getUser(true);
-        $info = UserModel::where('id', $user['user_id'])->where("manager", 1)->find();
-        if ($info) {
-            return $info;
-        }
-        $this->error('not permission')->send();
-        exit();
-    }
-
-    protected function success($msg, $data = []): \think\response\Json
-    {
-        if (is_array($msg)) {
-            return json(['msg' => "", "code" => 1, "data" => $msg]);
-        }
-        return json(['msg' => $msg, "code" => 1, "data" => $data]);
-    }
-
-    protected function error($msg, $data = []): \think\response\Json
-    {
-        if (is_array($msg)) {
-            return json(['msg' => "", "code" => 0, "data" => $msg]);
-        }
-        return json(['msg' => $msg, "code" => 0, "data" => $data]);
-    }
-}
+<?php
+/*
+ * @description: 
+ * @Date: 2022-09-26 17:52:37
+ * @LastEditTime: 2022-09-26 20:28:17
+ */
+
+declare(strict_types=1);
+
+namespace app;
+
+use app\model\SettingModel;
+use app\model\TokenModel;
+use app\model\UserModel;
+
+use think\App;
+use think\db\exception\DataNotFoundException;
+use think\db\exception\DbException;
+use think\db\exception\ModelNotFoundException;
+use think\Exception;
+use think\facade\Config;
+use think\Model;
+
+/**
+ * 控制器基础类
+ */
+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 应用对象
+     */
+    private $SettingConfig = false;
+    public $auth = false;
+
+    public function __construct(App $app)
+    {
+
+        $this->app = $app;
+        $this->request = $this->app->request;
+        // 控制器初始化
+        $this->initialize();
+    }
+
+    // 初始化
+    protected function initialize()
+    {
+        if ($this->systemSetting('authCode', env('authCode', false), true)) {
+            $this->auth = true;
+        }
+        if ($this->systemSetting("app_debug", '0') === '1') {
+            $this->app->debug(true);
+            Config::set([
+                'show_error_msg' => true,
+                'exception_tmpl' => app()->getThinkPath() . 'tpl/think_exception.tpl'
+            ], 'app');
+        }
+    }
+
+    //系统设置项
+    protected function systemSetting($key = false, $def = false, $emptyReplace = false)
+    {
+        if ($this->SettingConfig === false) {
+            $this->SettingConfig = SettingModel::Config();
+        }
+        if ($key) {
+            if (isset($this->SettingConfig[$key])) {
+                if ($emptyReplace && empty($this->SettingConfig[$key])) {
+                    return $def;
+                }
+                return $this->SettingConfig[$key];
+            }
+            return $def;
+        }
+        return $this->SettingConfig;
+    }
+
+    /**
+     * @description :用户信息获取
+     * @param false $must 是否强制验证,true则强制验证程序退出
+     * @return TokenModel|array|bool|mixed|Model|void
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     */
+    protected function getUser(bool $must = false)
+    {
+        return UserModel::getUser($must);
+    }
+
+    //admin认证
+    protected function getAdmin()
+    {
+        $user = $this->getUser(true);
+        $info = UserModel::where('id', $user['user_id'])->where("manager", 1)->find();
+        if ($info) {
+            return $info;
+        }
+        $this->error('not permission')->send();
+        exit();
+    }
+
+    protected function success($msg, $data = []): \think\response\Json
+    {
+        if (is_array($msg)) {
+            return json(['msg' => "", "code" => 1, "data" => $msg]);
+        }
+        return json(['msg' => $msg, "code" => 1, "data" => $data]);
+    }
+
+    protected function error($msg, $data = []): \think\response\Json
+    {
+        if (is_array($msg)) {
+            return json(['msg' => "", "code" => 0, "data" => $msg]);
+        }
+        return json(['msg' => $msg, "code" => 0, "data" => $data]);
+    }
+}

+ 62 - 62
app/ExceptionHandle.php

@@ -1,62 +1,62 @@
-<?php
-namespace app;
-
-use think\db\exception\DataNotFoundException;
-use think\db\exception\ModelNotFoundException;
-use think\exception\Handle;
-use think\exception\HttpException;
-use think\exception\HttpResponseException;
-use think\exception\ValidateException;
-use think\facade\Log;
-use think\Response;
-use Throwable;
-/**
- * 应用异常处理类
- */
-class ExceptionHandle extends Handle
-{
-    /**
-     * 不需要记录信息(日志)的异常类列表
-     * @var array
-     */
-    protected $ignoreReport = [
-        HttpException::class,
-        HttpResponseException::class,
-        ModelNotFoundException::class,
-        DataNotFoundException::class,
-        ValidateException::class,
-    ];
-
-    /**
-     * 记录异常信息(包括日志或者其它方式记录)
-     *
-     * @access public
-     * @param  Throwable $exception
-     * @return void
-     */
-    public function report(Throwable $exception): void
-    {
-        // 使用内置的方式记录异常日志
-        parent::report($exception);
-    }
-
-    /**
-     * Render an exception into an HTTP response.
-     *
-     * @access public
-     * @param \think\Request   $request
-     * @param Throwable $e
-     * @return Response
-     */
-    public function render($request, Throwable $e): Response
-    {
-        Log::error([
-            "message"=>$e->getMessage(),
-            "code"=>$e->getCode(),
-            "error_file"=>$e->getFile(),
-            "error_line"=>$e->getLine()
-        ]);
-        $_ENV["error_msg"] = $e->getMessage();
-        return parent::render($request, $e);
-    }
-}
+<?php
+namespace app;
+
+use think\db\exception\DataNotFoundException;
+use think\db\exception\ModelNotFoundException;
+use think\exception\Handle;
+use think\exception\HttpException;
+use think\exception\HttpResponseException;
+use think\exception\ValidateException;
+use think\facade\Log;
+use think\Response;
+use Throwable;
+/**
+ * 应用异常处理类
+ */
+class ExceptionHandle extends Handle
+{
+    /**
+     * 不需要记录信息(日志)的异常类列表
+     * @var array
+     */
+    protected $ignoreReport = [
+        HttpException::class,
+        HttpResponseException::class,
+        ModelNotFoundException::class,
+        DataNotFoundException::class,
+        ValidateException::class,
+    ];
+
+    /**
+     * 记录异常信息(包括日志或者其它方式记录)
+     *
+     * @access public
+     * @param  Throwable $exception
+     * @return void
+     */
+    public function report(Throwable $exception): void
+    {
+        // 使用内置的方式记录异常日志
+        parent::report($exception);
+    }
+
+    /**
+     * Render an exception into an HTTP response.
+     *
+     * @access public
+     * @param \think\Request   $request
+     * @param Throwable $e
+     * @return Response
+     */
+    public function render($request, Throwable $e): Response
+    {
+        Log::error([
+            "message"=>$e->getMessage(),
+            "code"=>$e->getCode(),
+            "error_file"=>$e->getFile(),
+            "error_line"=>$e->getLine()
+        ]);
+        $_ENV["error_msg"] = $e->getMessage();
+        return parent::render($request, $e);
+    }
+}

+ 32 - 32
app/PluginsBase.php

@@ -1,33 +1,33 @@
-<?php
-
-namespace app;
-
-use think\App;
-use think\View;
-
-class PluginsBase extends BaseController
-{
-    public ?View $view = null;
-    function __construct(App $app)
-    {
-        parent::__construct($app);
-        $this->_initialize();
-    }
-
-    function _initialize()
-    {
-        $this->view = new View($this->app);
-    }
-
-    function assign($key, $view)
-    {
-        $this->view->assign($key, $view);
-    }
-
-    function fetch($view, $opt = []): string
-    {
-        $view = plugins_path("view/" . $view);
-        return $this->view->fetch($view, $opt);
-    }
-
+<?php
+
+namespace app;
+
+use think\App;
+use think\View;
+
+class PluginsBase extends BaseController
+{
+    public ?View $view = null;
+    function __construct(App $app)
+    {
+        parent::__construct($app);
+        $this->_initialize();
+    }
+
+    function _initialize()
+    {
+        $this->view = new View($this->app);
+    }
+
+    function assign($key, $view)
+    {
+        $this->view->assign($key, $view);
+    }
+
+    function fetch($view, $opt = []): string
+    {
+        $view = plugins_path("view/" . $view);
+        return $this->view->fetch($view, $opt);
+    }
+
 }

+ 17 - 17
app/Request.php

@@ -1,17 +1,17 @@
-<?php
-
-namespace app;
-
-
-class Request extends \think\Request
-{
-
-    function __construct()
-    {
-        //解决跨域响应
-        header('Access-Control-Allow-Origin:*');
-        header('Access-Control-Allow-Methods:*');
-        header('Access-Control-Allow-Headers:*');
-        parent::__construct();
-    }
-}
+<?php
+
+namespace app;
+
+
+class Request extends \think\Request
+{
+
+    function __construct()
+    {
+        //解决跨域响应
+        header('Access-Control-Allow-Origin:*');
+        header('Access-Control-Allow-Methods:*');
+        header('Access-Control-Allow-Headers:*');
+        parent::__construct();
+    }
+}

+ 65 - 65
app/common.php

@@ -1,66 +1,66 @@
-<?php
-// 应用公共文件
-function validateEmail($email): bool
-{
-    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
-        return false;
-    } else {
-        return true;
-    }
-}
-
-function uuid(): string
-{
-    $chars = md5(uniqid(mt_rand(), true));
-    return substr($chars, 0, 8) . '-'
-        . substr($chars, 8, 4) . '-'
-        . substr($chars, 12, 4) . '-'
-        . substr($chars, 16, 4) . '-'
-        . substr($chars, 20, 12);
-}
-
-function renderToken($t = 'tab'): string
-{
-    $s = uuid() . strval(time()) . $t;
-    return md5($s);
-}
-
-function joinPath($path1, $path2)
-{
-    return preg_replace("#//#", "/", $path1 . $path2);
-}
-
-function getRealIp(): string
-{
-    $ip1 = request()->header('x-forwarded-for', false);
-    if ($ip1) {
-        $arr = explode(",", $ip1);
-        if(count($arr)>0){
-            return trim($arr[0]);
-        }
-    }
-    return request()->ip();
-}
-
-function plugins_path($path=''): string
-{
-    if (mb_strlen($path) > 0) {
-        if (strpos($path, "/") == 0) {
-            return $_ENV['plugins_dir_name'] . $path;
-        }
-        return $_ENV['plugins_dir_name'] . '/' . $path;
-    }
-    return $_ENV['plugins_dir_name'] . "/";
-}
-
-function is_demo_mode($is_exit = false)
-{
-    if (env('demo_mode')) {
-        if ($is_exit) {
-            json(["msg" => "演示模式,部分功能受限,禁止更新或删除!", "code" => 0])->send();
-            exit();
-        }
-        return true;
-    }
-    return false;
+<?php
+// 应用公共文件
+function validateEmail($email): bool
+{
+    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
+        return false;
+    } else {
+        return true;
+    }
+}
+
+function uuid(): string
+{
+    $chars = md5(uniqid(mt_rand(), true));
+    return substr($chars, 0, 8) . '-'
+        . substr($chars, 8, 4) . '-'
+        . substr($chars, 12, 4) . '-'
+        . substr($chars, 16, 4) . '-'
+        . substr($chars, 20, 12);
+}
+
+function renderToken($t = 'tab'): string
+{
+    $s = uuid() . strval(time()) . $t;
+    return md5($s);
+}
+
+function joinPath($path1, $path2)
+{
+    return preg_replace("#//#", "/", $path1 . $path2);
+}
+
+function getRealIp(): string
+{
+    $ip1 = request()->header('x-forwarded-for', false);
+    if ($ip1) {
+        $arr = explode(",", $ip1);
+        if(count($arr)>0){
+            return trim($arr[0]);
+        }
+    }
+    return request()->ip();
+}
+
+function plugins_path($path=''): string
+{
+    if (mb_strlen($path) > 0) {
+        if (strpos($path, "/") == 0) {
+            return $_ENV['plugins_dir_name'] . $path;
+        }
+        return $_ENV['plugins_dir_name'] . '/' . $path;
+    }
+    return $_ENV['plugins_dir_name'] . "/";
+}
+
+function is_demo_mode($is_exit = false)
+{
+    if (env('demo_mode')) {
+        if ($is_exit) {
+            json(["msg" => "演示模式,部分功能受限,禁止更新或删除!", "code" => 0])->send();
+            exit();
+        }
+        return true;
+    }
+    return false;
 }

+ 209 - 209
app/controller/Admin.php

@@ -1,209 +1,209 @@
-<?php
-
-namespace app\controller;
-
-use app\BaseController;
-use app\command\repair;
-use app\command\util;
-use app\model\ConfigModel;
-use app\model\FileModel;
-use app\model\HistoryModel;
-use app\model\LinkModel;
-use app\model\LinkStoreModel;
-use app\model\NoteModel;
-use app\model\SettingModel;
-use app\model\TabbarModel;
-use app\model\TokenModel;
-use app\model\UserModel;
-use app\model\UserSearchEngineModel;
-use DateInterval;
-use DatePeriod;
-use DateTime;
-use mysqli;
-use think\facade\Cache;
-use think\facade\Db;
-
-class Admin extends BaseController
-{
-    public function UserList(): \think\response\Json
-    {
-        $this->getAdmin();
-        $limit = $this->request->all('limit', 50);
-        $search = $this->request->post('search');
-        $sql = [];
-        if (isset($search['mail']) && mb_strlen($search['mail']) > 0) {
-            $sql[] = ['mail', 'like', "%$search[mail]%"];
-        }
-        if (isset($search['nickname']) && mb_strlen($search['nickname']) > 0) {
-            $sql[] = ["nickname","like","%$search[nickname]%"];
-        }
-        $user = UserModel::where($sql)->withoutField('password')->order($this->request->post('sort.prop', 'id'), $this->request->post('sort.order', 'desc'))->paginate($limit);
-        return $this->success('ok', $user);
-    }
-
-    function userUpdate(): \think\response\Json
-    {
-        $this->getAdmin();
-        is_demo_mode(true);
-        $id = $this->request->post('id');
-        $user = UserModel::where('id', $id)->find();
-        $data = $this->request->post();
-        if (!$user) {
-            $user = new UserModel();
-        }
-        //如果字段中的password有内容则md5加密后保存
-        if (isset($data['password']) && mb_strlen($data['password']) > 0) {
-            $data['password'] = md5($data['password']);
-        } else {
-            unset($data['password']);
-        }
-        $user->save($data);
-        return $this->success('保存成功');
-    }
-
-    //用户删除函数
-    function userDelete(): \think\response\Json
-    {
-        $this->getAdmin();
-        is_demo_mode(true);
-        $id = $this->request->post('id');
-        $user = UserModel::where('id', $id)->find();
-        if ($user) {//删除当前用户下的所有数据。
-            LinkModel::where("user_id", $user['id'])->delete();//删除标签
-            TabbarModel::where("user_id", $user['id'])->delete();//删除快捷图标
-            HistoryModel::where('user_id', $user['id'])->delete();//删除历史图标
-            ConfigModel::where('user_id', $user['id'])->delete();//删除配置信息
-            NoteModel::where('user_id', $user['id'])->delete();//删除笔记
-            UserSearchEngineModel::where('user_id', $user['id'])->delete();//删除自定义搜索引擎
-            TokenModel::where('user_id', $user['id'])->delete();//删除所有Token
-            $user->delete();//删除用户
-        }
-        return $this->success("删除完毕");
-    }
-
-    function export(): \think\response\Json
-    {
-        $this->getAdmin();
-        is_demo_mode(true);
-        $link = $this->request->post('link', []);
-        if ($link) {
-            $saveName = public_path() . 'static/exportsTabLink.json';
-            $status = file_put_contents($saveName, json_encode($link, true, JSON_UNESCAPED_UNICODE));
-            if ($status) {
-                $setting = new SettingModel();
-                if ($setting->find('defaultTab')) {
-                    $setting->update(['value' => 'static/exportsTabLink.json'], ['keys' => 'defaultTab']);
-                } else {
-                    $setting->save(['keys' => 'defaultTab', 'value' => 'static/exportsTabLink.json']);
-                }
-                Cache::delete('webConfig');
-                return $this->success('保存成功');
-            }
-        }
-        return $this->error('保存失败');
-    }
-
-    private function countFilesInDirectory($directory): int
-    {
-        $fileCount = 0;
-
-        // 获取目录中的文件和子目录
-        $files = scandir($directory);
-
-        foreach ($files as $file) {
-            // 排除"."和".."
-            if ($file != '.' && $file != '..') {
-                $filePath = $directory . '/' . $file;
-
-                // 如果是目录,则递归调用函数
-                if (is_dir($filePath)) {
-                    $fileCount += $this->countFilesInDirectory($filePath);
-                } else {
-                    // 如果是文件,则增加文件数量
-                    $fileCount++;
-                }
-            }
-        }
-
-        return $fileCount;
-    }
-
-    function getServicesStatus(): \think\response\Json
-    {
-        $this->getAdmin();
-        $userNum = UserModel::count('id');
-        $linkNum = LinkStoreModel::count('id');
-        $redisNum = 0;
-        $fileNum = FileModel::field('id')->count("id");
-        return $this->success('ok', ['userNum' => $userNum, 'linkNum' => $linkNum, 'redisNum' => $redisNum, 'fileNum' => $fileNum]);
-    }
-
-    function getUserLine(): \think\response\Json
-    {
-        $this->getAdmin();
-        $result = UserModel::whereMonth('create_time');
-        $result = $result->field('DATE_FORMAT(create_time, "%Y-%m-%d") as time, count(id) as total');
-        $result = $result->group('time')->select();
-        return $this->success('ok', $this->render($result));
-    }
-
-    function getHotTab(): \think\response\Json
-    {
-        $this->getAdmin();
-        $list = LinkStoreModel::order('install_num', 'desc')->limit(30)->cache('hotTab', 60)->select()->toArray();
-        return $this->success('ok', $list);
-    }
-
-    private function render($arr): array
-    {
-        $info = [];
-        foreach ($arr as $key => $value) {
-            $info[$value['time']] = $value['total'];
-        }
-        $time = [];
-        $total = [];
-        //当月的第一天
-        $start = date('Y-m-01', strtotime(date('Y-m-d')));
-        //当月的最后一天
-        $end = date('Y-m-d', strtotime(date('Y-m-01') . ' +1 month -1 day'));
-        $start_date = new DateTime($start);
-        $end_date = new DateTime($end);
-        $interval = new DateInterval('P1D');
-        $dateRange = new DatePeriod($start_date, $interval, $end_date);
-        $ts = null;
-        foreach ($dateRange as $date) {
-            $ts = $date->format('Y-m-d');
-            $time[] = $ts;
-            if (isset($info[$ts])) {
-                $total[] = $info[$ts];
-            } else {
-                $total[] = 0;
-            }
-        }
-        // 判断是否需要添加最后一天的数据
-        if ($end_date->format('Y-m-d') != $ts) {
-            $time[] = $end_date->format('Y-m-d');
-            $total[] = isset($info[$end_date->format('Y-m-d')]) ? $info[$end_date->format('Y-m-d')] : 0;
-        }
-        return ['time' => $time, 'total' => $total, 'sum' => array_sum($total)];
-    }
-
-    function userLoginRecord(): \think\response\Json
-    {
-        $this->getAdmin();
-        $user_id = $this->request->post('user_id');
-        if ($user_id && !is_demo_mode()) {
-            $list = TokenModel::where("user_id", $user_id)->field('user_id,FROM_UNIXTIME(create_time) as create_time,user_agent,ip')->order('create_time', 'desc')->limit(100)->select()->toArray();
-            return $this->success('', $list);
-        }
-        return $this->success('', []);
-    }
-
-    function repair(): \think\response\Json
-    {
-        $this->getAdmin();
-        is_demo_mode(true);
-        repair::repair();
-        return $this->success("修复完毕");
-    }
-}
+<?php
+
+namespace app\controller;
+
+use app\BaseController;
+use app\command\repair;
+use app\command\util;
+use app\model\ConfigModel;
+use app\model\FileModel;
+use app\model\HistoryModel;
+use app\model\LinkModel;
+use app\model\LinkStoreModel;
+use app\model\NoteModel;
+use app\model\SettingModel;
+use app\model\TabbarModel;
+use app\model\TokenModel;
+use app\model\UserModel;
+use app\model\UserSearchEngineModel;
+use DateInterval;
+use DatePeriod;
+use DateTime;
+use mysqli;
+use think\facade\Cache;
+use think\facade\Db;
+
+class Admin extends BaseController
+{
+    public function UserList(): \think\response\Json
+    {
+        $this->getAdmin();
+        $limit = $this->request->all('limit', 50);
+        $search = $this->request->post('search');
+        $sql = [];
+        if (isset($search['mail']) && mb_strlen($search['mail']) > 0) {
+            $sql[] = ['mail', 'like', "%$search[mail]%"];
+        }
+        if (isset($search['nickname']) && mb_strlen($search['nickname']) > 0) {
+            $sql[] = ["nickname","like","%$search[nickname]%"];
+        }
+        $user = UserModel::where($sql)->withoutField('password')->order($this->request->post('sort.prop', 'id'), $this->request->post('sort.order', 'desc'))->paginate($limit);
+        return $this->success('ok', $user);
+    }
+
+    function userUpdate(): \think\response\Json
+    {
+        $this->getAdmin();
+        is_demo_mode(true);
+        $id = $this->request->post('id');
+        $user = UserModel::where('id', $id)->find();
+        $data = $this->request->post();
+        if (!$user) {
+            $user = new UserModel();
+        }
+        //如果字段中的password有内容则md5加密后保存
+        if (isset($data['password']) && mb_strlen($data['password']) > 0) {
+            $data['password'] = md5($data['password']);
+        } else {
+            unset($data['password']);
+        }
+        $user->save($data);
+        return $this->success('保存成功');
+    }
+
+    //用户删除函数
+    function userDelete(): \think\response\Json
+    {
+        $this->getAdmin();
+        is_demo_mode(true);
+        $id = $this->request->post('id');
+        $user = UserModel::where('id', $id)->find();
+        if ($user) {//删除当前用户下的所有数据。
+            LinkModel::where("user_id", $user['id'])->delete();//删除标签
+            TabbarModel::where("user_id", $user['id'])->delete();//删除快捷图标
+            HistoryModel::where('user_id', $user['id'])->delete();//删除历史图标
+            ConfigModel::where('user_id', $user['id'])->delete();//删除配置信息
+            NoteModel::where('user_id', $user['id'])->delete();//删除笔记
+            UserSearchEngineModel::where('user_id', $user['id'])->delete();//删除自定义搜索引擎
+            TokenModel::where('user_id', $user['id'])->delete();//删除所有Token
+            $user->delete();//删除用户
+        }
+        return $this->success("删除完毕");
+    }
+
+    function export(): \think\response\Json
+    {
+        $this->getAdmin();
+        is_demo_mode(true);
+        $link = $this->request->post('link', []);
+        if ($link) {
+            $saveName = public_path() . 'static/exportsTabLink.json';
+            $status = file_put_contents($saveName, json_encode($link, true, JSON_UNESCAPED_UNICODE));
+            if ($status) {
+                $setting = new SettingModel();
+                if ($setting->find('defaultTab')) {
+                    $setting->update(['value' => 'static/exportsTabLink.json'], ['keys' => 'defaultTab']);
+                } else {
+                    $setting->save(['keys' => 'defaultTab', 'value' => 'static/exportsTabLink.json']);
+                }
+                Cache::delete('webConfig');
+                return $this->success('保存成功');
+            }
+        }
+        return $this->error('保存失败');
+    }
+
+    private function countFilesInDirectory($directory): int
+    {
+        $fileCount = 0;
+
+        // 获取目录中的文件和子目录
+        $files = scandir($directory);
+
+        foreach ($files as $file) {
+            // 排除"."和".."
+            if ($file != '.' && $file != '..') {
+                $filePath = $directory . '/' . $file;
+
+                // 如果是目录,则递归调用函数
+                if (is_dir($filePath)) {
+                    $fileCount += $this->countFilesInDirectory($filePath);
+                } else {
+                    // 如果是文件,则增加文件数量
+                    $fileCount++;
+                }
+            }
+        }
+
+        return $fileCount;
+    }
+
+    function getServicesStatus(): \think\response\Json
+    {
+        $this->getAdmin();
+        $userNum = UserModel::count('id');
+        $linkNum = LinkStoreModel::count('id');
+        $redisNum = 0;
+        $fileNum = FileModel::field('id')->count("id");
+        return $this->success('ok', ['userNum' => $userNum, 'linkNum' => $linkNum, 'redisNum' => $redisNum, 'fileNum' => $fileNum]);
+    }
+
+    function getUserLine(): \think\response\Json
+    {
+        $this->getAdmin();
+        $result = UserModel::whereMonth('create_time');
+        $result = $result->field('DATE_FORMAT(create_time, "%Y-%m-%d") as time, count(id) as total');
+        $result = $result->group('time')->select();
+        return $this->success('ok', $this->render($result));
+    }
+
+    function getHotTab(): \think\response\Json
+    {
+        $this->getAdmin();
+        $list = LinkStoreModel::order('install_num', 'desc')->limit(30)->cache('hotTab', 60)->select()->toArray();
+        return $this->success('ok', $list);
+    }
+
+    private function render($arr): array
+    {
+        $info = [];
+        foreach ($arr as $key => $value) {
+            $info[$value['time']] = $value['total'];
+        }
+        $time = [];
+        $total = [];
+        //当月的第一天
+        $start = date('Y-m-01', strtotime(date('Y-m-d')));
+        //当月的最后一天
+        $end = date('Y-m-d', strtotime(date('Y-m-01') . ' +1 month -1 day'));
+        $start_date = new DateTime($start);
+        $end_date = new DateTime($end);
+        $interval = new DateInterval('P1D');
+        $dateRange = new DatePeriod($start_date, $interval, $end_date);
+        $ts = null;
+        foreach ($dateRange as $date) {
+            $ts = $date->format('Y-m-d');
+            $time[] = $ts;
+            if (isset($info[$ts])) {
+                $total[] = $info[$ts];
+            } else {
+                $total[] = 0;
+            }
+        }
+        // 判断是否需要添加最后一天的数据
+        if ($end_date->format('Y-m-d') != $ts) {
+            $time[] = $end_date->format('Y-m-d');
+            $total[] = isset($info[$end_date->format('Y-m-d')]) ? $info[$end_date->format('Y-m-d')] : 0;
+        }
+        return ['time' => $time, 'total' => $total, 'sum' => array_sum($total)];
+    }
+
+    function userLoginRecord(): \think\response\Json
+    {
+        $this->getAdmin();
+        $user_id = $this->request->post('user_id');
+        if ($user_id && !is_demo_mode()) {
+            $list = TokenModel::where("user_id", $user_id)->field('user_id,FROM_UNIXTIME(create_time) as create_time,user_agent,ip')->order('create_time', 'desc')->limit(100)->select()->toArray();
+            return $this->success('', $list);
+        }
+        return $this->success('', []);
+    }
+
+    function repair(): \think\response\Json
+    {
+        $this->getAdmin();
+        is_demo_mode(true);
+        repair::repair();
+        return $this->success("修复完毕");
+    }
+}

+ 389 - 389
app/controller/Api.php

@@ -1,389 +1,389 @@
-<?php
-
-namespace app\controller;
-
-use app\BaseController;
-use app\model\FileModel;
-use app\model\LinkModel;
-use app\model\SettingModel;
-use GuzzleHttp\Client;
-use GuzzleHttp\Exception\RequestException;
-use PHPHtmlParser\Dom;
-use think\facade\Cache;
-use think\facade\Filesystem;
-use think\facade\View;
-use think\helper\Str;
-
-class Api extends BaseController
-{
-    public function site(): \think\response\Json
-    {
-        return $this->success("ok", [
-            'email' => $this->systemSetting('email', ''),
-            'qqGroup' => $this->systemSetting("qqGroup", ''),
-            'beianMps' => $this->systemSetting("beianMps", ''),
-            'copyright' => $this->systemSetting("copyright", ''),
-            "recordNumber" => $this->systemSetting("recordNumber", ''),
-            "mobileRecordNumber" => $this->systemSetting('mobileRecordNumber', '0'),
-            "auth" => $this->auth,
-            "logo" => $this->systemSetting('logo', ''),
-            "qq_login" => $this->systemSetting('qq_login', '0'),
-            "loginCloseRecordNumber" => $this->systemSetting('loginCloseRecordNumber', '0'),
-            "is_push_link_store" => $this->auth ? $this->systemSetting('is_push_link_store', '0') : '0',
-            "is_push_link_store_tips" => $this->systemSetting('is_push_link_store_tips', '0'),
-            "is_push_link_status" => $this->systemSetting("is_push_link_status", '0'),
-            'google_ext_link' => $this->systemSetting("google_ext_link", ''),
-            'edge_ext_link' => $this->systemSetting("edge_ext_link", ''),
-            'local_ext_link' => $this->systemSetting("local_ext_link", ''),
-            "customAbout" => $this->systemSetting("customAbout", '')
-        ]);
-    }
-
-    public function background(): \think\response\File
-    {
-        return download('static/background.jpeg', 'background.jpeg')->mimeType(\PluginStaticSystem::mimeType('static/background.jpeg'))->force(false)->expire(60 * 60 * 24 * 3);
-    }
-
-    //获取默认壁纸
-    function DefBg(): \think\response\Json
-    {
-        $config = $this->systemSetting('defaultTab', 'static/defaultTab.json', true);
-        if ($config) {
-            $fp = public_path() . $config;
-            if (file_exists($fp)) {
-                $file = file_get_contents($fp);
-                $json = json_decode($file, true);
-                if (isset($json['config']['theme']['backgroundImage'])) {
-                    $bg = $json['config']['theme']['backgroundImage'];
-                    $bgMime = $json['config']['theme']['backgroundMime'] ?? 0;
-                    return $this->success("ok", ['background' => $bg, "mime" => $bgMime]);
-                }
-            }
-        }
-        return $this->success("ok", ['background' => "static/background.jpeg", "mime" => 0]);
-    }
-
-    function globalNotify(): \think\response\Json
-    {
-        $info = SettingModel::Config("globalNotify", false);
-        if ($info) {
-            return $this->success('ok', $info);
-        }
-        return $this->error('empty');
-    }
-
-    //获取邮件验证码
-    function getMailCode(): \think\response\Json
-    {
-        $mail = $this->request->post("mail", false);
-        $code = rand(100000, 999999);
-        if ($mail) {
-            if (Cache::get('code' . $mail)) {
-                return $this->success("请勿频繁获取验证码");
-            }
-            $k = SettingModel::Config('smtp_code_template', false);
-            if ($k === false || mb_strlen(trim($k)) == 0) {
-                $k = '
-                        <div style="border:1px #DEDEDE solid;border-top:3px #009944 solid;padding:25px;background-color:#FFF;">
-                            <div style="font-size:17px;font-weight:bold;">邮箱验证码</div>
-                            <div style="font-size:14px;line-height:36px;padding-top:15px;padding-bottom:15px;">
-                                尊敬的用户,您好!<br>
-                                您的验证码是:<b style="color: #1e9fff">{$code}</b>。5分钟内有效,请尽快验证。
-                            </div>
-                            <div style="line-height:15px;">
-                                此致
-                            </div>
-                        </div>
-                ';
-            }
-            $html = View::display($k, ['time' => date('Y-m-d H:i:s'), 'code' => $code]);
-            try {
-                $status = \Mail::send($mail, $html);
-                if ($status) {
-                    Cache::set('code' . $mail, $code, 300);
-                    return $this->success('发送成功');
-                }
-            } catch (\Exception $e) {
-                return $this->error($e->getMessage());
-            }
-
-        }
-        return $this->error('发送失败');
-    }
-
-    private function addHttpProtocolRemovePath($url): string
-    {
-        // 解析URL
-        $parsedUrl = parse_url($url);
-        // 检查是否已经有协议,如果没有则添加http://
-        if (!isset($parsedUrl['scheme'])) {
-            // 检查是否以 // 开头,如果是,则转换为相对协议
-            if (isset($parsedUrl['host']) && strpos($url, '//') === 0) {
-                $url = 'http:' . $url;
-            } else {
-                $url = 'http://' . $url;
-            }
-        } else {
-            // 如果有协议但没有路径,保留原样
-            $url = $parsedUrl['scheme'] . '://';
-            // 如果有主机,则添加主机部分
-            if (isset($parsedUrl['host'])) {
-                $url .= $parsedUrl['host'];
-                // 如果有端口号,则添加端口号
-                if (isset($parsedUrl['port'])) {
-                    $url .= ':' . $parsedUrl['port'];
-                }
-            }
-        }
-        return $url;
-    }
-
-    private function addHttpProtocol($url)
-    {
-        // 检查是否已经有协议,如果没有则添加http://
-        if (!parse_url($url, PHP_URL_SCHEME)) {
-            // 检查是否以 // 开头,如果是,则转换为相对协议
-            if (strpos($url, '//') === 0) {
-                $url = 'https:' . $url;
-            } else {
-                $url = 'http://' . $url;
-            }
-        }
-        return $url;
-    }
-
-    private function hasOnlyPath($url): bool
-    {
-        $parsedUrl = parse_url($url);
-        // 检查是否存在路径但不存在域名和协议
-        if (isset($parsedUrl['path']) && !isset($parsedUrl['host']) && !isset($parsedUrl['scheme'])) {
-            return true;
-        }
-        return false;
-    }
-
-
-    function getIcon(): \think\response\Json
-    {
-        $avatar = $this->request->post('avatar');
-        if ($avatar) {
-            $remote_avatar = $this->systemSetting("remote_avatar", "https://avatar.mtab.cc/6.x/bottts/png?seed=", true);
-            $str = $this->downloadFile($remote_avatar . $avatar, md5($avatar) . '.png');
-            return $this->success(['src' => $str]);
-        }
-        $url = $this->request->post('url', false);
-        $icon = "";
-        $cdn = $this->systemSetting('assets_host', '');
-        if ($url) {
-            $realUrl = $this->addHttpProtocolRemovePath($url);
-            $client = \Axios::http();
-            try {
-                $response = $client->get($realUrl, [
-                    'headers' => [
-                        "User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
-                    ]
-                ]);
-                $status = $response->getStatusCode();
-            } catch (\Exception $e) {
-                return $this->error('无法连接远程目标服务器');
-            }
-            if ($status == 200) {
-                $body = $response->getBody()->getContents();
-                $dom = new Dom();
-                $dom->loadStr($body);
-                $title = $dom->find('title');
-                if (count($title) > 0) {
-                    $title = $title->innerText;
-                }
-                try {
-                    $list = $dom->find('[rel="icon"]');
-                    if (count($list) == 0) {
-                        $list = $dom->find('[rel="shortcut icon"]');
-                    }
-                    if (count($list) == 0) {
-                        $list = $dom->find('[rel="Shortcut Icon"]');
-                    }
-                    if (count($list) > 0) {
-                        $href = $list->href;
-                        if ($this->hasOnlyPath($href)) {
-                            if ($href[0] != '/') {
-                                $href = "/" . $href;
-                            }
-                            $href = $realUrl . $href;
-                        }
-                        $href = $this->addHttpProtocol($href);
-                        $icon = $href;
-                        $response = \Axios::http()->get($icon, [
-                            'headers' => [
-                                'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
-                            ]
-                        ]);
-                        $contentType = $response->getHeader('content-type');
-                        $contentType = $contentType[0];
-                        if (preg_match('/(png|jpg|jpeg|x-icon|svg\+xml)$/', $contentType, $matches)) {
-                            $contentType = array(
-                                'png' => 'png',
-                                'jpg' => 'jpg',
-                                'jpeg' => 'jpeg',
-                                'x-icon' => 'ico',
-                                'svg+xml' => 'svg',
-                            );
-                            $fileFormat = $matches[1];
-                            $icon = $this->downloadFile($icon, md5($realUrl) . '.' . $contentType[$fileFormat]);
-                            if ($icon) {
-                                $icon = $cdn . $icon;
-                            }
-                        } else {
-                            $icon = '';
-                        }
-                    }
-                } catch (\ErrorException $e) {
-                }
-            }
-            if (strlen($icon) == 0) {
-                try {
-                    $client = \Axios::http();
-                    $response = $client->get($realUrl . '/favicon.ico');
-                    $status = $response->getStatusCode();
-                    if ($status == 200) {
-                        $icon = $realUrl . '/favicon.ico';
-                        $icon = $this->downloadFile($icon, md5($realUrl) . '.ico');
-                        if ($icon) {
-                            $icon = $cdn . $icon;
-                        }
-                    }
-                } catch (\Exception $e) {
-                }
-            }
-            if (strlen($icon) > 0) {
-                return $this->success(['src' => $icon, 'name' => $title]);
-            }
-        }
-        return $this->error('没有抓取到图标');
-    }
-
-    private function downloadFile($url, $name)
-    {
-        $user = $this->getUser();
-        $client = \Axios::http();
-        $path = '/images/' . date('Y/m/d/');
-        $remotePath = public_path() . $path;
-        $downloadPath = $remotePath . $name;
-        if (!is_dir($remotePath)) {
-            mkdir($remotePath, 0755, true);
-        }
-        try {
-            $response = $client->request('GET', $url, [
-                'sink' => $downloadPath,
-                'headers' => [
-                    'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
-                ]
-            ]);
-            FileModel::addFile($path . $name, $user['user_id'] ?? null);
-            return $path . $name;
-        } catch (RequestException $e) {
-        }
-        return false;
-    }
-
-    function renderIco(): \think\Response
-    {
-        $send = $this->request->get('seed');
-        $client = new Client();
-        $remote_avatar = $this->systemSetting('remote_avatar', 'https://avatar.mtab.cc/6.x/bottts/png?seed=', true);
-        $response = $client->get($remote_avatar . urlencode($send), [
-            'stream' => true,
-            'timeout' => 10,
-        ]);
-        return response($response->getBody(), 200, ['Content-Type' => 'image/png']);
-    }
-
-    function upload(): \think\response\Json
-    {
-        $user = $this->getUser();
-        if (!$user) {
-            if ($this->systemSetting('touristUpload') !== '1') {
-                //如果没有开启游客上传
-                return $this->error('管理员已关闭游客上传!请登录后使用');
-            }
-        }
-        $type = $this->request->header("Up-Type", '');
-        $file = $this->request->file('file');
-        if (empty($file)) {
-            return $this->error('not File');
-        }
-        $maxSize = (double)$this->systemSetting('upload_size', '2');
-        if ($file->getSize() > 1024 * 1024 * $maxSize) {
-            $limit = $maxSize < 1 ? ($maxSize * 1000) . 'KB' : ($maxSize) . 'MB';
-            return $this->error("文件最大$limit,请压缩后再试");
-        }
-        if (in_array(strtolower($file->getOriginalExtension()), ['png', 'jpg', 'jpeg', 'webp', 'ico', 'svg'])) {
-            // 验证文件并保存
-            try {
-                // 构建保存路径
-                $savePath = '/images/' . date('Y/m/d');
-                $hash = Str::random(32);
-                $fileName = $hash . '.' . $file->getOriginalExtension();
-                $filePath = Filesystem::disk('images')->putFileAs($savePath, $file, $fileName);
-                $minPath = '';
-                if ($type == 'icon' || $type == 'avatar') {
-                    $fp = joinPath(public_path(), $filePath);
-                    $image = new \ImageBack($fp);
-                    $image->resize(120, 0)->save($fp);
-                } else if ($type == 'AdminBackground') {
-                    $minPath = joinPath($savePath, "/min_$fileName");
-                    $fp = joinPath(public_path(), $filePath);
-                    $image = new \ImageBack($fp);
-                    $image->resize(400, 0)->save(joinPath(public_path(), $minPath));
-                    FileModel::addFile($minPath, $user['user_id'] ?? null);
-                }
-                $cdn = $this->systemSetting('assets_host', '/', true);
-                FileModel::addFile($filePath, $user['user_id'] ?? null);
-                return $this->success(['url' => $cdn . $filePath, "minUrl" => joinPath($cdn, $minPath)]);
-            } catch (\think\exception\ValidateException $e) {
-                return $this->error($e->getMessage());
-                // 验证失败,给出错误提示
-                // ...
-            }
-        }
-        return $this->error('上传失败');
-    }
-
-    function AdminUpload(): \think\response\Json
-    {
-        $user = $this->getAdmin();
-        $file = $this->request->file('file');
-        if (empty($file)) {
-            return $this->error('not File');
-        }
-        if ($file->getSize() > 1024 * 1024 * 5) {
-            return $this->error('max fileSize is 5M');
-        }
-        // 验证文件并保存
-        try {
-            // 构建保存路径
-            $savePath = '/images/' . date('Y/m/d');
-            $hash = Str::random(32);
-            $fileName = $hash . '.' . $file->getOriginalExtension();
-            $filePath = Filesystem::disk('images')->putFileAs($savePath, $file, $fileName);
-            $cdn = $this->systemSetting('assets_host', '/', true);
-            FileModel::addFile($filePath, $user['user_id'] ?? null);
-            return $this->success(['url' => $cdn . $filePath]);
-        } catch (\think\exception\ValidateException $e) {
-            // 验证失败,给出错误提示
-            // ...
-        }
-        return $this->error('上传失败');
-    }
-
-    function refresh(): \think\response\Json
-    {
-        $user = $this->getUser();
-        if ($user) {
-            $data = [];
-            $data['link_update_time'] = LinkModel::where("user_id", $user['user_id'])->value("update_time");
-            return $this->success("ok", $data);
-        }
-        return $this->error("not login");
-    }
-}
+<?php
+
+namespace app\controller;
+
+use app\BaseController;
+use app\model\FileModel;
+use app\model\LinkModel;
+use app\model\SettingModel;
+use GuzzleHttp\Client;
+use GuzzleHttp\Exception\RequestException;
+use PHPHtmlParser\Dom;
+use think\facade\Cache;
+use think\facade\Filesystem;
+use think\facade\View;
+use think\helper\Str;
+
+class Api extends BaseController
+{
+    public function site(): \think\response\Json
+    {
+        return $this->success("ok", [
+            'email' => $this->systemSetting('email', ''),
+            'qqGroup' => $this->systemSetting("qqGroup", ''),
+            'beianMps' => $this->systemSetting("beianMps", ''),
+            'copyright' => $this->systemSetting("copyright", ''),
+            "recordNumber" => $this->systemSetting("recordNumber", ''),
+            "mobileRecordNumber" => $this->systemSetting('mobileRecordNumber', '0'),
+            "auth" => $this->auth,
+            "logo" => $this->systemSetting('logo', ''),
+            "qq_login" => $this->systemSetting('qq_login', '0'),
+            "loginCloseRecordNumber" => $this->systemSetting('loginCloseRecordNumber', '0'),
+            "is_push_link_store" => $this->auth ? $this->systemSetting('is_push_link_store', '0') : '0',
+            "is_push_link_store_tips" => $this->systemSetting('is_push_link_store_tips', '0'),
+            "is_push_link_status" => $this->systemSetting("is_push_link_status", '0'),
+            'google_ext_link' => $this->systemSetting("google_ext_link", ''),
+            'edge_ext_link' => $this->systemSetting("edge_ext_link", ''),
+            'local_ext_link' => $this->systemSetting("local_ext_link", ''),
+            "customAbout" => $this->systemSetting("customAbout", '')
+        ]);
+    }
+
+    public function background(): \think\response\File
+    {
+        return download('static/background.jpeg', 'background.jpeg')->mimeType(\PluginStaticSystem::mimeType('static/background.jpeg'))->force(false)->expire(60 * 60 * 24 * 3);
+    }
+
+    //获取默认壁纸
+    function DefBg(): \think\response\Json
+    {
+        $config = $this->systemSetting('defaultTab', 'static/defaultTab.json', true);
+        if ($config) {
+            $fp = public_path() . $config;
+            if (file_exists($fp)) {
+                $file = file_get_contents($fp);
+                $json = json_decode($file, true);
+                if (isset($json['config']['theme']['backgroundImage'])) {
+                    $bg = $json['config']['theme']['backgroundImage'];
+                    $bgMime = $json['config']['theme']['backgroundMime'] ?? 0;
+                    return $this->success("ok", ['background' => $bg, "mime" => $bgMime]);
+                }
+            }
+        }
+        return $this->success("ok", ['background' => "static/background.jpeg", "mime" => 0]);
+    }
+
+    function globalNotify(): \think\response\Json
+    {
+        $info = SettingModel::Config("globalNotify", false);
+        if ($info) {
+            return $this->success('ok', $info);
+        }
+        return $this->error('empty');
+    }
+
+    //获取邮件验证码
+    function getMailCode(): \think\response\Json
+    {
+        $mail = $this->request->post("mail", false);
+        $code = rand(100000, 999999);
+        if ($mail) {
+            if (Cache::get('code' . $mail)) {
+                return $this->success("请勿频繁获取验证码");
+            }
+            $k = SettingModel::Config('smtp_code_template', false);
+            if ($k === false || mb_strlen(trim($k)) == 0) {
+                $k = '
+                        <div style="border:1px #DEDEDE solid;border-top:3px #009944 solid;padding:25px;background-color:#FFF;">
+                            <div style="font-size:17px;font-weight:bold;">邮箱验证码</div>
+                            <div style="font-size:14px;line-height:36px;padding-top:15px;padding-bottom:15px;">
+                                尊敬的用户,您好!<br>
+                                您的验证码是:<b style="color: #1e9fff">{$code}</b>。5分钟内有效,请尽快验证。
+                            </div>
+                            <div style="line-height:15px;">
+                                此致
+                            </div>
+                        </div>
+                ';
+            }
+            $html = View::display($k, ['time' => date('Y-m-d H:i:s'), 'code' => $code]);
+            try {
+                $status = \Mail::send($mail, $html);
+                if ($status) {
+                    Cache::set('code' . $mail, $code, 300);
+                    return $this->success('发送成功');
+                }
+            } catch (\Exception $e) {
+                return $this->error($e->getMessage());
+            }
+
+        }
+        return $this->error('发送失败');
+    }
+
+    private function addHttpProtocolRemovePath($url): string
+    {
+        // 解析URL
+        $parsedUrl = parse_url($url);
+        // 检查是否已经有协议,如果没有则添加http://
+        if (!isset($parsedUrl['scheme'])) {
+            // 检查是否以 // 开头,如果是,则转换为相对协议
+            if (isset($parsedUrl['host']) && strpos($url, '//') === 0) {
+                $url = 'http:' . $url;
+            } else {
+                $url = 'http://' . $url;
+            }
+        } else {
+            // 如果有协议但没有路径,保留原样
+            $url = $parsedUrl['scheme'] . '://';
+            // 如果有主机,则添加主机部分
+            if (isset($parsedUrl['host'])) {
+                $url .= $parsedUrl['host'];
+                // 如果有端口号,则添加端口号
+                if (isset($parsedUrl['port'])) {
+                    $url .= ':' . $parsedUrl['port'];
+                }
+            }
+        }
+        return $url;
+    }
+
+    private function addHttpProtocol($url)
+    {
+        // 检查是否已经有协议,如果没有则添加http://
+        if (!parse_url($url, PHP_URL_SCHEME)) {
+            // 检查是否以 // 开头,如果是,则转换为相对协议
+            if (strpos($url, '//') === 0) {
+                $url = 'https:' . $url;
+            } else {
+                $url = 'http://' . $url;
+            }
+        }
+        return $url;
+    }
+
+    private function hasOnlyPath($url): bool
+    {
+        $parsedUrl = parse_url($url);
+        // 检查是否存在路径但不存在域名和协议
+        if (isset($parsedUrl['path']) && !isset($parsedUrl['host']) && !isset($parsedUrl['scheme'])) {
+            return true;
+        }
+        return false;
+    }
+
+
+    function getIcon(): \think\response\Json
+    {
+        $avatar = $this->request->post('avatar');
+        if ($avatar) {
+            $remote_avatar = $this->systemSetting("remote_avatar", "https://avatar.mtab.cc/6.x/bottts/png?seed=", true);
+            $str = $this->downloadFile($remote_avatar . $avatar, md5($avatar) . '.png');
+            return $this->success(['src' => $str]);
+        }
+        $url = $this->request->post('url', false);
+        $icon = "";
+        $cdn = $this->systemSetting('assets_host', '');
+        if ($url) {
+            $realUrl = $this->addHttpProtocolRemovePath($url);
+            $client = \Axios::http();
+            try {
+                $response = $client->get($realUrl, [
+                    'headers' => [
+                        "User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
+                    ]
+                ]);
+                $status = $response->getStatusCode();
+            } catch (\Exception $e) {
+                return $this->error('无法连接远程目标服务器');
+            }
+            if ($status == 200) {
+                $body = $response->getBody()->getContents();
+                $dom = new Dom();
+                $dom->loadStr($body);
+                $title = $dom->find('title');
+                if (count($title) > 0) {
+                    $title = $title->innerText;
+                }
+                try {
+                    $list = $dom->find('[rel="icon"]');
+                    if (count($list) == 0) {
+                        $list = $dom->find('[rel="shortcut icon"]');
+                    }
+                    if (count($list) == 0) {
+                        $list = $dom->find('[rel="Shortcut Icon"]');
+                    }
+                    if (count($list) > 0) {
+                        $href = $list->href;
+                        if ($this->hasOnlyPath($href)) {
+                            if ($href[0] != '/') {
+                                $href = "/" . $href;
+                            }
+                            $href = $realUrl . $href;
+                        }
+                        $href = $this->addHttpProtocol($href);
+                        $icon = $href;
+                        $response = \Axios::http()->get($icon, [
+                            'headers' => [
+                                'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
+                            ]
+                        ]);
+                        $contentType = $response->getHeader('content-type');
+                        $contentType = $contentType[0];
+                        if (preg_match('/(png|jpg|jpeg|x-icon|svg\+xml)$/', $contentType, $matches)) {
+                            $contentType = array(
+                                'png' => 'png',
+                                'jpg' => 'jpg',
+                                'jpeg' => 'jpeg',
+                                'x-icon' => 'ico',
+                                'svg+xml' => 'svg',
+                            );
+                            $fileFormat = $matches[1];
+                            $icon = $this->downloadFile($icon, md5($realUrl) . '.' . $contentType[$fileFormat]);
+                            if ($icon) {
+                                $icon = $cdn . $icon;
+                            }
+                        } else {
+                            $icon = '';
+                        }
+                    }
+                } catch (\ErrorException $e) {
+                }
+            }
+            if (strlen($icon) == 0) {
+                try {
+                    $client = \Axios::http();
+                    $response = $client->get($realUrl . '/favicon.ico');
+                    $status = $response->getStatusCode();
+                    if ($status == 200) {
+                        $icon = $realUrl . '/favicon.ico';
+                        $icon = $this->downloadFile($icon, md5($realUrl) . '.ico');
+                        if ($icon) {
+                            $icon = $cdn . $icon;
+                        }
+                    }
+                } catch (\Exception $e) {
+                }
+            }
+            if (strlen($icon) > 0) {
+                return $this->success(['src' => $icon, 'name' => $title]);
+            }
+        }
+        return $this->error('没有抓取到图标');
+    }
+
+    private function downloadFile($url, $name)
+    {
+        $user = $this->getUser();
+        $client = \Axios::http();
+        $path = '/images/' . date('Y/m/d/');
+        $remotePath = public_path() . $path;
+        $downloadPath = $remotePath . $name;
+        if (!is_dir($remotePath)) {
+            mkdir($remotePath, 0755, true);
+        }
+        try {
+            $response = $client->request('GET', $url, [
+                'sink' => $downloadPath,
+                'headers' => [
+                    'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
+                ]
+            ]);
+            FileModel::addFile($path . $name, $user['user_id'] ?? null);
+            return $path . $name;
+        } catch (RequestException $e) {
+        }
+        return false;
+    }
+
+    function renderIco(): \think\Response
+    {
+        $send = $this->request->get('seed');
+        $client = new Client();
+        $remote_avatar = $this->systemSetting('remote_avatar', 'https://avatar.mtab.cc/6.x/bottts/png?seed=', true);
+        $response = $client->get($remote_avatar . urlencode($send), [
+            'stream' => true,
+            'timeout' => 10,
+        ]);
+        return response($response->getBody(), 200, ['Content-Type' => 'image/png']);
+    }
+
+    function upload(): \think\response\Json
+    {
+        $user = $this->getUser();
+        if (!$user) {
+            if ($this->systemSetting('touristUpload') !== '1') {
+                //如果没有开启游客上传
+                return $this->error('管理员已关闭游客上传!请登录后使用');
+            }
+        }
+        $type = $this->request->header("Up-Type", '');
+        $file = $this->request->file('file');
+        if (empty($file)) {
+            return $this->error('not File');
+        }
+        $maxSize = (double)$this->systemSetting('upload_size', '2');
+        if ($file->getSize() > 1024 * 1024 * $maxSize) {
+            $limit = $maxSize < 1 ? ($maxSize * 1000) . 'KB' : ($maxSize) . 'MB';
+            return $this->error("文件最大$limit,请压缩后再试");
+        }
+        if (in_array(strtolower($file->getOriginalExtension()), ['png', 'jpg', 'jpeg', 'webp', 'ico', 'svg'])) {
+            // 验证文件并保存
+            try {
+                // 构建保存路径
+                $savePath = '/images/' . date('Y/m/d');
+                $hash = Str::random(32);
+                $fileName = $hash . '.' . $file->getOriginalExtension();
+                $filePath = Filesystem::disk('images')->putFileAs($savePath, $file, $fileName);
+                $minPath = '';
+                if ($type == 'icon' || $type == 'avatar') {
+                    $fp = joinPath(public_path(), $filePath);
+                    $image = new \ImageBack($fp);
+                    $image->resize(144, 0)->save($fp);
+                } else if ($type == 'AdminBackground') {
+                    $minPath = joinPath($savePath, "/min_$fileName");
+                    $fp = joinPath(public_path(), $filePath);
+                    $image = new \ImageBack($fp);
+                    $image->resize(400, 0)->save(joinPath(public_path(), $minPath));
+                    FileModel::addFile($minPath, $user['user_id'] ?? null);
+                }
+                $cdn = $this->systemSetting('assets_host', '/', true);
+                FileModel::addFile($filePath, $user['user_id'] ?? null);
+                return $this->success(['url' => $cdn . $filePath, "minUrl" => joinPath($cdn, $minPath)]);
+            } catch (\think\exception\ValidateException $e) {
+                return $this->error($e->getMessage());
+                // 验证失败,给出错误提示
+                // ...
+            }
+        }
+        return $this->error('上传失败');
+    }
+
+    function AdminUpload(): \think\response\Json
+    {
+        $user = $this->getAdmin();
+        $file = $this->request->file('file');
+        if (empty($file)) {
+            return $this->error('not File');
+        }
+        if ($file->getSize() > 1024 * 1024 * 5) {
+            return $this->error('max fileSize is 5M');
+        }
+        // 验证文件并保存
+        try {
+            // 构建保存路径
+            $savePath = '/images/' . date('Y/m/d');
+            $hash = Str::random(32);
+            $fileName = $hash . '.' . $file->getOriginalExtension();
+            $filePath = Filesystem::disk('images')->putFileAs($savePath, $file, $fileName);
+            $cdn = $this->systemSetting('assets_host', '/', true);
+            FileModel::addFile($filePath, $user['user_id'] ?? null);
+            return $this->success(['url' => $cdn . $filePath]);
+        } catch (\think\exception\ValidateException $e) {
+            // 验证失败,给出错误提示
+            // ...
+        }
+        return $this->error('上传失败');
+    }
+
+    function refresh(): \think\response\Json
+    {
+        $user = $this->getUser();
+        if ($user) {
+            $data = [];
+            $data['link_update_time'] = LinkModel::where("user_id", $user['user_id'])->value("update_time");
+            return $this->success("ok", $data);
+        }
+        return $this->error("not login");
+    }
+}

+ 28 - 28
app/controller/Card.php

@@ -1,29 +1,29 @@
-<?php
-
-namespace app\controller;
-
-use app\BaseController;
-use app\model\CardModel;
-use app\model\SettingModel;
-
-class Card extends BaseController
-{
-    function index(): \think\response\Json
-    {
-        $apps = CardModel::where('status', 1)->select();
-        return $this->success('ok', $apps);
-    }
-
-    function install_num(): \think\response\Json
-    {
-        $id = $this->request->post('id', 0);
-        if ($id) {
-            $find = CardModel::where("id", $id)->find();
-            if ($find) {
-                $find->install_num += 1;
-                $find->save();
-            }
-        }
-        return $this->success('ok');
-    }
+<?php
+
+namespace app\controller;
+
+use app\BaseController;
+use app\model\CardModel;
+use app\model\SettingModel;
+
+class Card extends BaseController
+{
+    function index(): \think\response\Json
+    {
+        $apps = CardModel::where('status', 1)->select();
+        return $this->success('ok', $apps);
+    }
+
+    function install_num(): \think\response\Json
+    {
+        $id = $this->request->post('id', 0);
+        if ($id) {
+            $find = CardModel::where("id", $id)->find();
+            if ($find) {
+                $find->install_num += 1;
+                $find->save();
+            }
+        }
+        return $this->success('ok');
+    }
 }

+ 59 - 59
app/controller/Config.php

@@ -1,59 +1,59 @@
-<?php
-
-
-namespace app\controller;
-
-
-use app\BaseController;
-use app\model\ConfigModel;
-use app\model\SearchEngineModel;
-use app\model\UserSearchEngineModel;
-use stdClass;
-
-class Config extends BaseController
-{
-    public function update(): \think\response\Json
-    {
-        $user = $this->getUser(true);
-        if ($user) {
-            $config = $this->request->post("config", []);
-            if ($config) {
-                $is = ConfigModel::where("user_id", $user['user_id'])->find();
-                if ($is) {
-                    $is->config = $config;
-                    $is->save();
-                } else {
-                    ConfigModel::create(["user_id" => $user['user_id'], "config" => $config]);
-                }
-                return $this->success('ok');
-            }
-        }
-        return $this->error('保存失败');
-    }
-
-    public function get(): \think\response\Json
-    {
-        $user = $this->getUser();
-        if ($user) {
-            $data = ConfigModel::find($user['user_id']);
-            if ($data) {
-                return $this->success("ok", $data['config']);
-            }
-        }
-        $config = $this->systemSetting('defaultTab', 'static/defaultTab.json', true);
-        if ($config) {
-            $fp = public_path() . $config;
-            if (!file_exists($fp)) {
-                $fp = public_path() . 'static/defaultTab.json';
-            }
-            if (file_exists($fp)) {
-                $file = file_get_contents($fp);
-                $json = json_decode($file, true);
-                if (isset($json['config'])) {
-                    return $this->success('noLogin', $json['config']);
-                }
-            }
-        }
-        return $this->success('no Config', new stdClass());
-    }
-}
+<?php
+
+
+namespace app\controller;
+
+
+use app\BaseController;
+use app\model\ConfigModel;
+use app\model\SearchEngineModel;
+use app\model\UserSearchEngineModel;
+use stdClass;
+
+class Config extends BaseController
+{
+    public function update(): \think\response\Json
+    {
+        $user = $this->getUser(true);
+        if ($user) {
+            $config = $this->request->post("config", []);
+            if ($config) {
+                $is = ConfigModel::where("user_id", $user['user_id'])->find();
+                if ($is) {
+                    $is->config = $config;
+                    $is->save();
+                } else {
+                    ConfigModel::create(["user_id" => $user['user_id'], "config" => $config]);
+                }
+                return $this->success('ok');
+            }
+        }
+        return $this->error('保存失败');
+    }
+
+    public function get(): \think\response\Json
+    {
+        $user = $this->getUser();
+        if ($user) {
+            $data = ConfigModel::find($user['user_id']);
+            if ($data) {
+                return $this->success("ok", $data['config']);
+            }
+        }
+        $config = $this->systemSetting('defaultTab', 'static/defaultTab.json', true);
+        if ($config) {
+            $fp = public_path() . $config;
+            if (!file_exists($fp)) {
+                $fp = public_path() . 'static/defaultTab.json';
+            }
+            if (file_exists($fp)) {
+                $file = file_get_contents($fp);
+                $json = json_decode($file, true);
+                if (isset($json['config'])) {
+                    return $this->success('noLogin', $json['config']);
+                }
+            }
+        }
+        return $this->success('no Config', new stdClass());
+    }
+}

+ 88 - 88
app/controller/Index.php

@@ -1,88 +1,88 @@
-<?php
-
-namespace app\controller;
-
-use app\BaseController;
-use app\model\SettingModel;
-use think\facade\View;
-use think\Request;
-
-class Index extends BaseController
-{
-    function index(Request $request, $s = ''): string
-    {
-        $title = SettingModel::Config('title', 'Mtab书签');
-        View::assign("title", $title);
-        View::assign("keywords", SettingModel::Config('keywords', 'Mtab书签'));
-        View::assign("description", SettingModel::Config('description', 'Mtab书签'));
-        View::assign("version", app_version);
-        $customHead = SettingModel::Config('customHead', '');
-        if (SettingModel::Config('pwa', 0) == '1') {
-            $customHead .= '<link rel="manifest" href="/manifest.json">';
-        }
-        View::assign("customHead", $customHead);
-        View::assign("favicon", SettingModel::Config('logo', '/favicon.ico'));
-        return View::fetch("dist/index.html");
-    }
-    function all()
-    {
-        $app = app();
-        $ids = $this->request->post("ids", []);
-        $dt = [];
-        if (!in_array("link", $ids)) {
-            $dt['link'] = (new Link($app))->get()->getData()['data'];
-        }
-        if (!in_array("tabbar", $ids)) {
-            $dt['tabbar'] = (new Tabbar($app))->get()->getData()['data'];
-        }
-        if (!in_array("config", $ids)) {
-            $dt['config'] = (new Config($app))->get()->getData()['data'];
-        }
-        $dt['site'] = (new Api($app))->site()->getData()['data'];
-        return $this->success("ok", $dt);
-    }
-
-    function privacy(): string
-    {
-        $content = $this->systemSetting("privacy", "");
-        return View::fetch('/privacy', ['content' => $content,'title'=>$this->systemSetting("title",''), 'logo' => $this->systemSetting('logo', '')]);
-    }
-
-    function favicon()
-    {
-        //从配置中获取logo
-        $favicon = $this->systemSetting('logo');
-        $file = public_path() . $favicon;
-        if (file_exists($file) && is_file($file)) {
-            return download($file)->mimeType(\PluginStaticSystem::mimeType($file))->force(false)->expire(60 * 60 * 24);
-        }
-        return redirect("/static/mtab.png");
-    }
-
-    function manifest(): \think\response\Json
-    {
-        $manifest = [
-            'name' => SettingModel::Config('title', 'Mtab书签'),
-            'short_name' => SettingModel::Config('title', 'Mtab书签'),
-            'description' => SettingModel::Config('description', 'Mtab书签'),
-            'manifest_version' => 2,
-            'version' => app_version,
-            'theme_color' => SettingModel::Config('theme_color', '#141414'),
-            'icons' => [
-                [
-                    'src' => SettingModel::Config('logo', '/favicon.ico'),
-                    'sizes' => '144x144'
-                ]
-            ],
-            'display' => 'standalone',
-            'orientation' => 'portrait',
-            'start_url' => '/',
-            'scope' => '/',
-            'permissions' => [
-                'geolocation',
-                'notifications'
-            ]
-        ];
-        return json($manifest);
-    }
-}
+<?php
+
+namespace app\controller;
+
+use app\BaseController;
+use app\model\SettingModel;
+use think\facade\View;
+use think\Request;
+
+class Index extends BaseController
+{
+    function index(Request $request, $s = ''): string
+    {
+        $title = SettingModel::Config('title', 'Mtab书签');
+        View::assign("title", $title);
+        View::assign("keywords", SettingModel::Config('keywords', 'Mtab书签'));
+        View::assign("description", SettingModel::Config('description', 'Mtab书签'));
+        View::assign("version", app_version);
+        $customHead = SettingModel::Config('customHead', '');
+        if (SettingModel::Config('pwa', 0) == '1') {
+            $customHead .= '<link rel="manifest" href="/manifest.json">';
+        }
+        View::assign("customHead", $customHead);
+        View::assign("favicon", SettingModel::Config('logo', '/favicon.ico'));
+        return View::fetch("dist/index.html");
+    }
+    function all()
+    {
+        $app = app();
+        $ids = $this->request->post("ids", []);
+        $dt = [];
+        if (!in_array("link", $ids)) {
+            $dt['link'] = (new Link($app))->get()->getData()['data'];
+        }
+        if (!in_array("tabbar", $ids)) {
+            $dt['tabbar'] = (new Tabbar($app))->get()->getData()['data'];
+        }
+        if (!in_array("config", $ids)) {
+            $dt['config'] = (new Config($app))->get()->getData()['data'];
+        }
+        $dt['site'] = (new Api($app))->site()->getData()['data'];
+        return $this->success("ok", $dt);
+    }
+
+    function privacy(): string
+    {
+        $content = $this->systemSetting("privacy", "");
+        return View::fetch('/privacy', ['content' => $content,'title'=>$this->systemSetting("title",''), 'logo' => $this->systemSetting('logo', '')]);
+    }
+
+    function favicon()
+    {
+        //从配置中获取logo
+        $favicon = $this->systemSetting('logo');
+        $file = public_path() . $favicon;
+        if (file_exists($file) && is_file($file)) {
+            return download($file)->mimeType(\PluginStaticSystem::mimeType($file))->force(false)->expire(60 * 60 * 24);
+        }
+        return redirect("/static/mtab.png");
+    }
+
+    function manifest(): \think\response\Json
+    {
+        $manifest = [
+            'name' => SettingModel::Config('title', 'Mtab书签'),
+            'short_name' => SettingModel::Config('title', 'Mtab书签'),
+            'description' => SettingModel::Config('description', 'Mtab书签'),
+            'manifest_version' => 2,
+            'version' => app_version,
+            'theme_color' => SettingModel::Config('theme_color', '#141414'),
+            'icons' => [
+                [
+                    'src' => SettingModel::Config('logo', '/favicon.ico'),
+                    'sizes' => '144x144'
+                ]
+            ],
+            'display' => 'standalone',
+            'orientation' => 'portrait',
+            'start_url' => '/',
+            'scope' => '/',
+            'permissions' => [
+                'geolocation',
+                'notifications'
+            ]
+        ];
+        return json($manifest);
+    }
+}

+ 134 - 134
app/controller/Link.php

@@ -1,134 +1,134 @@
-<?php
-
-namespace app\controller;
-
-use app\BaseController;
-use app\model\ConfigModel;
-use app\model\HistoryModel;
-use app\model\LinkModel;
-use app\model\TabbarModel;
-use app\model\UserSearchEngineModel;
-use think\facade\Cache;
-
-class Link extends BaseController
-{
-    public function update(): \think\response\Json
-    {
-        $user = $this->getUser(true);
-        if ($user) {
-            $link = $this->request->post("link", []);
-            if ($link) {
-                $is = LinkModel::where("user_id", $user['user_id'])->find();
-                if ($is) {
-                    HistoryModel::create(['user_id' => $user['user_id'], 'link' => $is['link']]); //历史记录备份,用于用户误操作恢复用途
-                    $is->link = $link;
-                    $is->save();
-                } else {
-                    LinkModel::create(["user_id" => $user['user_id'], "link" => $link]);
-                }
-                Cache::delete("Link.{$user['user_id']}");
-                return $this->success('ok');
-            }
-        }
-        return $this->error('保存失败');
-    }
-
-    public function get(): \think\response\Json
-    {
-
-        $user = $this->getUser();
-        if ($user) {
-            $c = Cache::get("Link.{$user['user_id']}");
-            if ($c) {
-                return $this->success('ok', $c);
-            }
-            $data = LinkModel::where('user_id', $user['user_id'])->find();
-            if ($data) {
-                $c = $data['link'];
-                Cache::tag("linkCache")->set("Link.{$user['user_id']}", $c, 60 * 60);
-                return $this->success('ok', $c);
-            }
-        }
-        $config = $this->systemSetting("defaultTab", 'static/defaultTab.json', true);
-        if ($config) {
-            $fp = public_path() . $config;
-            if (!file_exists($fp)) {
-                $fp = public_path() . "static/defaultTab.json";
-            }
-            if (file_exists($fp)) {
-                $file = file_get_contents($fp);
-                $json = json_decode($file, true);
-                return $this->success('ok', $json['link'] ?? []);
-            }
-        }
-        return $this->success('ok', []);
-    }
-
-    function refreshWebAppCache(): \think\response\Json
-    {
-        $this->getAdmin();
-        Cache::tag('linkCache')->clear();
-        return $this->success('刷新完毕');
-    }
-
-    public function history(): \think\response\Json
-    {
-        $user = $this->getUser(true);
-        $history = HistoryModel::where("user_id", $user['user_id'])->whereNotNull("create_time")->field('id,user_id,create_time')->limit(100)->order("id", "desc")->select();
-        return $this->success('ok', $history);
-    }
-
-    public function delBack(): \think\response\Json
-    {
-        $user = $this->getUser(true);
-        $id = $this->request->post('id');
-        if ($id) {
-            $res = HistoryModel::where('id', $id)->where('user_id', $user['user_id'])->delete();
-            if ($res) {
-                return $this->success('ok');
-            }
-        }
-        return $this->error('备份节点不存在');
-    }
-
-    public function rollBack(): \think\response\Json
-    {
-        $user = $this->getUser(true);
-        $id = $this->request->post("id");
-        if ($id) {
-            $res = HistoryModel::where('id', $id)->where("user_id", $user['user_id'])->find();
-            if ($res) {
-                $link = $res['link'];
-                Cache::delete("Link.{$user['user_id']}");
-                LinkModel::update(["user_id" => $user['user_id'], "link" => $link]);
-                return $this->success('ok');
-            }
-        }
-        return $this->error("备份节点不存在");
-    }
-
-    public function reset(): \think\response\Json
-    {
-        $user = $this->getUser();
-        if ($user) {
-            $data = LinkModel::find($user['user_id']);
-            if ($data) {
-                Cache::delete("Link.{$user['user_id']}");
-                $data->delete();
-            }
-            $data = TabbarModel::find($user['user_id']);
-            if ($data) {
-                $data->delete();
-            }
-            $data = ConfigModel::find($user['user_id']);
-            if ($data) {
-                $data->delete();
-            }
-            $data = UserSearchEngineModel::find($user['user_id']);
-            if ($data) {
-                $data->delete();
-            }
-        }
-        return $this->success('ok');
-    }
-}
+<?php
+
+namespace app\controller;
+
+use app\BaseController;
+use app\model\ConfigModel;
+use app\model\HistoryModel;
+use app\model\LinkModel;
+use app\model\TabbarModel;
+use app\model\UserSearchEngineModel;
+use think\facade\Cache;
+
+class Link extends BaseController
+{
+    public function update(): \think\response\Json
+    {
+        $user = $this->getUser(true);
+        if ($user) {
+            $link = $this->request->post("link", []);
+            if ($link) {
+                $is = LinkModel::where("user_id", $user['user_id'])->find();
+                if ($is) {
+                    HistoryModel::create(['user_id' => $user['user_id'], 'link' => $is['link']]); //历史记录备份,用于用户误操作恢复用途
+                    $is->link = $link;
+                    $is->save();
+                } else {
+                    LinkModel::create(["user_id" => $user['user_id'], "link" => $link]);
+                }
+                Cache::delete("Link.{$user['user_id']}");
+                return $this->success('ok');
+            }
+        }
+        return $this->error('保存失败');
+    }
+
+    public function get(): \think\response\Json
+    {
+
+        $user = $this->getUser();
+        if ($user) {
+            $c = Cache::get("Link.{$user['user_id']}");
+            if ($c) {
+                return $this->success('ok', $c);
+            }
+            $data = LinkModel::where('user_id', $user['user_id'])->find();
+            if ($data) {
+                $c = $data['link'];
+                Cache::tag("linkCache")->set("Link.{$user['user_id']}", $c, 60 * 60);
+                return $this->success('ok', $c);
+            }
+        }
+        $config = $this->systemSetting("defaultTab", 'static/defaultTab.json', true);
+        if ($config) {
+            $fp = public_path() . $config;
+            if (!file_exists($fp)) {
+                $fp = public_path() . "static/defaultTab.json";
+            }
+            if (file_exists($fp)) {
+                $file = file_get_contents($fp);
+                $json = json_decode($file, true);
+                return $this->success('ok', $json['link'] ?? []);
+            }
+        }
+        return $this->success('ok', []);
+    }
+
+    function refreshWebAppCache(): \think\response\Json
+    {
+        $this->getAdmin();
+        Cache::tag('linkCache')->clear();
+        return $this->success('刷新完毕');
+    }
+
+    public function history(): \think\response\Json
+    {
+        $user = $this->getUser(true);
+        $history = HistoryModel::where("user_id", $user['user_id'])->whereNotNull("create_time")->field('id,user_id,create_time')->limit(100)->order("id", "desc")->select();
+        return $this->success('ok', $history);
+    }
+
+    public function delBack(): \think\response\Json
+    {
+        $user = $this->getUser(true);
+        $id = $this->request->post('id');
+        if ($id) {
+            $res = HistoryModel::where('id', $id)->where('user_id', $user['user_id'])->delete();
+            if ($res) {
+                return $this->success('ok');
+            }
+        }
+        return $this->error('备份节点不存在');
+    }
+
+    public function rollBack(): \think\response\Json
+    {
+        $user = $this->getUser(true);
+        $id = $this->request->post("id");
+        if ($id) {
+            $res = HistoryModel::where('id', $id)->where("user_id", $user['user_id'])->find();
+            if ($res) {
+                $link = $res['link'];
+                Cache::delete("Link.{$user['user_id']}");
+                LinkModel::update(["user_id" => $user['user_id'], "link" => $link]);
+                return $this->success('ok');
+            }
+        }
+        return $this->error("备份节点不存在");
+    }
+
+    public function reset(): \think\response\Json
+    {
+        $user = $this->getUser();
+        if ($user) {
+            $data = LinkModel::find($user['user_id']);
+            if ($data) {
+                Cache::delete("Link.{$user['user_id']}");
+                $data->delete();
+            }
+            $data = TabbarModel::find($user['user_id']);
+            if ($data) {
+                $data->delete();
+            }
+            $data = ConfigModel::find($user['user_id']);
+            if ($data) {
+                $data->delete();
+            }
+            $data = UserSearchEngineModel::find($user['user_id']);
+            if ($data) {
+                $data->delete();
+            }
+        }
+        return $this->success('ok');
+    }
+}

+ 282 - 282
app/controller/LinkStore.php

@@ -1,282 +1,282 @@
-<?php
-
-namespace app\controller;
-
-use app\BaseController;
-use app\model\ConfigModel;
-use app\model\FileModel;
-use app\model\LinkFolderModel;
-use app\model\LinkStoreModel;
-use think\facade\Db;
-
-class LinkStore extends BaseController
-{
-
-    public function list(): \think\response\Json
-    {
-        $limit = $this->request->post('limit', 15);
-        $name = $this->request->post('name', false);
-        $area = $this->request->post('area', false);
-        $sql = [];
-        if ($name) {
-            $sql[] = ['name|tips', 'like', "%" . $name . "%"];
-        }
-        $list = LinkStoreModel::where($sql)->where('status', 1)->order('hot', "desc")->withoutField('user_id');
-        //area需要使用find_in_set来匹配
-        if ($area && $area != 0) {
-            $list = $list->whereRaw("find_in_set('$area',area)");
-        }
-        $list = $list->order("create_time", 'desc')->paginate($limit);
-        return $this->success('ok', $list);
-    }
-
-    public function ListManager(): \think\response\Json
-    {
-        $admin = $this->getAdmin();
-        $limit = $this->request->post('limit', 15);
-        $name = $this->request->post('search.name', false);
-        $area = $this->request->post('search.area', false);
-        $sql = [];
-        if ($name) {
-            $sql[] = ['name|tips', 'like', '%' . $name . '%'];
-        }
-        $list = LinkStoreModel::with(['userInfo'])->where($sql);
-        //area需要使用find_in_set来匹配
-        if ($area && $area != '全部') {
-            $list = $list->whereRaw("find_in_set('$area',area)");
-        }
-        $list = $list->order($this->request->post('sort.prop', 'id'), $this->request->post('sort.order', 'asc'))->paginate($limit);
-        return json(["msg" => "ok", 'data' => $list, 'auth' => $this->auth]);
-    }
-
-    function getFolder(): \think\response\Json
-    {
-        return $this->success("ok", LinkFolderModel::order("sort", "desc")->select());
-    }
-
-    private function update(): \think\response\Json
-    {
-        is_demo_mode(true);
-        $admin = $this->getAdmin();
-        $data = $this->request->post("form");
-        try {
-            unset($data['userInfo']);
-        } catch (\Exception $exception) {
-
-        }
-        $info = LinkStoreModel::where("id", $data['id'])->withoutField(['userInfo'])->update($data);
-        return $this->success('修改成功', $info);
-    }
-
-    function addPublic(): \think\response\Json
-    {
-        $user = $this->getAdmin();
-        $info = $this->request->post();
-        $info['create_time'] = date("Y-m-d H:i:s");
-        $info['domain'] = $this->getDomain($info['url']);
-        $info['src'] = $this->downloadLogo($info['src']);
-        FileModel::addFile($info['src'],$user['id']);
-        if (isset($info['id'])) {
-            unset($info['id']);
-        }
-        (new \app\model\LinkStoreModel)->allowField(["name", "src", "url", "domain", "create_time", "tips", "app"])->insert($info);
-        return $this->success('添加成功', $info);
-    }
-
-    private function downloadLogo($src): string
-    {
-        $f = file_get_contents($src);
-        $pathinfo = pathinfo($src);
-        try {
-            mkdir(public_path() . 'images/' . date("Y/m/d"), 0755, true);
-        } catch (\Throwable $th) {
-            //throw $th;
-        }
-        $filePath = '/images/' . date("Y/m/d") . '/' . md5($src) . '.' . $pathinfo['extension'];
-        file_put_contents(joinPath(public_path(), $filePath), $f);
-        return $filePath;
-    }
-
-    function push(): \think\response\Json
-    {
-        $user = $this->getUser(true);
-        $data = $this->request->post();
-        $info = [];
-        if ($data) {
-            if (isset($data['name'])) {
-                $info['name'] = $data['name'];
-            }
-            if (isset($data['src'])) {
-                $info['src'] = $data['src'];
-            }
-            if (isset($data['url']) && mb_strlen($data['url']) > 2) {
-                $info['url'] = $data['url'];
-            } else {
-                return $this->error('推送失败');
-            }
-            if (isset($data['bgColor'])) {
-                $info['bgColor'] = $data['bgColor'];
-            }
-            if (isset($data['app'])) {
-                $info['app'] = $data['app'];
-            }
-            if (isset($data['tips'])) {
-                $info['tips'] = $data['tips'];
-            }
-            $info['domain'] = $this->getDomain($info['url']);
-            $info['user_id'] = $user['user_id'];
-            $info['status'] = 0;
-            $info['create_time'] = date('Y-m-d H:i:s');
-            if (!LinkStoreModel::where("url", $info['url'])->find()) {
-                LinkStoreModel::create($info);
-                return $this->success('推送完毕');
-            }
-        }
-        return $this->error('推送失败');
-    }
-
-    private function getDomain($url)
-    {
-        $domain = $url;
-        $p = parse_url($domain);
-        if (isset($p['host'])) {
-            return $p['host'];
-        }
-        if (isset($p['path'])) {
-            return $p['path'];
-        }
-        return '';
-    }
-
-    public function add(): \think\response\Json
-    {
-        $admin = $this->getAdmin();
-        is_demo_mode(true);
-        $data = $this->request->post('form', []);
-        if ($data) {
-            try {
-                unset($data['userInfo']);
-            } catch (\Exception $exception) {
-
-            }
-            if (isset($data['id']) && $data['id']) { //更新
-                return $this->update();
-            } else {
-                $data['create_time'] = date("Y-m-d H:i:s");
-                $info = (new \app\model\LinkStoreModel)->insert($data);
-                return $this->success('添加成功', $info);
-            }
-        }
-        return $this->error('缺少数据');
-    }
-
-    public function getIcon(): \think\response\Json
-    {
-        $url = $this->request->post('url', false);
-        if ($url) {
-            if (mb_substr($url, 0, 4) == 'tab:') {
-            } else {
-                if (mb_substr($url, 0, 4) != 'http') {
-                    $url = 'https://' . $url;
-                }
-                $url = parse_url($url);
-                $url = $url['host'];
-            }
-            $data = LinkStoreModel::whereRaw("FIND_IN_SET('$url',domain)")->find();
-            if ($data) {
-                return $this->success('ok', $data);
-            }
-        }
-        return $this->error('no', '未查询到相关信息');
-    }
-
-    function install_num(): \think\response\Json
-    {
-        $id = $this->request->post('id', false);
-        //给标签+=1
-        $res = Db::table("linkstore")->where('id', $id)->inc('install_num')->update();
-        if ($res) {
-            return $this->success('ok');
-        }
-        return $this->error('fail');
-    }
-
-    function createFolder(): \think\response\Json
-    {
-        is_demo_mode(true);
-        $type = $this->request->post('type', false);
-        $this->getAdmin();
-        if ($type === 'edit') {
-            $form = $this->request->post('info');
-            $id = $this->request->post('info.id', false);
-            if ($id && $id > 0) {
-                $model = LinkFolderModel::find($id);
-                $model->update($form);
-            } else {
-                $model = new LinkFolderModel();
-                $model->insert($form);
-            }
-        } else if ($type === 'del') {
-            $id = $this->request->post('id');
-            $result = LinkFolderModel::where("id", $id)->find();
-            if ($result) {
-                $result->delete();
-                Db::query(
-                    "UPDATE linkstore
-                     SET area = TRIM(BOTH ',' FROM REPLACE(CONCAT(',', area, ','), ',$id,', ','))
-                     WHERE FIND_IN_SET('$id', area) > 0;"
-                );
-            }
-        }
-        return $this->success('处理完毕!');
-    }
-
-    function moveFolder(): \think\response\Json
-    {
-        is_demo_mode(true);
-        $this->getAdmin();
-        $ids = $this->request->post('link', []);
-        $area = $this->request->post('area', '');
-        LinkStoreModel::where('id', 'in', $ids)->update(['area' => $area]);
-        return $this->success('处理完毕!');
-    }
-
-    function sortFolder()
-    {
-        $sort = (array)$this->request->post();
-        foreach ($sort as $key => $value) {
-            LinkFolderModel::where("id", $value['id'])->update(['sort' => $value['sort']]);
-        }
-        return $this->success("ok");
-    }
-
-    public function del(): \think\response\Json
-    {
-        is_demo_mode(true);
-        $this->getAdmin();
-        $ids = $this->request->post('ids', []);
-        LinkStoreModel::where("id", 'in', $ids)->delete();
-        return $this->success('删除成功');
-    }
-
-    function domains(): \think\response\Json
-    {
-        $domains = $this->request->post('domains', []);
-        $tmp = [];
-        foreach (LinkStoreModel::where('status', 1)->cursor() as $value) {
-            $d = $this->getDomain($value['url']);
-            if (in_array($d, $domains)) {
-                $tmp[$d] = ["domain" => $d, "name" => $value['name'], "src" => $value['src'], "bgColor" => $value['bgColor'],'tips'=>$value['tips']];
-            } else if ($value['domain']) {
-                $r = explode(",", $value['domain']);
-                foreach ($r as $v) {
-                    if (in_array($v, $domains)) {
-                        $tmp[$v] = ['domain' => $v, 'name' => $value['name'], 'src' => $value['src'], 'bgColor' => $value['bgColor'],'tips'=>$value['tips']];
-                        break;
-                    }
-                }
-            }
-        }
-        return $this->success('ok', $tmp);
-    }
-}
+<?php
+
+namespace app\controller;
+
+use app\BaseController;
+use app\model\ConfigModel;
+use app\model\FileModel;
+use app\model\LinkFolderModel;
+use app\model\LinkStoreModel;
+use think\facade\Db;
+
+class LinkStore extends BaseController
+{
+
+    public function list(): \think\response\Json
+    {
+        $limit = $this->request->post('limit', 15);
+        $name = $this->request->post('name', false);
+        $area = $this->request->post('area', false);
+        $sql = [];
+        if ($name) {
+            $sql[] = ['name|tips|url', 'like', "%" . $name . "%"];
+        }
+        $list = LinkStoreModel::where($sql)->where('status', 1)->order('hot', "desc")->withoutField('user_id');
+        //area需要使用find_in_set来匹配
+        if ($area && $area != 0) {
+            $list = $list->whereRaw("find_in_set('$area',area)");
+        }
+        $list = $list->order("create_time", 'desc')->paginate($limit);
+        return $this->success('ok', $list);
+    }
+
+    public function ListManager(): \think\response\Json
+    {
+        $admin = $this->getAdmin();
+        $limit = $this->request->post('limit', 15);
+        $name = $this->request->post('search.name', false);
+        $area = $this->request->post('search.area', false);
+        $sql = [];
+        if ($name) {
+            $sql[] = ['name|tips', 'like', '%' . $name . '%'];
+        }
+        $list = LinkStoreModel::with(['userInfo'])->where($sql);
+        //area需要使用find_in_set来匹配
+        if ($area && $area != '全部') {
+            $list = $list->whereRaw("find_in_set('$area',area)");
+        }
+        $list = $list->order($this->request->post('sort.prop', 'id'), $this->request->post('sort.order', 'asc'))->paginate($limit);
+        return json(["msg" => "ok", 'data' => $list, 'auth' => $this->auth]);
+    }
+
+    function getFolder(): \think\response\Json
+    {
+        return $this->success("ok", LinkFolderModel::order("sort", "desc")->select());
+    }
+
+    private function update(): \think\response\Json
+    {
+        is_demo_mode(true);
+        $admin = $this->getAdmin();
+        $data = $this->request->post("form");
+        try {
+            unset($data['userInfo']);
+        } catch (\Exception $exception) {
+
+        }
+        $info = LinkStoreModel::where("id", $data['id'])->withoutField(['userInfo'])->update($data);
+        return $this->success('修改成功', $info);
+    }
+
+    function addPublic(): \think\response\Json
+    {
+        $user = $this->getAdmin();
+        $info = $this->request->post();
+        $info['create_time'] = date("Y-m-d H:i:s");
+        $info['domain'] = $this->getDomain($info['url']);
+        $info['src'] = $this->downloadLogo($info['src']);
+        FileModel::addFile($info['src'],$user['id']);
+        if (isset($info['id'])) {
+            unset($info['id']);
+        }
+        (new \app\model\LinkStoreModel)->allowField(["name", "src", "url", "domain", "create_time", "tips", "app"])->insert($info);
+        return $this->success('添加成功', $info);
+    }
+
+    private function downloadLogo($src): string
+    {
+        $f = file_get_contents($src);
+        $pathinfo = pathinfo($src);
+        try {
+            mkdir(public_path() . 'images/' . date("Y/m/d"), 0755, true);
+        } catch (\Throwable $th) {
+            //throw $th;
+        }
+        $filePath = '/images/' . date("Y/m/d") . '/' . md5($src) . '.' . $pathinfo['extension'];
+        file_put_contents(joinPath(public_path(), $filePath), $f);
+        return $filePath;
+    }
+
+    function push(): \think\response\Json
+    {
+        $user = $this->getUser(true);
+        $data = $this->request->post();
+        $info = [];
+        if ($data) {
+            if (isset($data['name'])) {
+                $info['name'] = $data['name'];
+            }
+            if (isset($data['src'])) {
+                $info['src'] = $data['src'];
+            }
+            if (isset($data['url']) && mb_strlen($data['url']) > 2) {
+                $info['url'] = $data['url'];
+            } else {
+                return $this->error('推送失败');
+            }
+            if (isset($data['bgColor'])) {
+                $info['bgColor'] = $data['bgColor'];
+            }
+            if (isset($data['app'])) {
+                $info['app'] = $data['app'];
+            }
+            if (isset($data['tips'])) {
+                $info['tips'] = $data['tips'];
+            }
+            $info['domain'] = $this->getDomain($info['url']);
+            $info['user_id'] = $user['user_id'];
+            $info['status'] = 0;
+            $info['create_time'] = date('Y-m-d H:i:s');
+            if (!LinkStoreModel::where("url", $info['url'])->find()) {
+                LinkStoreModel::create($info);
+                return $this->success('推送完毕');
+            }
+        }
+        return $this->error('推送失败');
+    }
+
+    private function getDomain($url)
+    {
+        $domain = $url;
+        $p = parse_url($domain);
+        if (isset($p['host'])) {
+            return $p['host'];
+        }
+        if (isset($p['path'])) {
+            return $p['path'];
+        }
+        return '';
+    }
+
+    public function add(): \think\response\Json
+    {
+        $admin = $this->getAdmin();
+        is_demo_mode(true);
+        $data = $this->request->post('form', []);
+        if ($data) {
+            try {
+                unset($data['userInfo']);
+            } catch (\Exception $exception) {
+
+            }
+            if (isset($data['id']) && $data['id']) { //更新
+                return $this->update();
+            } else {
+                $data['create_time'] = date("Y-m-d H:i:s");
+                $info = (new \app\model\LinkStoreModel)->insert($data);
+                return $this->success('添加成功', $info);
+            }
+        }
+        return $this->error('缺少数据');
+    }
+
+    public function getIcon(): \think\response\Json
+    {
+        $url = $this->request->post('url', false);
+        if ($url) {
+            if (mb_substr($url, 0, 4) == 'tab:') {
+            } else {
+                if (mb_substr($url, 0, 4) != 'http') {
+                    $url = 'https://' . $url;
+                }
+                $url = parse_url($url);
+                $url = $url['host'];
+            }
+            $data = LinkStoreModel::whereRaw("FIND_IN_SET('$url',domain)")->find();
+            if ($data) {
+                return $this->success('ok', $data);
+            }
+        }
+        return $this->error('no', '未查询到相关信息');
+    }
+
+    function install_num(): \think\response\Json
+    {
+        $id = $this->request->post('id', false);
+        //给标签+=1
+        $res = Db::table("linkstore")->where('id', $id)->inc('install_num')->update();
+        if ($res) {
+            return $this->success('ok');
+        }
+        return $this->error('fail');
+    }
+
+    function createFolder(): \think\response\Json
+    {
+        is_demo_mode(true);
+        $type = $this->request->post('type', false);
+        $this->getAdmin();
+        if ($type === 'edit') {
+            $form = $this->request->post('info');
+            $id = $this->request->post('info.id', false);
+            if ($id && $id > 0) {
+                $model = LinkFolderModel::find($id);
+                $model->update($form);
+            } else {
+                $model = new LinkFolderModel();
+                $model->insert($form);
+            }
+        } else if ($type === 'del') {
+            $id = $this->request->post('id');
+            $result = LinkFolderModel::where("id", $id)->find();
+            if ($result) {
+                $result->delete();
+                Db::query(
+                    "UPDATE linkstore
+                     SET area = TRIM(BOTH ',' FROM REPLACE(CONCAT(',', area, ','), ',$id,', ','))
+                     WHERE FIND_IN_SET('$id', area) > 0;"
+                );
+            }
+        }
+        return $this->success('处理完毕!');
+    }
+
+    function moveFolder(): \think\response\Json
+    {
+        is_demo_mode(true);
+        $this->getAdmin();
+        $ids = $this->request->post('link', []);
+        $area = $this->request->post('area', '');
+        LinkStoreModel::where('id', 'in', $ids)->update(['area' => $area]);
+        return $this->success('处理完毕!');
+    }
+
+    function sortFolder()
+    {
+        $sort = (array)$this->request->post();
+        foreach ($sort as $key => $value) {
+            LinkFolderModel::where("id", $value['id'])->update(['sort' => $value['sort']]);
+        }
+        return $this->success("ok");
+    }
+
+    public function del(): \think\response\Json
+    {
+        is_demo_mode(true);
+        $this->getAdmin();
+        $ids = $this->request->post('ids', []);
+        LinkStoreModel::where("id", 'in', $ids)->delete();
+        return $this->success('删除成功');
+    }
+
+    function domains(): \think\response\Json
+    {
+        $domains = $this->request->post('domains', []);
+        $tmp = [];
+        foreach (LinkStoreModel::where('status', 1)->cursor() as $value) {
+            $d = $this->getDomain($value['url']);
+            if (in_array($d, $domains)) {
+                $tmp[$d] = ["domain" => $d, "name" => $value['name'], "src" => $value['src'], "bgColor" => $value['bgColor'],'tips'=>$value['tips']];
+            } else if ($value['domain']) {
+                $r = explode(",", $value['domain']);
+                foreach ($r as $v) {
+                    if (in_array($v, $domains)) {
+                        $tmp[$v] = ['domain' => $v, 'name' => $value['name'], 'src' => $value['src'], 'bgColor' => $value['bgColor'],'tips'=>$value['tips']];
+                        break;
+                    }
+                }
+            }
+        }
+        return $this->success('ok', $tmp);
+    }
+}

+ 110 - 110
app/controller/Note.php

@@ -1,111 +1,111 @@
-<?php
-
-
-namespace app\controller;
-
-
-use app\BaseController;
-use think\Exception;
-
-class Note extends BaseController
-{
-    //获取列表
-    public function get(): \think\response\Json
-    {
-        $user = $this->getUser();
-        $sort = $this->request->get('sort', 'desc');
-        $limit = $this->request->get('limit', 999999);
-        if (!$user) {
-            return $this->success('', []);
-        }
-        $data = (new \app\model\NoteModel)->where("user_id", $user['user_id'])->field('user_id,id,title,create_time,update_time,weight')->order('id', $sort)->limit($limit)->select();
-        return $this->success('ok', $data);
-    }
-
-    //获取文本
-    public function getText(): \think\Response
-    {
-        $user = $this->getUser(true);
-        $id = $this->request->get('id');
-        $data = (new \app\model\NoteModel)->where("user_id", $user['user_id'])->field("text,id")->where('id', $id)->find();
-        try {
-            return response($data['text']);
-        } catch (Exception $e) {
-            return response('');
-        }
-    }
-
-    function setWeight(): \think\response\Json
-    {
-        $user = $this->getUser(true);
-        $weight = $this->request->post('weight', 0);
-        $id = $this->request->post('id', false);
-        if ($id) {
-            $data = array(
-                'weight' => $weight,
-                'update_time' => date('Y-m-d H:i:s'),
-            );
-            (new \app\model\NoteModel)->where('id', $id)->where('user_id', $user['user_id'])->update($data);
-        }
-        return $this->success("ok");
-    }
-
-    //删除
-    public function del(): \think\response\Json
-    {
-        $user = $this->getUser(true);
-        $id = $this->request->get('id');
-        $data = (new \app\model\NoteModel)->where("user_id", $user['user_id'])->where('id', $id)->delete();
-        return $this->success('删除成功', $data);
-    }
-
-    //添加内容
-    public function add(): \think\response\Json
-    {
-        $user = $this->getUser(true);
-        $title = $this->request->post('title', '');
-        $text = $this->request->post('text', '');
-        $id = $this->request->post('id', false);
-        if ($id != '') {
-            return $this->update();
-        }
-        $data = array(
-            "user_id" => $user['user_id'],
-            "text" => $text,
-            "title" => $title,
-            'weight' => $this->request->post("weight", 0),
-            "create_time" => date("Y-m-d H:i:s"),
-            "update_time" => date("Y-m-d H:i:s"),
-        );
-        $status = (new \app\model\NoteModel)->insertGetId($data);
-        if ($status) {
-            $data['id'] = $status;
-            return $this->success("创建成功", $data);
-        }
-        return $this->error('失败');
-    }
-
-    //更新内容
-    public function update(): \think\response\Json
-    {
-        $user = $this->getUser(true);
-        $id = $this->request->post('id', false);
-        if (!$id) {
-            return $this->error('no');
-        }
-        $title = $this->request->post('title', '');
-        $text = $this->request->post('text', '');
-        $data = array(
-            "text" => $text,
-            "title" => $title,
-            'weight' => $this->request->post('weight', 0),
-            "update_time" => date("Y-m-d H:i:s"),
-        );
-        $status = (new \app\model\NoteModel)->where("id", $id)->where('user_id', $user['user_id'])->update($data);
-        if ($status) {
-            $data['id'] = $id;
-            return $this->success("修改", $data);
-        }
-        return $this->error('失败');
-    }
+<?php
+
+
+namespace app\controller;
+
+
+use app\BaseController;
+use think\Exception;
+
+class Note extends BaseController
+{
+    //获取列表
+    public function get(): \think\response\Json
+    {
+        $user = $this->getUser();
+        $sort = $this->request->get('sort', 'desc');
+        $limit = $this->request->get('limit', 999999);
+        if (!$user) {
+            return $this->success('', []);
+        }
+        $data = (new \app\model\NoteModel)->where("user_id", $user['user_id'])->field('user_id,id,title,create_time,update_time,weight')->order('id', $sort)->limit($limit)->select();
+        return $this->success('ok', $data);
+    }
+
+    //获取文本
+    public function getText(): \think\Response
+    {
+        $user = $this->getUser(true);
+        $id = $this->request->get('id');
+        $data = (new \app\model\NoteModel)->where("user_id", $user['user_id'])->field("text,id")->where('id', $id)->find();
+        try {
+            return response($data['text']);
+        } catch (Exception $e) {
+            return response('');
+        }
+    }
+
+    function setWeight(): \think\response\Json
+    {
+        $user = $this->getUser(true);
+        $weight = $this->request->post('weight', 0);
+        $id = $this->request->post('id', false);
+        if ($id) {
+            $data = array(
+                'weight' => $weight,
+                'update_time' => date('Y-m-d H:i:s'),
+            );
+            (new \app\model\NoteModel)->where('id', $id)->where('user_id', $user['user_id'])->update($data);
+        }
+        return $this->success("ok");
+    }
+
+    //删除
+    public function del(): \think\response\Json
+    {
+        $user = $this->getUser(true);
+        $id = $this->request->get('id');
+        $data = (new \app\model\NoteModel)->where("user_id", $user['user_id'])->where('id', $id)->delete();
+        return $this->success('删除成功', $data);
+    }
+
+    //添加内容
+    public function add(): \think\response\Json
+    {
+        $user = $this->getUser(true);
+        $title = $this->request->post('title', '');
+        $text = $this->request->post('text', '');
+        $id = $this->request->post('id', false);
+        if ($id != '') {
+            return $this->update();
+        }
+        $data = array(
+            "user_id" => $user['user_id'],
+            "text" => $text,
+            "title" => $title,
+            'weight' => $this->request->post("weight", 0),
+            "create_time" => date("Y-m-d H:i:s"),
+            "update_time" => date("Y-m-d H:i:s"),
+        );
+        $status = (new \app\model\NoteModel)->insertGetId($data);
+        if ($status) {
+            $data['id'] = $status;
+            return $this->success("创建成功", $data);
+        }
+        return $this->error('失败');
+    }
+
+    //更新内容
+    public function update(): \think\response\Json
+    {
+        $user = $this->getUser(true);
+        $id = $this->request->post('id', false);
+        if (!$id) {
+            return $this->error('no');
+        }
+        $title = $this->request->post('title', '');
+        $text = $this->request->post('text', '');
+        $data = array(
+            "text" => $text,
+            "title" => $title,
+            'weight' => $this->request->post('weight', 0),
+            "update_time" => date("Y-m-d H:i:s"),
+        );
+        $status = (new \app\model\NoteModel)->where("id", $id)->where('user_id', $user['user_id'])->update($data);
+        if ($status) {
+            $data['id'] = $id;
+            return $this->success("修改", $data);
+        }
+        return $this->error('失败');
+    }
 }

+ 102 - 102
app/controller/SearchEngine.php

@@ -1,102 +1,102 @@
-<?php
-
-namespace app\controller;
-
-use app\BaseController;
-use app\model\SearchEngineModel;
-use app\model\UserSearchEngineModel;
-use think\facade\Cache;
-
-class SearchEngine extends BaseController
-{
-    function index(): \think\response\Json
-    {
-        $list = SearchEngineModel::where("status", 1)->order('sort', 'desc')->select();
-        return $this->success("ok", $list);
-    }
-
-    public function list(): \think\response\Json
-    {
-        $this->getAdmin();
-        $name = $this->request->post('search.name', false);
-        $sql = [];
-        if ($name) {
-            $sql[] = ['name|tips', 'like', '%' . $name . '%'];
-        }
-        $list = SearchEngineModel::where($sql);
-        $list = $list->order('sort', 'desc')->select();
-        return $this->success('ok', $list);
-    }
-
-    function add(): \think\response\Json
-    {
-        is_demo_mode(true);
-        $this->getAdmin();
-        $data = $this->request->post('form');
-        if ($data) {
-            $model = new SearchEngineModel();
-            if (isset($data['id']) && $data['id']) { //更新
-                $model = $model->find($data['id']);
-            }
-            $model->save($data);
-            Cache::delete("searchEngine");
-            return $this->success("保存成功!");
-        }
-        return $this->error('缺少数据');
-    }
-
-    function del(): \think\response\Json
-    {
-        is_demo_mode(true);
-        $this->getAdmin();
-        $ids = $this->request->post('ids', []);
-        SearchEngineModel::where('id', 'in', $ids)->delete();
-        Cache::delete("searchEngine");
-        return $this->success('删除成功');
-    }
-
-    function searchEngine(): \think\response\Json
-    {
-        $user = $this->getUser();
-        if ($user) {
-            $data = UserSearchEngineModel::find($user['user_id']);
-            if ($data) {
-                return $this->success('ok', $data['list']);
-            }
-        }
-        $list = Cache::get("searchEngine", false);
-        if (!$list) {
-            $list = SearchEngineModel::where('status', 1)->order('sort', 'desc')->limit(10)->select()->toArray();
-            Cache::set("searchEngine", $list, 60 * 60 * 24);
-        }
-        return $this->success('ok', $list);
-    }
-
-    function saveSearchEngine(): \think\response\Json
-    {
-        $user = $this->getUser(true);
-        if ($user) {
-            $config = $this->request->post('searchEngine', []);
-            if ($config) {
-                $is = UserSearchEngineModel::where('user_id', $user['user_id'])->find();
-                if ($is) {
-                    $is->list = $config;
-                    $is->force()->save();
-                } else {
-                    UserSearchEngineModel::create(['user_id' => $user['user_id'], 'list' => $config]);
-                }
-                return $this->success('ok');
-            }
-        }
-        return $this->error('保存失败');
-    }
-    function sort(): \think\response\Json
-    {
-        $sort = (array)$this->request->post();
-        foreach ($sort as $key => $value) {
-            SearchEngineModel::where("id", $value['id'])->update(['sort' => $value['sort']]);
-        }
-        Cache::delete('searchEngine');
-        return $this->success("ok");
-    }
-}
+<?php
+
+namespace app\controller;
+
+use app\BaseController;
+use app\model\SearchEngineModel;
+use app\model\UserSearchEngineModel;
+use think\facade\Cache;
+
+class SearchEngine extends BaseController
+{
+    function index(): \think\response\Json
+    {
+        $list = SearchEngineModel::where("status", 1)->order('sort', 'desc')->select();
+        return $this->success("ok", $list);
+    }
+
+    public function list(): \think\response\Json
+    {
+        $this->getAdmin();
+        $name = $this->request->post('search.name', false);
+        $sql = [];
+        if ($name) {
+            $sql[] = ['name|tips', 'like', '%' . $name . '%'];
+        }
+        $list = SearchEngineModel::where($sql);
+        $list = $list->order('sort', 'desc')->select();
+        return $this->success('ok', $list);
+    }
+
+    function add(): \think\response\Json
+    {
+        is_demo_mode(true);
+        $this->getAdmin();
+        $data = $this->request->post('form');
+        if ($data) {
+            $model = new SearchEngineModel();
+            if (isset($data['id']) && $data['id']) { //更新
+                $model = $model->find($data['id']);
+            }
+            $model->save($data);
+            Cache::delete("searchEngine");
+            return $this->success("保存成功!");
+        }
+        return $this->error('缺少数据');
+    }
+
+    function del(): \think\response\Json
+    {
+        is_demo_mode(true);
+        $this->getAdmin();
+        $ids = $this->request->post('ids', []);
+        SearchEngineModel::where('id', 'in', $ids)->delete();
+        Cache::delete("searchEngine");
+        return $this->success('删除成功');
+    }
+
+    function searchEngine(): \think\response\Json
+    {
+        $user = $this->getUser();
+        if ($user) {
+            $data = UserSearchEngineModel::find($user['user_id']);
+            if ($data) {
+                return $this->success('ok', $data['list']);
+            }
+        }
+        $list = Cache::get("searchEngine", false);
+        if (!$list) {
+            $list = SearchEngineModel::where('status', 1)->order('sort', 'desc')->limit(10)->select()->toArray();
+            Cache::set("searchEngine", $list, 60 * 60 * 24);
+        }
+        return $this->success('ok', $list);
+    }
+
+    function saveSearchEngine(): \think\response\Json
+    {
+        $user = $this->getUser(true);
+        if ($user) {
+            $config = $this->request->post('searchEngine', []);
+            if ($config) {
+                $is = UserSearchEngineModel::where('user_id', $user['user_id'])->find();
+                if ($is) {
+                    $is->list = $config;
+                    $is->force()->save();
+                } else {
+                    UserSearchEngineModel::create(['user_id' => $user['user_id'], 'list' => $config]);
+                }
+                return $this->success('ok');
+            }
+        }
+        return $this->error('保存失败');
+    }
+    function sort(): \think\response\Json
+    {
+        $sort = (array)$this->request->post();
+        foreach ($sort as $key => $value) {
+            SearchEngineModel::where("id", $value['id'])->update(['sort' => $value['sort']]);
+        }
+        Cache::delete('searchEngine');
+        return $this->success("ok");
+    }
+}

+ 71 - 71
app/controller/Setting.php

@@ -1,71 +1,71 @@
-<?php
-
-
-namespace app\controller;
-
-
-use app\BaseController;
-use app\model\SettingModel;
-use think\facade\Cache;
-use think\facade\Db;
-
-class Setting extends BaseController
-{
-    function saveSetting(): \think\response\Json
-    {
-        $this->getAdmin();
-        is_demo_mode(true);
-        $list = $this->request->post('form');
-        $tmp = [];
-        foreach ($list as $key => $value) {
-            $tmp[] = [
-                'keys' => $key,
-                'value' => $value
-            ];
-        }
-        Db::table('setting')->replace()->insertAll($tmp);
-        Cache::delete('webConfig');
-        (new \app\controller\admin\Index(app()))->authorization();
-        return $this->success('保存成功');
-    }
-
-    function refreshCache(): \think\response\Json
-    {
-        $this->getAdmin();
-        Cache::delete('webConfig');
-        return $this->success('刷新成功');
-    }
-
-    function getSetting(): \think\response\Json
-    {
-        $admin = $this->getAdmin();
-        $role = $this->request->post('role', []);
-        $info = SettingModel::Config();
-        $tmp = [];
-        $url = '';
-        if (in_array('ext_name', $role)) {
-            if (file_exists(public_path() . '/browserExt.zip')) {
-                $url = '/browserExt.zip';
-            }
-        }
-        if ($info) {
-            if (count($role) > 0) {
-                foreach ($info as $key => $val) {
-                    if (in_array($key, $role)) {
-                        $tmp[$key] = $val;
-                    }
-                }
-            }
-            return json(['msg' => "ok", "data" => $tmp, 'success' => $this->auth, 'code' => 1, "url" => $url]);
-        }
-        return json(['msg' => 'ok', 'data' => false, 'success' => $this->auth, 'code' => 0, 'url' => $url]);
-    }
-    function delExt(): \think\response\Json
-    {
-        $this->getAdmin();
-        if (file_exists(public_path() . '/browserExt.zip')) {
-            unlink(public_path() . '/browserExt.zip');
-        }
-        return $this->success("ok");
-    }
-}
+<?php
+
+
+namespace app\controller;
+
+
+use app\BaseController;
+use app\model\SettingModel;
+use think\facade\Cache;
+use think\facade\Db;
+
+class Setting extends BaseController
+{
+    function saveSetting(): \think\response\Json
+    {
+        $this->getAdmin();
+        is_demo_mode(true);
+        $list = $this->request->post('form');
+        $tmp = [];
+        foreach ($list as $key => $value) {
+            $tmp[] = [
+                'keys' => $key,
+                'value' => $value
+            ];
+        }
+        Db::table('setting')->replace()->insertAll($tmp);
+        Cache::delete('webConfig');
+        (new \app\controller\admin\Index(app()))->authorization();
+        return $this->success('保存成功');
+    }
+
+    function refreshCache(): \think\response\Json
+    {
+        $this->getAdmin();
+        Cache::delete('webConfig');
+        return $this->success('刷新成功');
+    }
+
+    function getSetting(): \think\response\Json
+    {
+        $admin = $this->getAdmin();
+        $role = $this->request->post('role', []);
+        $info = SettingModel::Config();
+        $tmp = [];
+        $url = '';
+        if (in_array('ext_name', $role)) {
+            if (file_exists(public_path() . '/browserExt.zip')) {
+                $url = '/browserExt.zip';
+            }
+        }
+        if ($info) {
+            if (count($role) > 0) {
+                foreach ($info as $key => $val) {
+                    if (in_array($key, $role)) {
+                        $tmp[$key] = $val;
+                    }
+                }
+            }
+            return json(['msg' => "ok", "data" => $tmp, 'success' => $this->auth, 'code' => 1, "url" => $url]);
+        }
+        return json(['msg' => 'ok', 'data' => false, 'success' => $this->auth, 'code' => 0, 'url' => $url]);
+    }
+    function delExt(): \think\response\Json
+    {
+        $this->getAdmin();
+        if (file_exists(public_path() . '/browserExt.zip')) {
+            unlink(public_path() . '/browserExt.zip');
+        }
+        return $this->success("ok");
+    }
+}

+ 55 - 55
app/controller/Tabbar.php

@@ -1,55 +1,55 @@
-<?php
-
-
-namespace app\controller;
-
-
-use app\BaseController;
-use app\model\TabbarModel;
-use think\facade\Cache;
-
-class Tabbar extends BaseController
-{
-    public function update(): \think\response\Json
-    {
-        $user = $this->getUser(true);
-        if ($user) {
-            $tabbar = $this->request->post("tabbar", []);
-            if (is_array($tabbar)) {
-                $is = TabbarModel::where("user_id", $user['user_id'])->find();
-                if ($is) {
-                    $is->tabs = $tabbar;
-                    $is->save();
-                } else {
-                    TabbarModel::create(["user_id" => $user['user_id'], "tabs" => $tabbar]);
-                }
-                return $this->success('ok');
-            }
-        }
-        return $this->error('保存失败');
-    }
-
-    public function get(): \think\response\Json
-    {
-        $user = $this->getUser();
-        if ($user) {
-            $data = TabbarModel::where("user_id",$user['user_id'])->find();
-            if ($data) {
-                return $this->success('ok', $data['tabs']);
-            }
-        }
-        $config = $this->systemSetting('defaultTab', '/static/defaultTab.json', true);
-        if ($config) {
-            $fp = joinPath(public_path(), $config);
-            if (!file_exists($fp)) {
-                $fp = public_path() . 'static/defaultTab.json';
-            }
-            if (file_exists($fp)) {
-                $file = file_get_contents($fp);
-                $json = json_decode($file, true);
-                return $this->success('ok', $json['tabbar'] ?? []);
-            }
-        }
-        return $this->success('ok', []);
-    }
-}
+<?php
+
+
+namespace app\controller;
+
+
+use app\BaseController;
+use app\model\TabbarModel;
+use think\facade\Cache;
+
+class Tabbar extends BaseController
+{
+    public function update(): \think\response\Json
+    {
+        $user = $this->getUser(true);
+        if ($user) {
+            $tabbar = $this->request->post("tabbar", []);
+            if (is_array($tabbar)) {
+                $is = TabbarModel::where("user_id", $user['user_id'])->find();
+                if ($is) {
+                    $is->tabs = $tabbar;
+                    $is->save();
+                } else {
+                    TabbarModel::create(["user_id" => $user['user_id'], "tabs" => $tabbar]);
+                }
+                return $this->success('ok');
+            }
+        }
+        return $this->error('保存失败');
+    }
+
+    public function get(): \think\response\Json
+    {
+        $user = $this->getUser();
+        if ($user) {
+            $data = TabbarModel::where("user_id",$user['user_id'])->find();
+            if ($data) {
+                return $this->success('ok', $data['tabs']);
+            }
+        }
+        $config = $this->systemSetting('defaultTab', '/static/defaultTab.json', true);
+        if ($config) {
+            $fp = joinPath(public_path(), $config);
+            if (!file_exists($fp)) {
+                $fp = public_path() . 'static/defaultTab.json';
+            }
+            if (file_exists($fp)) {
+                $file = file_get_contents($fp);
+                $json = json_decode($file, true);
+                return $this->success('ok', $json['tabbar'] ?? []);
+            }
+        }
+        return $this->success('ok', []);
+    }
+}

+ 338 - 338
app/controller/User.php

@@ -1,338 +1,338 @@
-<?php
-
-
-namespace app\controller;
-
-use app\BaseController;
-use app\model\ConfigModel;
-use app\model\SettingModel;
-use app\model\TokenModel;
-use app\model\UserModel;
-use think\facade\Cache;
-use think\facade\Log;
-use think\facade\View;
-
-class User extends BaseController
-{
-    protected $qq_bind_mode = false;
-
-    public function login(): \think\response\Json
-    {
-        $user = $this->request->post('username', '0');
-        $pass = $this->request->post('password', '0');
-        $user = trim($user);
-        $pass = trim($pass);
-        $info = UserModel::where('mail', $user)->find();
-
-        if (Cache::get('login.' . $user)) {
-            return $this->error('账号已被安全锁定,您可以修改密码然后登录');
-        }
-        if (!$info) {
-            return $this->error('账号不存在');
-        }
-        if ($info['login_fail_count'] == 10) {
-            Cache::set('login.' . $user, 'lock', 7200);
-            $info->login_fail_count = 0;
-            $info->save();
-            return $this->error('账号已被锁定2小时');
-        }
-        if ($info['password'] != md5($pass)) {
-            $info->login_fail_count += 1;
-            $info->save();
-            return $this->error('账号不存在或密码错误');
-        }
-        if ($info['status'] === 1) {
-            return $this->error('账号已被冻结');
-        }
-        $auth = $this->refreshToken($info);
-        $info->login_ip = getRealIp();
-        $info->login_time = date('Y-m-d H:i:s');
-        $info->login_fail_count = 0;//登陆成功将失败次数归零
-        $info->save();
-        return $this->success('登录成功', $auth);
-    }
-
-
-    private function refreshToken($info): array
-    {
-        $token = renderToken($info['id']);
-        $agent = $this->request->header('User-Agent');
-        $agent = mb_substr($agent, 0, 250);
-        $auth = ['user_id' => $info['id'], 'token' => $token, 'create_time' => time(), 'ip' => getRealIp(), 'user_agent' => $agent];
-        if (isset($info['access_token'])) {
-            $auth['access_token'] = $info['access_token'];
-        }
-        TokenModel::insert($auth);
-        unset($auth['user_agent']);
-        unset($auth['access_token']);
-        unset($auth['ip']);
-        return $auth;
-    }
-
-    function register(): \think\response\Json
-    {
-        $user = $this->request->post('username', false);
-        $pass = $this->request->post('password', false);
-        $code = $this->request->post('code', '0000');
-        if ($user && $pass) {
-            $user = trim($user);
-            $pass = trim($pass);
-            if (!validateEmail($user)) {
-                return $this->error('邮箱格式错误');
-            }
-            if (strlen($pass) < 6) {
-                return $this->error('密码过短');
-            }
-            $cacheCode = Cache::get('code' . $user);
-            if (!$cacheCode || $cacheCode != $code) {
-                return $this->error('验证码错误');
-            }
-            if (UserModel::where('mail', $user)->field('id,mail')->find()) {
-                return $this->error('账号已存在');
-            }
-            $add = UserModel::insert(['mail' => $user, 'password' => md5($pass), 'create_time' => date('Y-m-d H:i:s'), 'register_ip' => getRealIp()]);
-            if ($add) {
-                Cache::delete('code' . $user);
-                return $this->success('ok');
-            }
-        }
-        return $this->error('注册失败');
-    }
-
-    public function forgetPass(): \think\response\Json
-    {
-        $user = $this->request->post('username', false);
-        $pass = $this->request->post('password', false);
-        $code = $this->request->post('code', '0000');
-        if ($user && $pass) {
-            $user = trim($user);
-            $pass = trim($pass);
-            if (!validateEmail($user)) {
-                return $this->error('邮箱格式错误');
-            }
-            if (strlen($pass) < 6) {
-                return $this->error('密码过短');
-            }
-            $info = UserModel::where('mail', $user)->field('id,mail')->find();
-            if (!$info) {
-                return $this->error('账号不存在');
-            }
-            $cacheCode = Cache::get('code' . $user);
-            if ($cacheCode && $cacheCode == $code) {
-                $info->password = md5($pass);
-                $add = $info->save();
-                if ($add) {
-                    TokenModel::where('user_id', $info['id'])->delete(); //删除所有登录记录
-                    Cache::delete('login.' . $user);
-                    return $this->success('ok');
-                }
-            } else {
-                return $this->error('验证码错误');
-            }
-        }
-        return $this->error('修改失败');
-    }
-
-    function newMail(): \think\response\Json
-    {
-        $userinfo = $this->getUser(true);
-        $user = $this->request->post('mail', false);
-        $code = $this->request->post('code', false);
-        if ($user && $code) {
-            $user = trim($user);
-            if (!validateEmail($user)) {
-                return $this->error('邮箱格式错误');
-            }
-            $cacheCode = Cache::get('code' . $user);
-            if ($cacheCode && $cacheCode == $code) {
-                $info = UserModel::where('mail', $user)->field('id,mail')->find();
-                if ($info) {
-                    return $this->error('该邮箱已被使用!');
-                }
-                $info = UserModel::where('id', $userinfo['user_id'])->field('id,mail')->find();
-                $info->mail = $user;
-                $info->save();
-                Cache::delete('code' . $user);
-                return $this->success('修改成功');
-            } else {
-                return $this->error('验证码错误');
-            }
-        }
-        return $this->error('请认真填写表单');
-    }
-
-    function loginOut(): \think\response\Json
-    {
-        $user = $this->getUser();
-        if ($user) {
-            TokenModel::where('user_id', $user['user_id'])->where('token', $user['token'])->delete();
-        }
-        return $this->success('ok');
-    }
-
-    public function get(): \think\response\Json
-    {
-        $info = $this->getUser(true);
-        if ($info) {
-            $info = UserModel::field('id,mail,manager,nickname,avatar,qq_open_id')->find($info['user_id']);
-            if ($info['qq_open_id']) {
-                $info['qqBind'] = true;
-                unset($info['qq_open_id']);
-            }
-            return $this->success('ok', $info);
-        }
-        return $this->error('获取失败');
-    }
-
-    public function unbindQQ(): \think\response\Json
-    {
-        $info = $this->getUser(true);
-        if ($info) {
-            $info = UserModel::field('id,mail,manager,nickname,avatar,qq_open_id')->find($info['user_id']);
-            if (empty($info->mail)) {
-                return $this->error("请先绑定邮箱后再解绑");
-            }
-            $info->qq_open_id = "";
-            $info->save();
-        }
-        return $this->success('解绑成功', $info);
-    }
-
-    public function updateInfo(): \think\response\Json
-    {
-        $info = $this->getUser(true);
-        $field = $this->request->post('field', false);
-        $value = $this->request->post('value', false);
-        //允许修改的字段
-        $allow = ['nickname', 'avatar'];
-        if ($info && $field && $value && in_array($field, $allow)) {
-            UserModel::where('id', $info['user_id'])->update([$field => $value]);
-        }
-        return $this->success('修改成功');
-    }
-
-    function qLogin(): \think\response\Redirect
-    {
-        $appId = SettingModel::Config('qq_login_appid', false);
-        $callback = 'https://' . $this->request->host() . '/qq_login';
-        $type = $this->request->get('type', '');
-        $query = [
-            'redirect_uri' => $callback,
-            'state' => md5(uniqid()),
-            'response_type' => 'code',
-            'scope' => 'get_user_info,list_album,upload_pic',
-            'client_id' => $appId
-        ];
-        if ($type === 'bind') {
-            $query['state'] = $query['state'] . 'bind';
-        }
-        $http = http_build_query($query);
-        return redirect('https://graph.qq.com/oauth2.0/authorize?' . $http);
-    }
-
-    function qq_login(): string
-    {
-        $appId = SettingModel::Config('qq_login_appid', false);
-        $code = $this->request->get('code', false);
-        $state = $this->request->get('state');
-        if (strpos($state, 'bind')) {
-            //绑定模式
-            $this->qq_bind_mode = true;
-        }
-        $callback = 'https://' . $this->request->host() . '/qq_login';
-        $result = \Axios::http()->get('https://graph.qq.com/oauth2.0/token', [
-            'query' => [
-                'grant_type' => 'authorization_code',
-                'client_id' => $appId,
-                'client_secret' => SettingModel::Config('qq_login_appkey', false),
-                'code' => $code,
-                'redirect_uri' => $callback,
-                'fmt' => 'json'
-            ]
-        ]);
-        if ($result->getStatusCode() === 200) {
-            $content = $result->getBody()->getContents();
-            $js = \Axios::toJson($content);
-            if (isset($js['access_token'])) {
-                $access_token = $js['access_token'];
-                return $this->getOpenId($access_token);
-            }
-        }
-        return View::fetch('/qq_login_error');
-    }
-
-    //此方法禁止网络访问
-    private function getOpenId($access_token): string
-    {
-        $result = \Axios::http()->get('https://graph.qq.com/oauth2.0/me', [
-            'query' => [
-                'access_token' => $access_token,
-                'fmt' => 'json'
-            ]
-        ]);
-        if ($result->getStatusCode() === 200) {
-            $content = $result->getBody()->getContents();
-            $js = \Axios::toJson($content);
-            if (isset($js['openid'])) {
-                $openid = $js['openid'];
-                if ($this->qq_bind_mode) {
-                    //绑定模式
-                    if (UserModel::where('qq_open_id', $openid)->field('id,qq_open_id')->find()) {
-                        return View::fetch('/qq_login_error');
-                    }
-                    //如果openid数据库不存在说明QQ没有被绑定过,可以绑定
-                    $this->BindQQ($openid);//绑定后需要替换Token,不然之前的QQ登录会失效
-                }
-                $info = UserModel::where('qq_open_id', $openid)->find();
-                if (!$info) {//不存在就创建一个新用户,如果上一个步骤绑定成功的话,是不可能进入此步骤的
-                    UserModel::insert(['mail' => '', 'password' => md5(time()), 'create_time' => date('Y-m-d H:i:s'), 'register_ip' => getRealIp(), 'qq_open_id' => $openid]);
-                    $info = UserModel::where('qq_open_id', $openid)->find();
-                    $this->getUserOpenInfo($access_token, $openid);//获取一些用户的默认信息
-                }
-                if ($info) {//如果用户存在
-                    $info->login_ip = getRealIp();
-                    $info->login_time = date('Y-m-d H:i:s');
-                    $info->login_fail_count = 0;//登陆成功将失败次数归零
-                    $info->save();
-                    $info['access_token'] = $access_token;
-                    $auth = $this->refreshToken($info);
-                    if ($info['status'] === 1) {
-                        return View::fetch('/qq_login_error');
-                    }
-                    return View::fetch('/qq_login', ['info' => $auth]);
-                }
-            }
-        }
-        return View::fetch('/qq_login_error');
-    }
-
-    private function BindQQ($qq_open_id)
-    {
-        $user = $this->getUser();
-        if ($user) {
-            $info = UserModel::where('id', $user['user_id'])->field('id,mail,qq_open_id,password,login_fail_count,login_ip,login_time')->find();
-            if ($info) {
-                $info->qq_open_id = $qq_open_id;
-                $info->save();
-            }
-        }
-    }
-
-    private function getUserOpenInfo($access_token, $openid)
-    {
-        $result = \Axios::http()->get('https://graph.qq.com/user/get_user_info', [
-            'query' => [
-                'openid' => $openid,
-                'oauth_consumer_key' => SettingModel::Config('qq_login_appid', false),
-                'access_token' => $access_token
-            ]
-        ]);
-        if ($result->getStatusCode() === 200) {
-            $content = $result->getBody()->getContents();
-            $js = \Axios::toJson($content);
-            if ($js['ret'] === 0) {
-                UserModel::where('qq_open_id', $openid)->update(['nickname' => $js['nickname'], 'avatar' => $js['figureurl_qq_1']]);
-            }
-        }
-    }
-}
+<?php
+
+
+namespace app\controller;
+
+use app\BaseController;
+use app\model\ConfigModel;
+use app\model\SettingModel;
+use app\model\TokenModel;
+use app\model\UserModel;
+use think\facade\Cache;
+use think\facade\Log;
+use think\facade\View;
+
+class User extends BaseController
+{
+    protected $qq_bind_mode = false;
+
+    public function login(): \think\response\Json
+    {
+        $user = $this->request->post('username', '0');
+        $pass = $this->request->post('password', '0');
+        $user = trim($user);
+        $pass = trim($pass);
+        $info = UserModel::where('mail', $user)->find();
+
+        if (Cache::get('login.' . $user)) {
+            return $this->error('账号已被安全锁定,您可以修改密码然后登录');
+        }
+        if (!$info) {
+            return $this->error('账号不存在');
+        }
+        if ($info['login_fail_count'] == 10) {
+            Cache::set('login.' . $user, 'lock', 7200);
+            $info->login_fail_count = 0;
+            $info->save();
+            return $this->error('账号已被锁定2小时');
+        }
+        if ($info['password'] != md5($pass)) {
+            $info->login_fail_count += 1;
+            $info->save();
+            return $this->error('账号不存在或密码错误');
+        }
+        if ($info['status'] === 1) {
+            return $this->error('账号已被冻结');
+        }
+        $auth = $this->refreshToken($info);
+        $info->login_ip = getRealIp();
+        $info->login_time = date('Y-m-d H:i:s');
+        $info->login_fail_count = 0;//登陆成功将失败次数归零
+        $info->save();
+        return $this->success('登录成功', $auth);
+    }
+
+
+    private function refreshToken($info): array
+    {
+        $token = renderToken($info['id']);
+        $agent = $this->request->header('User-Agent');
+        $agent = mb_substr($agent, 0, 250);
+        $auth = ['user_id' => $info['id'], 'token' => $token, 'create_time' => time(), 'ip' => getRealIp(), 'user_agent' => $agent];
+        if (isset($info['access_token'])) {
+            $auth['access_token'] = $info['access_token'];
+        }
+        TokenModel::insert($auth);
+        unset($auth['user_agent']);
+        unset($auth['access_token']);
+        unset($auth['ip']);
+        return $auth;
+    }
+
+    function register(): \think\response\Json
+    {
+        $user = $this->request->post('username', false);
+        $pass = $this->request->post('password', false);
+        $code = $this->request->post('code', '0000');
+        if ($user && $pass) {
+            $user = trim($user);
+            $pass = trim($pass);
+            if (!validateEmail($user)) {
+                return $this->error('邮箱格式错误');
+            }
+            if (strlen($pass) < 6) {
+                return $this->error('密码过短');
+            }
+            $cacheCode = Cache::get('code' . $user);
+            if (!$cacheCode || $cacheCode != $code) {
+                return $this->error('验证码错误');
+            }
+            if (UserModel::where('mail', $user)->field('id,mail')->find()) {
+                return $this->error('账号已存在');
+            }
+            $add = UserModel::insert(['mail' => $user, 'password' => md5($pass), 'create_time' => date('Y-m-d H:i:s'), 'register_ip' => getRealIp()]);
+            if ($add) {
+                Cache::delete('code' . $user);
+                return $this->success('ok');
+            }
+        }
+        return $this->error('注册失败');
+    }
+
+    public function forgetPass(): \think\response\Json
+    {
+        $user = $this->request->post('username', false);
+        $pass = $this->request->post('password', false);
+        $code = $this->request->post('code', '0000');
+        if ($user && $pass) {
+            $user = trim($user);
+            $pass = trim($pass);
+            if (!validateEmail($user)) {
+                return $this->error('邮箱格式错误');
+            }
+            if (strlen($pass) < 6) {
+                return $this->error('密码过短');
+            }
+            $info = UserModel::where('mail', $user)->field('id,mail')->find();
+            if (!$info) {
+                return $this->error('账号不存在');
+            }
+            $cacheCode = Cache::get('code' . $user);
+            if ($cacheCode && $cacheCode == $code) {
+                $info->password = md5($pass);
+                $add = $info->save();
+                if ($add) {
+                    TokenModel::where('user_id', $info['id'])->delete(); //删除所有登录记录
+                    Cache::delete('login.' . $user);
+                    return $this->success('ok');
+                }
+            } else {
+                return $this->error('验证码错误');
+            }
+        }
+        return $this->error('修改失败');
+    }
+
+    function newMail(): \think\response\Json
+    {
+        $userinfo = $this->getUser(true);
+        $user = $this->request->post('mail', false);
+        $code = $this->request->post('code', false);
+        if ($user && $code) {
+            $user = trim($user);
+            if (!validateEmail($user)) {
+                return $this->error('邮箱格式错误');
+            }
+            $cacheCode = Cache::get('code' . $user);
+            if ($cacheCode && $cacheCode == $code) {
+                $info = UserModel::where('mail', $user)->field('id,mail')->find();
+                if ($info) {
+                    return $this->error('该邮箱已被使用!');
+                }
+                $info = UserModel::where('id', $userinfo['user_id'])->field('id,mail')->find();
+                $info->mail = $user;
+                $info->save();
+                Cache::delete('code' . $user);
+                return $this->success('修改成功');
+            } else {
+                return $this->error('验证码错误');
+            }
+        }
+        return $this->error('请认真填写表单');
+    }
+
+    function loginOut(): \think\response\Json
+    {
+        $user = $this->getUser();
+        if ($user) {
+            TokenModel::where('user_id', $user['user_id'])->where('token', $user['token'])->delete();
+        }
+        return $this->success('ok');
+    }
+
+    public function get(): \think\response\Json
+    {
+        $info = $this->getUser(true);
+        if ($info) {
+            $info = UserModel::field('id,mail,manager,nickname,avatar,qq_open_id')->find($info['user_id']);
+            if ($info['qq_open_id']) {
+                $info['qqBind'] = true;
+                unset($info['qq_open_id']);
+            }
+            return $this->success('ok', $info);
+        }
+        return $this->error('获取失败');
+    }
+
+    public function unbindQQ(): \think\response\Json
+    {
+        $info = $this->getUser(true);
+        if ($info) {
+            $info = UserModel::field('id,mail,manager,nickname,avatar,qq_open_id')->find($info['user_id']);
+            if (empty($info->mail)) {
+                return $this->error("请先绑定邮箱后再解绑");
+            }
+            $info->qq_open_id = "";
+            $info->save();
+        }
+        return $this->success('解绑成功', $info);
+    }
+
+    public function updateInfo(): \think\response\Json
+    {
+        $info = $this->getUser(true);
+        $field = $this->request->post('field', false);
+        $value = $this->request->post('value', false);
+        //允许修改的字段
+        $allow = ['nickname', 'avatar'];
+        if ($info && $field && $value && in_array($field, $allow)) {
+            UserModel::where('id', $info['user_id'])->update([$field => $value]);
+        }
+        return $this->success('修改成功');
+    }
+
+    function qLogin(): \think\response\Redirect
+    {
+        $appId = SettingModel::Config('qq_login_appid', false);
+        $callback = 'https://' . $this->request->host() . '/qq_login';
+        $type = $this->request->get('type', '');
+        $query = [
+            'redirect_uri' => $callback,
+            'state' => md5(uniqid()),
+            'response_type' => 'code',
+            'scope' => 'get_user_info,list_album,upload_pic',
+            'client_id' => $appId
+        ];
+        if ($type === 'bind') {
+            $query['state'] = $query['state'] . 'bind';
+        }
+        $http = http_build_query($query);
+        return redirect('https://graph.qq.com/oauth2.0/authorize?' . $http);
+    }
+
+    function qq_login(): string
+    {
+        $appId = SettingModel::Config('qq_login_appid', false);
+        $code = $this->request->get('code', false);
+        $state = $this->request->get('state');
+        if (strpos($state, 'bind')) {
+            //绑定模式
+            $this->qq_bind_mode = true;
+        }
+        $callback = 'https://' . $this->request->host() . '/qq_login';
+        $result = \Axios::http()->get('https://graph.qq.com/oauth2.0/token', [
+            'query' => [
+                'grant_type' => 'authorization_code',
+                'client_id' => $appId,
+                'client_secret' => SettingModel::Config('qq_login_appkey', false),
+                'code' => $code,
+                'redirect_uri' => $callback,
+                'fmt' => 'json'
+            ]
+        ]);
+        if ($result->getStatusCode() === 200) {
+            $content = $result->getBody()->getContents();
+            $js = \Axios::toJson($content);
+            if (isset($js['access_token'])) {
+                $access_token = $js['access_token'];
+                return $this->getOpenId($access_token);
+            }
+        }
+        return View::fetch('/qq_login_error');
+    }
+
+    //此方法禁止网络访问
+    private function getOpenId($access_token): string
+    {
+        $result = \Axios::http()->get('https://graph.qq.com/oauth2.0/me', [
+            'query' => [
+                'access_token' => $access_token,
+                'fmt' => 'json'
+            ]
+        ]);
+        if ($result->getStatusCode() === 200) {
+            $content = $result->getBody()->getContents();
+            $js = \Axios::toJson($content);
+            if (isset($js['openid'])) {
+                $openid = $js['openid'];
+                if ($this->qq_bind_mode) {
+                    //绑定模式
+                    if (UserModel::where('qq_open_id', $openid)->field('id,qq_open_id')->find()) {
+                        return View::fetch('/qq_login_error');
+                    }
+                    //如果openid数据库不存在说明QQ没有被绑定过,可以绑定
+                    $this->BindQQ($openid);//绑定后需要替换Token,不然之前的QQ登录会失效
+                }
+                $info = UserModel::where('qq_open_id', $openid)->find();
+                if (!$info) {//不存在就创建一个新用户,如果上一个步骤绑定成功的话,是不可能进入此步骤的
+                    UserModel::insert(['mail' => '', 'password' => md5(time()), 'create_time' => date('Y-m-d H:i:s'), 'register_ip' => getRealIp(), 'qq_open_id' => $openid]);
+                    $info = UserModel::where('qq_open_id', $openid)->find();
+                    $this->getUserOpenInfo($access_token, $openid);//获取一些用户的默认信息
+                }
+                if ($info) {//如果用户存在
+                    $info->login_ip = getRealIp();
+                    $info->login_time = date('Y-m-d H:i:s');
+                    $info->login_fail_count = 0;//登陆成功将失败次数归零
+                    $info->save();
+                    $info['access_token'] = $access_token;
+                    $auth = $this->refreshToken($info);
+                    if ($info['status'] === 1) {
+                        return View::fetch('/qq_login_error');
+                    }
+                    return View::fetch('/qq_login', ['info' => $auth]);
+                }
+            }
+        }
+        return View::fetch('/qq_login_error');
+    }
+
+    private function BindQQ($qq_open_id)
+    {
+        $user = $this->getUser();
+        if ($user) {
+            $info = UserModel::where('id', $user['user_id'])->field('id,mail,qq_open_id,password,login_fail_count,login_ip,login_time')->find();
+            if ($info) {
+                $info->qq_open_id = $qq_open_id;
+                $info->save();
+            }
+        }
+    }
+
+    private function getUserOpenInfo($access_token, $openid)
+    {
+        $result = \Axios::http()->get('https://graph.qq.com/user/get_user_info', [
+            'query' => [
+                'openid' => $openid,
+                'oauth_consumer_key' => SettingModel::Config('qq_login_appid', false),
+                'access_token' => $access_token
+            ]
+        ]);
+        if ($result->getStatusCode() === 200) {
+            $content = $result->getBody()->getContents();
+            $js = \Axios::toJson($content);
+            if ($js['ret'] === 0) {
+                UserModel::where('qq_open_id', $openid)->update(['nickname' => $js['nickname'], 'avatar' => $js['figureurl_qq_1']]);
+            }
+        }
+    }
+}

+ 368 - 368
app/controller/admin/Index.php

@@ -1,368 +1,368 @@
-<?php
-
-namespace app\controller\admin;
-
-use app\BaseController;
-use app\model\CardModel;
-use app\model\LinkStoreModel;
-use app\model\SettingModel;
-use think\facade\Cache;
-use think\facade\Db;
-
-
-class Index extends BaseController
-{
-    public $authService = "https://auth.mtab.cc";
-    public $authCode = '';
-
-
-    function setSubscription(): \think\response\Json
-    {
-        $this->getAdmin();
-        $code = $this->request->post("code", "");
-        if (trim($code)) {
-            Db::table('setting')->replace()->insert(['keys' => 'authCode', 'value' => $code]);
-            SettingModel::refreshSetting();
-        }
-        return $this->success("ok");
-    }
-
-    private function initAuth()
-    {
-        $authCode = $this->systemSetting('authCode', '', true);
-        if (strlen($authCode) == 0) {
-            $authCode = env('authCode', '');
-        }
-        $this->authCode = $authCode;
-        $this->authService = $this->systemSetting('authServer', 'https://auth.mtab.cc', true);
-    }
-
-
-    function updateApp($n = 0): \think\response\Json
-    {
-        $this->getAdmin();
-        $this->initAuth();
-        $result = \Axios::http()->post($this->authService . '/getUpGrade', [
-            'timeout' => 10,
-            'form_params' => [
-                'authorization_code' => $this->authCode,
-                'version_code' => app_version_code,
-            ]
-        ]);
-        if ($result->getStatusCode() == 200) {
-            $json = json_decode($result->getBody()->getContents(), true);
-            if ($json['code'] === 1) {
-                $upgradePhp = runtime_path() . 'update.php';
-                $f = "";
-                $upGrade = null;
-                if (!empty($json['info']['update_php'])) {
-                    try { //用远程脚本更新,一般用不到,除非上一个版本发生一些问题需要额外脚本处理
-                        $f = file_get_contents($json['info']['update_php']);
-                        file_put_contents(runtime_path() . 'update.php', $f);
-                        require_once $upgradePhp;
-                        $upGrade = new \Upgrade();
-                    } catch (\Exception $e) {
-                        return $this->error($e->getMessage());
-                    }
-                }
-                if ($upGrade === null) {
-                    $upGrade = new \Upgrade2();
-                }
-                if (!empty($json['info']['update_zip'])) {
-                    $upGrade->update_download_url = $json['info']['update_zip'];
-                }
-                if (!empty($json['info']['update_sql'])) {
-                    $upGrade->update_sql_url = $json['info']['update_sql'];
-                }
-                try {
-                    $upGrade->run(); //启动任务
-                    if (file_exists($upgradePhp)) {
-                        unlink($upgradePhp);
-                    }
-                    return $this->success('更新完毕');
-                } catch (\Exception $e) {
-                    return $this->error($e->getMessage());
-                }
-            } else {
-                return $this->error($json['msg']);
-            }
-        }
-        return $this->error("没有更新的版本");
-    }
-
-    function authorization(): \think\response\Json
-    {
-        $this->getAdmin();
-        $this->initAuth();
-        $info = [];
-        $info['version'] = app_version;
-        $info['version_code'] = app_version_code;
-        $info['php_version'] = phpversion();
-        try {
-            $result = \Axios::http()->post($this->authService . '/checkAuth', [
-                'timeout' => 10,
-                'form_params' => [
-                    'authorization_code' => $this->authCode,
-                    'version_code' => app_version_code,
-                    'domain' => request()->domain()
-                ]
-            ]);
-            if ($result->getStatusCode() == 200) {
-                $jsonStr = $result->getBody()->getContents();
-                $json = json_decode($jsonStr, true);
-                $info['remote'] = $json;
-                if (!isset($json['auth'])) {
-                    $f = SettingModel::where('keys', 'authCode')->find();
-                    if ($f) {
-                        $f->value = '';
-                        $f->save();
-                    }
-                    Cache::delete('webConfig');
-                }
-                return $this->success($info);
-            }
-        } catch (\Exception $e) {
-        }
-        $info['remote'] = [
-            "auth" => (bool)$this->authCode
-        ];
-        return $this->success('授权服务器连接失败', $info);
-    }
-
-
-    function cardList(): \think\response\Json
-    {
-        $this->getAdmin();
-        $this->initAuth();
-        try {
-            $result = \Axios::http()->post($this->authService . '/card', [
-                'timeout' => 15,
-                'form_params' => [
-                    'authorization_code' => $this->authCode
-                ]
-            ]);
-            $json = $result->getBody()->getContents();
-            $json = json_decode($json, true);
-            if ($json['code'] === 1) {
-                return $this->success('ok', $json['data']);
-            }
-        } catch (\Exception $e) {
-        }
-        return $this->error('远程卡片获取失败');
-    }
-
-    //获取本地应用
-    function localCard(): \think\response\Json
-    {
-        $this->getAdmin();
-        $apps = CardModel::select();
-        return $this->success('ok', $apps);
-    }
-
-    function stopCard(): \think\response\Json
-    {
-        $this->getAdmin();
-        is_demo_mode(true);
-        $name_en = $this->request->post('name_en', '');
-        CardModel::where('name_en', $name_en)->update(['status' => 0]);
-        Cache::delete('cardList');
-        return $this->success('设置成功');
-    }
-
-    function startCard(): \think\response\Json
-    {
-        $this->getAdmin();
-        $name_en = $this->request->post('name_en', '');
-        CardModel::where('name_en', $name_en)->update(['status' => 1]);
-        Cache::delete('cardList');
-        return $this->success('设置成功');
-    }
-
-    function installCard(): \think\response\Json
-    {
-        $this->getAdmin();
-        $this->initAuth();
-        $name_en = $this->request->post("name_en", '');
-        $version = 0;
-        $type = $this->request->post('type', 'install');
-        if (mb_strlen($name_en) > 0) {
-            $card = CardModel::where('name_en', $name_en)->find();
-            if ($card) {
-                if ($type == 'install') {
-                    return $this->error('您已安装当前卡片组件');
-                }
-                if ($type == 'update') {
-                    $version = $card['version'];
-                }
-            }
-            $result = \Axios::http()->post($this->authService . '/installCard', [
-                'timeout' => 15,
-                'form_params' => [
-                    'authorization_code' => $this->authCode,
-                    'name_en' => $name_en,
-                    'version' => $version
-                ]
-            ]);
-            try {
-                $json = $result->getBody()->getContents();
-                $json = json_decode($json, true, JSON_UNESCAPED_UNICODE);
-                if ($json['code'] == 0) {
-                    return $this->error($json['msg']);
-                }
-                return $this->installCardTask($json['data']);
-            } catch (\Exception $e) {
-                return $this->error($e->getMessage());
-            }
-        }
-        return $this->error("没有需要安装的卡片插件!");
-    }
-
-    function uninstallCard(): \think\response\Json
-    {
-        $this->getAdmin();
-        is_demo_mode(true);
-        $name_en = $this->request->post("name_en");
-        if ($name_en) {
-            $this->deleteDirectory(root_path() . 'plugins/' . $name_en);
-            CardModel::where('name_en', $name_en)->delete();
-            Cache::delete('cardList');
-        }
-        return $this->success('卸载完毕!');
-    }
-
-    private function deleteDirectory($dir)
-    {
-        if (!is_dir($dir)) {
-            return;
-        }
-        $files = scandir($dir);
-        foreach ($files as $file) {
-            if ($file != '.' && $file != '..') {
-                if (is_dir("$dir/$file")) {
-                    $this->deleteDirectory("$dir/$file");
-                } else {
-                    unlink("$dir/$file");
-                }
-            }
-        }
-        rmdir($dir);
-    }
-
-    private function readCardInfo($name_en)
-    {
-        $file = root_path() . 'plugins/' . $name_en . '/info.json';
-        $info = file_get_contents($file);
-        try {
-            return json_decode($info, true);
-        } catch (\Exception $e) {
-        }
-        return false;
-    }
-
-    private function installCardTask($info): \think\response\Json
-    {
-        if ($info['download']) {
-            $task = new \PluginsInstall($info);
-            $state = $task->run();
-            if ($state === true) {
-                $config = $this->readCardInfo($info['name_en']);
-                $data = [
-                    'name' => $config['name'],
-                    'name_en' => $config['name_en'],
-                    'version' => $config['version'],
-                    'tips' => $config['tips'],
-                    'src' => $config['src'],
-                    'url' => $config['url'],
-                    'window' => $config['window'],
-                ];
-                if (isset($config['setting'])) {
-                    $data['setting'] = $config['setting'];
-                }
-                $find = CardModel::where('name_en', $info['name_en'])->find();
-                if ($find) {
-                    $find->force()->save($data);
-                } else {
-                    CardModel::create($data);
-                }
-                Cache::delete('cardList');
-                return $this->success("安装成功");
-            }
-            return $this->error($state);
-        }
-        abort(0, "新版本没有提供下载地址!");
-    }
-
-    //打包扩展
-    function build(): \think\response\Json
-    {
-        $this->getAdmin();
-        is_demo_mode(true);
-        if (!extension_loaded('zip')) {
-            return $this->error("系统未安装或开启zip扩展,请安装后重试!");
-        }
-        if (!$this->auth) {
-            return $this->error("请获取授权后进行操作");
-        }
-        $ExtInfo = $this->request->post("extInfo", []);
-        $build = new \BrowserExtBuild($ExtInfo);
-        try {
-            $status = $build->runBuild();
-            if ($status) {
-                return $this->success('打包完毕', ['url' => '/browserExt.zip']);
-            }
-        } catch (\Exception $e) {
-            return $this->error($e->getMessage());
-        }
-        return $this->success('打包失败');
-    }
-
-
-    function folders(): \think\response\Json
-    {
-        $this->getAdmin();
-        $this->initAuth();
-        $result = \Axios::http()->post($this->authService . '/client/folders', [
-            'timeout' => 15,
-            'form_params' => [
-                'authorization_code' => $this->authCode
-            ]
-        ]);
-        $json = $result->getBody()->getContents();
-        $json = json_decode($json, true);
-        if ($json['code'] === 1) {
-            return $this->success('ok', $json['data']);
-        }
-        return $this->success('获取失败');
-    }
-
-    function links(): \think\response\Json
-    {
-        $this->getAdmin();
-        $this->initAuth();
-        $folders = $this->request->get("folders");
-        $page = $this->request->get("page", 1);
-        $limit = $this->request->get("limit", 18);
-        $result = \Axios::http()->post($this->authService . '/client/links', [
-            'timeout' => 15,
-            'form_params' => [
-                'folders' => $folders,
-                'limit' => $limit,
-                'page' => $page,
-                'authorization_code' => $this->authCode
-            ]
-        ]);
-        $json = $result->getBody()->getContents();
-        $json = json_decode($json, true);
-        if ($json['code'] === 1) {
-            $arrName = [];
-            $arrUrl = [];
-            foreach ($json['data']['data'] as $key => $value) {
-                $arrName[] = $value['name'];
-                $arrUrl[] = $value['url'];
-            }
-            $res = LinkStoreModel::whereOr([["name",'in', $arrName],['url','in',$arrUrl]])->select();
-            return json(['code'=>1,'msg'=>'ok','data'=>$json['data'],'local'=>$res]);
-        }
-        return $this->success('获取失败');
-    }
-}
+<?php
+
+namespace app\controller\admin;
+
+use app\BaseController;
+use app\model\CardModel;
+use app\model\LinkStoreModel;
+use app\model\SettingModel;
+use think\facade\Cache;
+use think\facade\Db;
+
+
+class Index extends BaseController
+{
+    public $authService = "https://auth.mtab.cc";
+    public $authCode = '';
+
+
+    function setSubscription(): \think\response\Json
+    {
+        $this->getAdmin();
+        $code = $this->request->post("code", "");
+        if (trim($code)) {
+            Db::table('setting')->replace()->insert(['keys' => 'authCode', 'value' => $code]);
+            SettingModel::refreshSetting();
+        }
+        return $this->success("ok");
+    }
+
+    private function initAuth()
+    {
+        $authCode = $this->systemSetting('authCode', '', true);
+        if (strlen($authCode) == 0) {
+            $authCode = env('authCode', '');
+        }
+        $this->authCode = $authCode;
+        $this->authService = $this->systemSetting('authServer', 'https://auth.mtab.cc', true);
+    }
+
+
+    function updateApp($n = 0): \think\response\Json
+    {
+        $this->getAdmin();
+        $this->initAuth();
+        $result = \Axios::http()->post($this->authService . '/getUpGrade', [
+            'timeout' => 10,
+            'form_params' => [
+                'authorization_code' => $this->authCode,
+                'version_code' => app_version_code,
+            ]
+        ]);
+        if ($result->getStatusCode() == 200) {
+            $json = json_decode($result->getBody()->getContents(), true);
+            if ($json['code'] === 1) {
+                $upgradePhp = runtime_path() . 'update.php';
+                $f = "";
+                $upGrade = null;
+                if (!empty($json['info']['update_php'])) {
+                    try { //用远程脚本更新,一般用不到,除非上一个版本发生一些问题需要额外脚本处理
+                        $f = file_get_contents($json['info']['update_php']);
+                        file_put_contents(runtime_path() . 'update.php', $f);
+                        require_once $upgradePhp;
+                        $upGrade = new \Upgrade();
+                    } catch (\Exception $e) {
+                        return $this->error($e->getMessage());
+                    }
+                }
+                if ($upGrade === null) {
+                    $upGrade = new \Upgrade2();
+                }
+                if (!empty($json['info']['update_zip'])) {
+                    $upGrade->update_download_url = $json['info']['update_zip'];
+                }
+                if (!empty($json['info']['update_sql'])) {
+                    $upGrade->update_sql_url = $json['info']['update_sql'];
+                }
+                try {
+                    $upGrade->run(); //启动任务
+                    if (file_exists($upgradePhp)) {
+                        unlink($upgradePhp);
+                    }
+                    return $this->success('更新完毕');
+                } catch (\Exception $e) {
+                    return $this->error($e->getMessage());
+                }
+            } else {
+                return $this->error($json['msg']);
+            }
+        }
+        return $this->error("没有更新的版本");
+    }
+
+    function authorization(): \think\response\Json
+    {
+        $this->getAdmin();
+        $this->initAuth();
+        $info = [];
+        $info['version'] = app_version;
+        $info['version_code'] = app_version_code;
+        $info['php_version'] = phpversion();
+        try {
+            $result = \Axios::http()->post($this->authService . '/checkAuth', [
+                'timeout' => 10,
+                'form_params' => [
+                    'authorization_code' => $this->authCode,
+                    'version_code' => app_version_code,
+                    'domain' => request()->domain()
+                ]
+            ]);
+            if ($result->getStatusCode() == 200) {
+                $jsonStr = $result->getBody()->getContents();
+                $json = json_decode($jsonStr, true);
+                $info['remote'] = $json;
+                if (!isset($json['auth'])) {
+                    $f = SettingModel::where('keys', 'authCode')->find();
+                    if ($f) {
+                        $f->value = '';
+                        $f->save();
+                    }
+                    Cache::delete('webConfig');
+                }
+                return $this->success($info);
+            }
+        } catch (\Exception $e) {
+        }
+        $info['remote'] = [
+            "auth" => (bool)$this->authCode
+        ];
+        return $this->success('授权服务器连接失败', $info);
+    }
+
+
+    function cardList(): \think\response\Json
+    {
+        $this->getAdmin();
+        $this->initAuth();
+        try {
+            $result = \Axios::http()->post($this->authService . '/card', [
+                'timeout' => 15,
+                'form_params' => [
+                    'authorization_code' => $this->authCode
+                ]
+            ]);
+            $json = $result->getBody()->getContents();
+            $json = json_decode($json, true);
+            if ($json['code'] === 1) {
+                return $this->success('ok', $json['data']);
+            }
+        } catch (\Exception $e) {
+        }
+        return $this->error('远程卡片获取失败');
+    }
+
+    //获取本地应用
+    function localCard(): \think\response\Json
+    {
+        $this->getAdmin();
+        $apps = CardModel::select();
+        return $this->success('ok', $apps);
+    }
+
+    function stopCard(): \think\response\Json
+    {
+        $this->getAdmin();
+        is_demo_mode(true);
+        $name_en = $this->request->post('name_en', '');
+        CardModel::where('name_en', $name_en)->update(['status' => 0]);
+        Cache::delete('cardList');
+        return $this->success('设置成功');
+    }
+
+    function startCard(): \think\response\Json
+    {
+        $this->getAdmin();
+        $name_en = $this->request->post('name_en', '');
+        CardModel::where('name_en', $name_en)->update(['status' => 1]);
+        Cache::delete('cardList');
+        return $this->success('设置成功');
+    }
+
+    function installCard(): \think\response\Json
+    {
+        $this->getAdmin();
+        $this->initAuth();
+        $name_en = $this->request->post("name_en", '');
+        $version = 0;
+        $type = $this->request->post('type', 'install');
+        if (mb_strlen($name_en) > 0) {
+            $card = CardModel::where('name_en', $name_en)->find();
+            if ($card) {
+                if ($type == 'install') {
+                    return $this->error('您已安装当前卡片组件');
+                }
+                if ($type == 'update') {
+                    $version = $card['version'];
+                }
+            }
+            $result = \Axios::http()->post($this->authService . '/installCard', [
+                'timeout' => 15,
+                'form_params' => [
+                    'authorization_code' => $this->authCode,
+                    'name_en' => $name_en,
+                    'version' => $version
+                ]
+            ]);
+            try {
+                $json = $result->getBody()->getContents();
+                $json = json_decode($json, true, JSON_UNESCAPED_UNICODE);
+                if ($json['code'] == 0) {
+                    return $this->error($json['msg']);
+                }
+                return $this->installCardTask($json['data']);
+            } catch (\Exception $e) {
+                return $this->error($e->getMessage());
+            }
+        }
+        return $this->error("没有需要安装的卡片插件!");
+    }
+
+    function uninstallCard(): \think\response\Json
+    {
+        $this->getAdmin();
+        is_demo_mode(true);
+        $name_en = $this->request->post("name_en");
+        if ($name_en) {
+            $this->deleteDirectory(root_path() . 'plugins/' . $name_en);
+            CardModel::where('name_en', $name_en)->delete();
+            Cache::delete('cardList');
+        }
+        return $this->success('卸载完毕!');
+    }
+
+    private function deleteDirectory($dir)
+    {
+        if (!is_dir($dir)) {
+            return;
+        }
+        $files = scandir($dir);
+        foreach ($files as $file) {
+            if ($file != '.' && $file != '..') {
+                if (is_dir("$dir/$file")) {
+                    $this->deleteDirectory("$dir/$file");
+                } else {
+                    unlink("$dir/$file");
+                }
+            }
+        }
+        rmdir($dir);
+    }
+
+    private function readCardInfo($name_en)
+    {
+        $file = root_path() . 'plugins/' . $name_en . '/info.json';
+        $info = file_get_contents($file);
+        try {
+            return json_decode($info, true);
+        } catch (\Exception $e) {
+        }
+        return false;
+    }
+
+    private function installCardTask($info): \think\response\Json
+    {
+        if ($info['download']) {
+            $task = new \PluginsInstall($info);
+            $state = $task->run();
+            if ($state === true) {
+                $config = $this->readCardInfo($info['name_en']);
+                $data = [
+                    'name' => $config['name'],
+                    'name_en' => $config['name_en'],
+                    'version' => $config['version'],
+                    'tips' => $config['tips'],
+                    'src' => $config['src'],
+                    'url' => $config['url'],
+                    'window' => $config['window'],
+                ];
+                if (isset($config['setting'])) {
+                    $data['setting'] = $config['setting'];
+                }
+                $find = CardModel::where('name_en', $info['name_en'])->find();
+                if ($find) {
+                    $find->force()->save($data);
+                } else {
+                    CardModel::create($data);
+                }
+                Cache::delete('cardList');
+                return $this->success("安装成功");
+            }
+            return $this->error($state);
+        }
+        abort(0, "新版本没有提供下载地址!");
+    }
+
+    //打包扩展
+    function build(): \think\response\Json
+    {
+        $this->getAdmin();
+        is_demo_mode(true);
+        if (!extension_loaded('zip')) {
+            return $this->error("系统未安装或开启zip扩展,请安装后重试!");
+        }
+        if (!$this->auth) {
+            return $this->error("请获取授权后进行操作");
+        }
+        $ExtInfo = $this->request->post("extInfo", []);
+        $build = new \BrowserExtBuild($ExtInfo);
+        try {
+            $status = $build->runBuild();
+            if ($status) {
+                return $this->success('打包完毕', ['url' => '/browserExt.zip']);
+            }
+        } catch (\Exception $e) {
+            return $this->error($e->getMessage());
+        }
+        return $this->success('打包失败');
+    }
+
+
+    function folders(): \think\response\Json
+    {
+        $this->getAdmin();
+        $this->initAuth();
+        $result = \Axios::http()->post($this->authService . '/client/folders', [
+            'timeout' => 15,
+            'form_params' => [
+                'authorization_code' => $this->authCode
+            ]
+        ]);
+        $json = $result->getBody()->getContents();
+        $json = json_decode($json, true);
+        if ($json['code'] === 1) {
+            return $this->success('ok', $json['data']);
+        }
+        return $this->success('获取失败');
+    }
+
+    function links(): \think\response\Json
+    {
+        $this->getAdmin();
+        $this->initAuth();
+        $folders = $this->request->get("folders");
+        $page = $this->request->get("page", 1);
+        $limit = $this->request->get("limit", 18);
+        $result = \Axios::http()->post($this->authService . '/client/links', [
+            'timeout' => 15,
+            'form_params' => [
+                'folders' => $folders,
+                'limit' => $limit,
+                'page' => $page,
+                'authorization_code' => $this->authCode
+            ]
+        ]);
+        $json = $result->getBody()->getContents();
+        $json = json_decode($json, true);
+        if ($json['code'] === 1) {
+            $arrName = [];
+            $arrUrl = [];
+            foreach ($json['data']['data'] as $key => $value) {
+                $arrName[] = $value['name'];
+                $arrUrl[] = $value['url'];
+            }
+            $res = LinkStoreModel::whereOr([["name",'in', $arrName],['url','in',$arrUrl]])->select();
+            return json(['code'=>1,'msg'=>'ok','data'=>$json['data'],'local'=>$res]);
+        }
+        return $this->success('获取失败');
+    }
+}

+ 17 - 17
app/event.php

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

+ 10 - 10
app/middleware.php

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

+ 135 - 135
app/model/CardModel.php

@@ -1,136 +1,136 @@
-<?php
-
-namespace app\model;
-
-use think\db\exception\DataNotFoundException;
-use think\db\exception\DbException;
-use think\db\exception\ModelNotFoundException;
-use think\facade\Cache;
-use think\Model;
-
-class CardModel extends Model
-{
-    protected $name = "card";
-    protected $pk = "id";
-    static array $stopCard = [];
-
-    public static function cardStatus($name_en = ''): bool
-    {
-        $config = self::$stopCard;
-        if (count($config) == 0) {
-            $config = self::cache('cardList', 60 * 60)->select()->toArray();
-            self::$stopCard = $config;
-        }
-        foreach ($config as $item) {
-            if ($item['name_en'] == $name_en) {
-                if ($item['status'] === 1) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    public static function cardInfo($name_en = '')
-    {
-        $config = self::$stopCard;
-        if (count($config) == 0) {
-            $config = self::cache('cardList', 60 * 60)->select()->toArray();
-            self::$stopCard = $config;
-        }
-        foreach ($config as $item) {
-            if ($item['name_en'] == $name_en) {
-                return $item;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * 获取单个配置信息
-     * @param string $cardName
-     * @param string $key
-     * @param string $default
-     * @return mixed|string|null
-     */
-    public static function config(string $cardName = '', string $key = '', string $default = '')
-    {
-        $card = self::where('name_en', $cardName)->value('dict_option');
-        try {
-            $json = json_decode($card, true);
-            if (isset($json[$key])) {
-                return $json[$key];
-            }
-        } catch (\Exception $e) {
-        }
-
-        if ($default) {
-            return $default;
-        }
-        return null;
-    }
-
-    /**
-     * 获取整个配置信息 返回数组对象
-     * @param string $cardName
-     * @return mixed|string|null
-     */
-    public static function configs(string $cardName = '')
-    {
-        $card = self::where('name_en', $cardName)->value('dict_option');
-        if ($card) {
-            try {
-                return json_decode($card, true);
-            } catch (\Exception $e) {
-            }
-            return [];
-        }
-        return [];
-    }
-
-    /**
-     * 保存配置信息,完整的配置数组
-     * @param string $cardName
-     * @param array $option
-     * @return bool
-     * @throws DataNotFoundException
-     * @throws DbException
-     * @throws ModelNotFoundException
-     */
-    public static function saveConfigs(string $cardName = '', array $option = []): bool
-    {
-        $card = self::where('name_en', $cardName)->find();
-        if ($card) {
-            try {
-                $json = json_encode($option);
-                $card->save(['dict_option' => $json]);
-                return true;
-            } catch (\Exception $e) {
-            }
-        }
-        return false;
-    }
-
-
-    /**
-     * 保存单个配置信息,不存在则创建
-     * @param string $cardName
-     * @param string $key
-     * @param $value
-     * @return bool
-     * @throws DataNotFoundException
-     * @throws DbException
-     * @throws ModelNotFoundException
-     */
-    public static function saveConfig(string $cardName = '', string $key = '', $value = ''): bool
-    {
-        $card = self::where('name_en', $cardName)->find();
-        if ($card) {
-            $config = self::configs($cardName);
-            $config[$key] = $value;
-            $card->save(['dict_option' => json_encode($config)]);
-            return true;
-        }
-        return false;
-    }
+<?php
+
+namespace app\model;
+
+use think\db\exception\DataNotFoundException;
+use think\db\exception\DbException;
+use think\db\exception\ModelNotFoundException;
+use think\facade\Cache;
+use think\Model;
+
+class CardModel extends Model
+{
+    protected $name = "card";
+    protected $pk = "id";
+    static array $stopCard = [];
+
+    public static function cardStatus($name_en = ''): bool
+    {
+        $config = self::$stopCard;
+        if (count($config) == 0) {
+            $config = self::cache('cardList', 60 * 60)->select()->toArray();
+            self::$stopCard = $config;
+        }
+        foreach ($config as $item) {
+            if ($item['name_en'] == $name_en) {
+                if ($item['status'] === 1) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public static function cardInfo($name_en = '')
+    {
+        $config = self::$stopCard;
+        if (count($config) == 0) {
+            $config = self::cache('cardList', 60 * 60)->select()->toArray();
+            self::$stopCard = $config;
+        }
+        foreach ($config as $item) {
+            if ($item['name_en'] == $name_en) {
+                return $item;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 获取单个配置信息
+     * @param string $cardName
+     * @param string $key
+     * @param string $default
+     * @return mixed|string|null
+     */
+    public static function config(string $cardName = '', string $key = '', string $default = '')
+    {
+        $card = self::where('name_en', $cardName)->value('dict_option');
+        try {
+            $json = json_decode($card, true);
+            if (isset($json[$key])) {
+                return $json[$key];
+            }
+        } catch (\Exception $e) {
+        }
+
+        if ($default) {
+            return $default;
+        }
+        return null;
+    }
+
+    /**
+     * 获取整个配置信息 返回数组对象
+     * @param string $cardName
+     * @return mixed|string|null
+     */
+    public static function configs(string $cardName = '')
+    {
+        $card = self::where('name_en', $cardName)->value('dict_option');
+        if ($card) {
+            try {
+                return json_decode($card, true);
+            } catch (\Exception $e) {
+            }
+            return [];
+        }
+        return [];
+    }
+
+    /**
+     * 保存配置信息,完整的配置数组
+     * @param string $cardName
+     * @param array $option
+     * @return bool
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     */
+    public static function saveConfigs(string $cardName = '', array $option = []): bool
+    {
+        $card = self::where('name_en', $cardName)->find();
+        if ($card) {
+            try {
+                $json = json_encode($option);
+                $card->save(['dict_option' => $json]);
+                return true;
+            } catch (\Exception $e) {
+            }
+        }
+        return false;
+    }
+
+
+    /**
+     * 保存单个配置信息,不存在则创建
+     * @param string $cardName
+     * @param string $key
+     * @param $value
+     * @return bool
+     * @throws DataNotFoundException
+     * @throws DbException
+     * @throws ModelNotFoundException
+     */
+    public static function saveConfig(string $cardName = '', string $key = '', $value = ''): bool
+    {
+        $card = self::where('name_en', $cardName)->find();
+        if ($card) {
+            $config = self::configs($cardName);
+            $config[$key] = $value;
+            $card->save(['dict_option' => json_encode($config)]);
+            return true;
+        }
+        return false;
+    }
 }

+ 18 - 18
app/model/ConfigModel.php

@@ -1,18 +1,18 @@
-<?php
-/*
- * @description:
- * @Date: 2022-09-26 20:27:01
- * @LastEditTime: 2022-09-26 20:27:53
- */
-
-namespace app\model;
-
-use think\Model;
-
-class ConfigModel extends Model
-{
-    protected $name = "config";
-    protected $pk = "user_id";
-    protected $jsonAssoc = true;
-    protected $json = ['config'];
-}
+<?php
+/*
+ * @description:
+ * @Date: 2022-09-26 20:27:01
+ * @LastEditTime: 2022-09-26 20:27:53
+ */
+
+namespace app\model;
+
+use think\Model;
+
+class ConfigModel extends Model
+{
+    protected $name = "config";
+    protected $pk = "user_id";
+    protected $jsonAssoc = true;
+    protected $json = ['config'];
+}

+ 18 - 18
app/model/HistoryModel.php

@@ -1,18 +1,18 @@
-<?php
-/*
- * @description:
- * @Date: 2022-09-26 20:27:01
- * @LastEditTime: 2022-09-26 20:27:53
- */
-
-namespace app\model;
-
-use think\Model;
-
-class HistoryModel extends Model
-{
-    protected $name = "history";
-    protected $pk = "id";
-    protected $jsonAssoc = true;
-    protected $json = ['link'];
-}
+<?php
+/*
+ * @description:
+ * @Date: 2022-09-26 20:27:01
+ * @LastEditTime: 2022-09-26 20:27:53
+ */
+
+namespace app\model;
+
+use think\Model;
+
+class HistoryModel extends Model
+{
+    protected $name = "history";
+    protected $pk = "id";
+    protected $jsonAssoc = true;
+    protected $json = ['link'];
+}

+ 14 - 14
app/model/LinkFolderModel.php

@@ -1,15 +1,15 @@
-<?php
-/*
- * @description:
- * @Date: 2022-09-26 20:27:01
- * @LastEditTime: 2022-09-26 20:27:53
- */
-
-namespace app\model;
-
-use think\Model;
-
-class LinkFolderModel extends Model{
-    protected $pk = 'id';
-    protected $name = 'link_folder';
+<?php
+/*
+ * @description:
+ * @Date: 2022-09-26 20:27:01
+ * @LastEditTime: 2022-09-26 20:27:53
+ */
+
+namespace app\model;
+
+use think\Model;
+
+class LinkFolderModel extends Model{
+    protected $pk = 'id';
+    protected $name = 'link_folder';
 }

+ 51 - 51
app/model/LinkModel.php

@@ -1,51 +1,51 @@
-<?php
-/*
- * @description:
- * @Date: 2022-09-26 20:27:01
- * @LastEditTime: 2022-09-26 20:27:53
- */
-
-namespace app\model;
-
-use think\Model;
-
-class LinkModel extends Model
-{
-    protected $name = "link";
-    protected $pk = "user_id";
-    protected $autoWriteTimestamp = "datetime";
-    protected $updateTime = "update_time";
-    protected $jsonAssoc = true;
-    protected $json = ['link'];
-    protected $WebApp = [];
-    protected $card = [];
-
-    public function __construct(array $data = [])
-    {
-        parent::__construct($data);
-        $list = LinkStoreModel::where("app", 1)->select()->toArray();
-        $tmp = [];
-        foreach ($list as $k => $v) {
-            $tmp[$v['id']] = $v;
-        }
-        $this->WebApp = $tmp;
-    }
-
-    function getLinkAttr($value): array
-    {
-        foreach ($value as $k => &$v) {
-            if (isset($v['app']) && $v['app'] == 1) {
-                if (isset($v['origin_id']) && $v['origin_id'] > 0 && $v['type'] === 'icon') {
-                    if (isset($this->WebApp[(int)$v['origin_id']])) {
-                        $v['custom'] = $this->WebApp[(int)$v['origin_id']]['custom'];
-                        $v['url'] = $this->WebApp[(int)$v['origin_id']]['url'];
-                        $v['src'] = $this->WebApp[(int)$v['origin_id']]['src'];
-                        $v['name'] = $this->WebApp[(int)$v['origin_id']]['name'];
-                        $v['bgColor'] = $this->WebApp[(int)$v['origin_id']]['bgColor'];
-                    }
-                }
-            }
-        }
-        return (array)$value;
-    }
-}
+<?php
+/*
+ * @description:
+ * @Date: 2022-09-26 20:27:01
+ * @LastEditTime: 2022-09-26 20:27:53
+ */
+
+namespace app\model;
+
+use think\Model;
+
+class LinkModel extends Model
+{
+    protected $name = "link";
+    protected $pk = "user_id";
+    protected $autoWriteTimestamp = "datetime";
+    protected $updateTime = "update_time";
+    protected $jsonAssoc = true;
+    protected $json = ['link'];
+    protected $WebApp = [];
+    protected $card = [];
+
+    public function __construct(array $data = [])
+    {
+        parent::__construct($data);
+        $list = LinkStoreModel::where("app", 1)->select()->toArray();
+        $tmp = [];
+        foreach ($list as $k => $v) {
+            $tmp[$v['id']] = $v;
+        }
+        $this->WebApp = $tmp;
+    }
+
+    function getLinkAttr($value): array
+    {
+        foreach ($value as $k => &$v) {
+            if (isset($v['app']) && $v['app'] == 1) {
+                if (isset($v['origin_id']) && $v['origin_id'] > 0 && $v['type'] === 'icon') {
+                    if (isset($this->WebApp[(int)$v['origin_id']])) {
+                        $v['custom'] = $this->WebApp[(int)$v['origin_id']]['custom'];
+                        $v['url'] = $this->WebApp[(int)$v['origin_id']]['url'];
+                        $v['src'] = $this->WebApp[(int)$v['origin_id']]['src'];
+                        $v['name'] = $this->WebApp[(int)$v['origin_id']]['name'];
+                        $v['bgColor'] = $this->WebApp[(int)$v['origin_id']]['bgColor'];
+                    }
+                }
+            }
+        }
+        return (array)$value;
+    }
+}

+ 24 - 24
app/model/LinkStoreModel.php

@@ -1,24 +1,24 @@
-<?php
-
-/*
- * @description:
- * @Date: 2022-09-26 20:27:01
- * @LastEditTime: 2022-09-26 20:27:53
- */
-
-namespace app\model;
-
-use think\Model;
-
-class LinkStoreModel extends Model
-{
-    protected $name = "linkstore";
-    protected $pk = "id";
-    protected $jsonAssoc = true;
-    protected $json = ['custom'];
-
-    function userInfo(): \think\model\relation\HasOne
-    {
-        return $this->hasOne(UserModel::class, 'id', 'user_id')->field('id,nickname');
-    }
-}
+<?php
+
+/*
+ * @description:
+ * @Date: 2022-09-26 20:27:01
+ * @LastEditTime: 2022-09-26 20:27:53
+ */
+
+namespace app\model;
+
+use think\Model;
+
+class LinkStoreModel extends Model
+{
+    protected $name = "linkstore";
+    protected $pk = "id";
+    protected $jsonAssoc = true;
+    protected $json = ['custom'];
+
+    function userInfo(): \think\model\relation\HasOne
+    {
+        return $this->hasOne(UserModel::class, 'id', 'user_id')->field('id,nickname');
+    }
+}

+ 12 - 12
app/model/NoteModel.php

@@ -1,13 +1,13 @@
-<?php
-
-
-namespace app\model;
-
-
-use think\Model;
-
-class NoteModel extends Model
-{
-    protected $name = 'note';
-    protected $pk = 'id';
+<?php
+
+
+namespace app\model;
+
+
+use think\Model;
+
+class NoteModel extends Model
+{
+    protected $name = 'note';
+    protected $pk = 'id';
 }

+ 10 - 10
app/model/SearchEngineModel.php

@@ -1,11 +1,11 @@
-<?php
-
-namespace app\model;
-
-use think\Model;
-
-class SearchEngineModel extends Model
-{
-    protected $name = 'search_engine';
-    protected $pk = 'id';
+<?php
+
+namespace app\model;
+
+use think\Model;
+
+class SearchEngineModel extends Model
+{
+    protected $name = 'search_engine';
+    protected $pk = 'id';
 }

+ 47 - 47
app/model/SettingModel.php

@@ -1,47 +1,47 @@
-<?php
-/*
- * @description: 
- * @Date: 2022-09-26 20:27:01
- * @LastEditTime: 2022-09-26 20:27:53
- */
-
-namespace app\model;
-
-use think\facade\Cache;
-use think\Model;
-
-class SettingModel extends Model
-{
-    protected $name = "setting";
-    protected $pk = "keys";
-    static array $CacheConfig = [];
-
-
-    public static function Config($key = false, $default = '##')
-    {
-        $config = self::$CacheConfig;
-        if (count($config) == 0) {
-            $config = Cache::get('webConfig');
-            if (!$config) {
-                $config = self::select()->toArray();
-                $config = array_column($config, 'value', 'keys');
-                Cache::set('webConfig', $config, 300);
-                self::$CacheConfig = $config;
-            }
-        }
-        if ($key) {
-            if (isset($config[$key])) {
-                return $config[$key];
-            }
-            if ($default !== '##') {
-                return $default;
-            }
-        }
-        return $config;
-    }
-
-    public static function refreshSetting()
-    {
-        Cache::delete('webConfig');
-    }
-}
+<?php
+/*
+ * @description: 
+ * @Date: 2022-09-26 20:27:01
+ * @LastEditTime: 2022-09-26 20:27:53
+ */
+
+namespace app\model;
+
+use think\facade\Cache;
+use think\Model;
+
+class SettingModel extends Model
+{
+    protected $name = "setting";
+    protected $pk = "keys";
+    static array $CacheConfig = [];
+
+
+    public static function Config($key = false, $default = '##')
+    {
+        $config = self::$CacheConfig;
+        if (count($config) == 0) {
+            $config = Cache::get('webConfig');
+            if (!$config) {
+                $config = self::select()->toArray();
+                $config = array_column($config, 'value', 'keys');
+                Cache::set('webConfig', $config, 300);
+                self::$CacheConfig = $config;
+            }
+        }
+        if ($key) {
+            if (isset($config[$key])) {
+                return $config[$key];
+            }
+            if ($default !== '##') {
+                return $default;
+            }
+        }
+        return $config;
+    }
+
+    public static function refreshSetting()
+    {
+        Cache::delete('webConfig');
+    }
+}

+ 18 - 18
app/model/TabbarModel.php

@@ -1,18 +1,18 @@
-<?php
-/*
- * @description:
- * @Date: 2022-09-26 20:27:01
- * @LastEditTime: 2022-09-26 20:27:53
- */
-
-namespace app\model;
-
-use think\Model;
-
-class TabbarModel extends Model
-{
-    protected $name = "tabbar";
-    protected $pk = "user_id";
-    protected $jsonAssoc = true;
-    protected $json = ['tabs'];
-}
+<?php
+/*
+ * @description:
+ * @Date: 2022-09-26 20:27:01
+ * @LastEditTime: 2022-09-26 20:27:53
+ */
+
+namespace app\model;
+
+use think\Model;
+
+class TabbarModel extends Model
+{
+    protected $name = "tabbar";
+    protected $pk = "user_id";
+    protected $jsonAssoc = true;
+    protected $json = ['tabs'];
+}

+ 11 - 11
app/model/TokenModel.php

@@ -1,12 +1,12 @@
-<?php
-
-namespace app\model;
-
-use think\Model;
-
-class TokenModel extends Model
-{
-    protected $name = 'token';
-    protected $pk = 'id';
-    protected $autoWriteTimestamp = false;
+<?php
+
+namespace app\model;
+
+use think\Model;
+
+class TokenModel extends Model
+{
+    protected $name = 'token';
+    protected $pk = 'id';
+    protected $autoWriteTimestamp = false;
 }

+ 49 - 49
app/model/UserModel.php

@@ -1,50 +1,50 @@
-<?php
-
-
-namespace app\model;
-
-
-use think\Model;
-
-class UserModel extends Model
-{
-    protected $name = "user";
-    protected $pk = "id";
-    protected static $user_temp = null;
-
-    public static function getUser(bool $must = false)
-    {
-        $id = request()->header('Userid', '');
-        $token = request()->header('Token', '');
-        if (!$id) {
-            $id = request()->cookie('user_id', '');
-        }
-        if (!$token) {
-            $token = request()->cookie('token', '');
-        }
-        if ($id && $token) {
-            if (self::$user_temp) return self::$user_temp;
-            $user = TokenModel::where('user_id', $id)->where('token', $token)->field('user_id,token,create_time')->find();
-            if ($user) {
-                $status = UserModel::where('id', $user['user_id'])->find();
-                if ($status && $status['status'] === 0) {
-                    if (time() > ($user['create_time'] + 60 * 60 * 24 * 15)) {//如果创建时间大于15天则删除
-                        $user->delete();
-                    } else {
-                        if ((time() - $user['create_time']) > (864000)) { //token定时15天清理一次,10-15天内如果使用了则重新计算时间
-                            $user->create_time = time();
-                            $user->save();
-                        }
-                        self::$user_temp = $user;
-                        return $user;
-                    }
-                }
-            }
-        }
-        if ($must) {
-            json(['code' => 0, 'msg' => '请登录后操作'])->send();
-            exit();
-        }
-        return false;
-    }
+<?php
+
+
+namespace app\model;
+
+
+use think\Model;
+
+class UserModel extends Model
+{
+    protected $name = "user";
+    protected $pk = "id";
+    protected static $user_temp = null;
+
+    public static function getUser(bool $must = false)
+    {
+        $id = request()->header('Userid', '');
+        $token = request()->header('Token', '');
+        if (!$id) {
+            $id = request()->cookie('user_id', '');
+        }
+        if (!$token) {
+            $token = request()->cookie('token', '');
+        }
+        if ($id && $token) {
+            if (self::$user_temp) return self::$user_temp;
+            $user = TokenModel::where('user_id', $id)->where('token', $token)->field('user_id,token,create_time')->find();
+            if ($user) {
+                $status = UserModel::where('id', $user['user_id'])->find();
+                if ($status && $status['status'] === 0) {
+                    if (time() > ($user['create_time'] + 60 * 60 * 24 * 15)) {//如果创建时间大于15天则删除
+                        $user->delete();
+                    } else {
+                        if ((time() - $user['create_time']) > (864000)) { //token定时15天清理一次,10-15天内如果使用了则重新计算时间
+                            $user->create_time = time();
+                            $user->save();
+                        }
+                        self::$user_temp = $user;
+                        return $user;
+                    }
+                }
+            }
+        }
+        if ($must) {
+            json(['code' => 0, 'msg' => '请登录后操作'])->send();
+            exit();
+        }
+        return false;
+    }
 }

+ 12 - 12
app/model/UserSearchEngineModel.php

@@ -1,13 +1,13 @@
-<?php
-
-namespace app\model;
-
-use think\Model;
-
-class UserSearchEngineModel extends Model
-{
-    protected $name = 'user_search_engine';
-    protected $pk = 'user_id';
-    protected $jsonAssoc = true;
-    protected $json = ['list'];
+<?php
+
+namespace app\model;
+
+use think\Model;
+
+class UserSearchEngineModel extends Model
+{
+    protected $name = 'user_search_engine';
+    protected $pk = 'user_id';
+    protected $jsonAssoc = true;
+    protected $json = ['list'];
 }

+ 10 - 10
app/provider.php

@@ -1,10 +1,10 @@
-<?php
-
-use app\ExceptionHandle;
-use app\Request;
-
-// 容器Provider定义文件
-return [
-    'think\Request'          => Request::class,
-    'think\exception\Handle' => ExceptionHandle::class,
-];
+<?php
+
+use app\ExceptionHandle;
+use app\Request;
+
+// 容器Provider定义文件
+return [
+    'think\Request'          => Request::class,
+    'think\exception\Handle' => ExceptionHandle::class,
+];

+ 9 - 9
app/service.php

@@ -1,9 +1,9 @@
-<?php
-
-use app\AppService;
-
-// 系统服务定义文件
-// 服务在完成全局初始化之后执行
-return [
-    AppService::class,
-];
+<?php
+
+use app\AppService;
+
+// 系统服务定义文件
+// 服务在完成全局初始化之后执行
+return [
+    AppService::class,
+];

+ 37 - 37
app/view/cardNotFound.html

@@ -1,38 +1,38 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <title>卡片不存在</title>
-    <style>
-        * {
-            margin: 0;
-            padding: 0;
-        }
-
-        .card {
-            width: 100vw;
-            height: 100vh;
-            background: #07182E;
-            position: relative;
-            display: flex;
-            place-content: center;
-            place-items: center;
-            overflow: hidden;
-            border-radius: 8px;
-            flex-direction: column;
-            color: white;
-        }
-
-        .card h2 {
-            z-index: 1;
-            font-size:14vw;
-        }
-    </style>
-</head>
-<body style="background:  #07182E">
-<div class="card">
-    <h2>卡片不存在</h2>
-    <div>请删除后重新安装</div>
-</div>
-</body>
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>卡片不存在</title>
+    <style>
+        * {
+            margin: 0;
+            padding: 0;
+        }
+
+        .card {
+            width: 100vw;
+            height: 100vh;
+            background: #07182E;
+            position: relative;
+            display: flex;
+            place-content: center;
+            place-items: center;
+            overflow: hidden;
+            border-radius: 8px;
+            flex-direction: column;
+            color: white;
+        }
+
+        .card h2 {
+            z-index: 1;
+            font-size:14vw;
+        }
+    </style>
+</head>
+<body style="background:  #07182E">
+<div class="card">
+    <h2>卡片不存在</h2>
+    <div>请删除后重新安装</div>
+</div>
+</body>
 </html>

+ 20 - 20
auto_install.json

@@ -1,21 +1,21 @@
-{
-  "php_ext": "redis,fileinfo,opcache",
-  "chmod": [
-    {
-      "mode": 755,
-      "path": "/"
-    }
-  ],
-  "success_url": "/install.php",
-  "php_versions": "74",
-  "db_config": "",
-  "admin_username": "",
-  "admin_password": "",
-  "run_path": "/public",
-  "remove_file": [
-    "/.user.ini"
-  ],
-  "enable_functions": [
-    "shell_exec","putenv"
-  ]
+{
+  "php_ext": "redis,fileinfo,opcache",
+  "chmod": [
+    {
+      "mode": 755,
+      "path": "/"
+    }
+  ],
+  "success_url": "/install.php",
+  "php_versions": "74",
+  "db_config": "",
+  "admin_username": "",
+  "admin_password": "",
+  "run_path": "/public",
+  "remove_file": [
+    "/.user.ini"
+  ],
+  "enable_functions": [
+    "shell_exec","putenv"
+  ]
 }

+ 67 - 67
composer.json

@@ -1,67 +1,67 @@
-{
-  "name": "topthink/think",
-  "description": "the new thinkphp framework",
-  "type": "project",
-  "keywords": [
-    "framework",
-    "thinkphp",
-    "ORM"
-  ],
-  "homepage": "https://www.thinkphp.cn/",
-  "license": "Apache-2.0",
-  "authors": [
-    {
-      "name": "liu21st",
-      "email": "liu21st@gmail.com"
-    },
-    {
-      "name": "yunwuxin",
-      "email": "448901948@qq.com"
-    }
-  ],
-  "require": {
-    "php": ">=7.4",
-    "topthink/framework": "^6.1.0",
-    "topthink/think-orm": "^2.0",
-    "topthink/think-view": "^1.0",
-    "ext-json": "*",
-    "guzzlehttp/guzzle": "^7.7",
-    "ext-openssl": "*",
-    "nette/mail": "^3.1",
-    "ext-fileinfo": "*",
-    "paquettg/php-html-parser": "^3.1",
-    "topthink/think-filesystem": "^2.0",
-    "ext-mysqli": "*",
-    "ext-redis": "*",
-    "ext-pcntl": "*",
-    "ext-zip": "*",
-    "ext-posix": "*",
-    "ext-dom": "*",
-    "ext-mbstring": "*",
-    "ext-bcmath": "*",
-    "ext-gd": "*"
-  },
-  "require-dev": {
-    "symfony/var-dumper": "^4.2",
-    "topthink/think-trace": "^1.0"
-  },
-  "autoload": {
-    "psr-4": {
-      "app\\": "app",
-      "plugins\\": "plugins"
-    },
-    "psr-0": {
-      "": "extend/"
-    }
-  },
-  "config": {
-    "preferred-install": "dist",
-    "platform-check": false
-  },
-  "scripts": {
-    "post-autoload-dump": [
-      "@php think service:discover",
-      "@php think vendor:publish"
-    ]
-  }
-}
+{
+  "name": "topthink/think",
+  "description": "the new thinkphp framework",
+  "type": "project",
+  "keywords": [
+    "framework",
+    "thinkphp",
+    "ORM"
+  ],
+  "homepage": "https://www.thinkphp.cn/",
+  "license": "Apache-2.0",
+  "authors": [
+    {
+      "name": "liu21st",
+      "email": "liu21st@gmail.com"
+    },
+    {
+      "name": "yunwuxin",
+      "email": "448901948@qq.com"
+    }
+  ],
+  "require": {
+    "php": ">=7.4",
+    "topthink/framework": "^6.1.0",
+    "topthink/think-orm": "^2.0",
+    "topthink/think-view": "^1.0",
+    "ext-json": "*",
+    "guzzlehttp/guzzle": "^7.7",
+    "ext-openssl": "*",
+    "nette/mail": "^3.1",
+    "ext-fileinfo": "*",
+    "paquettg/php-html-parser": "^3.1",
+    "topthink/think-filesystem": "^2.0",
+    "ext-mysqli": "*",
+    "ext-redis": "*",
+    "ext-pcntl": "*",
+    "ext-zip": "*",
+    "ext-posix": "*",
+    "ext-dom": "*",
+    "ext-mbstring": "*",
+    "ext-bcmath": "*",
+    "ext-gd": "*"
+  },
+  "require-dev": {
+    "symfony/var-dumper": "^4.2",
+    "topthink/think-trace": "^1.0"
+  },
+  "autoload": {
+    "psr-4": {
+      "app\\": "app",
+      "plugins\\": "plugins"
+    },
+    "psr-0": {
+      "": "extend/"
+    }
+  },
+  "config": {
+    "preferred-install": "dist",
+    "platform-check": false
+  },
+  "scripts": {
+    "post-autoload-dump": [
+      "@php think service:discover",
+      "@php think vendor:publish"
+    ]
+  }
+}

+ 1982 - 1982
composer.lock

@@ -1,1982 +1,1982 @@
-{
-    "_readme": [
-        "This file locks the dependencies of your project to a known state",
-        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
-        "This file is @generated automatically"
-    ],
-    "content-hash": "6ace120b2f8636eb61fcaba620b2d5c3",
-    "packages": [
-        {
-            "name": "guzzlehttp/guzzle",
-            "version": "7.7.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/guzzle/guzzle.git",
-                "reference": "fb7566caccf22d74d1ab270de3551f72a58399f5"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/fb7566caccf22d74d1ab270de3551f72a58399f5",
-                "reference": "fb7566caccf22d74d1ab270de3551f72a58399f5",
-                "shasum": ""
-            },
-            "require": {
-                "ext-json": "*",
-                "guzzlehttp/promises": "^1.5.3 || ^2.0",
-                "guzzlehttp/psr7": "^1.9.1 || ^2.4.5",
-                "php": "^7.2.5 || ^8.0",
-                "psr/http-client": "^1.0",
-                "symfony/deprecation-contracts": "^2.2 || ^3.0"
-            },
-            "provide": {
-                "psr/http-client-implementation": "1.0"
-            },
-            "require-dev": {
-                "bamarni/composer-bin-plugin": "^1.8.1",
-                "ext-curl": "*",
-                "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999",
-                "php-http/message-factory": "^1.1",
-                "phpunit/phpunit": "^8.5.29 || ^9.5.23",
-                "psr/log": "^1.1 || ^2.0 || ^3.0"
-            },
-            "suggest": {
-                "ext-curl": "Required for CURL handler support",
-                "ext-intl": "Required for Internationalized Domain Name (IDN) support",
-                "psr/log": "Required for using the Log middleware"
-            },
-            "type": "library",
-            "extra": {
-                "bamarni-bin": {
-                    "bin-links": true,
-                    "forward-command": false
-                }
-            },
-            "autoload": {
-                "files": [
-                    "src/functions_include.php"
-                ],
-                "psr-4": {
-                    "GuzzleHttp\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Graham Campbell",
-                    "email": "hello@gjcampbell.co.uk",
-                    "homepage": "https://github.com/GrahamCampbell"
-                },
-                {
-                    "name": "Michael Dowling",
-                    "email": "mtdowling@gmail.com",
-                    "homepage": "https://github.com/mtdowling"
-                },
-                {
-                    "name": "Jeremy Lindblom",
-                    "email": "jeremeamia@gmail.com",
-                    "homepage": "https://github.com/jeremeamia"
-                },
-                {
-                    "name": "George Mponos",
-                    "email": "gmponos@gmail.com",
-                    "homepage": "https://github.com/gmponos"
-                },
-                {
-                    "name": "Tobias Nyholm",
-                    "email": "tobias.nyholm@gmail.com",
-                    "homepage": "https://github.com/Nyholm"
-                },
-                {
-                    "name": "Márk Sági-Kazár",
-                    "email": "mark.sagikazar@gmail.com",
-                    "homepage": "https://github.com/sagikazarmark"
-                },
-                {
-                    "name": "Tobias Schultze",
-                    "email": "webmaster@tubo-world.de",
-                    "homepage": "https://github.com/Tobion"
-                }
-            ],
-            "description": "Guzzle is a PHP HTTP client library",
-            "keywords": [
-                "client",
-                "curl",
-                "framework",
-                "http",
-                "http client",
-                "psr-18",
-                "psr-7",
-                "rest",
-                "web service"
-            ],
-            "support": {
-                "issues": "https://github.com/guzzle/guzzle/issues",
-                "source": "https://github.com/guzzle/guzzle/tree/7.7.0"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/GrahamCampbell",
-                    "type": "github"
-                },
-                {
-                    "url": "https://github.com/Nyholm",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2023-05-21T14:04:53+00:00"
-        },
-        {
-            "name": "guzzlehttp/promises",
-            "version": "2.0.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/guzzle/promises.git",
-                "reference": "3a494dc7dc1d7d12e511890177ae2d0e6c107da6"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/promises/zipball/3a494dc7dc1d7d12e511890177ae2d0e6c107da6",
-                "reference": "3a494dc7dc1d7d12e511890177ae2d0e6c107da6",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^7.2.5 || ^8.0"
-            },
-            "require-dev": {
-                "bamarni/composer-bin-plugin": "^1.8.1",
-                "phpunit/phpunit": "^8.5.29 || ^9.5.23"
-            },
-            "type": "library",
-            "extra": {
-                "bamarni-bin": {
-                    "bin-links": true,
-                    "forward-command": false
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "GuzzleHttp\\Promise\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Graham Campbell",
-                    "email": "hello@gjcampbell.co.uk",
-                    "homepage": "https://github.com/GrahamCampbell"
-                },
-                {
-                    "name": "Michael Dowling",
-                    "email": "mtdowling@gmail.com",
-                    "homepage": "https://github.com/mtdowling"
-                },
-                {
-                    "name": "Tobias Nyholm",
-                    "email": "tobias.nyholm@gmail.com",
-                    "homepage": "https://github.com/Nyholm"
-                },
-                {
-                    "name": "Tobias Schultze",
-                    "email": "webmaster@tubo-world.de",
-                    "homepage": "https://github.com/Tobion"
-                }
-            ],
-            "description": "Guzzle promises library",
-            "keywords": [
-                "promise"
-            ],
-            "support": {
-                "issues": "https://github.com/guzzle/promises/issues",
-                "source": "https://github.com/guzzle/promises/tree/2.0.0"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/GrahamCampbell",
-                    "type": "github"
-                },
-                {
-                    "url": "https://github.com/Nyholm",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2023-05-21T13:50:22+00:00"
-        },
-        {
-            "name": "guzzlehttp/psr7",
-            "version": "1.9.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/guzzle/psr7.git",
-                "reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/psr7/zipball/e4490cabc77465aaee90b20cfc9a770f8c04be6b",
-                "reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.4.0",
-                "psr/http-message": "~1.0",
-                "ralouphie/getallheaders": "^2.0.5 || ^3.0.0"
-            },
-            "provide": {
-                "psr/http-message-implementation": "1.0"
-            },
-            "require-dev": {
-                "ext-zlib": "*",
-                "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10"
-            },
-            "suggest": {
-                "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
-            },
-            "type": "library",
-            "autoload": {
-                "files": [
-                    "src/functions_include.php"
-                ],
-                "psr-4": {
-                    "GuzzleHttp\\Psr7\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Graham Campbell",
-                    "email": "hello@gjcampbell.co.uk",
-                    "homepage": "https://github.com/GrahamCampbell"
-                },
-                {
-                    "name": "Michael Dowling",
-                    "email": "mtdowling@gmail.com",
-                    "homepage": "https://github.com/mtdowling"
-                },
-                {
-                    "name": "George Mponos",
-                    "email": "gmponos@gmail.com",
-                    "homepage": "https://github.com/gmponos"
-                },
-                {
-                    "name": "Tobias Nyholm",
-                    "email": "tobias.nyholm@gmail.com",
-                    "homepage": "https://github.com/Nyholm"
-                },
-                {
-                    "name": "Márk Sági-Kazár",
-                    "email": "mark.sagikazar@gmail.com",
-                    "homepage": "https://github.com/sagikazarmark"
-                },
-                {
-                    "name": "Tobias Schultze",
-                    "email": "webmaster@tubo-world.de",
-                    "homepage": "https://github.com/Tobion"
-                }
-            ],
-            "description": "PSR-7 message implementation that also provides common utility methods",
-            "keywords": [
-                "http",
-                "message",
-                "psr-7",
-                "request",
-                "response",
-                "stream",
-                "uri",
-                "url"
-            ],
-            "support": {
-                "issues": "https://github.com/guzzle/psr7/issues",
-                "source": "https://github.com/guzzle/psr7/tree/1.9.1"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/GrahamCampbell",
-                    "type": "github"
-                },
-                {
-                    "url": "https://github.com/Nyholm",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2023-04-17T16:00:37+00:00"
-        },
-        {
-            "name": "league/flysystem",
-            "version": "2.5.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/thephpleague/flysystem.git",
-                "reference": "8aaffb653c5777781b0f7f69a5d937baf7ab6cdb"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/8aaffb653c5777781b0f7f69a5d937baf7ab6cdb",
-                "reference": "8aaffb653c5777781b0f7f69a5d937baf7ab6cdb",
-                "shasum": ""
-            },
-            "require": {
-                "ext-json": "*",
-                "league/mime-type-detection": "^1.0.0",
-                "php": "^7.2 || ^8.0"
-            },
-            "conflict": {
-                "guzzlehttp/ringphp": "<1.1.1"
-            },
-            "require-dev": {
-                "async-aws/s3": "^1.5",
-                "async-aws/simple-s3": "^1.0",
-                "aws/aws-sdk-php": "^3.132.4",
-                "composer/semver": "^3.0",
-                "ext-fileinfo": "*",
-                "ext-ftp": "*",
-                "friendsofphp/php-cs-fixer": "^3.2",
-                "google/cloud-storage": "^1.23",
-                "phpseclib/phpseclib": "^2.0",
-                "phpstan/phpstan": "^0.12.26",
-                "phpunit/phpunit": "^8.5 || ^9.4",
-                "sabre/dav": "^4.1"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "League\\Flysystem\\": "src"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Frank de Jonge",
-                    "email": "info@frankdejonge.nl"
-                }
-            ],
-            "description": "File storage abstraction for PHP",
-            "keywords": [
-                "WebDAV",
-                "aws",
-                "cloud",
-                "file",
-                "files",
-                "filesystem",
-                "filesystems",
-                "ftp",
-                "s3",
-                "sftp",
-                "storage"
-            ],
-            "support": {
-                "issues": "https://github.com/thephpleague/flysystem/issues",
-                "source": "https://github.com/thephpleague/flysystem/tree/2.5.0"
-            },
-            "funding": [
-                {
-                    "url": "https://ecologi.com/frankdejonge",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/frankdejonge",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/league/flysystem",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2022-09-17T21:02:32+00:00"
-        },
-        {
-            "name": "league/mime-type-detection",
-            "version": "1.11.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/thephpleague/mime-type-detection.git",
-                "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ff6248ea87a9f116e78edd6002e39e5128a0d4dd",
-                "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd",
-                "shasum": ""
-            },
-            "require": {
-                "ext-fileinfo": "*",
-                "php": "^7.2 || ^8.0"
-            },
-            "require-dev": {
-                "friendsofphp/php-cs-fixer": "^3.2",
-                "phpstan/phpstan": "^0.12.68",
-                "phpunit/phpunit": "^8.5.8 || ^9.3"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "League\\MimeTypeDetection\\": "src"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Frank de Jonge",
-                    "email": "info@frankdejonge.nl"
-                }
-            ],
-            "description": "Mime-type detection for Flysystem",
-            "support": {
-                "issues": "https://github.com/thephpleague/mime-type-detection/issues",
-                "source": "https://github.com/thephpleague/mime-type-detection/tree/1.11.0"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/frankdejonge",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/league/flysystem",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2022-04-17T13:12:02+00:00"
-        },
-        {
-            "name": "myclabs/php-enum",
-            "version": "1.8.4",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/myclabs/php-enum.git",
-                "reference": "a867478eae49c9f59ece437ae7f9506bfaa27483"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/myclabs/php-enum/zipball/a867478eae49c9f59ece437ae7f9506bfaa27483",
-                "reference": "a867478eae49c9f59ece437ae7f9506bfaa27483",
-                "shasum": ""
-            },
-            "require": {
-                "ext-json": "*",
-                "php": "^7.3 || ^8.0"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^9.5",
-                "squizlabs/php_codesniffer": "1.*",
-                "vimeo/psalm": "^4.6.2"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "MyCLabs\\Enum\\": "src/"
-                },
-                "classmap": [
-                    "stubs/Stringable.php"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "PHP Enum contributors",
-                    "homepage": "https://github.com/myclabs/php-enum/graphs/contributors"
-                }
-            ],
-            "description": "PHP Enum implementation",
-            "homepage": "http://github.com/myclabs/php-enum",
-            "keywords": [
-                "enum"
-            ],
-            "support": {
-                "issues": "https://github.com/myclabs/php-enum/issues",
-                "source": "https://github.com/myclabs/php-enum/tree/1.8.4"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/mnapoli",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2022-08-04T09:53:51+00:00"
-        },
-        {
-            "name": "nette/mail",
-            "version": "v3.1.10",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/nette/mail.git",
-                "reference": "23380ff0220c7a595d21253ac9ea64e32a1869c7"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/nette/mail/zipball/23380ff0220c7a595d21253ac9ea64e32a1869c7",
-                "reference": "23380ff0220c7a595d21253ac9ea64e32a1869c7",
-                "shasum": ""
-            },
-            "require": {
-                "ext-iconv": "*",
-                "nette/utils": "^3.1 || ~4.0.0",
-                "php": ">=7.1 <8.3"
-            },
-            "conflict": {
-                "nette/di": "<3.0-stable"
-            },
-            "require-dev": {
-                "nette/di": "^3.0.0",
-                "nette/tester": "^2.0",
-                "phpstan/phpstan-nette": "^0.12",
-                "tracy/tracy": "^2.4"
-            },
-            "suggest": {
-                "ext-fileinfo": "to detect type of attached files",
-                "ext-openssl": "to use Nette\\Mail\\DkimSigner"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "3.1-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause",
-                "GPL-2.0-only",
-                "GPL-3.0-only"
-            ],
-            "authors": [
-                {
-                    "name": "David Grudl",
-                    "homepage": "https://davidgrudl.com"
-                },
-                {
-                    "name": "Nette Community",
-                    "homepage": "https://nette.org/contributors"
-                }
-            ],
-            "description": "📧 Nette Mail: handy email creation and transfer library for PHP with both text and MIME-compliant support.",
-            "homepage": "https://nette.org",
-            "keywords": [
-                "mail",
-                "mailer",
-                "mime",
-                "nette",
-                "smtp"
-            ],
-            "support": {
-                "issues": "https://github.com/nette/mail/issues",
-                "source": "https://github.com/nette/mail/tree/v3.1.10"
-            },
-            "time": "2023-01-18T05:42:31+00:00"
-        },
-        {
-            "name": "nette/utils",
-            "version": "v3.2.9",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/nette/utils.git",
-                "reference": "c91bac3470c34b2ecd5400f6e6fdf0b64a836a5c"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/nette/utils/zipball/c91bac3470c34b2ecd5400f6e6fdf0b64a836a5c",
-                "reference": "c91bac3470c34b2ecd5400f6e6fdf0b64a836a5c",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.2 <8.3"
-            },
-            "conflict": {
-                "nette/di": "<3.0.6"
-            },
-            "require-dev": {
-                "jetbrains/phpstorm-attributes": "dev-master",
-                "nette/tester": "~2.0",
-                "phpstan/phpstan": "^1.0",
-                "tracy/tracy": "^2.3"
-            },
-            "suggest": {
-                "ext-gd": "to use Image",
-                "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()",
-                "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()",
-                "ext-json": "to use Nette\\Utils\\Json",
-                "ext-mbstring": "to use Strings::lower() etc...",
-                "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()",
-                "ext-xml": "to use Strings::length() etc. when mbstring is not available"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "3.2-dev"
-                }
-            },
-            "autoload": {
-                "classmap": [
-                    "src/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause",
-                "GPL-2.0-only",
-                "GPL-3.0-only"
-            ],
-            "authors": [
-                {
-                    "name": "David Grudl",
-                    "homepage": "https://davidgrudl.com"
-                },
-                {
-                    "name": "Nette Community",
-                    "homepage": "https://nette.org/contributors"
-                }
-            ],
-            "description": "🛠  Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.",
-            "homepage": "https://nette.org",
-            "keywords": [
-                "array",
-                "core",
-                "datetime",
-                "images",
-                "json",
-                "nette",
-                "paginator",
-                "password",
-                "slugify",
-                "string",
-                "unicode",
-                "utf-8",
-                "utility",
-                "validation"
-            ],
-            "support": {
-                "issues": "https://github.com/nette/utils/issues",
-                "source": "https://github.com/nette/utils/tree/v3.2.9"
-            },
-            "time": "2023-01-18T03:26:20+00:00"
-        },
-        {
-            "name": "paquettg/php-html-parser",
-            "version": "3.1.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/paquettg/php-html-parser.git",
-                "reference": "4e01a438ad5961cc2d7427eb9798d213c8a12629"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/paquettg/php-html-parser/zipball/4e01a438ad5961cc2d7427eb9798d213c8a12629",
-                "reference": "4e01a438ad5961cc2d7427eb9798d213c8a12629",
-                "shasum": ""
-            },
-            "require": {
-                "ext-curl": "*",
-                "ext-mbstring": "*",
-                "ext-zlib": "*",
-                "guzzlehttp/guzzle": "^7.0",
-                "guzzlehttp/psr7": "^1.6",
-                "myclabs/php-enum": "^1.7",
-                "paquettg/string-encode": "~1.0.0",
-                "php": ">=7.2",
-                "php-http/httplug": "^2.1"
-            },
-            "require-dev": {
-                "friendsofphp/php-cs-fixer": "^2.16",
-                "infection/infection": "^0.13.4",
-                "mockery/mockery": "^1.2",
-                "phan/phan": "^2.4",
-                "phpunit/phpunit": "^7.5.1"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "PHPHtmlParser\\": "src/PHPHtmlParser"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Gilles Paquette",
-                    "email": "paquettg@gmail.com",
-                    "homepage": "http://gillespaquette.ca"
-                }
-            ],
-            "description": "An HTML DOM parser. It allows you to manipulate HTML. Find tags on an HTML page with selectors just like jQuery.",
-            "homepage": "https://github.com/paquettg/php-html-parser",
-            "keywords": [
-                "dom",
-                "html",
-                "parser"
-            ],
-            "support": {
-                "issues": "https://github.com/paquettg/php-html-parser/issues",
-                "source": "https://github.com/paquettg/php-html-parser/tree/3.1.1"
-            },
-            "funding": [
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/paquettg/php-html-parser",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2020-11-01T20:34:43+00:00"
-        },
-        {
-            "name": "paquettg/string-encode",
-            "version": "1.0.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/paquettg/string-encoder.git",
-                "reference": "a8708e9fac9d5ddfc8fc2aac6004e2cd05d80fee"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/paquettg/string-encoder/zipball/a8708e9fac9d5ddfc8fc2aac6004e2cd05d80fee",
-                "reference": "a8708e9fac9d5ddfc8fc2aac6004e2cd05d80fee",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^7.5.1"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-0": {
-                    "stringEncode": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Gilles Paquette",
-                    "email": "paquettg@gmail.com",
-                    "homepage": "http://gillespaquette.ca"
-                }
-            ],
-            "description": "Facilitating the process of altering string encoding in PHP.",
-            "homepage": "https://github.com/paquettg/string-encoder",
-            "keywords": [
-                "charset",
-                "encoding",
-                "string"
-            ],
-            "support": {
-                "issues": "https://github.com/paquettg/string-encoder/issues",
-                "source": "https://github.com/paquettg/string-encoder/tree/1.0.1"
-            },
-            "time": "2018-12-21T02:25:09+00:00"
-        },
-        {
-            "name": "php-http/httplug",
-            "version": "2.4.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/php-http/httplug.git",
-                "reference": "625ad742c360c8ac580fcc647a1541d29e257f67"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/php-http/httplug/zipball/625ad742c360c8ac580fcc647a1541d29e257f67",
-                "reference": "625ad742c360c8ac580fcc647a1541d29e257f67",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^7.1 || ^8.0",
-                "php-http/promise": "^1.1",
-                "psr/http-client": "^1.0",
-                "psr/http-message": "^1.0 || ^2.0"
-            },
-            "require-dev": {
-                "friends-of-phpspec/phpspec-code-coverage": "^4.1 || ^5.0 || ^6.0",
-                "phpspec/phpspec": "^5.1 || ^6.0 || ^7.0"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "Http\\Client\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Eric GELOEN",
-                    "email": "geloen.eric@gmail.com"
-                },
-                {
-                    "name": "Márk Sági-Kazár",
-                    "email": "mark.sagikazar@gmail.com",
-                    "homepage": "https://sagikazarmark.hu"
-                }
-            ],
-            "description": "HTTPlug, the HTTP client abstraction for PHP",
-            "homepage": "http://httplug.io",
-            "keywords": [
-                "client",
-                "http"
-            ],
-            "support": {
-                "issues": "https://github.com/php-http/httplug/issues",
-                "source": "https://github.com/php-http/httplug/tree/2.4.0"
-            },
-            "time": "2023-04-14T15:10:03+00:00"
-        },
-        {
-            "name": "php-http/promise",
-            "version": "1.1.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/php-http/promise.git",
-                "reference": "4c4c1f9b7289a2ec57cde7f1e9762a5789506f88"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/php-http/promise/zipball/4c4c1f9b7289a2ec57cde7f1e9762a5789506f88",
-                "reference": "4c4c1f9b7289a2ec57cde7f1e9762a5789506f88",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^7.1 || ^8.0"
-            },
-            "require-dev": {
-                "friends-of-phpspec/phpspec-code-coverage": "^4.3.2",
-                "phpspec/phpspec": "^5.1.2 || ^6.2"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.1-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Http\\Promise\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Joel Wurtz",
-                    "email": "joel.wurtz@gmail.com"
-                },
-                {
-                    "name": "Márk Sági-Kazár",
-                    "email": "mark.sagikazar@gmail.com"
-                }
-            ],
-            "description": "Promise used for asynchronous HTTP requests",
-            "homepage": "http://httplug.io",
-            "keywords": [
-                "promise"
-            ],
-            "support": {
-                "issues": "https://github.com/php-http/promise/issues",
-                "source": "https://github.com/php-http/promise/tree/1.1.0"
-            },
-            "time": "2020-07-07T09:29:14+00:00"
-        },
-        {
-            "name": "psr/container",
-            "version": "1.1.2",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/php-fig/container.git",
-                "reference": "513e0666f7216c7459170d56df27dfcefe1689ea"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea",
-                "reference": "513e0666f7216c7459170d56df27dfcefe1689ea",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.4.0"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "Psr\\Container\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "PHP-FIG",
-                    "homepage": "https://www.php-fig.org/"
-                }
-            ],
-            "description": "Common Container Interface (PHP FIG PSR-11)",
-            "homepage": "https://github.com/php-fig/container",
-            "keywords": [
-                "PSR-11",
-                "container",
-                "container-interface",
-                "container-interop",
-                "psr"
-            ],
-            "support": {
-                "issues": "https://github.com/php-fig/container/issues",
-                "source": "https://github.com/php-fig/container/tree/1.1.2"
-            },
-            "time": "2021-11-05T16:50:12+00:00"
-        },
-        {
-            "name": "psr/http-client",
-            "version": "1.0.2",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/php-fig/http-client.git",
-                "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/php-fig/http-client/zipball/0955afe48220520692d2d09f7ab7e0f93ffd6a31",
-                "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^7.0 || ^8.0",
-                "psr/http-message": "^1.0 || ^2.0"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.0.x-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Psr\\Http\\Client\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "PHP-FIG",
-                    "homepage": "https://www.php-fig.org/"
-                }
-            ],
-            "description": "Common interface for HTTP clients",
-            "homepage": "https://github.com/php-fig/http-client",
-            "keywords": [
-                "http",
-                "http-client",
-                "psr",
-                "psr-18"
-            ],
-            "support": {
-                "source": "https://github.com/php-fig/http-client/tree/1.0.2"
-            },
-            "time": "2023-04-10T20:12:12+00:00"
-        },
-        {
-            "name": "psr/http-message",
-            "version": "1.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/php-fig/http-message.git",
-                "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
-                "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^7.2 || ^8.0"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.1.x-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Psr\\Http\\Message\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "PHP-FIG",
-                    "homepage": "http://www.php-fig.org/"
-                }
-            ],
-            "description": "Common interface for HTTP messages",
-            "homepage": "https://github.com/php-fig/http-message",
-            "keywords": [
-                "http",
-                "http-message",
-                "psr",
-                "psr-7",
-                "request",
-                "response"
-            ],
-            "support": {
-                "source": "https://github.com/php-fig/http-message/tree/1.1"
-            },
-            "time": "2023-04-04T09:50:52+00:00"
-        },
-        {
-            "name": "psr/log",
-            "version": "1.1.4",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/php-fig/log.git",
-                "reference": "d49695b909c3b7628b6289db5479a1c204601f11"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11",
-                "reference": "d49695b909c3b7628b6289db5479a1c204601f11",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.3.0"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.1.x-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Psr\\Log\\": "Psr/Log/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "PHP-FIG",
-                    "homepage": "https://www.php-fig.org/"
-                }
-            ],
-            "description": "Common interface for logging libraries",
-            "homepage": "https://github.com/php-fig/log",
-            "keywords": [
-                "log",
-                "psr",
-                "psr-3"
-            ],
-            "support": {
-                "source": "https://github.com/php-fig/log/tree/1.1.4"
-            },
-            "time": "2021-05-03T11:20:27+00:00"
-        },
-        {
-            "name": "psr/simple-cache",
-            "version": "1.0.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/php-fig/simple-cache.git",
-                "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
-                "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.3.0"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.0.x-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Psr\\SimpleCache\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "PHP-FIG",
-                    "homepage": "http://www.php-fig.org/"
-                }
-            ],
-            "description": "Common interfaces for simple caching",
-            "keywords": [
-                "cache",
-                "caching",
-                "psr",
-                "psr-16",
-                "simple-cache"
-            ],
-            "support": {
-                "source": "https://github.com/php-fig/simple-cache/tree/master"
-            },
-            "time": "2017-10-23T01:57:42+00:00"
-        },
-        {
-            "name": "ralouphie/getallheaders",
-            "version": "3.0.3",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/ralouphie/getallheaders.git",
-                "reference": "120b605dfeb996808c31b6477290a714d356e822"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
-                "reference": "120b605dfeb996808c31b6477290a714d356e822",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.6"
-            },
-            "require-dev": {
-                "php-coveralls/php-coveralls": "^2.1",
-                "phpunit/phpunit": "^5 || ^6.5"
-            },
-            "type": "library",
-            "autoload": {
-                "files": [
-                    "src/getallheaders.php"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Ralph Khattar",
-                    "email": "ralph.khattar@gmail.com"
-                }
-            ],
-            "description": "A polyfill for getallheaders.",
-            "support": {
-                "issues": "https://github.com/ralouphie/getallheaders/issues",
-                "source": "https://github.com/ralouphie/getallheaders/tree/develop"
-            },
-            "time": "2019-03-08T08:55:37+00:00"
-        },
-        {
-            "name": "symfony/deprecation-contracts",
-            "version": "v2.5.2",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/deprecation-contracts.git",
-                "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66",
-                "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-main": "2.5-dev"
-                },
-                "thanks": {
-                    "name": "symfony/contracts",
-                    "url": "https://github.com/symfony/contracts"
-                }
-            },
-            "autoload": {
-                "files": [
-                    "function.php"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "A generic function and convention to trigger deprecation notices",
-            "homepage": "https://symfony.com",
-            "support": {
-                "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2022-01-02T09:53:40+00:00"
-        },
-        {
-            "name": "topthink/framework",
-            "version": "v6.1.4",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/top-think/framework.git",
-                "reference": "66eb9cf4d627df12911344cd328faf9bb596bf2c"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/top-think/framework/zipball/66eb9cf4d627df12911344cd328faf9bb596bf2c",
-                "reference": "66eb9cf4d627df12911344cd328faf9bb596bf2c",
-                "shasum": ""
-            },
-            "require": {
-                "ext-json": "*",
-                "ext-mbstring": "*",
-                "php": ">=7.2.5",
-                "psr/container": "~1.0",
-                "psr/http-message": "^1.0",
-                "psr/log": "~1.0",
-                "psr/simple-cache": "^1.0",
-                "topthink/think-helper": "^3.1.1",
-                "topthink/think-orm": "^2.0|^3.0"
-            },
-            "require-dev": {
-                "guzzlehttp/psr7": "^2.1.0",
-                "mikey179/vfsstream": "^1.6",
-                "mockery/mockery": "^1.2",
-                "phpunit/phpunit": "^7.0"
-            },
-            "type": "library",
-            "autoload": {
-                "files": [],
-                "psr-4": {
-                    "think\\": "src/think/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "Apache-2.0"
-            ],
-            "authors": [
-                {
-                    "name": "liu21st",
-                    "email": "liu21st@gmail.com"
-                },
-                {
-                    "name": "yunwuxin",
-                    "email": "448901948@qq.com"
-                }
-            ],
-            "description": "The ThinkPHP Framework.",
-            "homepage": "http://thinkphp.cn/",
-            "keywords": [
-                "framework",
-                "orm",
-                "thinkphp"
-            ],
-            "support": {
-                "issues": "https://github.com/top-think/framework/issues",
-                "source": "https://github.com/top-think/framework/tree/v6.1.4"
-            },
-            "time": "2023-07-11T15:16:03+00:00"
-        },
-        {
-            "name": "topthink/think-filesystem",
-            "version": "v2.0.2",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/top-think/think-filesystem.git",
-                "reference": "c08503232fcae0c3c7fefae5e6b5c841ffe09f2f"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/top-think/think-filesystem/zipball/c08503232fcae0c3c7fefae5e6b5c841ffe09f2f",
-                "reference": "c08503232fcae0c3c7fefae5e6b5c841ffe09f2f",
-                "shasum": ""
-            },
-            "require": {
-                "league/flysystem": "^2.0",
-                "topthink/framework": "^6.1|^8.0"
-            },
-            "require-dev": {
-                "mikey179/vfsstream": "^1.6",
-                "mockery/mockery": "^1.2",
-                "phpunit/phpunit": "^8.0"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "think\\": "src"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "Apache-2.0"
-            ],
-            "authors": [
-                {
-                    "name": "yunwuxin",
-                    "email": "448901948@qq.com"
-                }
-            ],
-            "description": "The ThinkPHP6.1 Filesystem Package",
-            "support": {
-                "issues": "https://github.com/top-think/think-filesystem/issues",
-                "source": "https://github.com/top-think/think-filesystem/tree/v2.0.2"
-            },
-            "time": "2023-02-08T01:23:42+00:00"
-        },
-        {
-            "name": "topthink/think-helper",
-            "version": "v3.1.6",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/top-think/think-helper.git",
-                "reference": "769acbe50a4274327162f9c68ec2e89a38eb2aff"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/top-think/think-helper/zipball/769acbe50a4274327162f9c68ec2e89a38eb2aff",
-                "reference": "769acbe50a4274327162f9c68ec2e89a38eb2aff",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1.0"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^9.5"
-            },
-            "type": "library",
-            "autoload": {
-                "files": [
-                    "src/helper.php"
-                ],
-                "psr-4": {
-                    "think\\": "src"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "Apache-2.0"
-            ],
-            "authors": [
-                {
-                    "name": "yunwuxin",
-                    "email": "448901948@qq.com"
-                }
-            ],
-            "description": "The ThinkPHP6 Helper Package",
-            "support": {
-                "issues": "https://github.com/top-think/think-helper/issues",
-                "source": "https://github.com/top-think/think-helper/tree/v3.1.6"
-            },
-            "time": "2021-12-15T04:27:55+00:00"
-        },
-        {
-            "name": "topthink/think-orm",
-            "version": "v2.0.61",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/top-think/think-orm.git",
-                "reference": "10528ebf4a5106b19c3bac9c6deae7a67ff49de6"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/top-think/think-orm/zipball/10528ebf4a5106b19c3bac9c6deae7a67ff49de6",
-                "reference": "10528ebf4a5106b19c3bac9c6deae7a67ff49de6",
-                "shasum": ""
-            },
-            "require": {
-                "ext-json": "*",
-                "ext-pdo": "*",
-                "php": ">=7.1.0",
-                "psr/log": "^1.0|^2.0",
-                "psr/simple-cache": "^1.0|^2.0",
-                "topthink/think-helper": "^3.1"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^7|^8|^9.5"
-            },
-            "type": "library",
-            "autoload": {
-                "files": [
-                    "stubs/load_stubs.php"
-                ],
-                "psr-4": {
-                    "think\\": "src"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "Apache-2.0"
-            ],
-            "authors": [
-                {
-                    "name": "liu21st",
-                    "email": "liu21st@gmail.com"
-                }
-            ],
-            "description": "think orm",
-            "keywords": [
-                "database",
-                "orm"
-            ],
-            "support": {
-                "issues": "https://github.com/top-think/think-orm/issues",
-                "source": "https://github.com/top-think/think-orm/tree/v2.0.61"
-            },
-            "time": "2023-04-20T14:27:51+00:00"
-        },
-        {
-            "name": "topthink/think-template",
-            "version": "v2.0.9",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/top-think/think-template.git",
-                "reference": "6d25642ae0e306166742fd7073dc7a159e18073c"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/top-think/think-template/zipball/6d25642ae0e306166742fd7073dc7a159e18073c",
-                "reference": "6d25642ae0e306166742fd7073dc7a159e18073c",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1.0",
-                "psr/simple-cache": "^1.0"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "think\\": "src"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "Apache-2.0"
-            ],
-            "authors": [
-                {
-                    "name": "liu21st",
-                    "email": "liu21st@gmail.com"
-                }
-            ],
-            "description": "the php template engine",
-            "support": {
-                "issues": "https://github.com/top-think/think-template/issues",
-                "source": "https://github.com/top-think/think-template/tree/v2.0.9"
-            },
-            "time": "2023-02-14T10:50:39+00:00"
-        },
-        {
-            "name": "topthink/think-view",
-            "version": "v1.0.14",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/top-think/think-view.git",
-                "reference": "edce0ae2c9551ab65f9e94a222604b0dead3576d"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/top-think/think-view/zipball/edce0ae2c9551ab65f9e94a222604b0dead3576d",
-                "reference": "edce0ae2c9551ab65f9e94a222604b0dead3576d",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1.0",
-                "topthink/think-template": "^2.0"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "think\\view\\driver\\": "src"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "Apache-2.0"
-            ],
-            "authors": [
-                {
-                    "name": "liu21st",
-                    "email": "liu21st@gmail.com"
-                }
-            ],
-            "description": "thinkphp template driver",
-            "support": {
-                "issues": "https://github.com/top-think/think-view/issues",
-                "source": "https://github.com/top-think/think-view/tree/v1.0.14"
-            },
-            "time": "2019-11-06T11:40:13+00:00"
-        }
-    ],
-    "packages-dev": [
-        {
-            "name": "symfony/polyfill-mbstring",
-            "version": "v1.27.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/polyfill-mbstring.git",
-                "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
-                "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1"
-            },
-            "provide": {
-                "ext-mbstring": "*"
-            },
-            "suggest": {
-                "ext-mbstring": "For best performance"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-main": "1.27-dev"
-                },
-                "thanks": {
-                    "name": "symfony/polyfill",
-                    "url": "https://github.com/symfony/polyfill"
-                }
-            },
-            "autoload": {
-                "files": [
-                    "bootstrap.php"
-                ],
-                "psr-4": {
-                    "Symfony\\Polyfill\\Mbstring\\": ""
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Symfony polyfill for the Mbstring extension",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "compatibility",
-                "mbstring",
-                "polyfill",
-                "portable",
-                "shim"
-            ],
-            "support": {
-                "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2022-11-03T14:55:06+00:00"
-        },
-        {
-            "name": "symfony/polyfill-php72",
-            "version": "v1.27.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/polyfill-php72.git",
-                "reference": "869329b1e9894268a8a61dabb69153029b7a8c97"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/869329b1e9894268a8a61dabb69153029b7a8c97",
-                "reference": "869329b1e9894268a8a61dabb69153029b7a8c97",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-main": "1.27-dev"
-                },
-                "thanks": {
-                    "name": "symfony/polyfill",
-                    "url": "https://github.com/symfony/polyfill"
-                }
-            },
-            "autoload": {
-                "files": [
-                    "bootstrap.php"
-                ],
-                "psr-4": {
-                    "Symfony\\Polyfill\\Php72\\": ""
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "compatibility",
-                "polyfill",
-                "portable",
-                "shim"
-            ],
-            "support": {
-                "source": "https://github.com/symfony/polyfill-php72/tree/v1.27.0"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2022-11-03T14:55:06+00:00"
-        },
-        {
-            "name": "symfony/polyfill-php80",
-            "version": "v1.27.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/polyfill-php80.git",
-                "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936",
-                "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-main": "1.27-dev"
-                },
-                "thanks": {
-                    "name": "symfony/polyfill",
-                    "url": "https://github.com/symfony/polyfill"
-                }
-            },
-            "autoload": {
-                "files": [
-                    "bootstrap.php"
-                ],
-                "psr-4": {
-                    "Symfony\\Polyfill\\Php80\\": ""
-                },
-                "classmap": [
-                    "Resources/stubs"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Ion Bazan",
-                    "email": "ion.bazan@gmail.com"
-                },
-                {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "compatibility",
-                "polyfill",
-                "portable",
-                "shim"
-            ],
-            "support": {
-                "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2022-11-03T14:55:06+00:00"
-        },
-        {
-            "name": "symfony/var-dumper",
-            "version": "v4.4.47",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/var-dumper.git",
-                "reference": "1069c7a3fca74578022fab6f81643248d02f8e63"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/var-dumper/zipball/1069c7a3fca74578022fab6f81643248d02f8e63",
-                "reference": "1069c7a3fca74578022fab6f81643248d02f8e63",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1.3",
-                "symfony/polyfill-mbstring": "~1.0",
-                "symfony/polyfill-php72": "~1.5",
-                "symfony/polyfill-php80": "^1.16"
-            },
-            "conflict": {
-                "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0",
-                "symfony/console": "<3.4"
-            },
-            "require-dev": {
-                "ext-iconv": "*",
-                "symfony/console": "^3.4|^4.0|^5.0",
-                "symfony/process": "^4.4|^5.0",
-                "twig/twig": "^1.43|^2.13|^3.0.4"
-            },
-            "suggest": {
-                "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).",
-                "ext-intl": "To show region name in time zone dump",
-                "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script"
-            },
-            "bin": [
-                "Resources/bin/var-dump-server"
-            ],
-            "type": "library",
-            "autoload": {
-                "files": [
-                    "Resources/functions/dump.php"
-                ],
-                "psr-4": {
-                    "Symfony\\Component\\VarDumper\\": ""
-                },
-                "exclude-from-classmap": [
-                    "/Tests/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Provides mechanisms for walking through any arbitrary PHP variable",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "debug",
-                "dump"
-            ],
-            "support": {
-                "source": "https://github.com/symfony/var-dumper/tree/v4.4.47"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2022-10-03T15:15:11+00:00"
-        },
-        {
-            "name": "topthink/think-trace",
-            "version": "v1.6",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/top-think/think-trace.git",
-                "reference": "136cd5d97e8bdb780e4b5c1637c588ed7ca3e142"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/top-think/think-trace/zipball/136cd5d97e8bdb780e4b5c1637c588ed7ca3e142",
-                "reference": "136cd5d97e8bdb780e4b5c1637c588ed7ca3e142",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1.0",
-                "topthink/framework": "^6.0|^8.0"
-            },
-            "type": "library",
-            "extra": {
-                "think": {
-                    "services": [
-                        "think\\trace\\Service"
-                    ],
-                    "config": {
-                        "trace": "src/config.php"
-                    }
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "think\\trace\\": "src"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "Apache-2.0"
-            ],
-            "authors": [
-                {
-                    "name": "liu21st",
-                    "email": "liu21st@gmail.com"
-                }
-            ],
-            "description": "thinkphp debug trace",
-            "support": {
-                "issues": "https://github.com/top-think/think-trace/issues",
-                "source": "https://github.com/top-think/think-trace/tree/v1.6"
-            },
-            "time": "2023-02-07T08:36:32+00:00"
-        }
-    ],
-    "aliases": [],
-    "minimum-stability": "stable",
-    "stability-flags": [],
-    "prefer-stable": false,
-    "prefer-lowest": false,
-    "platform": {
-        "php": ">=7.4",
-        "ext-json": "*",
-        "ext-openssl": "*",
-        "ext-fileinfo": "*",
-        "ext-mysqli": "*",
-        "ext-redis": "*",
-        "ext-pcntl": "*",
-        "ext-zip": "*",
-        "ext-posix": "*"
-    },
-    "platform-dev": [],
-    "plugin-api-version": "2.0.0"
-}
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "6ace120b2f8636eb61fcaba620b2d5c3",
+    "packages": [
+        {
+            "name": "guzzlehttp/guzzle",
+            "version": "7.7.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/guzzle.git",
+                "reference": "fb7566caccf22d74d1ab270de3551f72a58399f5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/fb7566caccf22d74d1ab270de3551f72a58399f5",
+                "reference": "fb7566caccf22d74d1ab270de3551f72a58399f5",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "guzzlehttp/promises": "^1.5.3 || ^2.0",
+                "guzzlehttp/psr7": "^1.9.1 || ^2.4.5",
+                "php": "^7.2.5 || ^8.0",
+                "psr/http-client": "^1.0",
+                "symfony/deprecation-contracts": "^2.2 || ^3.0"
+            },
+            "provide": {
+                "psr/http-client-implementation": "1.0"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.1",
+                "ext-curl": "*",
+                "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999",
+                "php-http/message-factory": "^1.1",
+                "phpunit/phpunit": "^8.5.29 || ^9.5.23",
+                "psr/log": "^1.1 || ^2.0 || ^3.0"
+            },
+            "suggest": {
+                "ext-curl": "Required for CURL handler support",
+                "ext-intl": "Required for Internationalized Domain Name (IDN) support",
+                "psr/log": "Required for using the Log middleware"
+            },
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": false
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/functions_include.php"
+                ],
+                "psr-4": {
+                    "GuzzleHttp\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "Jeremy Lindblom",
+                    "email": "jeremeamia@gmail.com",
+                    "homepage": "https://github.com/jeremeamia"
+                },
+                {
+                    "name": "George Mponos",
+                    "email": "gmponos@gmail.com",
+                    "homepage": "https://github.com/gmponos"
+                },
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                },
+                {
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com",
+                    "homepage": "https://github.com/sagikazarmark"
+                },
+                {
+                    "name": "Tobias Schultze",
+                    "email": "webmaster@tubo-world.de",
+                    "homepage": "https://github.com/Tobion"
+                }
+            ],
+            "description": "Guzzle is a PHP HTTP client library",
+            "keywords": [
+                "client",
+                "curl",
+                "framework",
+                "http",
+                "http client",
+                "psr-18",
+                "psr-7",
+                "rest",
+                "web service"
+            ],
+            "support": {
+                "issues": "https://github.com/guzzle/guzzle/issues",
+                "source": "https://github.com/guzzle/guzzle/tree/7.7.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/Nyholm",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2023-05-21T14:04:53+00:00"
+        },
+        {
+            "name": "guzzlehttp/promises",
+            "version": "2.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/promises.git",
+                "reference": "3a494dc7dc1d7d12e511890177ae2d0e6c107da6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/promises/zipball/3a494dc7dc1d7d12e511890177ae2d0e6c107da6",
+                "reference": "3a494dc7dc1d7d12e511890177ae2d0e6c107da6",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2.5 || ^8.0"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.1",
+                "phpunit/phpunit": "^8.5.29 || ^9.5.23"
+            },
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": false
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Promise\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                },
+                {
+                    "name": "Tobias Schultze",
+                    "email": "webmaster@tubo-world.de",
+                    "homepage": "https://github.com/Tobion"
+                }
+            ],
+            "description": "Guzzle promises library",
+            "keywords": [
+                "promise"
+            ],
+            "support": {
+                "issues": "https://github.com/guzzle/promises/issues",
+                "source": "https://github.com/guzzle/promises/tree/2.0.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/Nyholm",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2023-05-21T13:50:22+00:00"
+        },
+        {
+            "name": "guzzlehttp/psr7",
+            "version": "1.9.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/psr7.git",
+                "reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/psr7/zipball/e4490cabc77465aaee90b20cfc9a770f8c04be6b",
+                "reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4.0",
+                "psr/http-message": "~1.0",
+                "ralouphie/getallheaders": "^2.0.5 || ^3.0.0"
+            },
+            "provide": {
+                "psr/http-message-implementation": "1.0"
+            },
+            "require-dev": {
+                "ext-zlib": "*",
+                "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10"
+            },
+            "suggest": {
+                "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/functions_include.php"
+                ],
+                "psr-4": {
+                    "GuzzleHttp\\Psr7\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "George Mponos",
+                    "email": "gmponos@gmail.com",
+                    "homepage": "https://github.com/gmponos"
+                },
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                },
+                {
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com",
+                    "homepage": "https://github.com/sagikazarmark"
+                },
+                {
+                    "name": "Tobias Schultze",
+                    "email": "webmaster@tubo-world.de",
+                    "homepage": "https://github.com/Tobion"
+                }
+            ],
+            "description": "PSR-7 message implementation that also provides common utility methods",
+            "keywords": [
+                "http",
+                "message",
+                "psr-7",
+                "request",
+                "response",
+                "stream",
+                "uri",
+                "url"
+            ],
+            "support": {
+                "issues": "https://github.com/guzzle/psr7/issues",
+                "source": "https://github.com/guzzle/psr7/tree/1.9.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/Nyholm",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2023-04-17T16:00:37+00:00"
+        },
+        {
+            "name": "league/flysystem",
+            "version": "2.5.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/thephpleague/flysystem.git",
+                "reference": "8aaffb653c5777781b0f7f69a5d937baf7ab6cdb"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/8aaffb653c5777781b0f7f69a5d937baf7ab6cdb",
+                "reference": "8aaffb653c5777781b0f7f69a5d937baf7ab6cdb",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "league/mime-type-detection": "^1.0.0",
+                "php": "^7.2 || ^8.0"
+            },
+            "conflict": {
+                "guzzlehttp/ringphp": "<1.1.1"
+            },
+            "require-dev": {
+                "async-aws/s3": "^1.5",
+                "async-aws/simple-s3": "^1.0",
+                "aws/aws-sdk-php": "^3.132.4",
+                "composer/semver": "^3.0",
+                "ext-fileinfo": "*",
+                "ext-ftp": "*",
+                "friendsofphp/php-cs-fixer": "^3.2",
+                "google/cloud-storage": "^1.23",
+                "phpseclib/phpseclib": "^2.0",
+                "phpstan/phpstan": "^0.12.26",
+                "phpunit/phpunit": "^8.5 || ^9.4",
+                "sabre/dav": "^4.1"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "League\\Flysystem\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Frank de Jonge",
+                    "email": "info@frankdejonge.nl"
+                }
+            ],
+            "description": "File storage abstraction for PHP",
+            "keywords": [
+                "WebDAV",
+                "aws",
+                "cloud",
+                "file",
+                "files",
+                "filesystem",
+                "filesystems",
+                "ftp",
+                "s3",
+                "sftp",
+                "storage"
+            ],
+            "support": {
+                "issues": "https://github.com/thephpleague/flysystem/issues",
+                "source": "https://github.com/thephpleague/flysystem/tree/2.5.0"
+            },
+            "funding": [
+                {
+                    "url": "https://ecologi.com/frankdejonge",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/frankdejonge",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/league/flysystem",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2022-09-17T21:02:32+00:00"
+        },
+        {
+            "name": "league/mime-type-detection",
+            "version": "1.11.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/thephpleague/mime-type-detection.git",
+                "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ff6248ea87a9f116e78edd6002e39e5128a0d4dd",
+                "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd",
+                "shasum": ""
+            },
+            "require": {
+                "ext-fileinfo": "*",
+                "php": "^7.2 || ^8.0"
+            },
+            "require-dev": {
+                "friendsofphp/php-cs-fixer": "^3.2",
+                "phpstan/phpstan": "^0.12.68",
+                "phpunit/phpunit": "^8.5.8 || ^9.3"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "League\\MimeTypeDetection\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Frank de Jonge",
+                    "email": "info@frankdejonge.nl"
+                }
+            ],
+            "description": "Mime-type detection for Flysystem",
+            "support": {
+                "issues": "https://github.com/thephpleague/mime-type-detection/issues",
+                "source": "https://github.com/thephpleague/mime-type-detection/tree/1.11.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/frankdejonge",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/league/flysystem",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2022-04-17T13:12:02+00:00"
+        },
+        {
+            "name": "myclabs/php-enum",
+            "version": "1.8.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/myclabs/php-enum.git",
+                "reference": "a867478eae49c9f59ece437ae7f9506bfaa27483"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/myclabs/php-enum/zipball/a867478eae49c9f59ece437ae7f9506bfaa27483",
+                "reference": "a867478eae49c9f59ece437ae7f9506bfaa27483",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "php": "^7.3 || ^8.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.5",
+                "squizlabs/php_codesniffer": "1.*",
+                "vimeo/psalm": "^4.6.2"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "MyCLabs\\Enum\\": "src/"
+                },
+                "classmap": [
+                    "stubs/Stringable.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP Enum contributors",
+                    "homepage": "https://github.com/myclabs/php-enum/graphs/contributors"
+                }
+            ],
+            "description": "PHP Enum implementation",
+            "homepage": "http://github.com/myclabs/php-enum",
+            "keywords": [
+                "enum"
+            ],
+            "support": {
+                "issues": "https://github.com/myclabs/php-enum/issues",
+                "source": "https://github.com/myclabs/php-enum/tree/1.8.4"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/mnapoli",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2022-08-04T09:53:51+00:00"
+        },
+        {
+            "name": "nette/mail",
+            "version": "v3.1.10",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/nette/mail.git",
+                "reference": "23380ff0220c7a595d21253ac9ea64e32a1869c7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/nette/mail/zipball/23380ff0220c7a595d21253ac9ea64e32a1869c7",
+                "reference": "23380ff0220c7a595d21253ac9ea64e32a1869c7",
+                "shasum": ""
+            },
+            "require": {
+                "ext-iconv": "*",
+                "nette/utils": "^3.1 || ~4.0.0",
+                "php": ">=7.1 <8.3"
+            },
+            "conflict": {
+                "nette/di": "<3.0-stable"
+            },
+            "require-dev": {
+                "nette/di": "^3.0.0",
+                "nette/tester": "^2.0",
+                "phpstan/phpstan-nette": "^0.12",
+                "tracy/tracy": "^2.4"
+            },
+            "suggest": {
+                "ext-fileinfo": "to detect type of attached files",
+                "ext-openssl": "to use Nette\\Mail\\DkimSigner"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.1-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause",
+                "GPL-2.0-only",
+                "GPL-3.0-only"
+            ],
+            "authors": [
+                {
+                    "name": "David Grudl",
+                    "homepage": "https://davidgrudl.com"
+                },
+                {
+                    "name": "Nette Community",
+                    "homepage": "https://nette.org/contributors"
+                }
+            ],
+            "description": "📧 Nette Mail: handy email creation and transfer library for PHP with both text and MIME-compliant support.",
+            "homepage": "https://nette.org",
+            "keywords": [
+                "mail",
+                "mailer",
+                "mime",
+                "nette",
+                "smtp"
+            ],
+            "support": {
+                "issues": "https://github.com/nette/mail/issues",
+                "source": "https://github.com/nette/mail/tree/v3.1.10"
+            },
+            "time": "2023-01-18T05:42:31+00:00"
+        },
+        {
+            "name": "nette/utils",
+            "version": "v3.2.9",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/nette/utils.git",
+                "reference": "c91bac3470c34b2ecd5400f6e6fdf0b64a836a5c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/nette/utils/zipball/c91bac3470c34b2ecd5400f6e6fdf0b64a836a5c",
+                "reference": "c91bac3470c34b2ecd5400f6e6fdf0b64a836a5c",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2 <8.3"
+            },
+            "conflict": {
+                "nette/di": "<3.0.6"
+            },
+            "require-dev": {
+                "jetbrains/phpstorm-attributes": "dev-master",
+                "nette/tester": "~2.0",
+                "phpstan/phpstan": "^1.0",
+                "tracy/tracy": "^2.3"
+            },
+            "suggest": {
+                "ext-gd": "to use Image",
+                "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()",
+                "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()",
+                "ext-json": "to use Nette\\Utils\\Json",
+                "ext-mbstring": "to use Strings::lower() etc...",
+                "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()",
+                "ext-xml": "to use Strings::length() etc. when mbstring is not available"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.2-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause",
+                "GPL-2.0-only",
+                "GPL-3.0-only"
+            ],
+            "authors": [
+                {
+                    "name": "David Grudl",
+                    "homepage": "https://davidgrudl.com"
+                },
+                {
+                    "name": "Nette Community",
+                    "homepage": "https://nette.org/contributors"
+                }
+            ],
+            "description": "🛠  Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.",
+            "homepage": "https://nette.org",
+            "keywords": [
+                "array",
+                "core",
+                "datetime",
+                "images",
+                "json",
+                "nette",
+                "paginator",
+                "password",
+                "slugify",
+                "string",
+                "unicode",
+                "utf-8",
+                "utility",
+                "validation"
+            ],
+            "support": {
+                "issues": "https://github.com/nette/utils/issues",
+                "source": "https://github.com/nette/utils/tree/v3.2.9"
+            },
+            "time": "2023-01-18T03:26:20+00:00"
+        },
+        {
+            "name": "paquettg/php-html-parser",
+            "version": "3.1.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/paquettg/php-html-parser.git",
+                "reference": "4e01a438ad5961cc2d7427eb9798d213c8a12629"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/paquettg/php-html-parser/zipball/4e01a438ad5961cc2d7427eb9798d213c8a12629",
+                "reference": "4e01a438ad5961cc2d7427eb9798d213c8a12629",
+                "shasum": ""
+            },
+            "require": {
+                "ext-curl": "*",
+                "ext-mbstring": "*",
+                "ext-zlib": "*",
+                "guzzlehttp/guzzle": "^7.0",
+                "guzzlehttp/psr7": "^1.6",
+                "myclabs/php-enum": "^1.7",
+                "paquettg/string-encode": "~1.0.0",
+                "php": ">=7.2",
+                "php-http/httplug": "^2.1"
+            },
+            "require-dev": {
+                "friendsofphp/php-cs-fixer": "^2.16",
+                "infection/infection": "^0.13.4",
+                "mockery/mockery": "^1.2",
+                "phan/phan": "^2.4",
+                "phpunit/phpunit": "^7.5.1"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "PHPHtmlParser\\": "src/PHPHtmlParser"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Gilles Paquette",
+                    "email": "paquettg@gmail.com",
+                    "homepage": "http://gillespaquette.ca"
+                }
+            ],
+            "description": "An HTML DOM parser. It allows you to manipulate HTML. Find tags on an HTML page with selectors just like jQuery.",
+            "homepage": "https://github.com/paquettg/php-html-parser",
+            "keywords": [
+                "dom",
+                "html",
+                "parser"
+            ],
+            "support": {
+                "issues": "https://github.com/paquettg/php-html-parser/issues",
+                "source": "https://github.com/paquettg/php-html-parser/tree/3.1.1"
+            },
+            "funding": [
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/paquettg/php-html-parser",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-11-01T20:34:43+00:00"
+        },
+        {
+            "name": "paquettg/string-encode",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/paquettg/string-encoder.git",
+                "reference": "a8708e9fac9d5ddfc8fc2aac6004e2cd05d80fee"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/paquettg/string-encoder/zipball/a8708e9fac9d5ddfc8fc2aac6004e2cd05d80fee",
+                "reference": "a8708e9fac9d5ddfc8fc2aac6004e2cd05d80fee",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^7.5.1"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-0": {
+                    "stringEncode": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Gilles Paquette",
+                    "email": "paquettg@gmail.com",
+                    "homepage": "http://gillespaquette.ca"
+                }
+            ],
+            "description": "Facilitating the process of altering string encoding in PHP.",
+            "homepage": "https://github.com/paquettg/string-encoder",
+            "keywords": [
+                "charset",
+                "encoding",
+                "string"
+            ],
+            "support": {
+                "issues": "https://github.com/paquettg/string-encoder/issues",
+                "source": "https://github.com/paquettg/string-encoder/tree/1.0.1"
+            },
+            "time": "2018-12-21T02:25:09+00:00"
+        },
+        {
+            "name": "php-http/httplug",
+            "version": "2.4.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-http/httplug.git",
+                "reference": "625ad742c360c8ac580fcc647a1541d29e257f67"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-http/httplug/zipball/625ad742c360c8ac580fcc647a1541d29e257f67",
+                "reference": "625ad742c360c8ac580fcc647a1541d29e257f67",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1 || ^8.0",
+                "php-http/promise": "^1.1",
+                "psr/http-client": "^1.0",
+                "psr/http-message": "^1.0 || ^2.0"
+            },
+            "require-dev": {
+                "friends-of-phpspec/phpspec-code-coverage": "^4.1 || ^5.0 || ^6.0",
+                "phpspec/phpspec": "^5.1 || ^6.0 || ^7.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Http\\Client\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Eric GELOEN",
+                    "email": "geloen.eric@gmail.com"
+                },
+                {
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com",
+                    "homepage": "https://sagikazarmark.hu"
+                }
+            ],
+            "description": "HTTPlug, the HTTP client abstraction for PHP",
+            "homepage": "http://httplug.io",
+            "keywords": [
+                "client",
+                "http"
+            ],
+            "support": {
+                "issues": "https://github.com/php-http/httplug/issues",
+                "source": "https://github.com/php-http/httplug/tree/2.4.0"
+            },
+            "time": "2023-04-14T15:10:03+00:00"
+        },
+        {
+            "name": "php-http/promise",
+            "version": "1.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-http/promise.git",
+                "reference": "4c4c1f9b7289a2ec57cde7f1e9762a5789506f88"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-http/promise/zipball/4c4c1f9b7289a2ec57cde7f1e9762a5789506f88",
+                "reference": "4c4c1f9b7289a2ec57cde7f1e9762a5789506f88",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1 || ^8.0"
+            },
+            "require-dev": {
+                "friends-of-phpspec/phpspec-code-coverage": "^4.3.2",
+                "phpspec/phpspec": "^5.1.2 || ^6.2"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Http\\Promise\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Joel Wurtz",
+                    "email": "joel.wurtz@gmail.com"
+                },
+                {
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com"
+                }
+            ],
+            "description": "Promise used for asynchronous HTTP requests",
+            "homepage": "http://httplug.io",
+            "keywords": [
+                "promise"
+            ],
+            "support": {
+                "issues": "https://github.com/php-http/promise/issues",
+                "source": "https://github.com/php-http/promise/tree/1.1.0"
+            },
+            "time": "2020-07-07T09:29:14+00:00"
+        },
+        {
+            "name": "psr/container",
+            "version": "1.1.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/container.git",
+                "reference": "513e0666f7216c7459170d56df27dfcefe1689ea"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea",
+                "reference": "513e0666f7216c7459170d56df27dfcefe1689ea",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.4.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Container\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "https://www.php-fig.org/"
+                }
+            ],
+            "description": "Common Container Interface (PHP FIG PSR-11)",
+            "homepage": "https://github.com/php-fig/container",
+            "keywords": [
+                "PSR-11",
+                "container",
+                "container-interface",
+                "container-interop",
+                "psr"
+            ],
+            "support": {
+                "issues": "https://github.com/php-fig/container/issues",
+                "source": "https://github.com/php-fig/container/tree/1.1.2"
+            },
+            "time": "2021-11-05T16:50:12+00:00"
+        },
+        {
+            "name": "psr/http-client",
+            "version": "1.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-client.git",
+                "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-client/zipball/0955afe48220520692d2d09f7ab7e0f93ffd6a31",
+                "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.0 || ^8.0",
+                "psr/http-message": "^1.0 || ^2.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Client\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "https://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for HTTP clients",
+            "homepage": "https://github.com/php-fig/http-client",
+            "keywords": [
+                "http",
+                "http-client",
+                "psr",
+                "psr-18"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/http-client/tree/1.0.2"
+            },
+            "time": "2023-04-10T20:12:12+00:00"
+        },
+        {
+            "name": "psr/http-message",
+            "version": "1.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-message.git",
+                "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
+                "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2 || ^8.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Message\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for HTTP messages",
+            "homepage": "https://github.com/php-fig/http-message",
+            "keywords": [
+                "http",
+                "http-message",
+                "psr",
+                "psr-7",
+                "request",
+                "response"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/http-message/tree/1.1"
+            },
+            "time": "2023-04-04T09:50:52+00:00"
+        },
+        {
+            "name": "psr/log",
+            "version": "1.1.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/log.git",
+                "reference": "d49695b909c3b7628b6289db5479a1c204601f11"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11",
+                "reference": "d49695b909c3b7628b6289db5479a1c204601f11",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Log\\": "Psr/Log/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "https://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for logging libraries",
+            "homepage": "https://github.com/php-fig/log",
+            "keywords": [
+                "log",
+                "psr",
+                "psr-3"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/log/tree/1.1.4"
+            },
+            "time": "2021-05-03T11:20:27+00:00"
+        },
+        {
+            "name": "psr/simple-cache",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/simple-cache.git",
+                "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+                "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\SimpleCache\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interfaces for simple caching",
+            "keywords": [
+                "cache",
+                "caching",
+                "psr",
+                "psr-16",
+                "simple-cache"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/simple-cache/tree/master"
+            },
+            "time": "2017-10-23T01:57:42+00:00"
+        },
+        {
+            "name": "ralouphie/getallheaders",
+            "version": "3.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ralouphie/getallheaders.git",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.6"
+            },
+            "require-dev": {
+                "php-coveralls/php-coveralls": "^2.1",
+                "phpunit/phpunit": "^5 || ^6.5"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/getallheaders.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ralph Khattar",
+                    "email": "ralph.khattar@gmail.com"
+                }
+            ],
+            "description": "A polyfill for getallheaders.",
+            "support": {
+                "issues": "https://github.com/ralouphie/getallheaders/issues",
+                "source": "https://github.com/ralouphie/getallheaders/tree/develop"
+            },
+            "time": "2019-03-08T08:55:37+00:00"
+        },
+        {
+            "name": "symfony/deprecation-contracts",
+            "version": "v2.5.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/deprecation-contracts.git",
+                "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66",
+                "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "2.5-dev"
+                },
+                "thanks": {
+                    "name": "symfony/contracts",
+                    "url": "https://github.com/symfony/contracts"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "function.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "A generic function and convention to trigger deprecation notices",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2022-01-02T09:53:40+00:00"
+        },
+        {
+            "name": "topthink/framework",
+            "version": "v6.1.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/framework.git",
+                "reference": "66eb9cf4d627df12911344cd328faf9bb596bf2c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/framework/zipball/66eb9cf4d627df12911344cd328faf9bb596bf2c",
+                "reference": "66eb9cf4d627df12911344cd328faf9bb596bf2c",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "ext-mbstring": "*",
+                "php": ">=7.2.5",
+                "psr/container": "~1.0",
+                "psr/http-message": "^1.0",
+                "psr/log": "~1.0",
+                "psr/simple-cache": "^1.0",
+                "topthink/think-helper": "^3.1.1",
+                "topthink/think-orm": "^2.0|^3.0"
+            },
+            "require-dev": {
+                "guzzlehttp/psr7": "^2.1.0",
+                "mikey179/vfsstream": "^1.6",
+                "mockery/mockery": "^1.2",
+                "phpunit/phpunit": "^7.0"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [],
+                "psr-4": {
+                    "think\\": "src/think/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "liu21st",
+                    "email": "liu21st@gmail.com"
+                },
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "description": "The ThinkPHP Framework.",
+            "homepage": "http://thinkphp.cn/",
+            "keywords": [
+                "framework",
+                "orm",
+                "thinkphp"
+            ],
+            "support": {
+                "issues": "https://github.com/top-think/framework/issues",
+                "source": "https://github.com/top-think/framework/tree/v6.1.4"
+            },
+            "time": "2023-07-11T15:16:03+00:00"
+        },
+        {
+            "name": "topthink/think-filesystem",
+            "version": "v2.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-filesystem.git",
+                "reference": "c08503232fcae0c3c7fefae5e6b5c841ffe09f2f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-filesystem/zipball/c08503232fcae0c3c7fefae5e6b5c841ffe09f2f",
+                "reference": "c08503232fcae0c3c7fefae5e6b5c841ffe09f2f",
+                "shasum": ""
+            },
+            "require": {
+                "league/flysystem": "^2.0",
+                "topthink/framework": "^6.1|^8.0"
+            },
+            "require-dev": {
+                "mikey179/vfsstream": "^1.6",
+                "mockery/mockery": "^1.2",
+                "phpunit/phpunit": "^8.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "think\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "description": "The ThinkPHP6.1 Filesystem Package",
+            "support": {
+                "issues": "https://github.com/top-think/think-filesystem/issues",
+                "source": "https://github.com/top-think/think-filesystem/tree/v2.0.2"
+            },
+            "time": "2023-02-08T01:23:42+00:00"
+        },
+        {
+            "name": "topthink/think-helper",
+            "version": "v3.1.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-helper.git",
+                "reference": "769acbe50a4274327162f9c68ec2e89a38eb2aff"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-helper/zipball/769acbe50a4274327162f9c68ec2e89a38eb2aff",
+                "reference": "769acbe50a4274327162f9c68ec2e89a38eb2aff",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.5"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/helper.php"
+                ],
+                "psr-4": {
+                    "think\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "description": "The ThinkPHP6 Helper Package",
+            "support": {
+                "issues": "https://github.com/top-think/think-helper/issues",
+                "source": "https://github.com/top-think/think-helper/tree/v3.1.6"
+            },
+            "time": "2021-12-15T04:27:55+00:00"
+        },
+        {
+            "name": "topthink/think-orm",
+            "version": "v2.0.61",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-orm.git",
+                "reference": "10528ebf4a5106b19c3bac9c6deae7a67ff49de6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-orm/zipball/10528ebf4a5106b19c3bac9c6deae7a67ff49de6",
+                "reference": "10528ebf4a5106b19c3bac9c6deae7a67ff49de6",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "ext-pdo": "*",
+                "php": ">=7.1.0",
+                "psr/log": "^1.0|^2.0",
+                "psr/simple-cache": "^1.0|^2.0",
+                "topthink/think-helper": "^3.1"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^7|^8|^9.5"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "stubs/load_stubs.php"
+                ],
+                "psr-4": {
+                    "think\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "liu21st",
+                    "email": "liu21st@gmail.com"
+                }
+            ],
+            "description": "think orm",
+            "keywords": [
+                "database",
+                "orm"
+            ],
+            "support": {
+                "issues": "https://github.com/top-think/think-orm/issues",
+                "source": "https://github.com/top-think/think-orm/tree/v2.0.61"
+            },
+            "time": "2023-04-20T14:27:51+00:00"
+        },
+        {
+            "name": "topthink/think-template",
+            "version": "v2.0.9",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-template.git",
+                "reference": "6d25642ae0e306166742fd7073dc7a159e18073c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-template/zipball/6d25642ae0e306166742fd7073dc7a159e18073c",
+                "reference": "6d25642ae0e306166742fd7073dc7a159e18073c",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1.0",
+                "psr/simple-cache": "^1.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "think\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "liu21st",
+                    "email": "liu21st@gmail.com"
+                }
+            ],
+            "description": "the php template engine",
+            "support": {
+                "issues": "https://github.com/top-think/think-template/issues",
+                "source": "https://github.com/top-think/think-template/tree/v2.0.9"
+            },
+            "time": "2023-02-14T10:50:39+00:00"
+        },
+        {
+            "name": "topthink/think-view",
+            "version": "v1.0.14",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-view.git",
+                "reference": "edce0ae2c9551ab65f9e94a222604b0dead3576d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-view/zipball/edce0ae2c9551ab65f9e94a222604b0dead3576d",
+                "reference": "edce0ae2c9551ab65f9e94a222604b0dead3576d",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1.0",
+                "topthink/think-template": "^2.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "think\\view\\driver\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "liu21st",
+                    "email": "liu21st@gmail.com"
+                }
+            ],
+            "description": "thinkphp template driver",
+            "support": {
+                "issues": "https://github.com/top-think/think-view/issues",
+                "source": "https://github.com/top-think/think-view/tree/v1.0.14"
+            },
+            "time": "2019-11-06T11:40:13+00:00"
+        }
+    ],
+    "packages-dev": [
+        {
+            "name": "symfony/polyfill-mbstring",
+            "version": "v1.27.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-mbstring.git",
+                "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
+                "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "provide": {
+                "ext-mbstring": "*"
+            },
+            "suggest": {
+                "ext-mbstring": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.27-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Mbstring\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for the Mbstring extension",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "mbstring",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2022-11-03T14:55:06+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php72",
+            "version": "v1.27.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php72.git",
+                "reference": "869329b1e9894268a8a61dabb69153029b7a8c97"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/869329b1e9894268a8a61dabb69153029b7a8c97",
+                "reference": "869329b1e9894268a8a61dabb69153029b7a8c97",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.27-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php72\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-php72/tree/v1.27.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2022-11-03T14:55:06+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php80",
+            "version": "v1.27.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php80.git",
+                "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936",
+                "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.27-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php80\\": ""
+                },
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ion Bazan",
+                    "email": "ion.bazan@gmail.com"
+                },
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2022-11-03T14:55:06+00:00"
+        },
+        {
+            "name": "symfony/var-dumper",
+            "version": "v4.4.47",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/var-dumper.git",
+                "reference": "1069c7a3fca74578022fab6f81643248d02f8e63"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/var-dumper/zipball/1069c7a3fca74578022fab6f81643248d02f8e63",
+                "reference": "1069c7a3fca74578022fab6f81643248d02f8e63",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1.3",
+                "symfony/polyfill-mbstring": "~1.0",
+                "symfony/polyfill-php72": "~1.5",
+                "symfony/polyfill-php80": "^1.16"
+            },
+            "conflict": {
+                "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0",
+                "symfony/console": "<3.4"
+            },
+            "require-dev": {
+                "ext-iconv": "*",
+                "symfony/console": "^3.4|^4.0|^5.0",
+                "symfony/process": "^4.4|^5.0",
+                "twig/twig": "^1.43|^2.13|^3.0.4"
+            },
+            "suggest": {
+                "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).",
+                "ext-intl": "To show region name in time zone dump",
+                "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script"
+            },
+            "bin": [
+                "Resources/bin/var-dump-server"
+            ],
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "Resources/functions/dump.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Component\\VarDumper\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Provides mechanisms for walking through any arbitrary PHP variable",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "debug",
+                "dump"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/var-dumper/tree/v4.4.47"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2022-10-03T15:15:11+00:00"
+        },
+        {
+            "name": "topthink/think-trace",
+            "version": "v1.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-trace.git",
+                "reference": "136cd5d97e8bdb780e4b5c1637c588ed7ca3e142"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-trace/zipball/136cd5d97e8bdb780e4b5c1637c588ed7ca3e142",
+                "reference": "136cd5d97e8bdb780e4b5c1637c588ed7ca3e142",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1.0",
+                "topthink/framework": "^6.0|^8.0"
+            },
+            "type": "library",
+            "extra": {
+                "think": {
+                    "services": [
+                        "think\\trace\\Service"
+                    ],
+                    "config": {
+                        "trace": "src/config.php"
+                    }
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "think\\trace\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "liu21st",
+                    "email": "liu21st@gmail.com"
+                }
+            ],
+            "description": "thinkphp debug trace",
+            "support": {
+                "issues": "https://github.com/top-think/think-trace/issues",
+                "source": "https://github.com/top-think/think-trace/tree/v1.6"
+            },
+            "time": "2023-02-07T08:36:32+00:00"
+        }
+    ],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": {
+        "php": ">=7.4",
+        "ext-json": "*",
+        "ext-openssl": "*",
+        "ext-fileinfo": "*",
+        "ext-mysqli": "*",
+        "ext-redis": "*",
+        "ext-pcntl": "*",
+        "ext-zip": "*",
+        "ext-posix": "*"
+    },
+    "platform-dev": [],
+    "plugin-api-version": "2.0.0"
+}

+ 124 - 124
config/500.html

@@ -1,125 +1,125 @@
-<!DOCTYPE html>
-<html lang="zh-cn">
-
-<head>
-    <meta charset="UTF-8">
-    <meta http-equiv="X-UA-Compatible" content="IE=edge">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>500-服务内部故障!</title>
-    <style>
-        .icon {
-            display: flex;
-            justify-content: center;
-            margin-top: 80px;
-        }
-
-        .tips {
-            display: flex;
-            flex-direction: column;
-            align-items: center;
-            margin-top: 40px;
-        }
-
-        .tips > h1 {
-            color: #223051;
-            font-size: 40px;
-            font-weight: bolder;
-            text-align: center;
-            line-height: 0;
-        }
-
-        .tips > p {
-            line-height: 20px;
-            color: #7C869C;
-            font-weight: 600;
-            text-align: center;
-        }
-
-        .go-home {
-            display: flex;
-            justify-content: center;
-            margin-top: 30px;
-        }
-
-        .go-home > a {
-            padding: 15px 80px;
-            text-decoration: none;
-            font-size: 18px;
-            background-color: #223051;
-            border-radius: 8px;
-            outline: none;
-            border: none;
-            color: #fff;
-        }
-
-        @media screen and (max-width: 568px) {
-            .icon > svg {
-                height: 200px;
-            }
-
-            .tips > h1 {
-                font-size: 20px;
-            }
-
-            .tips > p {
-                padding: 0 10px;
-                line-height: 20px;
-                text-align: center;
-            }
-        }
-    </style>
-</head>
-
-<body>
-<div class="icon">
-    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="874" height="490"
-         viewBox="0 0 874 490" fill="none">
-        <g opacity="1" transform="translate(0 0)  rotate(0)">
-            <g opacity="1" transform="translate(0 2)  rotate(0)">
-                <text>
-                    <tspan x="0" y="387.20000000000005" font-size="400" line-height="0" fill="#223051" opacity="1"
-                           font-family="Montserrat-Bold" font-weight="Bold" letter-spacing="0">5
-                    </tspan>
-                </text>
-            </g>
-            <g opacity="1" transform="translate(602 0)  rotate(0)">
-                <text>
-                    <tspan x="0" y="387.20000000000005" font-size="400" line-height="0" fill="#223051" opacity="1"
-                           font-family="Montserrat-Bold" font-weight="Bold" letter-spacing="0">0
-                    </tspan>
-                </text>
-            </g>
-            <g opacity="1" transform="translate(264 107)  rotate(0)">
-                <path id="Fill 3" fill-rule="evenodd" style="fill:#2B3E51" opacity="1"
-                      d="M191.39 248.34C201.41 248.34 208.8 245.63 213.58 240.22C218.36 234.82 220.75 227.56 220.75 218.46L220.75 179L100 179L100 198.08L161.41 198.08L161.41 217.22C161.41 227.2 163.67 234.89 168.2 240.27C172.72 245.65 180.45 248.34 191.39 248.34ZM204.653 215.391L204.653 198.081L177.373 198.081L177.373 215.391C177.373 219.771 178.483 223.181 180.703 225.611C182.923 228.041 186.443 229.261 191.263 229.261C196.083 229.261 199.523 228.041 201.573 225.611C203.623 223.181 204.653 219.771 204.653 215.391Z"></path>
-                <mask id="mask-0" fill="white">
-                    <path d="M320.33 291.94L320.33 0L0 0L0 291.94L320.33 291.94Z"></path>
-                </mask>
-                <path d="M320.33 291.94L320.33 0L0 0L0 291.94L320.33 291.94Z"></path>
-                <g mask="url(#mask-0)">
-                    <path id="Fill 5" fill-rule="evenodd" style="fill:#E26C56" opacity="1"
-                          d="M0 277.38L0 14.47C0 6.36 6.72 0 14.9 0L305.33 0C313.52 0 320.33 6.36 320.33 14.47L320.33 277.38C320.33 285.5 313.52 291.94 305.33 291.94L14.9 291.94C6.72 291.94 0 285.5 0 277.38ZM29.5181 36.2314C29.5181 40.2314 32.7881 43.4814 36.8281 43.4814C40.8581 43.4814 44.1381 40.2314 44.1381 36.2314C44.1381 32.2314 40.8581 28.9814 36.8281 28.9814C32.7881 28.9814 29.5181 32.2314 29.5181 36.2314ZM59.521 36.2314C59.521 40.2314 62.791 43.4814 66.831 43.4814C70.861 43.4814 74.141 40.2314 74.141 36.2314C74.141 32.2314 70.861 28.9814 66.831 28.9814C62.791 28.9814 59.521 32.2314 59.521 36.2314ZM290.327 29.3251L290.327 43.7651L89.1265 43.7651L89.1265 29.3251L290.327 29.3251ZM290.322 73.0938L290.322 262.614L29.5615 262.614L29.5615 73.0938L290.322 73.0938Z"></path>
-                </g>
-                <g mask="url(#mask-0)">
-                    <path id="Fill 1 Copy" fill-rule="evenodd" style="fill:#2B3E51"
-                          d="M82.37 121.36L64 139.58L76.24 151.73L94.61 133.51L112.98 151.73L125.22 139.58L106.86 121.35L125.22 103.13L112.97 91L94.61 109.22L76.24 91L64 103.13L82.37 121.36Z"></path>
-                </g>
-                <g mask="url(#mask-0)">
-                    <path id="Fill 2 Copy" fill-rule="evenodd" style="fill:#2B3E51"
-                          d="M257.22 139.58L238.86 121.35L257.22 103.13L244.97 91L226.61 109.22L208.24 91L196 103.13L214.37 121.36L196 139.58L208.24 151.73L226.61 133.51L244.98 151.73L257.22 139.58Z"></path>
-                </g>
-            </g>
-        </g>
-    </svg>
-</div>
-<div class="tips">
-    <h1>Oops, 服务内部故障!</h1>
-    <p>An error occurred and your request could not be completed.</p>
-    <p><?php echo $_ENV["error_msg"]; ?></p>
-</div>
-<div class="go-home">
-    <a href="/">去首页</a>
-</div>
-</body>
-
+<!DOCTYPE html>
+<html lang="zh-cn">
+
+<head>
+    <meta charset="UTF-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>500-服务内部故障!</title>
+    <style>
+        .icon {
+            display: flex;
+            justify-content: center;
+            margin-top: 80px;
+        }
+
+        .tips {
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            margin-top: 40px;
+        }
+
+        .tips > h1 {
+            color: #223051;
+            font-size: 40px;
+            font-weight: bolder;
+            text-align: center;
+            line-height: 0;
+        }
+
+        .tips > p {
+            line-height: 20px;
+            color: #7C869C;
+            font-weight: 600;
+            text-align: center;
+        }
+
+        .go-home {
+            display: flex;
+            justify-content: center;
+            margin-top: 30px;
+        }
+
+        .go-home > a {
+            padding: 15px 80px;
+            text-decoration: none;
+            font-size: 18px;
+            background-color: #223051;
+            border-radius: 8px;
+            outline: none;
+            border: none;
+            color: #fff;
+        }
+
+        @media screen and (max-width: 568px) {
+            .icon > svg {
+                height: 200px;
+            }
+
+            .tips > h1 {
+                font-size: 20px;
+            }
+
+            .tips > p {
+                padding: 0 10px;
+                line-height: 20px;
+                text-align: center;
+            }
+        }
+    </style>
+</head>
+
+<body>
+<div class="icon">
+    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="874" height="490"
+         viewBox="0 0 874 490" fill="none">
+        <g opacity="1" transform="translate(0 0)  rotate(0)">
+            <g opacity="1" transform="translate(0 2)  rotate(0)">
+                <text>
+                    <tspan x="0" y="387.20000000000005" font-size="400" line-height="0" fill="#223051" opacity="1"
+                           font-family="Montserrat-Bold" font-weight="Bold" letter-spacing="0">5
+                    </tspan>
+                </text>
+            </g>
+            <g opacity="1" transform="translate(602 0)  rotate(0)">
+                <text>
+                    <tspan x="0" y="387.20000000000005" font-size="400" line-height="0" fill="#223051" opacity="1"
+                           font-family="Montserrat-Bold" font-weight="Bold" letter-spacing="0">0
+                    </tspan>
+                </text>
+            </g>
+            <g opacity="1" transform="translate(264 107)  rotate(0)">
+                <path id="Fill 3" fill-rule="evenodd" style="fill:#2B3E51" opacity="1"
+                      d="M191.39 248.34C201.41 248.34 208.8 245.63 213.58 240.22C218.36 234.82 220.75 227.56 220.75 218.46L220.75 179L100 179L100 198.08L161.41 198.08L161.41 217.22C161.41 227.2 163.67 234.89 168.2 240.27C172.72 245.65 180.45 248.34 191.39 248.34ZM204.653 215.391L204.653 198.081L177.373 198.081L177.373 215.391C177.373 219.771 178.483 223.181 180.703 225.611C182.923 228.041 186.443 229.261 191.263 229.261C196.083 229.261 199.523 228.041 201.573 225.611C203.623 223.181 204.653 219.771 204.653 215.391Z"></path>
+                <mask id="mask-0" fill="white">
+                    <path d="M320.33 291.94L320.33 0L0 0L0 291.94L320.33 291.94Z"></path>
+                </mask>
+                <path d="M320.33 291.94L320.33 0L0 0L0 291.94L320.33 291.94Z"></path>
+                <g mask="url(#mask-0)">
+                    <path id="Fill 5" fill-rule="evenodd" style="fill:#E26C56" opacity="1"
+                          d="M0 277.38L0 14.47C0 6.36 6.72 0 14.9 0L305.33 0C313.52 0 320.33 6.36 320.33 14.47L320.33 277.38C320.33 285.5 313.52 291.94 305.33 291.94L14.9 291.94C6.72 291.94 0 285.5 0 277.38ZM29.5181 36.2314C29.5181 40.2314 32.7881 43.4814 36.8281 43.4814C40.8581 43.4814 44.1381 40.2314 44.1381 36.2314C44.1381 32.2314 40.8581 28.9814 36.8281 28.9814C32.7881 28.9814 29.5181 32.2314 29.5181 36.2314ZM59.521 36.2314C59.521 40.2314 62.791 43.4814 66.831 43.4814C70.861 43.4814 74.141 40.2314 74.141 36.2314C74.141 32.2314 70.861 28.9814 66.831 28.9814C62.791 28.9814 59.521 32.2314 59.521 36.2314ZM290.327 29.3251L290.327 43.7651L89.1265 43.7651L89.1265 29.3251L290.327 29.3251ZM290.322 73.0938L290.322 262.614L29.5615 262.614L29.5615 73.0938L290.322 73.0938Z"></path>
+                </g>
+                <g mask="url(#mask-0)">
+                    <path id="Fill 1 Copy" fill-rule="evenodd" style="fill:#2B3E51"
+                          d="M82.37 121.36L64 139.58L76.24 151.73L94.61 133.51L112.98 151.73L125.22 139.58L106.86 121.35L125.22 103.13L112.97 91L94.61 109.22L76.24 91L64 103.13L82.37 121.36Z"></path>
+                </g>
+                <g mask="url(#mask-0)">
+                    <path id="Fill 2 Copy" fill-rule="evenodd" style="fill:#2B3E51"
+                          d="M257.22 139.58L238.86 121.35L257.22 103.13L244.97 91L226.61 109.22L208.24 91L196 103.13L214.37 121.36L196 139.58L208.24 151.73L226.61 133.51L244.98 151.73L257.22 139.58Z"></path>
+                </g>
+            </g>
+        </g>
+    </svg>
+</div>
+<div class="tips">
+    <h1>Oops, 服务内部故障!</h1>
+    <p>An error occurred and your request could not be completed.</p>
+    <p><?php echo $_ENV["error_msg"]; ?></p>
+</div>
+<div class="go-home">
+    <a href="/">去首页</a>
+</div>
+</body>
+
 </html>

+ 34 - 34
config/app.php

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

+ 39 - 39
config/cache.php

@@ -1,39 +1,39 @@
-<?php
-
-// +----------------------------------------------------------------------
-// | 缓存设置
-// +----------------------------------------------------------------------
-
-return [
-    // 默认缓存驱动
-    'default' => env('cache.driver', 'file'),
-
-    // 缓存连接方式配置
-    'stores' => [
-        'file' => [
-            // 驱动方式
-            'type' => 'File',
-            // 缓存保存目录
-            'path' => '',
-            // 缓存前缀
-            'prefix' => '',
-            // 缓存有效期 0表示永久缓存
-            'expire' => 0,
-            // 缓存标签前缀
-            'tag_prefix' => 'tag:',
-            // 序列化机制 例如 ['serialize', 'unserialize']
-            'serialize' => [],
-        ],
-        // 更多的缓存连接
-        'redis' => [
-            // 驱动方式
-            'type' => 'redis',
-            // 服务器地址
-            'select'=>env('redis.select',1),
-            'port'=>env("redis.port", 6379),
-            'host' => env('redis.host', '127.0.0.1'),
-            'password' => env('redis.password', '')
-        ]
-    ],
-
-];
+<?php
+
+// +----------------------------------------------------------------------
+// | 缓存设置
+// +----------------------------------------------------------------------
+
+return [
+    // 默认缓存驱动
+    'default' => env('cache.driver', 'file'),
+
+    // 缓存连接方式配置
+    'stores' => [
+        'file' => [
+            // 驱动方式
+            'type' => 'File',
+            // 缓存保存目录
+            'path' => '',
+            // 缓存前缀
+            'prefix' => '',
+            // 缓存有效期 0表示永久缓存
+            'expire' => 0,
+            // 缓存标签前缀
+            'tag_prefix' => 'tag:',
+            // 序列化机制 例如 ['serialize', 'unserialize']
+            'serialize' => [],
+        ],
+        // 更多的缓存连接
+        'redis' => [
+            // 驱动方式
+            'type' => 'redis',
+            // 服务器地址
+            'select'=>env('redis.select',1),
+            'port'=>env("redis.port", 6379),
+            'host' => env('redis.host', '127.0.0.1'),
+            'password' => env('redis.password', '')
+        ]
+    ],
+
+];

+ 12 - 12
config/console.php

@@ -1,12 +1,12 @@
-<?php
-// +----------------------------------------------------------------------
-// | 控制台配置
-// +----------------------------------------------------------------------
-return [
-    // 指令定义
-    'commands' => [
-        'repair' => 'app\command\repair',
-        'repass' => 'app\command\repass',
-        'test' => 'app\command\test',
-    ],
-];
+<?php
+// +----------------------------------------------------------------------
+// | 控制台配置
+// +----------------------------------------------------------------------
+return [
+    // 指令定义
+    'commands' => [
+        'repair' => 'app\command\repair',
+        'repass' => 'app\command\repass',
+        'test' => 'app\command\test',
+    ],
+];

+ 20 - 20
config/cookie.php

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

+ 68 - 68
config/database.php

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

+ 24 - 24
config/filesystem.php

@@ -1,24 +1,24 @@
-<?php
-
-return [
-    // 默认磁盘
-    'default' => env('filesystem.driver', 'local'),
-    // 磁盘列表
-    'disks' => [
-        'local' => [
-            'type' => 'local',
-            'root' => app()->getRuntimePath() . 'storage',
-        ],
-        'images' => [
-            // 磁盘类型
-            'type' => 'local',
-            // 磁盘路径
-            'root' => app()->getRootPath() . 'public',
-            // 磁盘路径对应的外部URL路径
-            'url' => 'images',
-            // 可见性
-            'visibility' => 'public',
-        ],
-        // 更多的磁盘配置信息
-    ],
-];
+<?php
+
+return [
+    // 默认磁盘
+    'default' => env('filesystem.driver', 'local'),
+    // 磁盘列表
+    'disks' => [
+        'local' => [
+            'type' => 'local',
+            'root' => app()->getRuntimePath() . 'storage',
+        ],
+        'images' => [
+            // 磁盘类型
+            'type' => 'local',
+            // 磁盘路径
+            'root' => app()->getRootPath() . 'public',
+            // 磁盘路径对应的外部URL路径
+            'url' => 'images',
+            // 可见性
+            'visibility' => 'public',
+        ],
+        // 更多的磁盘配置信息
+    ],
+];

+ 27 - 27
config/lang.php

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

+ 45 - 45
config/log.php

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

+ 8 - 8
config/middleware.php

@@ -1,8 +1,8 @@
-<?php
-// 中间件配置
-return [
-    // 别名或分组
-    'alias'    => [],
-    // 优先级设置,此数组中的中间件会按照数组中的顺序优先执行
-    'priority' => [],
-];
+<?php
+// 中间件配置
+return [
+    // 别名或分组
+    'alias'    => [],
+    // 优先级设置,此数组中的中间件会按照数组中的顺序优先执行
+    'priority' => [],
+];

+ 45 - 45
config/route.php

@@ -1,45 +1,45 @@
-<?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,
-    // 访问控制器层名称
-    'controller_layer'      => 'controller',
-    // 空控制器名
-    'empty_controller'      => 'Error',
-    // 是否使用控制器后缀
-    'controller_suffix'     => false,
-    // 默认的路由变量规则
-    'default_route_pattern' => '/[\w\.]+',
-    // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则
-    'request_cache_key'     => true,
-    // 请求缓存有效期
-    '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',
-];
+<?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,
+    // 访问控制器层名称
+    'controller_layer'      => 'controller',
+    // 空控制器名
+    'empty_controller'      => 'Error',
+    // 是否使用控制器后缀
+    'controller_suffix'     => false,
+    // 默认的路由变量规则
+    'default_route_pattern' => '/[\w\.]+',
+    // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则
+    'request_cache_key'     => true,
+    // 请求缓存有效期
+    '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 - 19
config/session.php

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

+ 10 - 10
config/trace.php

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

+ 25 - 25
config/view.php

@@ -1,25 +1,25 @@
-<?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'    => '}',
-];
+<?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'    => '}',
+];

+ 0 - 1
defaultData.sql

@@ -18,7 +18,6 @@ INSERT INTO search_engine (id, name, icon, url, sort, create_time, status, tips)
 INSERT INTO search_engine (id, name, icon, url, sort, create_time, status, tips) VALUES (7, '开发者搜索', '/static/searchEngine/baidudev.png', 'https://kaifa.baidu.com/searchPage?module=SEARCH&wd={1}', 0, '2024-01-14 23:21:45', 1, '专注于技术文档、API 和开发者资源的搜索引擎,为开发者提供快速准确的技术信息检索服务,支持多种编程');
 INSERT INTO search_engine (id, name, icon, url, sort, create_time, status, tips) VALUES (8, 'B站', '/static/searchEngine/bilibiliico.png', 'https://search.bilibili.com/all?vt=21160573&keyword={1}', 0, '2024-01-14 23:21:57', 1, '中国弹幕视频平台,以二次元文化为特色,提供丰富的动画、游戏、音乐等内容,用户可通过弹幕互动分享观感。');
 INSERT INTO search_engine (id, name, icon, url, sort, create_time, status, tips) VALUES (9, '微博', '/static/searchEngine/weiboico.png', 'https://s.weibo.com/weibo?q={1}', 0, '2024-01-14 23:22:12', 1, '中国社交媒体平台,用户可以发布短文、图片和视频,关注他人并互动评论,是实时新闻、话题讨论和社交分享的');
-INSERT INTO search_engine (id, name, icon, url, sort, create_time, status, tips) VALUES (10, 'DuckDuckGo', '/static/searchEngine/DuckDuckGo.svg', 'https://duckduckgo.com/?t=h_&q={1}&ia=web', 96, '2024-01-15 21:37:44', 1, '注重隐私保护的搜索引擎,致力于不追踪用户个人信息,提供匿名、安全的搜索服务,受到关注的隐私倡导者青睐');
 
 INSERT INTO wallpaper (id, type, folder, mime, url, cover, create_time, name, sort) VALUES (1, 1, null, 0, null, null, '2024-02-22 12:29:21', '默认壁纸', 999);
 INSERT INTO wallpaper (id, type, folder, mime, url, cover, create_time, name, sort) VALUES (2, 0, 1, 0, '/static/wallpaper/wallpaper-1.jpeg', '/static/wallpaper/m_wallpaper-1.jpeg', '2024-02-22 12:35:59', null, 999);

+ 28 - 28
docker/default.conf

@@ -1,28 +1,28 @@
-# This is a default site configuration which will simply return 404, preventing
-# chance access to any other virtualhost.
-
-server {
-	listen 80 default_server;
-   	root /app/public;
-	client_max_body_size 50M;
-	index index.php index.html index.htm default.php default.htm default.html;
-	# Everything is a 404
-	location ~ [^/]\.php(/|$) {
-            try_files $uri =404;
-            fastcgi_pass unix:/tmp/php-cgi-74.sock;
-            fastcgi_index index.php;
-            include fastcgi.conf;
-    }
-
-	location / {
-    	if (!-e $request_filename){
-    		rewrite  ^(.*)$  /index.php?s=$1  last;
-          break;
-    	}
-    }
-
-	# You may need this to prevent return 404 recursion.
-	location = /404.html {
-		internal;
-	}
-}
+# This is a default site configuration which will simply return 404, preventing
+# chance access to any other virtualhost.
+
+server {
+	listen 80 default_server;
+   	root /app/public;
+	client_max_body_size 50M;
+	index index.php index.html index.htm default.php default.htm default.html;
+	# Everything is a 404
+	location ~ [^/]\.php(/|$) {
+            try_files $uri =404;
+            fastcgi_pass unix:/tmp/php-cgi-74.sock;
+            fastcgi_index index.php;
+            include fastcgi.conf;
+    }
+
+	location / {
+    	if (!-e $request_filename){
+    		rewrite  ^(.*)$  /index.php?s=$1  last;
+          break;
+    	}
+    }
+
+	# You may need this to prevent return 404 recursion.
+	location = /404.html {
+		internal;
+	}
+}

+ 109 - 109
docker/nginx.conf

@@ -1,109 +1,109 @@
-# /etc/nginx/nginx.conf
-
-user nginx;
-
-# Set number of worker processes automatically based on number of CPU cores.
-worker_processes auto;
-
-# Enables the use of JIT for regular expressions to speed-up their processing.
-pcre_jit on;
-
-# Configures default error logger.
-error_log /var/log/nginx/error.log warn;
-
-# Includes files with directives to load dynamic modules.
-include /etc/nginx/modules/*.conf;
-
-
-events {
-	# The maximum number of simultaneous connections that can be opened by
-	# a worker process.
-	worker_connections 1024;
-}
-
-http {
-	# Includes mapping of file name extensions to MIME types of responses
-	# and defines the default type.
-	include /etc/nginx/mime.types;
-	default_type application/octet-stream;
-    set_real_ip_from 0.0.0.0/0;
-    real_ip_header X-Forwarded-For;
-	# Name servers used to resolve names of upstream servers into addresses.
-	# It's also needed when using tcpsocket and udpsocket in Lua modules.
-	#resolver 1.1.1.1 1.0.0.1 2606:4700:4700::1111 2606:4700:4700::1001;
-
-	# Don't tell nginx version to the clients. Default is 'on'.
-	server_tokens off;
-
-	# Specifies the maximum accepted body size of a client request, as
-	# indicated by the request header Content-Length. If the stated content
-	# length is greater than this size, then the client receives the HTTP
-	# error code 413. Set to 0 to disable. Default is '1m'.
-	client_max_body_size 1m;
-
-	# Sendfile copies data between one FD and other from within the kernel,
-	# which is more efficient than read() + write(). Default is off.
-	sendfile on;
-
-	# Causes nginx to attempt to send its HTTP response head in one packet,
-	# instead of using partial frames. Default is 'off'.
-	tcp_nopush on;
-
-
-	# Enables the specified protocols. Default is TLSv1 TLSv1.1 TLSv1.2.
-	# TIP: If you're not obligated to support ancient clients, remove TLSv1.1.
-	ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
-
-	# Path of the file with Diffie-Hellman parameters for EDH ciphers.
-	# TIP: Generate with: `openssl dhparam -out /etc/ssl/nginx/dh2048.pem 2048`
-	#ssl_dhparam /etc/ssl/nginx/dh2048.pem;
-
-	# Specifies that our cipher suits should be preferred over client ciphers.
-	# Default is 'off'.
-	ssl_prefer_server_ciphers on;
-
-	# Enables a shared SSL cache with size that can hold around 8000 sessions.
-	# Default is 'none'.
-	ssl_session_cache shared:SSL:2m;
-
-	# Specifies a time during which a client may reuse the session parameters.
-	# Default is '5m'.
-	ssl_session_timeout 1h;
-
-	# Disable TLS session tickets (they are insecure). Default is 'on'.
-	ssl_session_tickets off;
-
-
-	# Enable gzipping of responses.
-	#gzip on;
-
-	# Set the Vary HTTP header as defined in the RFC 2616. Default is 'off'.
-	gzip_vary on;
-
-
-	# Helper variable for proxying websockets.
-	map $http_upgrade $connection_upgrade {
-		default upgrade;
-		'' close;
-	}
-
-
-	# Specifies the main log format.
-	log_format main '$remote_addr - $remote_user [$time_local] "$request" '
-			'$status $body_bytes_sent "$http_referer" '
-			'"$http_user_agent" "$http_x_forwarded_for"';
-
-	# Sets the path, format, and configuration for a buffered log write.
-	access_log /var/log/nginx/access.log main;
-
-
-	# Includes virtual hosts configs.
-	include /etc/nginx/http.d/*.conf;
-
-	# WARNING: Don't use this directory for virtual hosts anymore.
-	# This include will be moved to the root context in Alpine 3.14.
-	#include /etc/nginx/conf.d/*.conf;
-}
-
-# TIP: Uncomment if you use stream module.
-#include /etc/nginx/stream.conf;
+# /etc/nginx/nginx.conf
+
+user nginx;
+
+# Set number of worker processes automatically based on number of CPU cores.
+worker_processes auto;
+
+# Enables the use of JIT for regular expressions to speed-up their processing.
+pcre_jit on;
+
+# Configures default error logger.
+error_log /var/log/nginx/error.log warn;
+
+# Includes files with directives to load dynamic modules.
+include /etc/nginx/modules/*.conf;
+
+
+events {
+	# The maximum number of simultaneous connections that can be opened by
+	# a worker process.
+	worker_connections 1024;
+}
+
+http {
+	# Includes mapping of file name extensions to MIME types of responses
+	# and defines the default type.
+	include /etc/nginx/mime.types;
+	default_type application/octet-stream;
+    set_real_ip_from 0.0.0.0/0;
+    real_ip_header X-Forwarded-For;
+	# Name servers used to resolve names of upstream servers into addresses.
+	# It's also needed when using tcpsocket and udpsocket in Lua modules.
+	#resolver 1.1.1.1 1.0.0.1 2606:4700:4700::1111 2606:4700:4700::1001;
+
+	# Don't tell nginx version to the clients. Default is 'on'.
+	server_tokens off;
+
+	# Specifies the maximum accepted body size of a client request, as
+	# indicated by the request header Content-Length. If the stated content
+	# length is greater than this size, then the client receives the HTTP
+	# error code 413. Set to 0 to disable. Default is '1m'.
+	client_max_body_size 1m;
+
+	# Sendfile copies data between one FD and other from within the kernel,
+	# which is more efficient than read() + write(). Default is off.
+	sendfile on;
+
+	# Causes nginx to attempt to send its HTTP response head in one packet,
+	# instead of using partial frames. Default is 'off'.
+	tcp_nopush on;
+
+
+	# Enables the specified protocols. Default is TLSv1 TLSv1.1 TLSv1.2.
+	# TIP: If you're not obligated to support ancient clients, remove TLSv1.1.
+	ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
+
+	# Path of the file with Diffie-Hellman parameters for EDH ciphers.
+	# TIP: Generate with: `openssl dhparam -out /etc/ssl/nginx/dh2048.pem 2048`
+	#ssl_dhparam /etc/ssl/nginx/dh2048.pem;
+
+	# Specifies that our cipher suits should be preferred over client ciphers.
+	# Default is 'off'.
+	ssl_prefer_server_ciphers on;
+
+	# Enables a shared SSL cache with size that can hold around 8000 sessions.
+	# Default is 'none'.
+	ssl_session_cache shared:SSL:2m;
+
+	# Specifies a time during which a client may reuse the session parameters.
+	# Default is '5m'.
+	ssl_session_timeout 1h;
+
+	# Disable TLS session tickets (they are insecure). Default is 'on'.
+	ssl_session_tickets off;
+
+
+	# Enable gzipping of responses.
+	#gzip on;
+
+	# Set the Vary HTTP header as defined in the RFC 2616. Default is 'off'.
+	gzip_vary on;
+
+
+	# Helper variable for proxying websockets.
+	map $http_upgrade $connection_upgrade {
+		default upgrade;
+		'' close;
+	}
+
+
+	# Specifies the main log format.
+	log_format main '$remote_addr - $remote_user [$time_local] "$request" '
+			'$status $body_bytes_sent "$http_referer" '
+			'"$http_user_agent" "$http_x_forwarded_for"';
+
+	# Sets the path, format, and configuration for a buffered log write.
+	access_log /var/log/nginx/access.log main;
+
+
+	# Includes virtual hosts configs.
+	include /etc/nginx/http.d/*.conf;
+
+	# WARNING: Don't use this directory for virtual hosts anymore.
+	# This include will be moved to the root context in Alpine 3.14.
+	#include /etc/nginx/conf.d/*.conf;
+}
+
+# TIP: Uncomment if you use stream module.
+#include /etc/nginx/stream.conf;

+ 1947 - 1947
docker/php.ini

@@ -1,1947 +1,1947 @@
-[PHP]
-
-;;;;;;;;;;;;;;;;;;;
-; About php.ini   ;
-;;;;;;;;;;;;;;;;;;;
-; PHP's initialization file, generally called php.ini, is responsible for
-; configuring many of the aspects of PHP's behavior.
-
-; PHP attempts to find and load this configuration from a number of locations.
-; The following is a summary of its search order:
-; 1. SAPI module specific location.
-; 2. The PHPRC environment variable. (As of PHP 5.2.0)
-; 3. A number of predefined registry keys on Windows (As of PHP 5.2.0)
-; 4. Current working directory (except CLI)
-; 5. The web server's directory (for SAPI modules), or directory of PHP
-; (otherwise in Windows)
-; 6. The directory from the --with-config-file-path compile time option, or the
-; Windows directory (usually C:\windows)
-; See the PHP docs for more specific information.
-; http://php.net/configuration.file
-
-; The syntax of the file is extremely simple.  Whitespace and lines
-; beginning with a semicolon are silently ignored (as you probably guessed).
-; Section headers (e.g. [Foo]) are also silently ignored, even though
-; they might mean something in the future.
-
-; Directives following the section heading [PATH=/www/mysite] only
-; apply to PHP files in the /www/mysite directory.  Directives
-; following the section heading [HOST=www.example.com] only apply to
-; PHP files served from www.example.com.  Directives set in these
-; special sections cannot be overridden by user-defined INI files or
-; at runtime. Currently, [PATH=] and [HOST=] sections only work under
-; CGI/FastCGI.
-; http://php.net/ini.sections
-
-; Directives are specified using the following syntax:
-; directive = value
-; Directive names are *case sensitive* - foo=bar is different from FOO=bar.
-; Directives are variables used to configure PHP or PHP extensions.
-; There is no name validation.  If PHP can't find an expected
-; directive because it is not set or is mistyped, a default value will be used.
-
-; The value can be a string, a number, a PHP constant (e.g. E_ALL or M_PI), one
-; of the INI constants (On, Off, True, False, Yes, No and None) or an expression
-; (e.g. E_ALL & ~E_NOTICE), a quoted string ("bar"), or a reference to a
-; previously set variable or directive (e.g. ${foo})
-
-; Expressions in the INI file are limited to bitwise operators and parentheses:
-; |  bitwise OR
-; ^  bitwise XOR
-; &  bitwise AND
-; ~  bitwise NOT
-; !  boolean NOT
-
-; Boolean flags can be turned on using the values 1, On, True or Yes.
-; They can be turned off using the values 0, Off, False or No.
-
-; An empty string can be denoted by simply not writing anything after the equal
-; sign, or by using the None keyword:
-
-; foo =         ; sets foo to an empty string
-; foo = None    ; sets foo to an empty string
-; foo = "None"  ; sets foo to the string 'None'
-
-; If you use constants in your value, and these constants belong to a
-; dynamically loaded extension (either a PHP extension or a Zend extension),
-; you may only use these constants *after* the line that loads the extension.
-
-;;;;;;;;;;;;;;;;;;;
-; About this file ;
-;;;;;;;;;;;;;;;;;;;
-; PHP comes packaged with two INI files. One that is recommended to be used
-; in production environments and one that is recommended to be used in
-; development environments.
-
-; php.ini-production contains settings which hold security, performance and
-; best practices at its core. But please be aware, these settings may break
-; compatibility with older or less security conscience applications. We
-; recommending using the production ini in production and testing environments.
-
-; php.ini-development is very similar to its production variant, except it is
-; much more verbose when it comes to errors. We recommend using the
-; development version only in development environments, as errors shown to
-; application users can inadvertently leak otherwise secure information.
-
-; This is the php.ini-production INI file.
-
-;;;;;;;;;;;;;;;;;;;
-; Quick Reference ;
-;;;;;;;;;;;;;;;;;;;
-; The following are all the settings which are different in either the production
-; or development versions of the INIs with respect to PHP's default behavior.
-; Please see the actual settings later in the document for more details as to why
-; we recommend these changes in PHP's behavior.
-
-; display_errors
-;   Default Value: On
-;   Development Value: On
-;   Production Value: Off
-
-; display_startup_errors
-;   Default Value: Off
-;   Development Value: On
-;   Production Value: Off
-
-; error_reporting
-;   Default Value: E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED
-;   Development Value: E_ALL
-;   Production Value: E_ALL & ~E_DEPRECATED & ~E_STRICT
-
-; log_errors
-;   Default Value: Off
-;   Development Value: On
-;   Production Value: On
-
-; max_input_time
-;   Default Value: -1 (Unlimited)
-;   Development Value: 60 (60 seconds)
-;   Production Value: 60 (60 seconds)
-
-; output_buffering
-;   Default Value: Off
-;   Development Value: 4096
-;   Production Value: 4096
-
-; register_argc_argv
-;   Default Value: On
-;   Development Value: Off
-;   Production Value: Off
-
-; request_order
-;   Default Value: None
-;   Development Value: "GP"
-;   Production Value: "GP"
-
-; session.gc_divisor
-;   Default Value: 100
-;   Development Value: 1000
-;   Production Value: 1000
-
-; session.sid_bits_per_character
-;   Default Value: 4
-;   Development Value: 5
-;   Production Value: 5
-
-; short_open_tag
-;   Default Value: On
-;   Development Value: Off
-;   Production Value: Off
-
-; variables_order
-;   Default Value: "EGPCS"
-;   Development Value: "GPCS"
-;   Production Value: "GPCS"
-
-;;;;;;;;;;;;;;;;;;;;
-; php.ini Options  ;
-;;;;;;;;;;;;;;;;;;;;
-; Name for user-defined php.ini (.htaccess) files. Default is ".user.ini"
-;user_ini.filename = ".user.ini"
-
-; To disable this feature set this option to an empty value
-;user_ini.filename =
-
-; TTL for user-defined php.ini files (time-to-live) in seconds. Default is 300 seconds (5 minutes)
-;user_ini.cache_ttl = 300
-
-;;;;;;;;;;;;;;;;;;;;
-; Language Options ;
-;;;;;;;;;;;;;;;;;;;;
-
-; Enable the PHP scripting language engine under Apache.
-; http://php.net/engine
-engine = On
-
-; This directive determines whether or not PHP will recognize code between
-; <? and ?> tags as PHP source which should be processed as such. It is
-; generally recommended that <?php and ?> should be used and that this feature
-; should be disabled, as enabling it may result in issues when generating XML
-; documents, however this remains supported for backward compatibility reasons.
-; Note that this directive does not control the <?= shorthand tag, which can be
-; used regardless of this directive.
-; Default Value: On
-; Development Value: Off
-; Production Value: Off
-; http://php.net/short-open-tag
-short_open_tag = Off
-
-; The number of significant digits displayed in floating point numbers.
-; http://php.net/precision
-precision = 14
-
-; Output buffering is a mechanism for controlling how much output data
-; (excluding headers and cookies) PHP should keep internally before pushing that
-; data to the client. If your application's output exceeds this setting, PHP
-; will send that data in chunks of roughly the size you specify.
-; Turning on this setting and managing its maximum buffer size can yield some
-; interesting side-effects depending on your application and web server.
-; You may be able to send headers and cookies after you've already sent output
-; through print or echo. You also may see performance benefits if your server is
-; emitting less packets due to buffered output versus PHP streaming the output
-; as it gets it. On production servers, 4096 bytes is a good setting for performance
-; reasons.
-; Note: Output buffering can also be controlled via Output Buffering Control
-;   functions.
-; Possible Values:
-;   On = Enabled and buffer is unlimited. (Use with caution)
-;   Off = Disabled
-;   Integer = Enables the buffer and sets its maximum size in bytes.
-; Note: This directive is hardcoded to Off for the CLI SAPI
-; Default Value: Off
-; Development Value: 4096
-; Production Value: 4096
-; http://php.net/output-buffering
-output_buffering = 4096
-
-; You can redirect all of the output of your scripts to a function.  For
-; example, if you set output_handler to "mb_output_handler", character
-; encoding will be transparently converted to the specified encoding.
-; Setting any output handler automatically turns on output buffering.
-; Note: People who wrote portable scripts should not depend on this ini
-;   directive. Instead, explicitly set the output handler using ob_start().
-;   Using this ini directive may cause problems unless you know what script
-;   is doing.
-; Note: You cannot use both "mb_output_handler" with "ob_iconv_handler"
-;   and you cannot use both "ob_gzhandler" and "zlib.output_compression".
-; Note: output_handler must be empty if this is set 'On' !!!!
-;   Instead you must use zlib.output_handler.
-; http://php.net/output-handler
-;output_handler =
-
-; URL rewriter function rewrites URL on the fly by using
-; output buffer. You can set target tags by this configuration.
-; "form" tag is special tag. It will add hidden input tag to pass values.
-; Refer to session.trans_sid_tags for usage.
-; Default Value: "form="
-; Development Value: "form="
-; Production Value: "form="
-;url_rewriter.tags
-
-; URL rewriter will not rewrite absolute URL nor form by default. To enable
-; absolute URL rewrite, allowed hosts must be defined at RUNTIME.
-; Refer to session.trans_sid_hosts for more details.
-; Default Value: ""
-; Development Value: ""
-; Production Value: ""
-;url_rewriter.hosts
-
-; Transparent output compression using the zlib library
-; Valid values for this option are 'off', 'on', or a specific buffer size
-; to be used for compression (default is 4KB)
-; Note: Resulting chunk size may vary due to nature of compression. PHP
-;   outputs chunks that are few hundreds bytes each as a result of
-;   compression. If you prefer a larger chunk size for better
-;   performance, enable output_buffering in addition.
-; Note: You need to use zlib.output_handler instead of the standard
-;   output_handler, or otherwise the output will be corrupted.
-; http://php.net/zlib.output-compression
-zlib.output_compression = Off
-
-; http://php.net/zlib.output-compression-level
-;zlib.output_compression_level = -1
-
-; You cannot specify additional output handlers if zlib.output_compression
-; is activated here. This setting does the same as output_handler but in
-; a different order.
-; http://php.net/zlib.output-handler
-;zlib.output_handler =
-
-; Implicit flush tells PHP to tell the output layer to flush itself
-; automatically after every output block.  This is equivalent to calling the
-; PHP function flush() after each and every call to print() or echo() and each
-; and every HTML block.  Turning this option on has serious performance
-; implications and is generally recommended for debugging purposes only.
-; http://php.net/implicit-flush
-; Note: This directive is hardcoded to On for the CLI SAPI
-implicit_flush = Off
-
-; The unserialize callback function will be called (with the undefined class'
-; name as parameter), if the unserializer finds an undefined class
-; which should be instantiated. A warning appears if the specified function is
-; not defined, or if the function doesn't include/implement the missing class.
-; So only set this entry, if you really want to implement such a
-; callback-function.
-unserialize_callback_func =
-
-; The unserialize_max_depth specifies the default depth limit for unserialized
-; structures. Setting the depth limit too high may result in stack overflows
-; during unserialization. The unserialize_max_depth ini setting can be
-; overridden by the max_depth option on individual unserialize() calls.
-; A value of 0 disables the depth limit.
-;unserialize_max_depth = 4096
-
-; When floats & doubles are serialized, store serialize_precision significant
-; digits after the floating point. The default value ensures that when floats
-; are decoded with unserialize, the data will remain the same.
-; The value is also used for json_encode when encoding double values.
-; If -1 is used, then dtoa mode 0 is used which automatically select the best
-; precision.
-serialize_precision = -1
-
-; open_basedir, if set, limits all file operations to the defined directory
-; and below.  This directive makes most sense if used in a per-directory
-; or per-virtualhost web server configuration file.
-; Note: disables the realpath cache
-; http://php.net/open-basedir
-;open_basedir =
-
-; This directive allows you to disable certain functions.
-; It receives a comma-delimited list of function names.
-; http://php.net/disable-functions
-disable_functions =
-
-; This directive allows you to disable certain classes.
-; It receives a comma-delimited list of class names.
-; http://php.net/disable-classes
-disable_classes =
-
-; Colors for Syntax Highlighting mode.  Anything that's acceptable in
-; <span style="color: ???????"> would work.
-; http://php.net/syntax-highlighting
-;highlight.string  = #DD0000
-;highlight.comment = #FF9900
-;highlight.keyword = #007700
-;highlight.default = #0000BB
-;highlight.html    = #000000
-
-; If enabled, the request will be allowed to complete even if the user aborts
-; the request. Consider enabling it if executing long requests, which may end up
-; being interrupted by the user or a browser timing out. PHP's default behavior
-; is to disable this feature.
-; http://php.net/ignore-user-abort
-;ignore_user_abort = On
-
-; Determines the size of the realpath cache to be used by PHP. This value should
-; be increased on systems where PHP opens many files to reflect the quantity of
-; the file operations performed.
-; Note: if open_basedir is set, the cache is disabled
-; http://php.net/realpath-cache-size
-;realpath_cache_size = 4096k
-
-; Duration of time, in seconds for which to cache realpath information for a given
-; file or directory. For systems with rarely changing files, consider increasing this
-; value.
-; http://php.net/realpath-cache-ttl
-;realpath_cache_ttl = 120
-
-; Enables or disables the circular reference collector.
-; http://php.net/zend.enable-gc
-zend.enable_gc = On
-
-; If enabled, scripts may be written in encodings that are incompatible with
-; the scanner.  CP936, Big5, CP949 and Shift_JIS are the examples of such
-; encodings.  To use this feature, mbstring extension must be enabled.
-; Default: Off
-;zend.multibyte = Off
-
-; Allows to set the default encoding for the scripts.  This value will be used
-; unless "declare(encoding=...)" directive appears at the top of the script.
-; Only affects if zend.multibyte is set.
-; Default: ""
-;zend.script_encoding =
-
-; Allows to include or exclude arguments from stack traces generated for exceptions.
-; In production, it is recommended to turn this setting on to prohibit the output
-; of sensitive information in stack traces
-; Default: Off
-zend.exception_ignore_args = On
-
-;;;;;;;;;;;;;;;;;
-; Miscellaneous ;
-;;;;;;;;;;;;;;;;;
-
-; Decides whether PHP may expose the fact that it is installed on the server
-; (e.g. by adding its signature to the Web server header).  It is no security
-; threat in any way, but it makes it possible to determine whether you use PHP
-; on your server or not.
-; http://php.net/expose-php
-expose_php = On
-
-;;;;;;;;;;;;;;;;;;;
-; Resource Limits ;
-;;;;;;;;;;;;;;;;;;;
-
-; Maximum execution time of each script, in seconds
-; http://php.net/max-execution-time
-; Note: This directive is hardcoded to 0 for the CLI SAPI
-max_execution_time = 30
-
-; Maximum amount of time each script may spend parsing request data. It's a good
-; idea to limit this time on productions servers in order to eliminate unexpectedly
-; long running scripts.
-; Note: This directive is hardcoded to -1 for the CLI SAPI
-; Default Value: -1 (Unlimited)
-; Development Value: 60 (60 seconds)
-; Production Value: 60 (60 seconds)
-; http://php.net/max-input-time
-max_input_time = 60
-
-; Maximum input variable nesting level
-; http://php.net/max-input-nesting-level
-;max_input_nesting_level = 64
-
-; How many GET/POST/COOKIE input variables may be accepted
-;max_input_vars = 1000
-
-; Maximum amount of memory a script may consume
-; http://php.net/memory-limit
-memory_limit = 128M
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-; Error handling and logging ;
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-; This directive informs PHP of which errors, warnings and notices you would like
-; it to take action for. The recommended way of setting values for this
-; directive is through the use of the error level constants and bitwise
-; operators. The error level constants are below here for convenience as well as
-; some common settings and their meanings.
-; By default, PHP is set to take action on all errors, notices and warnings EXCEPT
-; those related to E_NOTICE and E_STRICT, which together cover best practices and
-; recommended coding standards in PHP. For performance reasons, this is the
-; recommend error reporting setting. Your production server shouldn't be wasting
-; resources complaining about best practices and coding standards. That's what
-; development servers and development settings are for.
-; Note: The php.ini-development file has this setting as E_ALL. This
-; means it pretty much reports everything which is exactly what you want during
-; development and early testing.
-;
-; Error Level Constants:
-; E_ALL             - All errors and warnings (includes E_STRICT as of PHP 5.4.0)
-; E_ERROR           - fatal run-time errors
-; E_RECOVERABLE_ERROR  - almost fatal run-time errors
-; E_WARNING         - run-time warnings (non-fatal errors)
-; E_PARSE           - compile-time parse errors
-; E_NOTICE          - run-time notices (these are warnings which often result
-;                     from a bug in your code, but it's possible that it was
-;                     intentional (e.g., using an uninitialized variable and
-;                     relying on the fact it is automatically initialized to an
-;                     empty string)
-; E_STRICT          - run-time notices, enable to have PHP suggest changes
-;                     to your code which will ensure the best interoperability
-;                     and forward compatibility of your code
-; E_CORE_ERROR      - fatal errors that occur during PHP's initial startup
-; E_CORE_WARNING    - warnings (non-fatal errors) that occur during PHP's
-;                     initial startup
-; E_COMPILE_ERROR   - fatal compile-time errors
-; E_COMPILE_WARNING - compile-time warnings (non-fatal errors)
-; E_USER_ERROR      - user-generated error message
-; E_USER_WARNING    - user-generated warning message
-; E_USER_NOTICE     - user-generated notice message
-; E_DEPRECATED      - warn about code that will not work in future versions
-;                     of PHP
-; E_USER_DEPRECATED - user-generated deprecation warnings
-;
-; Common Values:
-;   E_ALL (Show all errors, warnings and notices including coding standards.)
-;   E_ALL & ~E_NOTICE  (Show all errors, except for notices)
-;   E_ALL & ~E_NOTICE & ~E_STRICT  (Show all errors, except for notices and coding standards warnings.)
-;   E_COMPILE_ERROR|E_RECOVERABLE_ERROR|E_ERROR|E_CORE_ERROR  (Show only errors)
-; Default Value: E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED
-; Development Value: E_ALL
-; Production Value: E_ALL & ~E_DEPRECATED & ~E_STRICT
-; http://php.net/error-reporting
-error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
-
-; This directive controls whether or not and where PHP will output errors,
-; notices and warnings too. Error output is very useful during development, but
-; it could be very dangerous in production environments. Depending on the code
-; which is triggering the error, sensitive information could potentially leak
-; out of your application such as database usernames and passwords or worse.
-; For production environments, we recommend logging errors rather than
-; sending them to STDOUT.
-; Possible Values:
-;   Off = Do not display any errors
-;   stderr = Display errors to STDERR (affects only CGI/CLI binaries!)
-;   On or stdout = Display errors to STDOUT
-; Default Value: On
-; Development Value: On
-; Production Value: Off
-; http://php.net/display-errors
-display_errors = Off
-
-; The display of errors which occur during PHP's startup sequence are handled
-; separately from display_errors. PHP's default behavior is to suppress those
-; errors from clients. Turning the display of startup errors on can be useful in
-; debugging configuration problems. We strongly recommend you
-; set this to 'off' for production servers.
-; Default Value: Off
-; Development Value: On
-; Production Value: Off
-; http://php.net/display-startup-errors
-display_startup_errors = Off
-
-; Besides displaying errors, PHP can also log errors to locations such as a
-; server-specific log, STDERR, or a location specified by the error_log
-; directive found below. While errors should not be displayed on productions
-; servers they should still be monitored and logging is a great way to do that.
-; Default Value: Off
-; Development Value: On
-; Production Value: On
-; http://php.net/log-errors
-log_errors = On
-
-; Set maximum length of log_errors. In error_log information about the source is
-; added. The default is 1024 and 0 allows to not apply any maximum length at all.
-; http://php.net/log-errors-max-len
-log_errors_max_len = 1024
-
-; Do not log repeated messages. Repeated errors must occur in same file on same
-; line unless ignore_repeated_source is set true.
-; http://php.net/ignore-repeated-errors
-ignore_repeated_errors = Off
-
-; Ignore source of message when ignoring repeated messages. When this setting
-; is On you will not log errors with repeated messages from different files or
-; source lines.
-; http://php.net/ignore-repeated-source
-ignore_repeated_source = Off
-
-; If this parameter is set to Off, then memory leaks will not be shown (on
-; stdout or in the log). This is only effective in a debug compile, and if
-; error reporting includes E_WARNING in the allowed list
-; http://php.net/report-memleaks
-report_memleaks = On
-
-; This setting is on by default.
-;report_zend_debug = 0
-
-; Store the last error/warning message in $php_errormsg (boolean). Setting this value
-; to On can assist in debugging and is appropriate for development servers. It should
-; however be disabled on production servers.
-; This directive is DEPRECATED.
-; Default Value: Off
-; Development Value: Off
-; Production Value: Off
-; http://php.net/track-errors
-;track_errors = Off
-
-; Turn off normal error reporting and emit XML-RPC error XML
-; http://php.net/xmlrpc-errors
-;xmlrpc_errors = 0
-
-; An XML-RPC faultCode
-;xmlrpc_error_number = 0
-
-; When PHP displays or logs an error, it has the capability of formatting the
-; error message as HTML for easier reading. This directive controls whether
-; the error message is formatted as HTML or not.
-; Note: This directive is hardcoded to Off for the CLI SAPI
-; http://php.net/html-errors
-;html_errors = On
-
-; If html_errors is set to On *and* docref_root is not empty, then PHP
-; produces clickable error messages that direct to a page describing the error
-; or function causing the error in detail.
-; You can download a copy of the PHP manual from http://php.net/docs
-; and change docref_root to the base URL of your local copy including the
-; leading '/'. You must also specify the file extension being used including
-; the dot. PHP's default behavior is to leave these settings empty, in which
-; case no links to documentation are generated.
-; Note: Never use this feature for production boxes.
-; http://php.net/docref-root
-; Examples
-;docref_root = "/phpmanual/"
-
-; http://php.net/docref-ext
-;docref_ext = .html
-
-; String to output before an error message. PHP's default behavior is to leave
-; this setting blank.
-; http://php.net/error-prepend-string
-; Example:
-;error_prepend_string = "<span style='color: #ff0000'>"
-
-; String to output after an error message. PHP's default behavior is to leave
-; this setting blank.
-; http://php.net/error-append-string
-; Example:
-;error_append_string = "</span>"
-
-; Log errors to specified file. PHP's default behavior is to leave this value
-; empty.
-; http://php.net/error-log
-; Example:
-;error_log = php_errors.log
-; Log errors to syslog (Event Log on Windows).
-;error_log = syslog
-
-; The syslog ident is a string which is prepended to every message logged
-; to syslog. Only used when error_log is set to syslog.
-;syslog.ident = php
-
-; The syslog facility is used to specify what type of program is logging
-; the message. Only used when error_log is set to syslog.
-;syslog.facility = user
-
-; Set this to disable filtering control characters (the default).
-; Some loggers only accept NVT-ASCII, others accept anything that's not
-; control characters. If your logger accepts everything, then no filtering
-; is needed at all.
-; Allowed values are:
-;   ascii (all printable ASCII characters and NL)
-;   no-ctrl (all characters except control characters)
-;   all (all characters)
-;   raw (like "all", but messages are not split at newlines)
-; http://php.net/syslog.filter
-;syslog.filter = ascii
-
-;windows.show_crt_warning
-; Default value: 0
-; Development value: 0
-; Production value: 0
-
-;;;;;;;;;;;;;;;;;
-; Data Handling ;
-;;;;;;;;;;;;;;;;;
-
-; The separator used in PHP generated URLs to separate arguments.
-; PHP's default setting is "&".
-; http://php.net/arg-separator.output
-; Example:
-;arg_separator.output = "&amp;"
-
-; List of separator(s) used by PHP to parse input URLs into variables.
-; PHP's default setting is "&".
-; NOTE: Every character in this directive is considered as separator!
-; http://php.net/arg-separator.input
-; Example:
-;arg_separator.input = ";&"
-
-; This directive determines which super global arrays are registered when PHP
-; starts up. G,P,C,E & S are abbreviations for the following respective super
-; globals: GET, POST, COOKIE, ENV and SERVER. There is a performance penalty
-; paid for the registration of these arrays and because ENV is not as commonly
-; used as the others, ENV is not recommended on productions servers. You
-; can still get access to the environment variables through getenv() should you
-; need to.
-; Default Value: "EGPCS"
-; Development Value: "GPCS"
-; Production Value: "GPCS";
-; http://php.net/variables-order
-variables_order = "GPCS"
-
-; This directive determines which super global data (G,P & C) should be
-; registered into the super global array REQUEST. If so, it also determines
-; the order in which that data is registered. The values for this directive
-; are specified in the same manner as the variables_order directive,
-; EXCEPT one. Leaving this value empty will cause PHP to use the value set
-; in the variables_order directive. It does not mean it will leave the super
-; globals array REQUEST empty.
-; Default Value: None
-; Development Value: "GP"
-; Production Value: "GP"
-; http://php.net/request-order
-request_order = "GP"
-
-; This directive determines whether PHP registers $argv & $argc each time it
-; runs. $argv contains an array of all the arguments passed to PHP when a script
-; is invoked. $argc contains an integer representing the number of arguments
-; that were passed when the script was invoked. These arrays are extremely
-; useful when running scripts from the command line. When this directive is
-; enabled, registering these variables consumes CPU cycles and memory each time
-; a script is executed. For performance reasons, this feature should be disabled
-; on production servers.
-; Note: This directive is hardcoded to On for the CLI SAPI
-; Default Value: On
-; Development Value: Off
-; Production Value: Off
-; http://php.net/register-argc-argv
-register_argc_argv = Off
-
-; When enabled, the ENV, REQUEST and SERVER variables are created when they're
-; first used (Just In Time) instead of when the script starts. If these
-; variables are not used within a script, having this directive on will result
-; in a performance gain. The PHP directive register_argc_argv must be disabled
-; for this directive to have any effect.
-; http://php.net/auto-globals-jit
-auto_globals_jit = On
-
-; Whether PHP will read the POST data.
-; This option is enabled by default.
-; Most likely, you won't want to disable this option globally. It causes $_POST
-; and $_FILES to always be empty; the only way you will be able to read the
-; POST data will be through the php://input stream wrapper. This can be useful
-; to proxy requests or to process the POST data in a memory efficient fashion.
-; http://php.net/enable-post-data-reading
-;enable_post_data_reading = Off
-
-; Maximum size of POST data that PHP will accept.
-; Its value may be 0 to disable the limit. It is ignored if POST data reading
-; is disabled through enable_post_data_reading.
-; http://php.net/post-max-size
-post_max_size = 20M
-
-; Automatically add files before PHP document.
-; http://php.net/auto-prepend-file
-auto_prepend_file =
-
-; Automatically add files after PHP document.
-; http://php.net/auto-append-file
-auto_append_file =
-
-; By default, PHP will output a media type using the Content-Type header. To
-; disable this, simply set it to be empty.
-;
-; PHP's built-in default media type is set to text/html.
-; http://php.net/default-mimetype
-default_mimetype = "text/html"
-
-; PHP's default character set is set to UTF-8.
-; http://php.net/default-charset
-default_charset = "UTF-8"
-
-; PHP internal character encoding is set to empty.
-; If empty, default_charset is used.
-; http://php.net/internal-encoding
-;internal_encoding =
-
-; PHP input character encoding is set to empty.
-; If empty, default_charset is used.
-; http://php.net/input-encoding
-;input_encoding =
-
-; PHP output character encoding is set to empty.
-; If empty, default_charset is used.
-; See also output_buffer.
-; http://php.net/output-encoding
-;output_encoding =
-
-;;;;;;;;;;;;;;;;;;;;;;;;;
-; Paths and Directories ;
-;;;;;;;;;;;;;;;;;;;;;;;;;
-
-; UNIX: "/path1:/path2"
-include_path = ".:/usr/share/php7"
-;
-; Windows: "\path1;\path2"
-;include_path = ".;c:\php\includes"
-;
-; PHP's default setting for include_path is ".;/path/to/php/pear"
-; http://php.net/include-path
-
-; The root of the PHP pages, used only if nonempty.
-; if PHP was not compiled with FORCE_REDIRECT, you SHOULD set doc_root
-; if you are running php as a CGI under any web server (other than IIS)
-; see documentation for security issues.  The alternate is to use the
-; cgi.force_redirect configuration below
-; http://php.net/doc-root
-doc_root =
-
-; The directory under which PHP opens the script using /~username used only
-; if nonempty.
-; http://php.net/user-dir
-user_dir =
-
-; Directory in which the loadable extensions (modules) reside.
-; http://php.net/extension-dir
-;extension_dir = "./"
-; On windows:
-;extension_dir = "ext"
-
-; Directory where the temporary files should be placed.
-; Defaults to the system default (see sys_get_temp_dir)
-;sys_temp_dir = "/tmp"
-
-; Whether or not to enable the dl() function.  The dl() function does NOT work
-; properly in multithreaded servers, such as IIS or Zeus, and is automatically
-; disabled on them.
-; http://php.net/enable-dl
-enable_dl = Off
-
-; cgi.force_redirect is necessary to provide security running PHP as a CGI under
-; most web servers.  Left undefined, PHP turns this on by default.  You can
-; turn it off here AT YOUR OWN RISK
-; **You CAN safely turn this off for IIS, in fact, you MUST.**
-; http://php.net/cgi.force-redirect
-;cgi.force_redirect = 1
-
-; if cgi.nph is enabled it will force cgi to always sent Status: 200 with
-; every request. PHP's default behavior is to disable this feature.
-;cgi.nph = 1
-
-; if cgi.force_redirect is turned on, and you are not running under Apache or Netscape
-; (iPlanet) web servers, you MAY need to set an environment variable name that PHP
-; will look for to know it is OK to continue execution.  Setting this variable MAY
-; cause security issues, KNOW WHAT YOU ARE DOING FIRST.
-; http://php.net/cgi.redirect-status-env
-;cgi.redirect_status_env =
-
-; cgi.fix_pathinfo provides *real* PATH_INFO/PATH_TRANSLATED support for CGI.  PHP's
-; previous behaviour was to set PATH_TRANSLATED to SCRIPT_FILENAME, and to not grok
-; what PATH_INFO is.  For more information on PATH_INFO, see the cgi specs.  Setting
-; this to 1 will cause PHP CGI to fix its paths to conform to the spec.  A setting
-; of zero causes PHP to behave as before.  Default is 1.  You should fix your scripts
-; to use SCRIPT_FILENAME rather than PATH_TRANSLATED.
-; http://php.net/cgi.fix-pathinfo
-;cgi.fix_pathinfo=1
-
-; if cgi.discard_path is enabled, the PHP CGI binary can safely be placed outside
-; of the web tree and people will not be able to circumvent .htaccess security.
-;cgi.discard_path=1
-
-; FastCGI under IIS supports the ability to impersonate
-; security tokens of the calling client.  This allows IIS to define the
-; security context that the request runs under.  mod_fastcgi under Apache
-; does not currently support this feature (03/17/2002)
-; Set to 1 if running under IIS.  Default is zero.
-; http://php.net/fastcgi.impersonate
-;fastcgi.impersonate = 1
-
-; Disable logging through FastCGI connection. PHP's default behavior is to enable
-; this feature.
-;fastcgi.logging = 0
-
-; cgi.rfc2616_headers configuration option tells PHP what type of headers to
-; use when sending HTTP response code. If set to 0, PHP sends Status: header that
-; is supported by Apache. When this option is set to 1, PHP will send
-; RFC2616 compliant header.
-; Default is zero.
-; http://php.net/cgi.rfc2616-headers
-;cgi.rfc2616_headers = 0
-
-; cgi.check_shebang_line controls whether CGI PHP checks for line starting with #!
-; (shebang) at the top of the running script. This line might be needed if the
-; script support running both as stand-alone script and via PHP CGI<. PHP in CGI
-; mode skips this line and ignores its content if this directive is turned on.
-; http://php.net/cgi.check-shebang-line
-;cgi.check_shebang_line=1
-
-;;;;;;;;;;;;;;;;
-; File Uploads ;
-;;;;;;;;;;;;;;;;
-
-; Whether to allow HTTP file uploads.
-; http://php.net/file-uploads
-file_uploads = On
-
-; Temporary directory for HTTP uploaded files (will use system default if not
-; specified).
-; http://php.net/upload-tmp-dir
-;upload_tmp_dir =
-
-; Maximum allowed size for uploaded files.
-; http://php.net/upload-max-filesize
-upload_max_filesize = 50M
-
-; Maximum number of files that can be uploaded via a single request
-max_file_uploads = 100
-
-;;;;;;;;;;;;;;;;;;
-; Fopen wrappers ;
-;;;;;;;;;;;;;;;;;;
-
-; Whether to allow the treatment of URLs (like http:// or ftp://) as files.
-; http://php.net/allow-url-fopen
-allow_url_fopen = On
-
-; Whether to allow include/require to open URLs (like http:// or ftp://) as files.
-; http://php.net/allow-url-include
-allow_url_include = Off
-
-; Define the anonymous ftp password (your email address). PHP's default setting
-; for this is empty.
-; http://php.net/from
-;from="john@doe.com"
-
-; Define the User-Agent string. PHP's default setting for this is empty.
-; http://php.net/user-agent
-;user_agent="PHP"
-
-; Default timeout for socket based streams (seconds)
-; http://php.net/default-socket-timeout
-default_socket_timeout = 60
-
-; If your scripts have to deal with files from Macintosh systems,
-; or you are running on a Mac and need to deal with files from
-; unix or win32 systems, setting this flag will cause PHP to
-; automatically detect the EOL character in those files so that
-; fgets() and file() will work regardless of the source of the file.
-; http://php.net/auto-detect-line-endings
-;auto_detect_line_endings = Off
-
-;;;;;;;;;;;;;;;;;;;;;;
-; Dynamic Extensions ;
-;;;;;;;;;;;;;;;;;;;;;;
-
-; If you wish to have an extension loaded automatically, use the following
-; syntax:
-;
-;   extension=modulename
-;
-; For example:
-;
-;   extension=mysqli
-;
-; When the extension library to load is not located in the default extension
-; directory, You may specify an absolute path to the library file:
-;
-;   extension=/path/to/extension/mysqli.so
-;
-; Note : The syntax used in previous PHP versions ('extension=<ext>.so' and
-; 'extension='php_<ext>.dll') is supported for legacy reasons and may be
-; deprecated in a future PHP major version. So, when it is possible, please
-; move to the new ('extension=<ext>) syntax.
-;
-; Notes for Windows environments :
-;
-; - Many DLL files are located in the extensions/ (PHP 4) or ext/ (PHP 5+)
-;   extension folders as well as the separate PECL DLL download (PHP 5+).
-;   Be sure to appropriately set the extension_dir directive.
-;
-;extension=bz2
-;extension=curl
-;extension=ffi
-;extension=ftp
-;extension=fileinfo
-;extension=gd2
-;extension=gettext
-;extension=gmp
-;extension=intl
-;extension=imap
-;extension=ldap
-;extension=mbstring
-;extension=exif      ; Must be after mbstring as it depends on it
-;extension=mysqli
-;extension=oci8_12c  ; Use with Oracle Database 12c Instant Client
-;extension=odbc
-;extension=openssl
-;extension=pdo_firebird
-;extension=pdo_mysql
-;extension=pdo_oci
-;extension=pdo_odbc
-;extension=pdo_pgsql
-;extension=pdo_sqlite
-;extension=pgsql
-;extension=shmop
-
-; The MIBS data available in the PHP distribution must be installed.
-; See http://www.php.net/manual/en/snmp.installation.php
-;extension=snmp
-
-;extension=soap
-;extension=sockets
-;extension=sodium
-;extension=sqlite3
-;extension=tidy
-;extension=xmlrpc
-;extension=xsl
-
-;;;;;;;;;;;;;;;;;;;
-; Module Settings ;
-;;;;;;;;;;;;;;;;;;;
-
-[CLI Server]
-; Whether the CLI web server uses ANSI color coding in its terminal output.
-cli_server.color = On
-
-[Date]
-; Defines the default timezone used by the date functions
-; http://php.net/date.timezone
-;date.timezone =
-
-; http://php.net/date.default-latitude
-;date.default_latitude = 31.7667
-
-; http://php.net/date.default-longitude
-;date.default_longitude = 35.2333
-
-; http://php.net/date.sunrise-zenith
-;date.sunrise_zenith = 90.583333
-
-; http://php.net/date.sunset-zenith
-;date.sunset_zenith = 90.583333
-
-[filter]
-; http://php.net/filter.default
-;filter.default = unsafe_raw
-
-; http://php.net/filter.default-flags
-;filter.default_flags =
-
-[iconv]
-; Use of this INI entry is deprecated, use global input_encoding instead.
-; If empty, default_charset or input_encoding or iconv.input_encoding is used.
-; The precedence is: default_charset < input_encoding < iconv.input_encoding
-;iconv.input_encoding =
-
-; Use of this INI entry is deprecated, use global internal_encoding instead.
-; If empty, default_charset or internal_encoding or iconv.internal_encoding is used.
-; The precedence is: default_charset < internal_encoding < iconv.internal_encoding
-;iconv.internal_encoding =
-
-; Use of this INI entry is deprecated, use global output_encoding instead.
-; If empty, default_charset or output_encoding or iconv.output_encoding is used.
-; The precedence is: default_charset < output_encoding < iconv.output_encoding
-; To use an output encoding conversion, iconv's output handler must be set
-; otherwise output encoding conversion cannot be performed.
-;iconv.output_encoding =
-
-[imap]
-; rsh/ssh logins are disabled by default. Use this INI entry if you want to
-; enable them. Note that the IMAP library does not filter mailbox names before
-; passing them to rsh/ssh command, thus passing untrusted data to this function
-; with rsh/ssh enabled is insecure.
-;imap.enable_insecure_rsh=0
-
-[intl]
-;intl.default_locale =
-; This directive allows you to produce PHP errors when some error
-; happens within intl functions. The value is the level of the error produced.
-; Default is 0, which does not produce any errors.
-;intl.error_level = E_WARNING
-;intl.use_exceptions = 0
-
-[sqlite3]
-; Directory pointing to SQLite3 extensions
-; http://php.net/sqlite3.extension-dir
-;sqlite3.extension_dir =
-
-; SQLite defensive mode flag (only available from SQLite 3.26+)
-; When the defensive flag is enabled, language features that allow ordinary
-; SQL to deliberately corrupt the database file are disabled. This forbids
-; writing directly to the schema, shadow tables (eg. FTS data tables), or
-; the sqlite_dbpage virtual table.
-; https://www.sqlite.org/c3ref/c_dbconfig_defensive.html
-; (for older SQLite versions, this flag has no use)
-;sqlite3.defensive = 1
-
-[Pcre]
-; PCRE library backtracking limit.
-; http://php.net/pcre.backtrack-limit
-;pcre.backtrack_limit=100000
-
-; PCRE library recursion limit.
-; Please note that if you set this value to a high number you may consume all
-; the available process stack and eventually crash PHP (due to reaching the
-; stack size limit imposed by the Operating System).
-; http://php.net/pcre.recursion-limit
-;pcre.recursion_limit=100000
-
-; Enables or disables JIT compilation of patterns. This requires the PCRE
-; library to be compiled with JIT support.
-;pcre.jit=1
-
-[Pdo]
-; Whether to pool ODBC connections. Can be one of "strict", "relaxed" or "off"
-; http://php.net/pdo-odbc.connection-pooling
-;pdo_odbc.connection_pooling=strict
-
-;pdo_odbc.db2_instance_name
-
-[Pdo_mysql]
-; Default socket name for local MySQL connects.  If empty, uses the built-in
-; MySQL defaults.
-pdo_mysql.default_socket=
-
-[Phar]
-; http://php.net/phar.readonly
-;phar.readonly = On
-
-; http://php.net/phar.require-hash
-;phar.require_hash = On
-
-;phar.cache_list =
-
-[mail function]
-; For Win32 only.
-; http://php.net/smtp
-SMTP = localhost
-; http://php.net/smtp-port
-smtp_port = 25
-
-; For Win32 only.
-; http://php.net/sendmail-from
-;sendmail_from = me@example.com
-
-; For Unix only.  You may supply arguments as well (default: "sendmail -t -i").
-; http://php.net/sendmail-path
-;sendmail_path =
-
-; Force the addition of the specified parameters to be passed as extra parameters
-; to the sendmail binary. These parameters will always replace the value of
-; the 5th parameter to mail().
-;mail.force_extra_parameters =
-
-; Add X-PHP-Originating-Script: that will include uid of the script followed by the filename
-mail.add_x_header = Off
-
-; The path to a log file that will log all mail() calls. Log entries include
-; the full path of the script, line number, To address and headers.
-;mail.log =
-; Log mail to syslog (Event Log on Windows).
-;mail.log = syslog
-
-[ODBC]
-; http://php.net/odbc.default-db
-;odbc.default_db    =  Not yet implemented
-
-; http://php.net/odbc.default-user
-;odbc.default_user  =  Not yet implemented
-
-; http://php.net/odbc.default-pw
-;odbc.default_pw    =  Not yet implemented
-
-; Controls the ODBC cursor model.
-; Default: SQL_CURSOR_STATIC (default).
-;odbc.default_cursortype
-
-; Allow or prevent persistent links.
-; http://php.net/odbc.allow-persistent
-odbc.allow_persistent = On
-
-; Check that a connection is still valid before reuse.
-; http://php.net/odbc.check-persistent
-odbc.check_persistent = On
-
-; Maximum number of persistent links.  -1 means no limit.
-; http://php.net/odbc.max-persistent
-odbc.max_persistent = -1
-
-; Maximum number of links (persistent + non-persistent).  -1 means no limit.
-; http://php.net/odbc.max-links
-odbc.max_links = -1
-
-; Handling of LONG fields.  Returns number of bytes to variables.  0 means
-; passthru.
-; http://php.net/odbc.defaultlrl
-odbc.defaultlrl = 4096
-
-; Handling of binary data.  0 means passthru, 1 return as is, 2 convert to char.
-; See the documentation on odbc_binmode and odbc_longreadlen for an explanation
-; of odbc.defaultlrl and odbc.defaultbinmode
-; http://php.net/odbc.defaultbinmode
-odbc.defaultbinmode = 1
-
-[MySQLi]
-
-; Maximum number of persistent links.  -1 means no limit.
-; http://php.net/mysqli.max-persistent
-mysqli.max_persistent = -1
-
-; Allow accessing, from PHP's perspective, local files with LOAD DATA statements
-; http://php.net/mysqli.allow_local_infile
-;mysqli.allow_local_infile = On
-
-; Allow or prevent persistent links.
-; http://php.net/mysqli.allow-persistent
-mysqli.allow_persistent = On
-
-; Maximum number of links.  -1 means no limit.
-; http://php.net/mysqli.max-links
-mysqli.max_links = -1
-
-; Default port number for mysqli_connect().  If unset, mysqli_connect() will use
-; the $MYSQL_TCP_PORT or the mysql-tcp entry in /etc/services or the
-; compile-time value defined MYSQL_PORT (in that order).  Win32 will only look
-; at MYSQL_PORT.
-; http://php.net/mysqli.default-port
-mysqli.default_port = 3306
-
-; Default socket name for local MySQL connects.  If empty, uses the built-in
-; MySQL defaults.
-; http://php.net/mysqli.default-socket
-mysqli.default_socket =
-
-; Default host for mysqli_connect() (doesn't apply in safe mode).
-; http://php.net/mysqli.default-host
-mysqli.default_host =
-
-; Default user for mysqli_connect() (doesn't apply in safe mode).
-; http://php.net/mysqli.default-user
-mysqli.default_user =
-
-; Default password for mysqli_connect() (doesn't apply in safe mode).
-; Note that this is generally a *bad* idea to store passwords in this file.
-; *Any* user with PHP access can run 'echo get_cfg_var("mysqli.default_pw")
-; and reveal this password!  And of course, any users with read access to this
-; file will be able to reveal the password as well.
-; http://php.net/mysqli.default-pw
-mysqli.default_pw =
-
-; Allow or prevent reconnect
-mysqli.reconnect = Off
-
-[mysqlnd]
-; Enable / Disable collection of general statistics by mysqlnd which can be
-; used to tune and monitor MySQL operations.
-mysqlnd.collect_statistics = On
-
-; Enable / Disable collection of memory usage statistics by mysqlnd which can be
-; used to tune and monitor MySQL operations.
-mysqlnd.collect_memory_statistics = Off
-
-; Records communication from all extensions using mysqlnd to the specified log
-; file.
-; http://php.net/mysqlnd.debug
-;mysqlnd.debug =
-
-; Defines which queries will be logged.
-;mysqlnd.log_mask = 0
-
-; Default size of the mysqlnd memory pool, which is used by result sets.
-;mysqlnd.mempool_default_size = 16000
-
-; Size of a pre-allocated buffer used when sending commands to MySQL in bytes.
-;mysqlnd.net_cmd_buffer_size = 2048
-
-; Size of a pre-allocated buffer used for reading data sent by the server in
-; bytes.
-;mysqlnd.net_read_buffer_size = 32768
-
-; Timeout for network requests in seconds.
-;mysqlnd.net_read_timeout = 31536000
-
-; SHA-256 Authentication Plugin related. File with the MySQL server public RSA
-; key.
-;mysqlnd.sha256_server_public_key =
-
-[OCI8]
-
-; Connection: Enables privileged connections using external
-; credentials (OCI_SYSOPER, OCI_SYSDBA)
-; http://php.net/oci8.privileged-connect
-;oci8.privileged_connect = Off
-
-; Connection: The maximum number of persistent OCI8 connections per
-; process. Using -1 means no limit.
-; http://php.net/oci8.max-persistent
-;oci8.max_persistent = -1
-
-; Connection: The maximum number of seconds a process is allowed to
-; maintain an idle persistent connection. Using -1 means idle
-; persistent connections will be maintained forever.
-; http://php.net/oci8.persistent-timeout
-;oci8.persistent_timeout = -1
-
-; Connection: The number of seconds that must pass before issuing a
-; ping during oci_pconnect() to check the connection validity. When
-; set to 0, each oci_pconnect() will cause a ping. Using -1 disables
-; pings completely.
-; http://php.net/oci8.ping-interval
-;oci8.ping_interval = 60
-
-; Connection: Set this to a user chosen connection class to be used
-; for all pooled server requests with Oracle 11g Database Resident
-; Connection Pooling (DRCP).  To use DRCP, this value should be set to
-; the same string for all web servers running the same application,
-; the database pool must be configured, and the connection string must
-; specify to use a pooled server.
-;oci8.connection_class =
-
-; High Availability: Using On lets PHP receive Fast Application
-; Notification (FAN) events generated when a database node fails. The
-; database must also be configured to post FAN events.
-;oci8.events = Off
-
-; Tuning: This option enables statement caching, and specifies how
-; many statements to cache. Using 0 disables statement caching.
-; http://php.net/oci8.statement-cache-size
-;oci8.statement_cache_size = 20
-
-; Tuning: Enables statement prefetching and sets the default number of
-; rows that will be fetched automatically after statement execution.
-; http://php.net/oci8.default-prefetch
-;oci8.default_prefetch = 100
-
-; Compatibility. Using On means oci_close() will not close
-; oci_connect() and oci_new_connect() connections.
-; http://php.net/oci8.old-oci-close-semantics
-;oci8.old_oci_close_semantics = Off
-
-[PostgreSQL]
-; Allow or prevent persistent links.
-; http://php.net/pgsql.allow-persistent
-pgsql.allow_persistent = On
-
-; Detect broken persistent links always with pg_pconnect().
-; Auto reset feature requires a little overheads.
-; http://php.net/pgsql.auto-reset-persistent
-pgsql.auto_reset_persistent = Off
-
-; Maximum number of persistent links.  -1 means no limit.
-; http://php.net/pgsql.max-persistent
-pgsql.max_persistent = -1
-
-; Maximum number of links (persistent+non persistent).  -1 means no limit.
-; http://php.net/pgsql.max-links
-pgsql.max_links = -1
-
-; Ignore PostgreSQL backends Notice message or not.
-; Notice message logging require a little overheads.
-; http://php.net/pgsql.ignore-notice
-pgsql.ignore_notice = 0
-
-; Log PostgreSQL backends Notice message or not.
-; Unless pgsql.ignore_notice=0, module cannot log notice message.
-; http://php.net/pgsql.log-notice
-pgsql.log_notice = 0
-
-[bcmath]
-; Number of decimal digits for all bcmath functions.
-; http://php.net/bcmath.scale
-bcmath.scale = 0
-
-[browscap]
-; http://php.net/browscap
-;browscap = extra/browscap.ini
-
-[Session]
-; Handler used to store/retrieve data.
-; http://php.net/session.save-handler
-session.save_handler = files
-
-; Argument passed to save_handler.  In the case of files, this is the path
-; where data files are stored. Note: Windows users have to change this
-; variable in order to use PHP's session functions.
-;
-; The path can be defined as:
-;
-;     session.save_path = "N;/path"
-;
-; where N is an integer.  Instead of storing all the session files in
-; /path, what this will do is use subdirectories N-levels deep, and
-; store the session data in those directories.  This is useful if
-; your OS has problems with many files in one directory, and is
-; a more efficient layout for servers that handle many sessions.
-;
-; NOTE 1: PHP will not create this directory structure automatically.
-;         You can use the script in the ext/session dir for that purpose.
-; NOTE 2: See the section on garbage collection below if you choose to
-;         use subdirectories for session storage
-;
-; The file storage module creates files using mode 600 by default.
-; You can change that by using
-;
-;     session.save_path = "N;MODE;/path"
-;
-; where MODE is the octal representation of the mode. Note that this
-; does not overwrite the process's umask.
-; http://php.net/session.save-path
-;session.save_path = "/tmp"
-
-; Whether to use strict session mode.
-; Strict session mode does not accept an uninitialized session ID, and
-; regenerates the session ID if the browser sends an uninitialized session ID.
-; Strict mode protects applications from session fixation via a session adoption
-; vulnerability. It is disabled by default for maximum compatibility, but
-; enabling it is encouraged.
-; https://wiki.php.net/rfc/strict_sessions
-session.use_strict_mode = 0
-
-; Whether to use cookies.
-; http://php.net/session.use-cookies
-session.use_cookies = 1
-
-; http://php.net/session.cookie-secure
-;session.cookie_secure =
-
-; This option forces PHP to fetch and use a cookie for storing and maintaining
-; the session id. We encourage this operation as it's very helpful in combating
-; session hijacking when not specifying and managing your own session id. It is
-; not the be-all and end-all of session hijacking defense, but it's a good start.
-; http://php.net/session.use-only-cookies
-session.use_only_cookies = 1
-
-; Name of the session (used as cookie name).
-; http://php.net/session.name
-session.name = PHPSESSID
-
-; Initialize session on request startup.
-; http://php.net/session.auto-start
-session.auto_start = 0
-
-; Lifetime in seconds of cookie or, if 0, until browser is restarted.
-; http://php.net/session.cookie-lifetime
-session.cookie_lifetime = 0
-
-; The path for which the cookie is valid.
-; http://php.net/session.cookie-path
-session.cookie_path = /
-
-; The domain for which the cookie is valid.
-; http://php.net/session.cookie-domain
-session.cookie_domain =
-
-; Whether or not to add the httpOnly flag to the cookie, which makes it
-; inaccessible to browser scripting languages such as JavaScript.
-; http://php.net/session.cookie-httponly
-session.cookie_httponly =
-
-; Add SameSite attribute to cookie to help mitigate Cross-Site Request Forgery (CSRF/XSRF)
-; Current valid values are "Strict", "Lax" or "None". When using "None",
-; make sure to include the quotes, as `none` is interpreted like `false` in ini files.
-; https://tools.ietf.org/html/draft-west-first-party-cookies-07
-session.cookie_samesite =
-
-; Handler used to serialize data. php is the standard serializer of PHP.
-; http://php.net/session.serialize-handler
-session.serialize_handler = php
-
-; Defines the probability that the 'garbage collection' process is started on every
-; session initialization. The probability is calculated by using gc_probability/gc_divisor,
-; e.g. 1/100 means there is a 1% chance that the GC process starts on each request.
-; Default Value: 1
-; Development Value: 1
-; Production Value: 1
-; http://php.net/session.gc-probability
-session.gc_probability = 1
-
-; Defines the probability that the 'garbage collection' process is started on every
-; session initialization. The probability is calculated by using gc_probability/gc_divisor,
-; e.g. 1/100 means there is a 1% chance that the GC process starts on each request.
-; For high volume production servers, using a value of 1000 is a more efficient approach.
-; Default Value: 100
-; Development Value: 1000
-; Production Value: 1000
-; http://php.net/session.gc-divisor
-session.gc_divisor = 1000
-
-; After this number of seconds, stored data will be seen as 'garbage' and
-; cleaned up by the garbage collection process.
-; http://php.net/session.gc-maxlifetime
-session.gc_maxlifetime = 1440
-
-; NOTE: If you are using the subdirectory option for storing session files
-;       (see session.save_path above), then garbage collection does *not*
-;       happen automatically.  You will need to do your own garbage
-;       collection through a shell script, cron entry, or some other method.
-;       For example, the following script is the equivalent of setting
-;       session.gc_maxlifetime to 1440 (1440 seconds = 24 minutes):
-;          find /path/to/sessions -cmin +24 -type f | xargs rm
-
-; Check HTTP Referer to invalidate externally stored URLs containing ids.
-; HTTP_REFERER has to contain this substring for the session to be
-; considered as valid.
-; http://php.net/session.referer-check
-session.referer_check =
-
-; Set to {nocache,private,public,} to determine HTTP caching aspects
-; or leave this empty to avoid sending anti-caching headers.
-; http://php.net/session.cache-limiter
-session.cache_limiter = nocache
-
-; Document expires after n minutes.
-; http://php.net/session.cache-expire
-session.cache_expire = 180
-
-; trans sid support is disabled by default.
-; Use of trans sid may risk your users' security.
-; Use this option with caution.
-; - User may send URL contains active session ID
-;   to other person via. email/irc/etc.
-; - URL that contains active session ID may be stored
-;   in publicly accessible computer.
-; - User may access your site with the same session ID
-;   always using URL stored in browser's history or bookmarks.
-; http://php.net/session.use-trans-sid
-session.use_trans_sid = 0
-
-; Set session ID character length. This value could be between 22 to 256.
-; Shorter length than default is supported only for compatibility reason.
-; Users should use 32 or more chars.
-; http://php.net/session.sid-length
-; Default Value: 32
-; Development Value: 26
-; Production Value: 26
-session.sid_length = 26
-
-; The URL rewriter will look for URLs in a defined set of HTML tags.
-; <form> is special; if you include them here, the rewriter will
-; add a hidden <input> field with the info which is otherwise appended
-; to URLs. <form> tag's action attribute URL will not be modified
-; unless it is specified.
-; Note that all valid entries require a "=", even if no value follows.
-; Default Value: "a=href,area=href,frame=src,form="
-; Development Value: "a=href,area=href,frame=src,form="
-; Production Value: "a=href,area=href,frame=src,form="
-; http://php.net/url-rewriter.tags
-session.trans_sid_tags = "a=href,area=href,frame=src,form="
-
-; URL rewriter does not rewrite absolute URLs by default.
-; To enable rewrites for absolute paths, target hosts must be specified
-; at RUNTIME. i.e. use ini_set()
-; <form> tags is special. PHP will check action attribute's URL regardless
-; of session.trans_sid_tags setting.
-; If no host is defined, HTTP_HOST will be used for allowed host.
-; Example value: php.net,www.php.net,wiki.php.net
-; Use "," for multiple hosts. No spaces are allowed.
-; Default Value: ""
-; Development Value: ""
-; Production Value: ""
-;session.trans_sid_hosts=""
-
-; Define how many bits are stored in each character when converting
-; the binary hash data to something readable.
-; Possible values:
-;   4  (4 bits: 0-9, a-f)
-;   5  (5 bits: 0-9, a-v)
-;   6  (6 bits: 0-9, a-z, A-Z, "-", ",")
-; Default Value: 4
-; Development Value: 5
-; Production Value: 5
-; http://php.net/session.hash-bits-per-character
-session.sid_bits_per_character = 5
-
-; Enable upload progress tracking in $_SESSION
-; Default Value: On
-; Development Value: On
-; Production Value: On
-; http://php.net/session.upload-progress.enabled
-;session.upload_progress.enabled = On
-
-; Cleanup the progress information as soon as all POST data has been read
-; (i.e. upload completed).
-; Default Value: On
-; Development Value: On
-; Production Value: On
-; http://php.net/session.upload-progress.cleanup
-;session.upload_progress.cleanup = On
-
-; A prefix used for the upload progress key in $_SESSION
-; Default Value: "upload_progress_"
-; Development Value: "upload_progress_"
-; Production Value: "upload_progress_"
-; http://php.net/session.upload-progress.prefix
-;session.upload_progress.prefix = "upload_progress_"
-
-; The index name (concatenated with the prefix) in $_SESSION
-; containing the upload progress information
-; Default Value: "PHP_SESSION_UPLOAD_PROGRESS"
-; Development Value: "PHP_SESSION_UPLOAD_PROGRESS"
-; Production Value: "PHP_SESSION_UPLOAD_PROGRESS"
-; http://php.net/session.upload-progress.name
-;session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
-
-; How frequently the upload progress should be updated.
-; Given either in percentages (per-file), or in bytes
-; Default Value: "1%"
-; Development Value: "1%"
-; Production Value: "1%"
-; http://php.net/session.upload-progress.freq
-;session.upload_progress.freq =  "1%"
-
-; The minimum delay between updates, in seconds
-; Default Value: 1
-; Development Value: 1
-; Production Value: 1
-; http://php.net/session.upload-progress.min-freq
-;session.upload_progress.min_freq = "1"
-
-; Only write session data when session data is changed. Enabled by default.
-; http://php.net/session.lazy-write
-;session.lazy_write = On
-
-[Assertion]
-; Switch whether to compile assertions at all (to have no overhead at run-time)
-; -1: Do not compile at all
-;  0: Jump over assertion at run-time
-;  1: Execute assertions
-; Changing from or to a negative value is only possible in php.ini! (For turning assertions on and off at run-time, see assert.active, when zend.assertions = 1)
-; Default Value: 1
-; Development Value: 1
-; Production Value: -1
-; http://php.net/zend.assertions
-zend.assertions = -1
-
-; Assert(expr); active by default.
-; http://php.net/assert.active
-;assert.active = On
-
-; Throw an AssertionError on failed assertions
-; http://php.net/assert.exception
-;assert.exception = On
-
-; Issue a PHP warning for each failed assertion. (Overridden by assert.exception if active)
-; http://php.net/assert.warning
-;assert.warning = On
-
-; Don't bail out by default.
-; http://php.net/assert.bail
-;assert.bail = Off
-
-; User-function to be called if an assertion fails.
-; http://php.net/assert.callback
-;assert.callback = 0
-
-; Eval the expression with current error_reporting().  Set to true if you want
-; error_reporting(0) around the eval().
-; http://php.net/assert.quiet-eval
-;assert.quiet_eval = 0
-
-[COM]
-; path to a file containing GUIDs, IIDs or filenames of files with TypeLibs
-; http://php.net/com.typelib-file
-;com.typelib_file =
-
-; allow Distributed-COM calls
-; http://php.net/com.allow-dcom
-;com.allow_dcom = true
-
-; autoregister constants of a component's typlib on com_load()
-; http://php.net/com.autoregister-typelib
-;com.autoregister_typelib = true
-
-; register constants casesensitive
-; http://php.net/com.autoregister-casesensitive
-;com.autoregister_casesensitive = false
-
-; show warnings on duplicate constant registrations
-; http://php.net/com.autoregister-verbose
-;com.autoregister_verbose = true
-
-; The default character set code-page to use when passing strings to and from COM objects.
-; Default: system ANSI code page
-;com.code_page=
-
-[mbstring]
-; language for internal character representation.
-; This affects mb_send_mail() and mbstring.detect_order.
-; http://php.net/mbstring.language
-;mbstring.language = Japanese
-
-; Use of this INI entry is deprecated, use global internal_encoding instead.
-; internal/script encoding.
-; Some encoding cannot work as internal encoding. (e.g. SJIS, BIG5, ISO-2022-*)
-; If empty, default_charset or internal_encoding or iconv.internal_encoding is used.
-; The precedence is: default_charset < internal_encoding < iconv.internal_encoding
-;mbstring.internal_encoding =
-
-; Use of this INI entry is deprecated, use global input_encoding instead.
-; http input encoding.
-; mbstring.encoding_translation = On is needed to use this setting.
-; If empty, default_charset or input_encoding or mbstring.input is used.
-; The precedence is: default_charset < input_encoding < mbstring.http_input
-; http://php.net/mbstring.http-input
-;mbstring.http_input =
-
-; Use of this INI entry is deprecated, use global output_encoding instead.
-; http output encoding.
-; mb_output_handler must be registered as output buffer to function.
-; If empty, default_charset or output_encoding or mbstring.http_output is used.
-; The precedence is: default_charset < output_encoding < mbstring.http_output
-; To use an output encoding conversion, mbstring's output handler must be set
-; otherwise output encoding conversion cannot be performed.
-; http://php.net/mbstring.http-output
-;mbstring.http_output =
-
-; enable automatic encoding translation according to
-; mbstring.internal_encoding setting. Input chars are
-; converted to internal encoding by setting this to On.
-; Note: Do _not_ use automatic encoding translation for
-;       portable libs/applications.
-; http://php.net/mbstring.encoding-translation
-;mbstring.encoding_translation = Off
-
-; automatic encoding detection order.
-; "auto" detect order is changed according to mbstring.language
-; http://php.net/mbstring.detect-order
-;mbstring.detect_order = auto
-
-; substitute_character used when character cannot be converted
-; one from another
-; http://php.net/mbstring.substitute-character
-;mbstring.substitute_character = none
-
-; overload(replace) single byte functions by mbstring functions.
-; mail(), ereg(), etc are overloaded by mb_send_mail(), mb_ereg(),
-; etc. Possible values are 0,1,2,4 or combination of them.
-; For example, 7 for overload everything.
-; 0: No overload
-; 1: Overload mail() function
-; 2: Overload str*() functions
-; 4: Overload ereg*() functions
-; http://php.net/mbstring.func-overload
-;mbstring.func_overload = 0
-
-; enable strict encoding detection.
-; Default: Off
-;mbstring.strict_detection = On
-
-; This directive specifies the regex pattern of content types for which mb_output_handler()
-; is activated.
-; Default: mbstring.http_output_conv_mimetype=^(text/|application/xhtml\+xml)
-;mbstring.http_output_conv_mimetype=
-
-; This directive specifies maximum stack depth for mbstring regular expressions. It is similar
-; to the pcre.recursion_limit for PCRE.
-; Default: 100000
-;mbstring.regex_stack_limit=100000
-
-; This directive specifies maximum retry count for mbstring regular expressions. It is similar
-; to the pcre.backtrack_limit for PCRE.
-; Default: 1000000
-;mbstring.regex_retry_limit=1000000
-
-[gd]
-; Tell the jpeg decode to ignore warnings and try to create
-; a gd image. The warning will then be displayed as notices
-; disabled by default
-; http://php.net/gd.jpeg-ignore-warning
-;gd.jpeg_ignore_warning = 1
-
-[exif]
-; Exif UNICODE user comments are handled as UCS-2BE/UCS-2LE and JIS as JIS.
-; With mbstring support this will automatically be converted into the encoding
-; given by corresponding encode setting. When empty mbstring.internal_encoding
-; is used. For the decode settings you can distinguish between motorola and
-; intel byte order. A decode setting cannot be empty.
-; http://php.net/exif.encode-unicode
-;exif.encode_unicode = ISO-8859-15
-
-; http://php.net/exif.decode-unicode-motorola
-;exif.decode_unicode_motorola = UCS-2BE
-
-; http://php.net/exif.decode-unicode-intel
-;exif.decode_unicode_intel    = UCS-2LE
-
-; http://php.net/exif.encode-jis
-;exif.encode_jis =
-
-; http://php.net/exif.decode-jis-motorola
-;exif.decode_jis_motorola = JIS
-
-; http://php.net/exif.decode-jis-intel
-;exif.decode_jis_intel    = JIS
-
-[Tidy]
-; The path to a default tidy configuration file to use when using tidy
-; http://php.net/tidy.default-config
-;tidy.default_config = /usr/local/lib/php/default.tcfg
-
-; Should tidy clean and repair output automatically?
-; WARNING: Do not use this option if you are generating non-html content
-; such as dynamic images
-; http://php.net/tidy.clean-output
-tidy.clean_output = Off
-
-[soap]
-; Enables or disables WSDL caching feature.
-; http://php.net/soap.wsdl-cache-enabled
-soap.wsdl_cache_enabled=1
-
-; Sets the directory name where SOAP extension will put cache files.
-; http://php.net/soap.wsdl-cache-dir
-soap.wsdl_cache_dir="/tmp"
-
-; (time to live) Sets the number of second while cached file will be used
-; instead of original one.
-; http://php.net/soap.wsdl-cache-ttl
-soap.wsdl_cache_ttl=86400
-
-; Sets the size of the cache limit. (Max. number of WSDL files to cache)
-soap.wsdl_cache_limit = 5
-
-[sysvshm]
-; A default size of the shared memory segment
-;sysvshm.init_mem = 10000
-
-[ldap]
-; Sets the maximum number of open links or -1 for unlimited.
-ldap.max_links = -1
-
-[dba]
-;dba.default_handler=
-
-[opcache]
-; Determines if Zend OPCache is enabled
-;opcache.enable=1
-
-; Determines if Zend OPCache is enabled for the CLI version of PHP
-;opcache.enable_cli=0
-
-; The OPcache shared memory storage size.
-;opcache.memory_consumption=128
-
-; The amount of memory for interned strings in Mbytes.
-;opcache.interned_strings_buffer=8
-
-; The maximum number of keys (scripts) in the OPcache hash table.
-; Only numbers between 200 and 1000000 are allowed.
-;opcache.max_accelerated_files=10000
-
-; The maximum percentage of "wasted" memory until a restart is scheduled.
-;opcache.max_wasted_percentage=5
-
-; When this directive is enabled, the OPcache appends the current working
-; directory to the script key, thus eliminating possible collisions between
-; files with the same name (basename). Disabling the directive improves
-; performance, but may break existing applications.
-;opcache.use_cwd=1
-
-; When disabled, you must reset the OPcache manually or restart the
-; webserver for changes to the filesystem to take effect.
-;opcache.validate_timestamps=1
-
-; How often (in seconds) to check file timestamps for changes to the shared
-; memory storage allocation. ("1" means validate once per second, but only
-; once per request. "0" means always validate)
-;opcache.revalidate_freq=2
-
-; Enables or disables file search in include_path optimization
-;opcache.revalidate_path=0
-
-; If disabled, all PHPDoc comments are dropped from the code to reduce the
-; size of the optimized code.
-;opcache.save_comments=1
-
-; Allow file existence override (file_exists, etc.) performance feature.
-;opcache.enable_file_override=0
-
-; A bitmask, where each bit enables or disables the appropriate OPcache
-; passes
-;opcache.optimization_level=0x7FFFBFFF
-
-;opcache.dups_fix=0
-
-; The location of the OPcache blacklist file (wildcards allowed).
-; Each OPcache blacklist file is a text file that holds the names of files
-; that should not be accelerated. The file format is to add each filename
-; to a new line. The filename may be a full path or just a file prefix
-; (i.e., /var/www/x  blacklists all the files and directories in /var/www
-; that start with 'x'). Line starting with a ; are ignored (comments).
-;opcache.blacklist_filename=
-
-; Allows exclusion of large files from being cached. By default all files
-; are cached.
-;opcache.max_file_size=0
-
-; Check the cache checksum each N requests.
-; The default value of "0" means that the checks are disabled.
-;opcache.consistency_checks=0
-
-; How long to wait (in seconds) for a scheduled restart to begin if the cache
-; is not being accessed.
-;opcache.force_restart_timeout=180
-
-; OPcache error_log file name. Empty string assumes "stderr".
-;opcache.error_log=
-
-; All OPcache errors go to the Web server log.
-; By default, only fatal errors (level 0) or errors (level 1) are logged.
-; You can also enable warnings (level 2), info messages (level 3) or
-; debug messages (level 4).
-;opcache.log_verbosity_level=1
-
-; Preferred Shared Memory back-end. Leave empty and let the system decide.
-;opcache.preferred_memory_model=
-
-; Protect the shared memory from unexpected writing during script execution.
-; Useful for internal debugging only.
-;opcache.protect_memory=0
-
-; Allows calling OPcache API functions only from PHP scripts which path is
-; started from specified string. The default "" means no restriction
-;opcache.restrict_api=
-
-; Mapping base of shared memory segments (for Windows only). All the PHP
-; processes have to map shared memory into the same address space. This
-; directive allows to manually fix the "Unable to reattach to base address"
-; errors.
-;opcache.mmap_base=
-
-; Facilitates multiple OPcache instances per user (for Windows only). All PHP
-; processes with the same cache ID and user share an OPcache instance.
-;opcache.cache_id=
-
-; Enables and sets the second level cache directory.
-; It should improve performance when SHM memory is full, at server restart or
-; SHM reset. The default "" disables file based caching.
-;opcache.file_cache=
-
-; Enables or disables opcode caching in shared memory.
-;opcache.file_cache_only=0
-
-; Enables or disables checksum validation when script loaded from file cache.
-;opcache.file_cache_consistency_checks=1
-
-; Implies opcache.file_cache_only=1 for a certain process that failed to
-; reattach to the shared memory (for Windows only). Explicitly enabled file
-; cache is required.
-;opcache.file_cache_fallback=1
-
-; Enables or disables copying of PHP code (text segment) into HUGE PAGES.
-; This should improve performance, but requires appropriate OS configuration.
-;opcache.huge_code_pages=1
-
-; Validate cached file permissions.
-;opcache.validate_permission=0
-
-; Prevent name collisions in chroot'ed environment.
-;opcache.validate_root=0
-
-; If specified, it produces opcode dumps for debugging different stages of
-; optimizations.
-;opcache.opt_debug_level=0
-
-; Specifies a PHP script that is going to be compiled and executed at server
-; start-up.
-; http://php.net/opcache.preload
-;opcache.preload=
-
-; Preloading code as root is not allowed for security reasons. This directive
-; facilitates to let the preloading to be run as another user.
-; http://php.net/opcache.preload_user
-;opcache.preload_user=
-
-; Prevents caching files that are less than this number of seconds old. It
-; protects from caching of incompletely updated files. In case all file updates
-; on your site are atomic, you may increase performance by setting it to "0".
-;opcache.file_update_protection=2
-
-; Absolute path used to store shared lockfiles (for *nix only).
-;opcache.lockfile_path=/tmp
-
-[curl]
-; A default value for the CURLOPT_CAINFO option. This is required to be an
-; absolute path.
-;curl.cainfo =
-
-[openssl]
-; The location of a Certificate Authority (CA) file on the local filesystem
-; to use when verifying the identity of SSL/TLS peers. Most users should
-; not specify a value for this directive as PHP will attempt to use the
-; OS-managed cert stores in its absence. If specified, this value may still
-; be overridden on a per-stream basis via the "cafile" SSL stream context
-; option.
-;openssl.cafile=
-
-; If openssl.cafile is not specified or if the CA file is not found, the
-; directory pointed to by openssl.capath is searched for a suitable
-; certificate. This value must be a correctly hashed certificate directory.
-; Most users should not specify a value for this directive as PHP will
-; attempt to use the OS-managed cert stores in its absence. If specified,
-; this value may still be overridden on a per-stream basis via the "capath"
-; SSL stream context option.
-;openssl.capath=
-
-[ffi]
-; FFI API restriction. Possible values:
-; "preload" - enabled in CLI scripts and preloaded files (default)
-; "false"   - always disabled
-; "true"    - always enabled
-;ffi.enable=preload
-
-; List of headers files to preload, wildcard patterns allowed.
-;ffi.preload=
+[PHP]
+
+;;;;;;;;;;;;;;;;;;;
+; About php.ini   ;
+;;;;;;;;;;;;;;;;;;;
+; PHP's initialization file, generally called php.ini, is responsible for
+; configuring many of the aspects of PHP's behavior.
+
+; PHP attempts to find and load this configuration from a number of locations.
+; The following is a summary of its search order:
+; 1. SAPI module specific location.
+; 2. The PHPRC environment variable. (As of PHP 5.2.0)
+; 3. A number of predefined registry keys on Windows (As of PHP 5.2.0)
+; 4. Current working directory (except CLI)
+; 5. The web server's directory (for SAPI modules), or directory of PHP
+; (otherwise in Windows)
+; 6. The directory from the --with-config-file-path compile time option, or the
+; Windows directory (usually C:\windows)
+; See the PHP docs for more specific information.
+; http://php.net/configuration.file
+
+; The syntax of the file is extremely simple.  Whitespace and lines
+; beginning with a semicolon are silently ignored (as you probably guessed).
+; Section headers (e.g. [Foo]) are also silently ignored, even though
+; they might mean something in the future.
+
+; Directives following the section heading [PATH=/www/mysite] only
+; apply to PHP files in the /www/mysite directory.  Directives
+; following the section heading [HOST=www.example.com] only apply to
+; PHP files served from www.example.com.  Directives set in these
+; special sections cannot be overridden by user-defined INI files or
+; at runtime. Currently, [PATH=] and [HOST=] sections only work under
+; CGI/FastCGI.
+; http://php.net/ini.sections
+
+; Directives are specified using the following syntax:
+; directive = value
+; Directive names are *case sensitive* - foo=bar is different from FOO=bar.
+; Directives are variables used to configure PHP or PHP extensions.
+; There is no name validation.  If PHP can't find an expected
+; directive because it is not set or is mistyped, a default value will be used.
+
+; The value can be a string, a number, a PHP constant (e.g. E_ALL or M_PI), one
+; of the INI constants (On, Off, True, False, Yes, No and None) or an expression
+; (e.g. E_ALL & ~E_NOTICE), a quoted string ("bar"), or a reference to a
+; previously set variable or directive (e.g. ${foo})
+
+; Expressions in the INI file are limited to bitwise operators and parentheses:
+; |  bitwise OR
+; ^  bitwise XOR
+; &  bitwise AND
+; ~  bitwise NOT
+; !  boolean NOT
+
+; Boolean flags can be turned on using the values 1, On, True or Yes.
+; They can be turned off using the values 0, Off, False or No.
+
+; An empty string can be denoted by simply not writing anything after the equal
+; sign, or by using the None keyword:
+
+; foo =         ; sets foo to an empty string
+; foo = None    ; sets foo to an empty string
+; foo = "None"  ; sets foo to the string 'None'
+
+; If you use constants in your value, and these constants belong to a
+; dynamically loaded extension (either a PHP extension or a Zend extension),
+; you may only use these constants *after* the line that loads the extension.
+
+;;;;;;;;;;;;;;;;;;;
+; About this file ;
+;;;;;;;;;;;;;;;;;;;
+; PHP comes packaged with two INI files. One that is recommended to be used
+; in production environments and one that is recommended to be used in
+; development environments.
+
+; php.ini-production contains settings which hold security, performance and
+; best practices at its core. But please be aware, these settings may break
+; compatibility with older or less security conscience applications. We
+; recommending using the production ini in production and testing environments.
+
+; php.ini-development is very similar to its production variant, except it is
+; much more verbose when it comes to errors. We recommend using the
+; development version only in development environments, as errors shown to
+; application users can inadvertently leak otherwise secure information.
+
+; This is the php.ini-production INI file.
+
+;;;;;;;;;;;;;;;;;;;
+; Quick Reference ;
+;;;;;;;;;;;;;;;;;;;
+; The following are all the settings which are different in either the production
+; or development versions of the INIs with respect to PHP's default behavior.
+; Please see the actual settings later in the document for more details as to why
+; we recommend these changes in PHP's behavior.
+
+; display_errors
+;   Default Value: On
+;   Development Value: On
+;   Production Value: Off
+
+; display_startup_errors
+;   Default Value: Off
+;   Development Value: On
+;   Production Value: Off
+
+; error_reporting
+;   Default Value: E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED
+;   Development Value: E_ALL
+;   Production Value: E_ALL & ~E_DEPRECATED & ~E_STRICT
+
+; log_errors
+;   Default Value: Off
+;   Development Value: On
+;   Production Value: On
+
+; max_input_time
+;   Default Value: -1 (Unlimited)
+;   Development Value: 60 (60 seconds)
+;   Production Value: 60 (60 seconds)
+
+; output_buffering
+;   Default Value: Off
+;   Development Value: 4096
+;   Production Value: 4096
+
+; register_argc_argv
+;   Default Value: On
+;   Development Value: Off
+;   Production Value: Off
+
+; request_order
+;   Default Value: None
+;   Development Value: "GP"
+;   Production Value: "GP"
+
+; session.gc_divisor
+;   Default Value: 100
+;   Development Value: 1000
+;   Production Value: 1000
+
+; session.sid_bits_per_character
+;   Default Value: 4
+;   Development Value: 5
+;   Production Value: 5
+
+; short_open_tag
+;   Default Value: On
+;   Development Value: Off
+;   Production Value: Off
+
+; variables_order
+;   Default Value: "EGPCS"
+;   Development Value: "GPCS"
+;   Production Value: "GPCS"
+
+;;;;;;;;;;;;;;;;;;;;
+; php.ini Options  ;
+;;;;;;;;;;;;;;;;;;;;
+; Name for user-defined php.ini (.htaccess) files. Default is ".user.ini"
+;user_ini.filename = ".user.ini"
+
+; To disable this feature set this option to an empty value
+;user_ini.filename =
+
+; TTL for user-defined php.ini files (time-to-live) in seconds. Default is 300 seconds (5 minutes)
+;user_ini.cache_ttl = 300
+
+;;;;;;;;;;;;;;;;;;;;
+; Language Options ;
+;;;;;;;;;;;;;;;;;;;;
+
+; Enable the PHP scripting language engine under Apache.
+; http://php.net/engine
+engine = On
+
+; This directive determines whether or not PHP will recognize code between
+; <? and ?> tags as PHP source which should be processed as such. It is
+; generally recommended that <?php and ?> should be used and that this feature
+; should be disabled, as enabling it may result in issues when generating XML
+; documents, however this remains supported for backward compatibility reasons.
+; Note that this directive does not control the <?= shorthand tag, which can be
+; used regardless of this directive.
+; Default Value: On
+; Development Value: Off
+; Production Value: Off
+; http://php.net/short-open-tag
+short_open_tag = Off
+
+; The number of significant digits displayed in floating point numbers.
+; http://php.net/precision
+precision = 14
+
+; Output buffering is a mechanism for controlling how much output data
+; (excluding headers and cookies) PHP should keep internally before pushing that
+; data to the client. If your application's output exceeds this setting, PHP
+; will send that data in chunks of roughly the size you specify.
+; Turning on this setting and managing its maximum buffer size can yield some
+; interesting side-effects depending on your application and web server.
+; You may be able to send headers and cookies after you've already sent output
+; through print or echo. You also may see performance benefits if your server is
+; emitting less packets due to buffered output versus PHP streaming the output
+; as it gets it. On production servers, 4096 bytes is a good setting for performance
+; reasons.
+; Note: Output buffering can also be controlled via Output Buffering Control
+;   functions.
+; Possible Values:
+;   On = Enabled and buffer is unlimited. (Use with caution)
+;   Off = Disabled
+;   Integer = Enables the buffer and sets its maximum size in bytes.
+; Note: This directive is hardcoded to Off for the CLI SAPI
+; Default Value: Off
+; Development Value: 4096
+; Production Value: 4096
+; http://php.net/output-buffering
+output_buffering = 4096
+
+; You can redirect all of the output of your scripts to a function.  For
+; example, if you set output_handler to "mb_output_handler", character
+; encoding will be transparently converted to the specified encoding.
+; Setting any output handler automatically turns on output buffering.
+; Note: People who wrote portable scripts should not depend on this ini
+;   directive. Instead, explicitly set the output handler using ob_start().
+;   Using this ini directive may cause problems unless you know what script
+;   is doing.
+; Note: You cannot use both "mb_output_handler" with "ob_iconv_handler"
+;   and you cannot use both "ob_gzhandler" and "zlib.output_compression".
+; Note: output_handler must be empty if this is set 'On' !!!!
+;   Instead you must use zlib.output_handler.
+; http://php.net/output-handler
+;output_handler =
+
+; URL rewriter function rewrites URL on the fly by using
+; output buffer. You can set target tags by this configuration.
+; "form" tag is special tag. It will add hidden input tag to pass values.
+; Refer to session.trans_sid_tags for usage.
+; Default Value: "form="
+; Development Value: "form="
+; Production Value: "form="
+;url_rewriter.tags
+
+; URL rewriter will not rewrite absolute URL nor form by default. To enable
+; absolute URL rewrite, allowed hosts must be defined at RUNTIME.
+; Refer to session.trans_sid_hosts for more details.
+; Default Value: ""
+; Development Value: ""
+; Production Value: ""
+;url_rewriter.hosts
+
+; Transparent output compression using the zlib library
+; Valid values for this option are 'off', 'on', or a specific buffer size
+; to be used for compression (default is 4KB)
+; Note: Resulting chunk size may vary due to nature of compression. PHP
+;   outputs chunks that are few hundreds bytes each as a result of
+;   compression. If you prefer a larger chunk size for better
+;   performance, enable output_buffering in addition.
+; Note: You need to use zlib.output_handler instead of the standard
+;   output_handler, or otherwise the output will be corrupted.
+; http://php.net/zlib.output-compression
+zlib.output_compression = Off
+
+; http://php.net/zlib.output-compression-level
+;zlib.output_compression_level = -1
+
+; You cannot specify additional output handlers if zlib.output_compression
+; is activated here. This setting does the same as output_handler but in
+; a different order.
+; http://php.net/zlib.output-handler
+;zlib.output_handler =
+
+; Implicit flush tells PHP to tell the output layer to flush itself
+; automatically after every output block.  This is equivalent to calling the
+; PHP function flush() after each and every call to print() or echo() and each
+; and every HTML block.  Turning this option on has serious performance
+; implications and is generally recommended for debugging purposes only.
+; http://php.net/implicit-flush
+; Note: This directive is hardcoded to On for the CLI SAPI
+implicit_flush = Off
+
+; The unserialize callback function will be called (with the undefined class'
+; name as parameter), if the unserializer finds an undefined class
+; which should be instantiated. A warning appears if the specified function is
+; not defined, or if the function doesn't include/implement the missing class.
+; So only set this entry, if you really want to implement such a
+; callback-function.
+unserialize_callback_func =
+
+; The unserialize_max_depth specifies the default depth limit for unserialized
+; structures. Setting the depth limit too high may result in stack overflows
+; during unserialization. The unserialize_max_depth ini setting can be
+; overridden by the max_depth option on individual unserialize() calls.
+; A value of 0 disables the depth limit.
+;unserialize_max_depth = 4096
+
+; When floats & doubles are serialized, store serialize_precision significant
+; digits after the floating point. The default value ensures that when floats
+; are decoded with unserialize, the data will remain the same.
+; The value is also used for json_encode when encoding double values.
+; If -1 is used, then dtoa mode 0 is used which automatically select the best
+; precision.
+serialize_precision = -1
+
+; open_basedir, if set, limits all file operations to the defined directory
+; and below.  This directive makes most sense if used in a per-directory
+; or per-virtualhost web server configuration file.
+; Note: disables the realpath cache
+; http://php.net/open-basedir
+;open_basedir =
+
+; This directive allows you to disable certain functions.
+; It receives a comma-delimited list of function names.
+; http://php.net/disable-functions
+disable_functions =
+
+; This directive allows you to disable certain classes.
+; It receives a comma-delimited list of class names.
+; http://php.net/disable-classes
+disable_classes =
+
+; Colors for Syntax Highlighting mode.  Anything that's acceptable in
+; <span style="color: ???????"> would work.
+; http://php.net/syntax-highlighting
+;highlight.string  = #DD0000
+;highlight.comment = #FF9900
+;highlight.keyword = #007700
+;highlight.default = #0000BB
+;highlight.html    = #000000
+
+; If enabled, the request will be allowed to complete even if the user aborts
+; the request. Consider enabling it if executing long requests, which may end up
+; being interrupted by the user or a browser timing out. PHP's default behavior
+; is to disable this feature.
+; http://php.net/ignore-user-abort
+;ignore_user_abort = On
+
+; Determines the size of the realpath cache to be used by PHP. This value should
+; be increased on systems where PHP opens many files to reflect the quantity of
+; the file operations performed.
+; Note: if open_basedir is set, the cache is disabled
+; http://php.net/realpath-cache-size
+;realpath_cache_size = 4096k
+
+; Duration of time, in seconds for which to cache realpath information for a given
+; file or directory. For systems with rarely changing files, consider increasing this
+; value.
+; http://php.net/realpath-cache-ttl
+;realpath_cache_ttl = 120
+
+; Enables or disables the circular reference collector.
+; http://php.net/zend.enable-gc
+zend.enable_gc = On
+
+; If enabled, scripts may be written in encodings that are incompatible with
+; the scanner.  CP936, Big5, CP949 and Shift_JIS are the examples of such
+; encodings.  To use this feature, mbstring extension must be enabled.
+; Default: Off
+;zend.multibyte = Off
+
+; Allows to set the default encoding for the scripts.  This value will be used
+; unless "declare(encoding=...)" directive appears at the top of the script.
+; Only affects if zend.multibyte is set.
+; Default: ""
+;zend.script_encoding =
+
+; Allows to include or exclude arguments from stack traces generated for exceptions.
+; In production, it is recommended to turn this setting on to prohibit the output
+; of sensitive information in stack traces
+; Default: Off
+zend.exception_ignore_args = On
+
+;;;;;;;;;;;;;;;;;
+; Miscellaneous ;
+;;;;;;;;;;;;;;;;;
+
+; Decides whether PHP may expose the fact that it is installed on the server
+; (e.g. by adding its signature to the Web server header).  It is no security
+; threat in any way, but it makes it possible to determine whether you use PHP
+; on your server or not.
+; http://php.net/expose-php
+expose_php = On
+
+;;;;;;;;;;;;;;;;;;;
+; Resource Limits ;
+;;;;;;;;;;;;;;;;;;;
+
+; Maximum execution time of each script, in seconds
+; http://php.net/max-execution-time
+; Note: This directive is hardcoded to 0 for the CLI SAPI
+max_execution_time = 30
+
+; Maximum amount of time each script may spend parsing request data. It's a good
+; idea to limit this time on productions servers in order to eliminate unexpectedly
+; long running scripts.
+; Note: This directive is hardcoded to -1 for the CLI SAPI
+; Default Value: -1 (Unlimited)
+; Development Value: 60 (60 seconds)
+; Production Value: 60 (60 seconds)
+; http://php.net/max-input-time
+max_input_time = 60
+
+; Maximum input variable nesting level
+; http://php.net/max-input-nesting-level
+;max_input_nesting_level = 64
+
+; How many GET/POST/COOKIE input variables may be accepted
+;max_input_vars = 1000
+
+; Maximum amount of memory a script may consume
+; http://php.net/memory-limit
+memory_limit = 128M
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Error handling and logging ;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+; This directive informs PHP of which errors, warnings and notices you would like
+; it to take action for. The recommended way of setting values for this
+; directive is through the use of the error level constants and bitwise
+; operators. The error level constants are below here for convenience as well as
+; some common settings and their meanings.
+; By default, PHP is set to take action on all errors, notices and warnings EXCEPT
+; those related to E_NOTICE and E_STRICT, which together cover best practices and
+; recommended coding standards in PHP. For performance reasons, this is the
+; recommend error reporting setting. Your production server shouldn't be wasting
+; resources complaining about best practices and coding standards. That's what
+; development servers and development settings are for.
+; Note: The php.ini-development file has this setting as E_ALL. This
+; means it pretty much reports everything which is exactly what you want during
+; development and early testing.
+;
+; Error Level Constants:
+; E_ALL             - All errors and warnings (includes E_STRICT as of PHP 5.4.0)
+; E_ERROR           - fatal run-time errors
+; E_RECOVERABLE_ERROR  - almost fatal run-time errors
+; E_WARNING         - run-time warnings (non-fatal errors)
+; E_PARSE           - compile-time parse errors
+; E_NOTICE          - run-time notices (these are warnings which often result
+;                     from a bug in your code, but it's possible that it was
+;                     intentional (e.g., using an uninitialized variable and
+;                     relying on the fact it is automatically initialized to an
+;                     empty string)
+; E_STRICT          - run-time notices, enable to have PHP suggest changes
+;                     to your code which will ensure the best interoperability
+;                     and forward compatibility of your code
+; E_CORE_ERROR      - fatal errors that occur during PHP's initial startup
+; E_CORE_WARNING    - warnings (non-fatal errors) that occur during PHP's
+;                     initial startup
+; E_COMPILE_ERROR   - fatal compile-time errors
+; E_COMPILE_WARNING - compile-time warnings (non-fatal errors)
+; E_USER_ERROR      - user-generated error message
+; E_USER_WARNING    - user-generated warning message
+; E_USER_NOTICE     - user-generated notice message
+; E_DEPRECATED      - warn about code that will not work in future versions
+;                     of PHP
+; E_USER_DEPRECATED - user-generated deprecation warnings
+;
+; Common Values:
+;   E_ALL (Show all errors, warnings and notices including coding standards.)
+;   E_ALL & ~E_NOTICE  (Show all errors, except for notices)
+;   E_ALL & ~E_NOTICE & ~E_STRICT  (Show all errors, except for notices and coding standards warnings.)
+;   E_COMPILE_ERROR|E_RECOVERABLE_ERROR|E_ERROR|E_CORE_ERROR  (Show only errors)
+; Default Value: E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED
+; Development Value: E_ALL
+; Production Value: E_ALL & ~E_DEPRECATED & ~E_STRICT
+; http://php.net/error-reporting
+error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
+
+; This directive controls whether or not and where PHP will output errors,
+; notices and warnings too. Error output is very useful during development, but
+; it could be very dangerous in production environments. Depending on the code
+; which is triggering the error, sensitive information could potentially leak
+; out of your application such as database usernames and passwords or worse.
+; For production environments, we recommend logging errors rather than
+; sending them to STDOUT.
+; Possible Values:
+;   Off = Do not display any errors
+;   stderr = Display errors to STDERR (affects only CGI/CLI binaries!)
+;   On or stdout = Display errors to STDOUT
+; Default Value: On
+; Development Value: On
+; Production Value: Off
+; http://php.net/display-errors
+display_errors = Off
+
+; The display of errors which occur during PHP's startup sequence are handled
+; separately from display_errors. PHP's default behavior is to suppress those
+; errors from clients. Turning the display of startup errors on can be useful in
+; debugging configuration problems. We strongly recommend you
+; set this to 'off' for production servers.
+; Default Value: Off
+; Development Value: On
+; Production Value: Off
+; http://php.net/display-startup-errors
+display_startup_errors = Off
+
+; Besides displaying errors, PHP can also log errors to locations such as a
+; server-specific log, STDERR, or a location specified by the error_log
+; directive found below. While errors should not be displayed on productions
+; servers they should still be monitored and logging is a great way to do that.
+; Default Value: Off
+; Development Value: On
+; Production Value: On
+; http://php.net/log-errors
+log_errors = On
+
+; Set maximum length of log_errors. In error_log information about the source is
+; added. The default is 1024 and 0 allows to not apply any maximum length at all.
+; http://php.net/log-errors-max-len
+log_errors_max_len = 1024
+
+; Do not log repeated messages. Repeated errors must occur in same file on same
+; line unless ignore_repeated_source is set true.
+; http://php.net/ignore-repeated-errors
+ignore_repeated_errors = Off
+
+; Ignore source of message when ignoring repeated messages. When this setting
+; is On you will not log errors with repeated messages from different files or
+; source lines.
+; http://php.net/ignore-repeated-source
+ignore_repeated_source = Off
+
+; If this parameter is set to Off, then memory leaks will not be shown (on
+; stdout or in the log). This is only effective in a debug compile, and if
+; error reporting includes E_WARNING in the allowed list
+; http://php.net/report-memleaks
+report_memleaks = On
+
+; This setting is on by default.
+;report_zend_debug = 0
+
+; Store the last error/warning message in $php_errormsg (boolean). Setting this value
+; to On can assist in debugging and is appropriate for development servers. It should
+; however be disabled on production servers.
+; This directive is DEPRECATED.
+; Default Value: Off
+; Development Value: Off
+; Production Value: Off
+; http://php.net/track-errors
+;track_errors = Off
+
+; Turn off normal error reporting and emit XML-RPC error XML
+; http://php.net/xmlrpc-errors
+;xmlrpc_errors = 0
+
+; An XML-RPC faultCode
+;xmlrpc_error_number = 0
+
+; When PHP displays or logs an error, it has the capability of formatting the
+; error message as HTML for easier reading. This directive controls whether
+; the error message is formatted as HTML or not.
+; Note: This directive is hardcoded to Off for the CLI SAPI
+; http://php.net/html-errors
+;html_errors = On
+
+; If html_errors is set to On *and* docref_root is not empty, then PHP
+; produces clickable error messages that direct to a page describing the error
+; or function causing the error in detail.
+; You can download a copy of the PHP manual from http://php.net/docs
+; and change docref_root to the base URL of your local copy including the
+; leading '/'. You must also specify the file extension being used including
+; the dot. PHP's default behavior is to leave these settings empty, in which
+; case no links to documentation are generated.
+; Note: Never use this feature for production boxes.
+; http://php.net/docref-root
+; Examples
+;docref_root = "/phpmanual/"
+
+; http://php.net/docref-ext
+;docref_ext = .html
+
+; String to output before an error message. PHP's default behavior is to leave
+; this setting blank.
+; http://php.net/error-prepend-string
+; Example:
+;error_prepend_string = "<span style='color: #ff0000'>"
+
+; String to output after an error message. PHP's default behavior is to leave
+; this setting blank.
+; http://php.net/error-append-string
+; Example:
+;error_append_string = "</span>"
+
+; Log errors to specified file. PHP's default behavior is to leave this value
+; empty.
+; http://php.net/error-log
+; Example:
+;error_log = php_errors.log
+; Log errors to syslog (Event Log on Windows).
+;error_log = syslog
+
+; The syslog ident is a string which is prepended to every message logged
+; to syslog. Only used when error_log is set to syslog.
+;syslog.ident = php
+
+; The syslog facility is used to specify what type of program is logging
+; the message. Only used when error_log is set to syslog.
+;syslog.facility = user
+
+; Set this to disable filtering control characters (the default).
+; Some loggers only accept NVT-ASCII, others accept anything that's not
+; control characters. If your logger accepts everything, then no filtering
+; is needed at all.
+; Allowed values are:
+;   ascii (all printable ASCII characters and NL)
+;   no-ctrl (all characters except control characters)
+;   all (all characters)
+;   raw (like "all", but messages are not split at newlines)
+; http://php.net/syslog.filter
+;syslog.filter = ascii
+
+;windows.show_crt_warning
+; Default value: 0
+; Development value: 0
+; Production value: 0
+
+;;;;;;;;;;;;;;;;;
+; Data Handling ;
+;;;;;;;;;;;;;;;;;
+
+; The separator used in PHP generated URLs to separate arguments.
+; PHP's default setting is "&".
+; http://php.net/arg-separator.output
+; Example:
+;arg_separator.output = "&amp;"
+
+; List of separator(s) used by PHP to parse input URLs into variables.
+; PHP's default setting is "&".
+; NOTE: Every character in this directive is considered as separator!
+; http://php.net/arg-separator.input
+; Example:
+;arg_separator.input = ";&"
+
+; This directive determines which super global arrays are registered when PHP
+; starts up. G,P,C,E & S are abbreviations for the following respective super
+; globals: GET, POST, COOKIE, ENV and SERVER. There is a performance penalty
+; paid for the registration of these arrays and because ENV is not as commonly
+; used as the others, ENV is not recommended on productions servers. You
+; can still get access to the environment variables through getenv() should you
+; need to.
+; Default Value: "EGPCS"
+; Development Value: "GPCS"
+; Production Value: "GPCS";
+; http://php.net/variables-order
+variables_order = "GPCS"
+
+; This directive determines which super global data (G,P & C) should be
+; registered into the super global array REQUEST. If so, it also determines
+; the order in which that data is registered. The values for this directive
+; are specified in the same manner as the variables_order directive,
+; EXCEPT one. Leaving this value empty will cause PHP to use the value set
+; in the variables_order directive. It does not mean it will leave the super
+; globals array REQUEST empty.
+; Default Value: None
+; Development Value: "GP"
+; Production Value: "GP"
+; http://php.net/request-order
+request_order = "GP"
+
+; This directive determines whether PHP registers $argv & $argc each time it
+; runs. $argv contains an array of all the arguments passed to PHP when a script
+; is invoked. $argc contains an integer representing the number of arguments
+; that were passed when the script was invoked. These arrays are extremely
+; useful when running scripts from the command line. When this directive is
+; enabled, registering these variables consumes CPU cycles and memory each time
+; a script is executed. For performance reasons, this feature should be disabled
+; on production servers.
+; Note: This directive is hardcoded to On for the CLI SAPI
+; Default Value: On
+; Development Value: Off
+; Production Value: Off
+; http://php.net/register-argc-argv
+register_argc_argv = Off
+
+; When enabled, the ENV, REQUEST and SERVER variables are created when they're
+; first used (Just In Time) instead of when the script starts. If these
+; variables are not used within a script, having this directive on will result
+; in a performance gain. The PHP directive register_argc_argv must be disabled
+; for this directive to have any effect.
+; http://php.net/auto-globals-jit
+auto_globals_jit = On
+
+; Whether PHP will read the POST data.
+; This option is enabled by default.
+; Most likely, you won't want to disable this option globally. It causes $_POST
+; and $_FILES to always be empty; the only way you will be able to read the
+; POST data will be through the php://input stream wrapper. This can be useful
+; to proxy requests or to process the POST data in a memory efficient fashion.
+; http://php.net/enable-post-data-reading
+;enable_post_data_reading = Off
+
+; Maximum size of POST data that PHP will accept.
+; Its value may be 0 to disable the limit. It is ignored if POST data reading
+; is disabled through enable_post_data_reading.
+; http://php.net/post-max-size
+post_max_size = 20M
+
+; Automatically add files before PHP document.
+; http://php.net/auto-prepend-file
+auto_prepend_file =
+
+; Automatically add files after PHP document.
+; http://php.net/auto-append-file
+auto_append_file =
+
+; By default, PHP will output a media type using the Content-Type header. To
+; disable this, simply set it to be empty.
+;
+; PHP's built-in default media type is set to text/html.
+; http://php.net/default-mimetype
+default_mimetype = "text/html"
+
+; PHP's default character set is set to UTF-8.
+; http://php.net/default-charset
+default_charset = "UTF-8"
+
+; PHP internal character encoding is set to empty.
+; If empty, default_charset is used.
+; http://php.net/internal-encoding
+;internal_encoding =
+
+; PHP input character encoding is set to empty.
+; If empty, default_charset is used.
+; http://php.net/input-encoding
+;input_encoding =
+
+; PHP output character encoding is set to empty.
+; If empty, default_charset is used.
+; See also output_buffer.
+; http://php.net/output-encoding
+;output_encoding =
+
+;;;;;;;;;;;;;;;;;;;;;;;;;
+; Paths and Directories ;
+;;;;;;;;;;;;;;;;;;;;;;;;;
+
+; UNIX: "/path1:/path2"
+include_path = ".:/usr/share/php7"
+;
+; Windows: "\path1;\path2"
+;include_path = ".;c:\php\includes"
+;
+; PHP's default setting for include_path is ".;/path/to/php/pear"
+; http://php.net/include-path
+
+; The root of the PHP pages, used only if nonempty.
+; if PHP was not compiled with FORCE_REDIRECT, you SHOULD set doc_root
+; if you are running php as a CGI under any web server (other than IIS)
+; see documentation for security issues.  The alternate is to use the
+; cgi.force_redirect configuration below
+; http://php.net/doc-root
+doc_root =
+
+; The directory under which PHP opens the script using /~username used only
+; if nonempty.
+; http://php.net/user-dir
+user_dir =
+
+; Directory in which the loadable extensions (modules) reside.
+; http://php.net/extension-dir
+;extension_dir = "./"
+; On windows:
+;extension_dir = "ext"
+
+; Directory where the temporary files should be placed.
+; Defaults to the system default (see sys_get_temp_dir)
+;sys_temp_dir = "/tmp"
+
+; Whether or not to enable the dl() function.  The dl() function does NOT work
+; properly in multithreaded servers, such as IIS or Zeus, and is automatically
+; disabled on them.
+; http://php.net/enable-dl
+enable_dl = Off
+
+; cgi.force_redirect is necessary to provide security running PHP as a CGI under
+; most web servers.  Left undefined, PHP turns this on by default.  You can
+; turn it off here AT YOUR OWN RISK
+; **You CAN safely turn this off for IIS, in fact, you MUST.**
+; http://php.net/cgi.force-redirect
+;cgi.force_redirect = 1
+
+; if cgi.nph is enabled it will force cgi to always sent Status: 200 with
+; every request. PHP's default behavior is to disable this feature.
+;cgi.nph = 1
+
+; if cgi.force_redirect is turned on, and you are not running under Apache or Netscape
+; (iPlanet) web servers, you MAY need to set an environment variable name that PHP
+; will look for to know it is OK to continue execution.  Setting this variable MAY
+; cause security issues, KNOW WHAT YOU ARE DOING FIRST.
+; http://php.net/cgi.redirect-status-env
+;cgi.redirect_status_env =
+
+; cgi.fix_pathinfo provides *real* PATH_INFO/PATH_TRANSLATED support for CGI.  PHP's
+; previous behaviour was to set PATH_TRANSLATED to SCRIPT_FILENAME, and to not grok
+; what PATH_INFO is.  For more information on PATH_INFO, see the cgi specs.  Setting
+; this to 1 will cause PHP CGI to fix its paths to conform to the spec.  A setting
+; of zero causes PHP to behave as before.  Default is 1.  You should fix your scripts
+; to use SCRIPT_FILENAME rather than PATH_TRANSLATED.
+; http://php.net/cgi.fix-pathinfo
+;cgi.fix_pathinfo=1
+
+; if cgi.discard_path is enabled, the PHP CGI binary can safely be placed outside
+; of the web tree and people will not be able to circumvent .htaccess security.
+;cgi.discard_path=1
+
+; FastCGI under IIS supports the ability to impersonate
+; security tokens of the calling client.  This allows IIS to define the
+; security context that the request runs under.  mod_fastcgi under Apache
+; does not currently support this feature (03/17/2002)
+; Set to 1 if running under IIS.  Default is zero.
+; http://php.net/fastcgi.impersonate
+;fastcgi.impersonate = 1
+
+; Disable logging through FastCGI connection. PHP's default behavior is to enable
+; this feature.
+;fastcgi.logging = 0
+
+; cgi.rfc2616_headers configuration option tells PHP what type of headers to
+; use when sending HTTP response code. If set to 0, PHP sends Status: header that
+; is supported by Apache. When this option is set to 1, PHP will send
+; RFC2616 compliant header.
+; Default is zero.
+; http://php.net/cgi.rfc2616-headers
+;cgi.rfc2616_headers = 0
+
+; cgi.check_shebang_line controls whether CGI PHP checks for line starting with #!
+; (shebang) at the top of the running script. This line might be needed if the
+; script support running both as stand-alone script and via PHP CGI<. PHP in CGI
+; mode skips this line and ignores its content if this directive is turned on.
+; http://php.net/cgi.check-shebang-line
+;cgi.check_shebang_line=1
+
+;;;;;;;;;;;;;;;;
+; File Uploads ;
+;;;;;;;;;;;;;;;;
+
+; Whether to allow HTTP file uploads.
+; http://php.net/file-uploads
+file_uploads = On
+
+; Temporary directory for HTTP uploaded files (will use system default if not
+; specified).
+; http://php.net/upload-tmp-dir
+;upload_tmp_dir =
+
+; Maximum allowed size for uploaded files.
+; http://php.net/upload-max-filesize
+upload_max_filesize = 50M
+
+; Maximum number of files that can be uploaded via a single request
+max_file_uploads = 100
+
+;;;;;;;;;;;;;;;;;;
+; Fopen wrappers ;
+;;;;;;;;;;;;;;;;;;
+
+; Whether to allow the treatment of URLs (like http:// or ftp://) as files.
+; http://php.net/allow-url-fopen
+allow_url_fopen = On
+
+; Whether to allow include/require to open URLs (like http:// or ftp://) as files.
+; http://php.net/allow-url-include
+allow_url_include = Off
+
+; Define the anonymous ftp password (your email address). PHP's default setting
+; for this is empty.
+; http://php.net/from
+;from="john@doe.com"
+
+; Define the User-Agent string. PHP's default setting for this is empty.
+; http://php.net/user-agent
+;user_agent="PHP"
+
+; Default timeout for socket based streams (seconds)
+; http://php.net/default-socket-timeout
+default_socket_timeout = 60
+
+; If your scripts have to deal with files from Macintosh systems,
+; or you are running on a Mac and need to deal with files from
+; unix or win32 systems, setting this flag will cause PHP to
+; automatically detect the EOL character in those files so that
+; fgets() and file() will work regardless of the source of the file.
+; http://php.net/auto-detect-line-endings
+;auto_detect_line_endings = Off
+
+;;;;;;;;;;;;;;;;;;;;;;
+; Dynamic Extensions ;
+;;;;;;;;;;;;;;;;;;;;;;
+
+; If you wish to have an extension loaded automatically, use the following
+; syntax:
+;
+;   extension=modulename
+;
+; For example:
+;
+;   extension=mysqli
+;
+; When the extension library to load is not located in the default extension
+; directory, You may specify an absolute path to the library file:
+;
+;   extension=/path/to/extension/mysqli.so
+;
+; Note : The syntax used in previous PHP versions ('extension=<ext>.so' and
+; 'extension='php_<ext>.dll') is supported for legacy reasons and may be
+; deprecated in a future PHP major version. So, when it is possible, please
+; move to the new ('extension=<ext>) syntax.
+;
+; Notes for Windows environments :
+;
+; - Many DLL files are located in the extensions/ (PHP 4) or ext/ (PHP 5+)
+;   extension folders as well as the separate PECL DLL download (PHP 5+).
+;   Be sure to appropriately set the extension_dir directive.
+;
+;extension=bz2
+;extension=curl
+;extension=ffi
+;extension=ftp
+;extension=fileinfo
+;extension=gd2
+;extension=gettext
+;extension=gmp
+;extension=intl
+;extension=imap
+;extension=ldap
+;extension=mbstring
+;extension=exif      ; Must be after mbstring as it depends on it
+;extension=mysqli
+;extension=oci8_12c  ; Use with Oracle Database 12c Instant Client
+;extension=odbc
+;extension=openssl
+;extension=pdo_firebird
+;extension=pdo_mysql
+;extension=pdo_oci
+;extension=pdo_odbc
+;extension=pdo_pgsql
+;extension=pdo_sqlite
+;extension=pgsql
+;extension=shmop
+
+; The MIBS data available in the PHP distribution must be installed.
+; See http://www.php.net/manual/en/snmp.installation.php
+;extension=snmp
+
+;extension=soap
+;extension=sockets
+;extension=sodium
+;extension=sqlite3
+;extension=tidy
+;extension=xmlrpc
+;extension=xsl
+
+;;;;;;;;;;;;;;;;;;;
+; Module Settings ;
+;;;;;;;;;;;;;;;;;;;
+
+[CLI Server]
+; Whether the CLI web server uses ANSI color coding in its terminal output.
+cli_server.color = On
+
+[Date]
+; Defines the default timezone used by the date functions
+; http://php.net/date.timezone
+;date.timezone =
+
+; http://php.net/date.default-latitude
+;date.default_latitude = 31.7667
+
+; http://php.net/date.default-longitude
+;date.default_longitude = 35.2333
+
+; http://php.net/date.sunrise-zenith
+;date.sunrise_zenith = 90.583333
+
+; http://php.net/date.sunset-zenith
+;date.sunset_zenith = 90.583333
+
+[filter]
+; http://php.net/filter.default
+;filter.default = unsafe_raw
+
+; http://php.net/filter.default-flags
+;filter.default_flags =
+
+[iconv]
+; Use of this INI entry is deprecated, use global input_encoding instead.
+; If empty, default_charset or input_encoding or iconv.input_encoding is used.
+; The precedence is: default_charset < input_encoding < iconv.input_encoding
+;iconv.input_encoding =
+
+; Use of this INI entry is deprecated, use global internal_encoding instead.
+; If empty, default_charset or internal_encoding or iconv.internal_encoding is used.
+; The precedence is: default_charset < internal_encoding < iconv.internal_encoding
+;iconv.internal_encoding =
+
+; Use of this INI entry is deprecated, use global output_encoding instead.
+; If empty, default_charset or output_encoding or iconv.output_encoding is used.
+; The precedence is: default_charset < output_encoding < iconv.output_encoding
+; To use an output encoding conversion, iconv's output handler must be set
+; otherwise output encoding conversion cannot be performed.
+;iconv.output_encoding =
+
+[imap]
+; rsh/ssh logins are disabled by default. Use this INI entry if you want to
+; enable them. Note that the IMAP library does not filter mailbox names before
+; passing them to rsh/ssh command, thus passing untrusted data to this function
+; with rsh/ssh enabled is insecure.
+;imap.enable_insecure_rsh=0
+
+[intl]
+;intl.default_locale =
+; This directive allows you to produce PHP errors when some error
+; happens within intl functions. The value is the level of the error produced.
+; Default is 0, which does not produce any errors.
+;intl.error_level = E_WARNING
+;intl.use_exceptions = 0
+
+[sqlite3]
+; Directory pointing to SQLite3 extensions
+; http://php.net/sqlite3.extension-dir
+;sqlite3.extension_dir =
+
+; SQLite defensive mode flag (only available from SQLite 3.26+)
+; When the defensive flag is enabled, language features that allow ordinary
+; SQL to deliberately corrupt the database file are disabled. This forbids
+; writing directly to the schema, shadow tables (eg. FTS data tables), or
+; the sqlite_dbpage virtual table.
+; https://www.sqlite.org/c3ref/c_dbconfig_defensive.html
+; (for older SQLite versions, this flag has no use)
+;sqlite3.defensive = 1
+
+[Pcre]
+; PCRE library backtracking limit.
+; http://php.net/pcre.backtrack-limit
+;pcre.backtrack_limit=100000
+
+; PCRE library recursion limit.
+; Please note that if you set this value to a high number you may consume all
+; the available process stack and eventually crash PHP (due to reaching the
+; stack size limit imposed by the Operating System).
+; http://php.net/pcre.recursion-limit
+;pcre.recursion_limit=100000
+
+; Enables or disables JIT compilation of patterns. This requires the PCRE
+; library to be compiled with JIT support.
+;pcre.jit=1
+
+[Pdo]
+; Whether to pool ODBC connections. Can be one of "strict", "relaxed" or "off"
+; http://php.net/pdo-odbc.connection-pooling
+;pdo_odbc.connection_pooling=strict
+
+;pdo_odbc.db2_instance_name
+
+[Pdo_mysql]
+; Default socket name for local MySQL connects.  If empty, uses the built-in
+; MySQL defaults.
+pdo_mysql.default_socket=
+
+[Phar]
+; http://php.net/phar.readonly
+;phar.readonly = On
+
+; http://php.net/phar.require-hash
+;phar.require_hash = On
+
+;phar.cache_list =
+
+[mail function]
+; For Win32 only.
+; http://php.net/smtp
+SMTP = localhost
+; http://php.net/smtp-port
+smtp_port = 25
+
+; For Win32 only.
+; http://php.net/sendmail-from
+;sendmail_from = me@example.com
+
+; For Unix only.  You may supply arguments as well (default: "sendmail -t -i").
+; http://php.net/sendmail-path
+;sendmail_path =
+
+; Force the addition of the specified parameters to be passed as extra parameters
+; to the sendmail binary. These parameters will always replace the value of
+; the 5th parameter to mail().
+;mail.force_extra_parameters =
+
+; Add X-PHP-Originating-Script: that will include uid of the script followed by the filename
+mail.add_x_header = Off
+
+; The path to a log file that will log all mail() calls. Log entries include
+; the full path of the script, line number, To address and headers.
+;mail.log =
+; Log mail to syslog (Event Log on Windows).
+;mail.log = syslog
+
+[ODBC]
+; http://php.net/odbc.default-db
+;odbc.default_db    =  Not yet implemented
+
+; http://php.net/odbc.default-user
+;odbc.default_user  =  Not yet implemented
+
+; http://php.net/odbc.default-pw
+;odbc.default_pw    =  Not yet implemented
+
+; Controls the ODBC cursor model.
+; Default: SQL_CURSOR_STATIC (default).
+;odbc.default_cursortype
+
+; Allow or prevent persistent links.
+; http://php.net/odbc.allow-persistent
+odbc.allow_persistent = On
+
+; Check that a connection is still valid before reuse.
+; http://php.net/odbc.check-persistent
+odbc.check_persistent = On
+
+; Maximum number of persistent links.  -1 means no limit.
+; http://php.net/odbc.max-persistent
+odbc.max_persistent = -1
+
+; Maximum number of links (persistent + non-persistent).  -1 means no limit.
+; http://php.net/odbc.max-links
+odbc.max_links = -1
+
+; Handling of LONG fields.  Returns number of bytes to variables.  0 means
+; passthru.
+; http://php.net/odbc.defaultlrl
+odbc.defaultlrl = 4096
+
+; Handling of binary data.  0 means passthru, 1 return as is, 2 convert to char.
+; See the documentation on odbc_binmode and odbc_longreadlen for an explanation
+; of odbc.defaultlrl and odbc.defaultbinmode
+; http://php.net/odbc.defaultbinmode
+odbc.defaultbinmode = 1
+
+[MySQLi]
+
+; Maximum number of persistent links.  -1 means no limit.
+; http://php.net/mysqli.max-persistent
+mysqli.max_persistent = -1
+
+; Allow accessing, from PHP's perspective, local files with LOAD DATA statements
+; http://php.net/mysqli.allow_local_infile
+;mysqli.allow_local_infile = On
+
+; Allow or prevent persistent links.
+; http://php.net/mysqli.allow-persistent
+mysqli.allow_persistent = On
+
+; Maximum number of links.  -1 means no limit.
+; http://php.net/mysqli.max-links
+mysqli.max_links = -1
+
+; Default port number for mysqli_connect().  If unset, mysqli_connect() will use
+; the $MYSQL_TCP_PORT or the mysql-tcp entry in /etc/services or the
+; compile-time value defined MYSQL_PORT (in that order).  Win32 will only look
+; at MYSQL_PORT.
+; http://php.net/mysqli.default-port
+mysqli.default_port = 3306
+
+; Default socket name for local MySQL connects.  If empty, uses the built-in
+; MySQL defaults.
+; http://php.net/mysqli.default-socket
+mysqli.default_socket =
+
+; Default host for mysqli_connect() (doesn't apply in safe mode).
+; http://php.net/mysqli.default-host
+mysqli.default_host =
+
+; Default user for mysqli_connect() (doesn't apply in safe mode).
+; http://php.net/mysqli.default-user
+mysqli.default_user =
+
+; Default password for mysqli_connect() (doesn't apply in safe mode).
+; Note that this is generally a *bad* idea to store passwords in this file.
+; *Any* user with PHP access can run 'echo get_cfg_var("mysqli.default_pw")
+; and reveal this password!  And of course, any users with read access to this
+; file will be able to reveal the password as well.
+; http://php.net/mysqli.default-pw
+mysqli.default_pw =
+
+; Allow or prevent reconnect
+mysqli.reconnect = Off
+
+[mysqlnd]
+; Enable / Disable collection of general statistics by mysqlnd which can be
+; used to tune and monitor MySQL operations.
+mysqlnd.collect_statistics = On
+
+; Enable / Disable collection of memory usage statistics by mysqlnd which can be
+; used to tune and monitor MySQL operations.
+mysqlnd.collect_memory_statistics = Off
+
+; Records communication from all extensions using mysqlnd to the specified log
+; file.
+; http://php.net/mysqlnd.debug
+;mysqlnd.debug =
+
+; Defines which queries will be logged.
+;mysqlnd.log_mask = 0
+
+; Default size of the mysqlnd memory pool, which is used by result sets.
+;mysqlnd.mempool_default_size = 16000
+
+; Size of a pre-allocated buffer used when sending commands to MySQL in bytes.
+;mysqlnd.net_cmd_buffer_size = 2048
+
+; Size of a pre-allocated buffer used for reading data sent by the server in
+; bytes.
+;mysqlnd.net_read_buffer_size = 32768
+
+; Timeout for network requests in seconds.
+;mysqlnd.net_read_timeout = 31536000
+
+; SHA-256 Authentication Plugin related. File with the MySQL server public RSA
+; key.
+;mysqlnd.sha256_server_public_key =
+
+[OCI8]
+
+; Connection: Enables privileged connections using external
+; credentials (OCI_SYSOPER, OCI_SYSDBA)
+; http://php.net/oci8.privileged-connect
+;oci8.privileged_connect = Off
+
+; Connection: The maximum number of persistent OCI8 connections per
+; process. Using -1 means no limit.
+; http://php.net/oci8.max-persistent
+;oci8.max_persistent = -1
+
+; Connection: The maximum number of seconds a process is allowed to
+; maintain an idle persistent connection. Using -1 means idle
+; persistent connections will be maintained forever.
+; http://php.net/oci8.persistent-timeout
+;oci8.persistent_timeout = -1
+
+; Connection: The number of seconds that must pass before issuing a
+; ping during oci_pconnect() to check the connection validity. When
+; set to 0, each oci_pconnect() will cause a ping. Using -1 disables
+; pings completely.
+; http://php.net/oci8.ping-interval
+;oci8.ping_interval = 60
+
+; Connection: Set this to a user chosen connection class to be used
+; for all pooled server requests with Oracle 11g Database Resident
+; Connection Pooling (DRCP).  To use DRCP, this value should be set to
+; the same string for all web servers running the same application,
+; the database pool must be configured, and the connection string must
+; specify to use a pooled server.
+;oci8.connection_class =
+
+; High Availability: Using On lets PHP receive Fast Application
+; Notification (FAN) events generated when a database node fails. The
+; database must also be configured to post FAN events.
+;oci8.events = Off
+
+; Tuning: This option enables statement caching, and specifies how
+; many statements to cache. Using 0 disables statement caching.
+; http://php.net/oci8.statement-cache-size
+;oci8.statement_cache_size = 20
+
+; Tuning: Enables statement prefetching and sets the default number of
+; rows that will be fetched automatically after statement execution.
+; http://php.net/oci8.default-prefetch
+;oci8.default_prefetch = 100
+
+; Compatibility. Using On means oci_close() will not close
+; oci_connect() and oci_new_connect() connections.
+; http://php.net/oci8.old-oci-close-semantics
+;oci8.old_oci_close_semantics = Off
+
+[PostgreSQL]
+; Allow or prevent persistent links.
+; http://php.net/pgsql.allow-persistent
+pgsql.allow_persistent = On
+
+; Detect broken persistent links always with pg_pconnect().
+; Auto reset feature requires a little overheads.
+; http://php.net/pgsql.auto-reset-persistent
+pgsql.auto_reset_persistent = Off
+
+; Maximum number of persistent links.  -1 means no limit.
+; http://php.net/pgsql.max-persistent
+pgsql.max_persistent = -1
+
+; Maximum number of links (persistent+non persistent).  -1 means no limit.
+; http://php.net/pgsql.max-links
+pgsql.max_links = -1
+
+; Ignore PostgreSQL backends Notice message or not.
+; Notice message logging require a little overheads.
+; http://php.net/pgsql.ignore-notice
+pgsql.ignore_notice = 0
+
+; Log PostgreSQL backends Notice message or not.
+; Unless pgsql.ignore_notice=0, module cannot log notice message.
+; http://php.net/pgsql.log-notice
+pgsql.log_notice = 0
+
+[bcmath]
+; Number of decimal digits for all bcmath functions.
+; http://php.net/bcmath.scale
+bcmath.scale = 0
+
+[browscap]
+; http://php.net/browscap
+;browscap = extra/browscap.ini
+
+[Session]
+; Handler used to store/retrieve data.
+; http://php.net/session.save-handler
+session.save_handler = files
+
+; Argument passed to save_handler.  In the case of files, this is the path
+; where data files are stored. Note: Windows users have to change this
+; variable in order to use PHP's session functions.
+;
+; The path can be defined as:
+;
+;     session.save_path = "N;/path"
+;
+; where N is an integer.  Instead of storing all the session files in
+; /path, what this will do is use subdirectories N-levels deep, and
+; store the session data in those directories.  This is useful if
+; your OS has problems with many files in one directory, and is
+; a more efficient layout for servers that handle many sessions.
+;
+; NOTE 1: PHP will not create this directory structure automatically.
+;         You can use the script in the ext/session dir for that purpose.
+; NOTE 2: See the section on garbage collection below if you choose to
+;         use subdirectories for session storage
+;
+; The file storage module creates files using mode 600 by default.
+; You can change that by using
+;
+;     session.save_path = "N;MODE;/path"
+;
+; where MODE is the octal representation of the mode. Note that this
+; does not overwrite the process's umask.
+; http://php.net/session.save-path
+;session.save_path = "/tmp"
+
+; Whether to use strict session mode.
+; Strict session mode does not accept an uninitialized session ID, and
+; regenerates the session ID if the browser sends an uninitialized session ID.
+; Strict mode protects applications from session fixation via a session adoption
+; vulnerability. It is disabled by default for maximum compatibility, but
+; enabling it is encouraged.
+; https://wiki.php.net/rfc/strict_sessions
+session.use_strict_mode = 0
+
+; Whether to use cookies.
+; http://php.net/session.use-cookies
+session.use_cookies = 1
+
+; http://php.net/session.cookie-secure
+;session.cookie_secure =
+
+; This option forces PHP to fetch and use a cookie for storing and maintaining
+; the session id. We encourage this operation as it's very helpful in combating
+; session hijacking when not specifying and managing your own session id. It is
+; not the be-all and end-all of session hijacking defense, but it's a good start.
+; http://php.net/session.use-only-cookies
+session.use_only_cookies = 1
+
+; Name of the session (used as cookie name).
+; http://php.net/session.name
+session.name = PHPSESSID
+
+; Initialize session on request startup.
+; http://php.net/session.auto-start
+session.auto_start = 0
+
+; Lifetime in seconds of cookie or, if 0, until browser is restarted.
+; http://php.net/session.cookie-lifetime
+session.cookie_lifetime = 0
+
+; The path for which the cookie is valid.
+; http://php.net/session.cookie-path
+session.cookie_path = /
+
+; The domain for which the cookie is valid.
+; http://php.net/session.cookie-domain
+session.cookie_domain =
+
+; Whether or not to add the httpOnly flag to the cookie, which makes it
+; inaccessible to browser scripting languages such as JavaScript.
+; http://php.net/session.cookie-httponly
+session.cookie_httponly =
+
+; Add SameSite attribute to cookie to help mitigate Cross-Site Request Forgery (CSRF/XSRF)
+; Current valid values are "Strict", "Lax" or "None". When using "None",
+; make sure to include the quotes, as `none` is interpreted like `false` in ini files.
+; https://tools.ietf.org/html/draft-west-first-party-cookies-07
+session.cookie_samesite =
+
+; Handler used to serialize data. php is the standard serializer of PHP.
+; http://php.net/session.serialize-handler
+session.serialize_handler = php
+
+; Defines the probability that the 'garbage collection' process is started on every
+; session initialization. The probability is calculated by using gc_probability/gc_divisor,
+; e.g. 1/100 means there is a 1% chance that the GC process starts on each request.
+; Default Value: 1
+; Development Value: 1
+; Production Value: 1
+; http://php.net/session.gc-probability
+session.gc_probability = 1
+
+; Defines the probability that the 'garbage collection' process is started on every
+; session initialization. The probability is calculated by using gc_probability/gc_divisor,
+; e.g. 1/100 means there is a 1% chance that the GC process starts on each request.
+; For high volume production servers, using a value of 1000 is a more efficient approach.
+; Default Value: 100
+; Development Value: 1000
+; Production Value: 1000
+; http://php.net/session.gc-divisor
+session.gc_divisor = 1000
+
+; After this number of seconds, stored data will be seen as 'garbage' and
+; cleaned up by the garbage collection process.
+; http://php.net/session.gc-maxlifetime
+session.gc_maxlifetime = 1440
+
+; NOTE: If you are using the subdirectory option for storing session files
+;       (see session.save_path above), then garbage collection does *not*
+;       happen automatically.  You will need to do your own garbage
+;       collection through a shell script, cron entry, or some other method.
+;       For example, the following script is the equivalent of setting
+;       session.gc_maxlifetime to 1440 (1440 seconds = 24 minutes):
+;          find /path/to/sessions -cmin +24 -type f | xargs rm
+
+; Check HTTP Referer to invalidate externally stored URLs containing ids.
+; HTTP_REFERER has to contain this substring for the session to be
+; considered as valid.
+; http://php.net/session.referer-check
+session.referer_check =
+
+; Set to {nocache,private,public,} to determine HTTP caching aspects
+; or leave this empty to avoid sending anti-caching headers.
+; http://php.net/session.cache-limiter
+session.cache_limiter = nocache
+
+; Document expires after n minutes.
+; http://php.net/session.cache-expire
+session.cache_expire = 180
+
+; trans sid support is disabled by default.
+; Use of trans sid may risk your users' security.
+; Use this option with caution.
+; - User may send URL contains active session ID
+;   to other person via. email/irc/etc.
+; - URL that contains active session ID may be stored
+;   in publicly accessible computer.
+; - User may access your site with the same session ID
+;   always using URL stored in browser's history or bookmarks.
+; http://php.net/session.use-trans-sid
+session.use_trans_sid = 0
+
+; Set session ID character length. This value could be between 22 to 256.
+; Shorter length than default is supported only for compatibility reason.
+; Users should use 32 or more chars.
+; http://php.net/session.sid-length
+; Default Value: 32
+; Development Value: 26
+; Production Value: 26
+session.sid_length = 26
+
+; The URL rewriter will look for URLs in a defined set of HTML tags.
+; <form> is special; if you include them here, the rewriter will
+; add a hidden <input> field with the info which is otherwise appended
+; to URLs. <form> tag's action attribute URL will not be modified
+; unless it is specified.
+; Note that all valid entries require a "=", even if no value follows.
+; Default Value: "a=href,area=href,frame=src,form="
+; Development Value: "a=href,area=href,frame=src,form="
+; Production Value: "a=href,area=href,frame=src,form="
+; http://php.net/url-rewriter.tags
+session.trans_sid_tags = "a=href,area=href,frame=src,form="
+
+; URL rewriter does not rewrite absolute URLs by default.
+; To enable rewrites for absolute paths, target hosts must be specified
+; at RUNTIME. i.e. use ini_set()
+; <form> tags is special. PHP will check action attribute's URL regardless
+; of session.trans_sid_tags setting.
+; If no host is defined, HTTP_HOST will be used for allowed host.
+; Example value: php.net,www.php.net,wiki.php.net
+; Use "," for multiple hosts. No spaces are allowed.
+; Default Value: ""
+; Development Value: ""
+; Production Value: ""
+;session.trans_sid_hosts=""
+
+; Define how many bits are stored in each character when converting
+; the binary hash data to something readable.
+; Possible values:
+;   4  (4 bits: 0-9, a-f)
+;   5  (5 bits: 0-9, a-v)
+;   6  (6 bits: 0-9, a-z, A-Z, "-", ",")
+; Default Value: 4
+; Development Value: 5
+; Production Value: 5
+; http://php.net/session.hash-bits-per-character
+session.sid_bits_per_character = 5
+
+; Enable upload progress tracking in $_SESSION
+; Default Value: On
+; Development Value: On
+; Production Value: On
+; http://php.net/session.upload-progress.enabled
+;session.upload_progress.enabled = On
+
+; Cleanup the progress information as soon as all POST data has been read
+; (i.e. upload completed).
+; Default Value: On
+; Development Value: On
+; Production Value: On
+; http://php.net/session.upload-progress.cleanup
+;session.upload_progress.cleanup = On
+
+; A prefix used for the upload progress key in $_SESSION
+; Default Value: "upload_progress_"
+; Development Value: "upload_progress_"
+; Production Value: "upload_progress_"
+; http://php.net/session.upload-progress.prefix
+;session.upload_progress.prefix = "upload_progress_"
+
+; The index name (concatenated with the prefix) in $_SESSION
+; containing the upload progress information
+; Default Value: "PHP_SESSION_UPLOAD_PROGRESS"
+; Development Value: "PHP_SESSION_UPLOAD_PROGRESS"
+; Production Value: "PHP_SESSION_UPLOAD_PROGRESS"
+; http://php.net/session.upload-progress.name
+;session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
+
+; How frequently the upload progress should be updated.
+; Given either in percentages (per-file), or in bytes
+; Default Value: "1%"
+; Development Value: "1%"
+; Production Value: "1%"
+; http://php.net/session.upload-progress.freq
+;session.upload_progress.freq =  "1%"
+
+; The minimum delay between updates, in seconds
+; Default Value: 1
+; Development Value: 1
+; Production Value: 1
+; http://php.net/session.upload-progress.min-freq
+;session.upload_progress.min_freq = "1"
+
+; Only write session data when session data is changed. Enabled by default.
+; http://php.net/session.lazy-write
+;session.lazy_write = On
+
+[Assertion]
+; Switch whether to compile assertions at all (to have no overhead at run-time)
+; -1: Do not compile at all
+;  0: Jump over assertion at run-time
+;  1: Execute assertions
+; Changing from or to a negative value is only possible in php.ini! (For turning assertions on and off at run-time, see assert.active, when zend.assertions = 1)
+; Default Value: 1
+; Development Value: 1
+; Production Value: -1
+; http://php.net/zend.assertions
+zend.assertions = -1
+
+; Assert(expr); active by default.
+; http://php.net/assert.active
+;assert.active = On
+
+; Throw an AssertionError on failed assertions
+; http://php.net/assert.exception
+;assert.exception = On
+
+; Issue a PHP warning for each failed assertion. (Overridden by assert.exception if active)
+; http://php.net/assert.warning
+;assert.warning = On
+
+; Don't bail out by default.
+; http://php.net/assert.bail
+;assert.bail = Off
+
+; User-function to be called if an assertion fails.
+; http://php.net/assert.callback
+;assert.callback = 0
+
+; Eval the expression with current error_reporting().  Set to true if you want
+; error_reporting(0) around the eval().
+; http://php.net/assert.quiet-eval
+;assert.quiet_eval = 0
+
+[COM]
+; path to a file containing GUIDs, IIDs or filenames of files with TypeLibs
+; http://php.net/com.typelib-file
+;com.typelib_file =
+
+; allow Distributed-COM calls
+; http://php.net/com.allow-dcom
+;com.allow_dcom = true
+
+; autoregister constants of a component's typlib on com_load()
+; http://php.net/com.autoregister-typelib
+;com.autoregister_typelib = true
+
+; register constants casesensitive
+; http://php.net/com.autoregister-casesensitive
+;com.autoregister_casesensitive = false
+
+; show warnings on duplicate constant registrations
+; http://php.net/com.autoregister-verbose
+;com.autoregister_verbose = true
+
+; The default character set code-page to use when passing strings to and from COM objects.
+; Default: system ANSI code page
+;com.code_page=
+
+[mbstring]
+; language for internal character representation.
+; This affects mb_send_mail() and mbstring.detect_order.
+; http://php.net/mbstring.language
+;mbstring.language = Japanese
+
+; Use of this INI entry is deprecated, use global internal_encoding instead.
+; internal/script encoding.
+; Some encoding cannot work as internal encoding. (e.g. SJIS, BIG5, ISO-2022-*)
+; If empty, default_charset or internal_encoding or iconv.internal_encoding is used.
+; The precedence is: default_charset < internal_encoding < iconv.internal_encoding
+;mbstring.internal_encoding =
+
+; Use of this INI entry is deprecated, use global input_encoding instead.
+; http input encoding.
+; mbstring.encoding_translation = On is needed to use this setting.
+; If empty, default_charset or input_encoding or mbstring.input is used.
+; The precedence is: default_charset < input_encoding < mbstring.http_input
+; http://php.net/mbstring.http-input
+;mbstring.http_input =
+
+; Use of this INI entry is deprecated, use global output_encoding instead.
+; http output encoding.
+; mb_output_handler must be registered as output buffer to function.
+; If empty, default_charset or output_encoding or mbstring.http_output is used.
+; The precedence is: default_charset < output_encoding < mbstring.http_output
+; To use an output encoding conversion, mbstring's output handler must be set
+; otherwise output encoding conversion cannot be performed.
+; http://php.net/mbstring.http-output
+;mbstring.http_output =
+
+; enable automatic encoding translation according to
+; mbstring.internal_encoding setting. Input chars are
+; converted to internal encoding by setting this to On.
+; Note: Do _not_ use automatic encoding translation for
+;       portable libs/applications.
+; http://php.net/mbstring.encoding-translation
+;mbstring.encoding_translation = Off
+
+; automatic encoding detection order.
+; "auto" detect order is changed according to mbstring.language
+; http://php.net/mbstring.detect-order
+;mbstring.detect_order = auto
+
+; substitute_character used when character cannot be converted
+; one from another
+; http://php.net/mbstring.substitute-character
+;mbstring.substitute_character = none
+
+; overload(replace) single byte functions by mbstring functions.
+; mail(), ereg(), etc are overloaded by mb_send_mail(), mb_ereg(),
+; etc. Possible values are 0,1,2,4 or combination of them.
+; For example, 7 for overload everything.
+; 0: No overload
+; 1: Overload mail() function
+; 2: Overload str*() functions
+; 4: Overload ereg*() functions
+; http://php.net/mbstring.func-overload
+;mbstring.func_overload = 0
+
+; enable strict encoding detection.
+; Default: Off
+;mbstring.strict_detection = On
+
+; This directive specifies the regex pattern of content types for which mb_output_handler()
+; is activated.
+; Default: mbstring.http_output_conv_mimetype=^(text/|application/xhtml\+xml)
+;mbstring.http_output_conv_mimetype=
+
+; This directive specifies maximum stack depth for mbstring regular expressions. It is similar
+; to the pcre.recursion_limit for PCRE.
+; Default: 100000
+;mbstring.regex_stack_limit=100000
+
+; This directive specifies maximum retry count for mbstring regular expressions. It is similar
+; to the pcre.backtrack_limit for PCRE.
+; Default: 1000000
+;mbstring.regex_retry_limit=1000000
+
+[gd]
+; Tell the jpeg decode to ignore warnings and try to create
+; a gd image. The warning will then be displayed as notices
+; disabled by default
+; http://php.net/gd.jpeg-ignore-warning
+;gd.jpeg_ignore_warning = 1
+
+[exif]
+; Exif UNICODE user comments are handled as UCS-2BE/UCS-2LE and JIS as JIS.
+; With mbstring support this will automatically be converted into the encoding
+; given by corresponding encode setting. When empty mbstring.internal_encoding
+; is used. For the decode settings you can distinguish between motorola and
+; intel byte order. A decode setting cannot be empty.
+; http://php.net/exif.encode-unicode
+;exif.encode_unicode = ISO-8859-15
+
+; http://php.net/exif.decode-unicode-motorola
+;exif.decode_unicode_motorola = UCS-2BE
+
+; http://php.net/exif.decode-unicode-intel
+;exif.decode_unicode_intel    = UCS-2LE
+
+; http://php.net/exif.encode-jis
+;exif.encode_jis =
+
+; http://php.net/exif.decode-jis-motorola
+;exif.decode_jis_motorola = JIS
+
+; http://php.net/exif.decode-jis-intel
+;exif.decode_jis_intel    = JIS
+
+[Tidy]
+; The path to a default tidy configuration file to use when using tidy
+; http://php.net/tidy.default-config
+;tidy.default_config = /usr/local/lib/php/default.tcfg
+
+; Should tidy clean and repair output automatically?
+; WARNING: Do not use this option if you are generating non-html content
+; such as dynamic images
+; http://php.net/tidy.clean-output
+tidy.clean_output = Off
+
+[soap]
+; Enables or disables WSDL caching feature.
+; http://php.net/soap.wsdl-cache-enabled
+soap.wsdl_cache_enabled=1
+
+; Sets the directory name where SOAP extension will put cache files.
+; http://php.net/soap.wsdl-cache-dir
+soap.wsdl_cache_dir="/tmp"
+
+; (time to live) Sets the number of second while cached file will be used
+; instead of original one.
+; http://php.net/soap.wsdl-cache-ttl
+soap.wsdl_cache_ttl=86400
+
+; Sets the size of the cache limit. (Max. number of WSDL files to cache)
+soap.wsdl_cache_limit = 5
+
+[sysvshm]
+; A default size of the shared memory segment
+;sysvshm.init_mem = 10000
+
+[ldap]
+; Sets the maximum number of open links or -1 for unlimited.
+ldap.max_links = -1
+
+[dba]
+;dba.default_handler=
+
+[opcache]
+; Determines if Zend OPCache is enabled
+;opcache.enable=1
+
+; Determines if Zend OPCache is enabled for the CLI version of PHP
+;opcache.enable_cli=0
+
+; The OPcache shared memory storage size.
+;opcache.memory_consumption=128
+
+; The amount of memory for interned strings in Mbytes.
+;opcache.interned_strings_buffer=8
+
+; The maximum number of keys (scripts) in the OPcache hash table.
+; Only numbers between 200 and 1000000 are allowed.
+;opcache.max_accelerated_files=10000
+
+; The maximum percentage of "wasted" memory until a restart is scheduled.
+;opcache.max_wasted_percentage=5
+
+; When this directive is enabled, the OPcache appends the current working
+; directory to the script key, thus eliminating possible collisions between
+; files with the same name (basename). Disabling the directive improves
+; performance, but may break existing applications.
+;opcache.use_cwd=1
+
+; When disabled, you must reset the OPcache manually or restart the
+; webserver for changes to the filesystem to take effect.
+;opcache.validate_timestamps=1
+
+; How often (in seconds) to check file timestamps for changes to the shared
+; memory storage allocation. ("1" means validate once per second, but only
+; once per request. "0" means always validate)
+;opcache.revalidate_freq=2
+
+; Enables or disables file search in include_path optimization
+;opcache.revalidate_path=0
+
+; If disabled, all PHPDoc comments are dropped from the code to reduce the
+; size of the optimized code.
+;opcache.save_comments=1
+
+; Allow file existence override (file_exists, etc.) performance feature.
+;opcache.enable_file_override=0
+
+; A bitmask, where each bit enables or disables the appropriate OPcache
+; passes
+;opcache.optimization_level=0x7FFFBFFF
+
+;opcache.dups_fix=0
+
+; The location of the OPcache blacklist file (wildcards allowed).
+; Each OPcache blacklist file is a text file that holds the names of files
+; that should not be accelerated. The file format is to add each filename
+; to a new line. The filename may be a full path or just a file prefix
+; (i.e., /var/www/x  blacklists all the files and directories in /var/www
+; that start with 'x'). Line starting with a ; are ignored (comments).
+;opcache.blacklist_filename=
+
+; Allows exclusion of large files from being cached. By default all files
+; are cached.
+;opcache.max_file_size=0
+
+; Check the cache checksum each N requests.
+; The default value of "0" means that the checks are disabled.
+;opcache.consistency_checks=0
+
+; How long to wait (in seconds) for a scheduled restart to begin if the cache
+; is not being accessed.
+;opcache.force_restart_timeout=180
+
+; OPcache error_log file name. Empty string assumes "stderr".
+;opcache.error_log=
+
+; All OPcache errors go to the Web server log.
+; By default, only fatal errors (level 0) or errors (level 1) are logged.
+; You can also enable warnings (level 2), info messages (level 3) or
+; debug messages (level 4).
+;opcache.log_verbosity_level=1
+
+; Preferred Shared Memory back-end. Leave empty and let the system decide.
+;opcache.preferred_memory_model=
+
+; Protect the shared memory from unexpected writing during script execution.
+; Useful for internal debugging only.
+;opcache.protect_memory=0
+
+; Allows calling OPcache API functions only from PHP scripts which path is
+; started from specified string. The default "" means no restriction
+;opcache.restrict_api=
+
+; Mapping base of shared memory segments (for Windows only). All the PHP
+; processes have to map shared memory into the same address space. This
+; directive allows to manually fix the "Unable to reattach to base address"
+; errors.
+;opcache.mmap_base=
+
+; Facilitates multiple OPcache instances per user (for Windows only). All PHP
+; processes with the same cache ID and user share an OPcache instance.
+;opcache.cache_id=
+
+; Enables and sets the second level cache directory.
+; It should improve performance when SHM memory is full, at server restart or
+; SHM reset. The default "" disables file based caching.
+;opcache.file_cache=
+
+; Enables or disables opcode caching in shared memory.
+;opcache.file_cache_only=0
+
+; Enables or disables checksum validation when script loaded from file cache.
+;opcache.file_cache_consistency_checks=1
+
+; Implies opcache.file_cache_only=1 for a certain process that failed to
+; reattach to the shared memory (for Windows only). Explicitly enabled file
+; cache is required.
+;opcache.file_cache_fallback=1
+
+; Enables or disables copying of PHP code (text segment) into HUGE PAGES.
+; This should improve performance, but requires appropriate OS configuration.
+;opcache.huge_code_pages=1
+
+; Validate cached file permissions.
+;opcache.validate_permission=0
+
+; Prevent name collisions in chroot'ed environment.
+;opcache.validate_root=0
+
+; If specified, it produces opcode dumps for debugging different stages of
+; optimizations.
+;opcache.opt_debug_level=0
+
+; Specifies a PHP script that is going to be compiled and executed at server
+; start-up.
+; http://php.net/opcache.preload
+;opcache.preload=
+
+; Preloading code as root is not allowed for security reasons. This directive
+; facilitates to let the preloading to be run as another user.
+; http://php.net/opcache.preload_user
+;opcache.preload_user=
+
+; Prevents caching files that are less than this number of seconds old. It
+; protects from caching of incompletely updated files. In case all file updates
+; on your site are atomic, you may increase performance by setting it to "0".
+;opcache.file_update_protection=2
+
+; Absolute path used to store shared lockfiles (for *nix only).
+;opcache.lockfile_path=/tmp
+
+[curl]
+; A default value for the CURLOPT_CAINFO option. This is required to be an
+; absolute path.
+;curl.cainfo =
+
+[openssl]
+; The location of a Certificate Authority (CA) file on the local filesystem
+; to use when verifying the identity of SSL/TLS peers. Most users should
+; not specify a value for this directive as PHP will attempt to use the
+; OS-managed cert stores in its absence. If specified, this value may still
+; be overridden on a per-stream basis via the "cafile" SSL stream context
+; option.
+;openssl.cafile=
+
+; If openssl.cafile is not specified or if the CA file is not found, the
+; directory pointed to by openssl.capath is searched for a suitable
+; certificate. This value must be a correctly hashed certificate directory.
+; Most users should not specify a value for this directive as PHP will
+; attempt to use the OS-managed cert stores in its absence. If specified,
+; this value may still be overridden on a per-stream basis via the "capath"
+; SSL stream context option.
+;openssl.capath=
+
+[ffi]
+; FFI API restriction. Possible values:
+; "preload" - enabled in CLI scripts and preloaded files (default)
+; "false"   - always disabled
+; "true"    - always enabled
+;ffi.enable=preload
+
+; List of headers files to preload, wildcard patterns allowed.
+;ffi.preload=

+ 1862 - 1862
docker/redis.conf

@@ -1,1862 +1,1862 @@
-# Redis configuration file example.
-requirepass 123456
-#
-# Note that in order to read the configuration file, Redis must be
-# started with the file path as first argument:
-#
-# ./redis-server /path/to/redis.conf
-
-# Note on units: when memory size is needed, it is possible to specify
-# it in the usual form of 1k 5GB 4M and so forth:
-#
-# 1k => 1000 bytes
-# 1kb => 1024 bytes
-# 1m => 1000000 bytes
-# 1mb => 1024*1024 bytes
-# 1g => 1000000000 bytes
-# 1gb => 1024*1024*1024 bytes
-#
-# units are case insensitive so 1GB 1Gb 1gB are all the same.
-################################## INCLUDES ###################################
-
-# Include one or more other config files here.  This is useful if you
-# have a standard template that goes to all Redis servers but also need
-# to customize a few per-server settings.  Include files can include
-# other files, so use this wisely.
-#
-# Note that option "include" won't be rewritten by command "CONFIG REWRITE"
-# from admin or Redis Sentinel. Since Redis always uses the last processed
-# line as value of a configuration directive, you'd better put includes
-# at the beginning of this file to avoid overwriting config change at runtime.
-#
-# If instead you are interested in using includes to override configuration
-# options, it is better to use include as the last line.
-#
-# include /path/to/local.conf
-# include /path/to/other.conf
-
-################################## MODULES #####################################
-
-# Load modules at startup. If the server is not able to load modules
-# it will abort. It is possible to use multiple loadmodule directives.
-#
-# loadmodule /path/to/my_module.so
-# loadmodule /path/to/other_module.so
-
-################################## NETWORK #####################################
-
-# By default, if no "bind" configuration directive is specified, Redis listens
-# for connections from all available network interfaces on the host machine.
-# It is possible to listen to just one or multiple selected interfaces using
-# the "bind" configuration directive, followed by one or more IP addresses.
-#
-# Examples:
-#
-# bind 192.168.1.100 10.0.0.1
-# bind 127.0.0.1 ::1
-#
-# ~~~ WARNING ~~~ If the computer running Redis is directly exposed to the
-# internet, binding to all the interfaces is dangerous and will expose the
-# instance to everybody on the internet. So by default we uncomment the
-# following bind directive, that will force Redis to listen only on the
-# IPv4 loopback interface address (this means Redis will only be able to
-# accept client connections from the same host that it is running on).
-#
-# IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES
-# JUST COMMENT OUT THE FOLLOWING LINE.
-# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-bind 0.0.0.0
-
-# Protected mode is a layer of security protection, in order to avoid that
-# Redis instances left open on the internet are accessed and exploited.
-#
-# When protected mode is on and if:
-#
-# 1) The server is not binding explicitly to a set of addresses using the
-#    "bind" directive.
-# 2) No password is configured.
-#
-# The server only accepts connections from clients connecting from the
-# IPv4 and IPv6 loopback addresses 127.0.0.1 and ::1, and from Unix domain
-# sockets.
-#
-# By default protected mode is enabled. You should disable it only if
-# you are sure you want clients from other hosts to connect to Redis
-# even if no authentication is configured, nor a specific set of interfaces
-# are explicitly listed using the "bind" directive.
-protected-mode no
-
-# Accept connections on the specified port, default is 6379 (IANA #815344).
-# If port 0 is specified Redis will not listen on a TCP socket.
-port 6379
-
-# TCP listen() backlog.
-#
-# In high requests-per-second environments you need a high backlog in order
-# to avoid slow clients connection issues. Note that the Linux kernel
-# will silently truncate it to the value of /proc/sys/net/core/somaxconn so
-# make sure to raise both the value of somaxconn and tcp_max_syn_backlog
-# in order to get the desired effect.
-tcp-backlog 511
-
-# Unix socket.
-#
-# Specify the path for the Unix socket that will be used to listen for
-# incoming connections. There is no default, so Redis will not listen
-# on a unix socket when not specified.
-#
-unixsocket /run/redis/redis.sock
-unixsocketperm 770
-
-# Close the connection after a client is idle for N seconds (0 to disable)
-timeout 0
-
-# TCP keepalive.
-#
-# If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence
-# of communication. This is useful for two reasons:
-#
-# 1) Detect dead peers.
-# 2) Force network equipment in the middle to consider the connection to be
-#    alive.
-#
-# On Linux, the specified value (in seconds) is the period used to send ACKs.
-# Note that to close the connection the double of the time is needed.
-# On other kernels the period depends on the kernel configuration.
-#
-# A reasonable value for this option is 300 seconds, which is the new
-# Redis default starting with Redis 3.2.1.
-tcp-keepalive 300
-
-################################# TLS/SSL #####################################
-
-# By default, TLS/SSL is disabled. To enable it, the "tls-port" configuration
-# directive can be used to define TLS-listening ports. To enable TLS on the
-# default port, use:
-#
-# port 0
-# tls-port 6379
-
-# Configure a X.509 certificate and private key to use for authenticating the
-# server to connected clients, masters or cluster peers.  These files should be
-# PEM formatted.
-#
-# tls-cert-file redis.crt
-# tls-key-file redis.key
-
-# Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange:
-#
-# tls-dh-params-file redis.dh
-
-# Configure a CA certificate(s) bundle or directory to authenticate TLS/SSL
-# clients and peers.  Redis requires an explicit configuration of at least one
-# of these, and will not implicitly use the system wide configuration.
-#
-# tls-ca-cert-file ca.crt
-# tls-ca-cert-dir /etc/ssl/certs
-
-# By default, clients (including replica servers) on a TLS port are required
-# to authenticate using valid client side certificates.
-#
-# If "no" is specified, client certificates are not required and not accepted.
-# If "optional" is specified, client certificates are accepted and must be
-# valid if provided, but are not required.
-#
-# tls-auth-clients no
-# tls-auth-clients optional
-
-# By default, a Redis replica does not attempt to establish a TLS connection
-# with its master.
-#
-# Use the following directive to enable TLS on replication links.
-#
-# tls-replication yes
-
-# By default, the Redis Cluster bus uses a plain TCP connection. To enable
-# TLS for the bus protocol, use the following directive:
-#
-# tls-cluster yes
-
-# Explicitly specify TLS versions to support. Allowed values are case insensitive
-# and include "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" (OpenSSL >= 1.1.1) or
-# any combination. To enable only TLSv1.2 and TLSv1.3, use:
-#
-# tls-protocols "TLSv1.2 TLSv1.3"
-
-# Configure allowed ciphers.  See the ciphers(1ssl) manpage for more information
-# about the syntax of this string.
-#
-# Note: this configuration applies only to <= TLSv1.2.
-#
-# tls-ciphers DEFAULT:!MEDIUM
-
-# Configure allowed TLSv1.3 ciphersuites.  See the ciphers(1ssl) manpage for more
-# information about the syntax of this string, and specifically for TLSv1.3
-# ciphersuites.
-#
-# tls-ciphersuites TLS_CHACHA20_POLY1305_SHA256
-
-# When choosing a cipher, use the server's preference instead of the client
-# preference. By default, the server follows the client's preference.
-#
-# tls-prefer-server-ciphers yes
-
-# By default, TLS session caching is enabled to allow faster and less expensive
-# reconnections by clients that support it. Use the following directive to disable
-# caching.
-#
-# tls-session-caching no
-
-# Change the default number of TLS sessions cached. A zero value sets the cache
-# to unlimited size. The default size is 20480.
-#
-# tls-session-cache-size 5000
-
-# Change the default timeout of cached TLS sessions. The default timeout is 300
-# seconds.
-#
-# tls-session-cache-timeout 60
-
-################################# GENERAL #####################################
-
-# If you run Redis from upstart or systemd, Redis can interact with your
-# supervision tree. Options:
-#   supervised no      - no supervision interaction
-#   supervised upstart - signal upstart by putting Redis into SIGSTOP mode
-#                        requires "expect stop" in your upstart job config
-#   supervised systemd - signal systemd by writing READY=1 to $NOTIFY_SOCKET
-#   supervised auto    - detect upstart or systemd method based on
-#                        UPSTART_JOB or NOTIFY_SOCKET environment variables
-# Note: these supervision methods only signal "process is ready."
-#       They do not enable continuous pings back to your supervisor.
-supervised no
-
-# Specify the server verbosity level.
-# This can be one of:
-# debug (a lot of information, useful for development/testing)
-# verbose (many rarely useful info, but not a mess like the debug level)
-# notice (moderately verbose, what you want in production probably)
-# warning (only very important / critical messages are logged)
-loglevel notice
-
-# Specify the log file name. Also the empty string can be used to force
-# Redis to log on the standard output. Note that if you use standard
-# output for logging but daemonize, logs will be sent to /dev/null
-logfile /var/log/redis/redis.log
-
-# To enable logging to the system logger, just set 'syslog-enabled' to yes,
-# and optionally update the other syslog parameters to suit your needs.
-# syslog-enabled no
-
-# Specify the syslog identity.
-# syslog-ident redis
-
-# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7.
-# syslog-facility local0
-
-# Set the number of databases. The default database is DB 0, you can select
-# a different one on a per-connection basis using SELECT <dbid> where
-# dbid is a number between 0 and 'databases'-1
-databases 16
-
-# By default Redis shows an ASCII art logo only when started to log to the
-# standard output and if the standard output is a TTY. Basically this means
-# that normally a logo is displayed only in interactive sessions.
-#
-# However it is possible to force the pre-4.0 behavior and always show a
-# ASCII art logo in startup logs by setting the following option to yes.
-always-show-logo no
-
-################################ SNAPSHOTTING  ################################
-#
-# Save the DB on disk:
-#
-#   save <seconds> <changes>
-#
-#   Will save the DB if both the given number of seconds and the given
-#   number of write operations against the DB occurred.
-#
-#   In the example below the behavior will be to save:
-#   after 900 sec (15 min) if at least 1 key changed
-#   after 300 sec (5 min) if at least 10 keys changed
-#   after 60 sec if at least 10000 keys changed
-#
-#   Note: you can disable saving completely by commenting out all "save" lines.
-#
-#   It is also possible to remove all the previously configured save
-#   points by adding a save directive with a single empty string argument
-#   like in the following example:
-#
-#   save ""
-
-save 900 1
-save 300 10
-save 60 10000
-
-# By default Redis will stop accepting writes if RDB snapshots are enabled
-# (at least one save point) and the latest background save failed.
-# This will make the user aware (in a hard way) that data is not persisting
-# on disk properly, otherwise chances are that no one will notice and some
-# disaster will happen.
-#
-# If the background saving process will start working again Redis will
-# automatically allow writes again.
-#
-# However if you have setup your proper monitoring of the Redis server
-# and persistence, you may want to disable this feature so that Redis will
-# continue to work as usual even if there are problems with disk,
-# permissions, and so forth.
-stop-writes-on-bgsave-error yes
-
-# Compress string objects using LZF when dump .rdb databases?
-# By default compression is enabled as it's almost always a win.
-# If you want to save some CPU in the saving child set it to 'no' but
-# the dataset will likely be bigger if you have compressible values or keys.
-rdbcompression yes
-
-# Since version 5 of RDB a CRC64 checksum is placed at the end of the file.
-# This makes the format more resistant to corruption but there is a performance
-# hit to pay (around 10%) when saving and loading RDB files, so you can disable it
-# for maximum performances.
-#
-# RDB files created with checksum disabled have a checksum of zero that will
-# tell the loading code to skip the check.
-rdbchecksum yes
-
-# The filename where to dump the DB
-dbfilename dump.rdb
-
-# Remove RDB files used by replication in instances without persistence
-# enabled. By default this option is disabled, however there are environments
-# where for regulations or other security concerns, RDB files persisted on
-# disk by masters in order to feed replicas, or stored on disk by replicas
-# in order to load them for the initial synchronization, should be deleted
-# ASAP. Note that this option ONLY WORKS in instances that have both AOF
-# and RDB persistence disabled, otherwise is completely ignored.
-#
-# An alternative (and sometimes better) way to obtain the same effect is
-# to use diskless replication on both master and replicas instances. However
-# in the case of replicas, diskless is not always an option.
-rdb-del-sync-files no
-
-# The working directory.
-#
-# The DB will be written inside this directory, with the filename specified
-# above using the 'dbfilename' configuration directive.
-#
-# The Append Only File will also be created inside this directory.
-#
-# Note that you must specify a directory here, not a file name.
-dir /var/lib/redis
-
-################################# REPLICATION #################################
-
-# Master-Replica replication. Use replicaof to make a Redis instance a copy of
-# another Redis server. A few things to understand ASAP about Redis replication.
-#
-#   +------------------+      +---------------+
-#   |      Master      | ---> |    Replica    |
-#   | (receive writes) |      |  (exact copy) |
-#   +------------------+      +---------------+
-#
-# 1) Redis replication is asynchronous, but you can configure a master to
-#    stop accepting writes if it appears to be not connected with at least
-#    a given number of replicas.
-# 2) Redis replicas are able to perform a partial resynchronization with the
-#    master if the replication link is lost for a relatively small amount of
-#    time. You may want to configure the replication backlog size (see the next
-#    sections of this file) with a sensible value depending on your needs.
-# 3) Replication is automatic and does not need user intervention. After a
-#    network partition replicas automatically try to reconnect to masters
-#    and resynchronize with them.
-#
-# replicaof <masterip> <masterport>
-
-# If the master is password protected (using the "requirepass" configuration
-# directive below) it is possible to tell the replica to authenticate before
-# starting the replication synchronization process, otherwise the master will
-# refuse the replica request.
-#
-# masterauth <master-password>
-#
-# However this is not enough if you are using Redis ACLs (for Redis version
-# 6 or greater), and the default user is not capable of running the PSYNC
-# command and/or other commands needed for replication. In this case it's
-# better to configure a special user to use with replication, and specify the
-# masteruser configuration as such:
-#
-# masteruser <username>
-#
-# When masteruser is specified, the replica will authenticate against its
-# master using the new AUTH form: AUTH <username> <password>.
-
-# When a replica loses its connection with the master, or when the replication
-# is still in progress, the replica can act in two different ways:
-#
-# 1) if replica-serve-stale-data is set to 'yes' (the default) the replica will
-#    still reply to client requests, possibly with out of date data, or the
-#    data set may just be empty if this is the first synchronization.
-#
-# 2) If replica-serve-stale-data is set to 'no' the replica will reply with
-#    an error "SYNC with master in progress" to all commands except:
-#    INFO, REPLICAOF, AUTH, PING, SHUTDOWN, REPLCONF, ROLE, CONFIG, SUBSCRIBE,
-#    UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, PUBSUB, COMMAND, POST,
-#    HOST and LATENCY.
-#
-replica-serve-stale-data yes
-
-# You can configure a replica instance to accept writes or not. Writing against
-# a replica instance may be useful to store some ephemeral data (because data
-# written on a replica will be easily deleted after resync with the master) but
-# may also cause problems if clients are writing to it because of a
-# misconfiguration.
-#
-# Since Redis 2.6 by default replicas are read-only.
-#
-# Note: read only replicas are not designed to be exposed to untrusted clients
-# on the internet. It's just a protection layer against misuse of the instance.
-# Still a read only replica exports by default all the administrative commands
-# such as CONFIG, DEBUG, and so forth. To a limited extent you can improve
-# security of read only replicas using 'rename-command' to shadow all the
-# administrative / dangerous commands.
-replica-read-only yes
-
-# Replication SYNC strategy: disk or socket.
-#
-# New replicas and reconnecting replicas that are not able to continue the
-# replication process just receiving differences, need to do what is called a
-# "full synchronization". An RDB file is transmitted from the master to the
-# replicas.
-#
-# The transmission can happen in two different ways:
-#
-# 1) Disk-backed: The Redis master creates a new process that writes the RDB
-#                 file on disk. Later the file is transferred by the parent
-#                 process to the replicas incrementally.
-# 2) Diskless: The Redis master creates a new process that directly writes the
-#              RDB file to replica sockets, without touching the disk at all.
-#
-# With disk-backed replication, while the RDB file is generated, more replicas
-# can be queued and served with the RDB file as soon as the current child
-# producing the RDB file finishes its work. With diskless replication instead
-# once the transfer starts, new replicas arriving will be queued and a new
-# transfer will start when the current one terminates.
-#
-# When diskless replication is used, the master waits a configurable amount of
-# time (in seconds) before starting the transfer in the hope that multiple
-# replicas will arrive and the transfer can be parallelized.
-#
-# With slow disks and fast (large bandwidth) networks, diskless replication
-# works better.
-repl-diskless-sync no
-
-# When diskless replication is enabled, it is possible to configure the delay
-# the server waits in order to spawn the child that transfers the RDB via socket
-# to the replicas.
-#
-# This is important since once the transfer starts, it is not possible to serve
-# new replicas arriving, that will be queued for the next RDB transfer, so the
-# server waits a delay in order to let more replicas arrive.
-#
-# The delay is specified in seconds, and by default is 5 seconds. To disable
-# it entirely just set it to 0 seconds and the transfer will start ASAP.
-repl-diskless-sync-delay 5
-
-# -----------------------------------------------------------------------------
-# WARNING: RDB diskless load is experimental. Since in this setup the replica
-# does not immediately store an RDB on disk, it may cause data loss during
-# failovers. RDB diskless load + Redis modules not handling I/O reads may also
-# cause Redis to abort in case of I/O errors during the initial synchronization
-# stage with the master. Use only if your do what you are doing.
-# -----------------------------------------------------------------------------
-#
-# Replica can load the RDB it reads from the replication link directly from the
-# socket, or store the RDB to a file and read that file after it was completely
-# received from the master.
-#
-# In many cases the disk is slower than the network, and storing and loading
-# the RDB file may increase replication time (and even increase the master's
-# Copy on Write memory and salve buffers).
-# However, parsing the RDB file directly from the socket may mean that we have
-# to flush the contents of the current database before the full rdb was
-# received. For this reason we have the following options:
-#
-# "disabled"    - Don't use diskless load (store the rdb file to the disk first)
-# "on-empty-db" - Use diskless load only when it is completely safe.
-# "swapdb"      - Keep a copy of the current db contents in RAM while parsing
-#                 the data directly from the socket. note that this requires
-#                 sufficient memory, if you don't have it, you risk an OOM kill.
-repl-diskless-load disabled
-
-# Replicas send PINGs to server in a predefined interval. It's possible to
-# change this interval with the repl_ping_replica_period option. The default
-# value is 10 seconds.
-#
-# repl-ping-replica-period 10
-
-# The following option sets the replication timeout for:
-#
-# 1) Bulk transfer I/O during SYNC, from the point of view of replica.
-# 2) Master timeout from the point of view of replicas (data, pings).
-# 3) Replica timeout from the point of view of masters (REPLCONF ACK pings).
-#
-# It is important to make sure that this value is greater than the value
-# specified for repl-ping-replica-period otherwise a timeout will be detected
-# every time there is low traffic between the master and the replica. The default
-# value is 60 seconds.
-#
-# repl-timeout 60
-
-# Disable TCP_NODELAY on the replica socket after SYNC?
-#
-# If you select "yes" Redis will use a smaller number of TCP packets and
-# less bandwidth to send data to replicas. But this can add a delay for
-# the data to appear on the replica side, up to 40 milliseconds with
-# Linux kernels using a default configuration.
-#
-# If you select "no" the delay for data to appear on the replica side will
-# be reduced but more bandwidth will be used for replication.
-#
-# By default we optimize for low latency, but in very high traffic conditions
-# or when the master and replicas are many hops away, turning this to "yes" may
-# be a good idea.
-repl-disable-tcp-nodelay no
-
-# Set the replication backlog size. The backlog is a buffer that accumulates
-# replica data when replicas are disconnected for some time, so that when a
-# replica wants to reconnect again, often a full resync is not needed, but a
-# partial resync is enough, just passing the portion of data the replica
-# missed while disconnected.
-#
-# The bigger the replication backlog, the longer the replica can endure the
-# disconnect and later be able to perform a partial resynchronization.
-#
-# The backlog is only allocated if there is at least one replica connected.
-#
-# repl-backlog-size 1mb
-
-# After a master has no connected replicas for some time, the backlog will be
-# freed. The following option configures the amount of seconds that need to
-# elapse, starting from the time the last replica disconnected, for the backlog
-# buffer to be freed.
-#
-# Note that replicas never free the backlog for timeout, since they may be
-# promoted to masters later, and should be able to correctly "partially
-# resynchronize" with other replicas: hence they should always accumulate backlog.
-#
-# A value of 0 means to never release the backlog.
-#
-# repl-backlog-ttl 3600
-
-# The replica priority is an integer number published by Redis in the INFO
-# output. It is used by Redis Sentinel in order to select a replica to promote
-# into a master if the master is no longer working correctly.
-#
-# A replica with a low priority number is considered better for promotion, so
-# for instance if there are three replicas with priority 10, 100, 25 Sentinel
-# will pick the one with priority 10, that is the lowest.
-#
-# However a special priority of 0 marks the replica as not able to perform the
-# role of master, so a replica with priority of 0 will never be selected by
-# Redis Sentinel for promotion.
-#
-# By default the priority is 100.
-replica-priority 100
-
-# It is possible for a master to stop accepting writes if there are less than
-# N replicas connected, having a lag less or equal than M seconds.
-#
-# The N replicas need to be in "online" state.
-#
-# The lag in seconds, that must be <= the specified value, is calculated from
-# the last ping received from the replica, that is usually sent every second.
-#
-# This option does not GUARANTEE that N replicas will accept the write, but
-# will limit the window of exposure for lost writes in case not enough replicas
-# are available, to the specified number of seconds.
-#
-# For example to require at least 3 replicas with a lag <= 10 seconds use:
-#
-# min-replicas-to-write 3
-# min-replicas-max-lag 10
-#
-# Setting one or the other to 0 disables the feature.
-#
-# By default min-replicas-to-write is set to 0 (feature disabled) and
-# min-replicas-max-lag is set to 10.
-
-# A Redis master is able to list the address and port of the attached
-# replicas in different ways. For example the "INFO replication" section
-# offers this information, which is used, among other tools, by
-# Redis Sentinel in order to discover replica instances.
-# Another place where this info is available is in the output of the
-# "ROLE" command of a master.
-#
-# The listed IP address and port normally reported by a replica is
-# obtained in the following way:
-#
-#   IP: The address is auto detected by checking the peer address
-#   of the socket used by the replica to connect with the master.
-#
-#   Port: The port is communicated by the replica during the replication
-#   handshake, and is normally the port that the replica is using to
-#   listen for connections.
-#
-# However when port forwarding or Network Address Translation (NAT) is
-# used, the replica may actually be reachable via different IP and port
-# pairs. The following two options can be used by a replica in order to
-# report to its master a specific set of IP and port, so that both INFO
-# and ROLE will report those values.
-#
-# There is no need to use both the options if you need to override just
-# the port or the IP address.
-#
-# replica-announce-ip 5.5.5.5
-# replica-announce-port 1234
-
-############################### KEYS TRACKING #################################
-
-# Redis implements server assisted support for client side caching of values.
-# This is implemented using an invalidation table that remembers, using
-# 16 millions of slots, what clients may have certain subsets of keys. In turn
-# this is used in order to send invalidation messages to clients. Please
-# check this page to understand more about the feature:
-#
-#   https://redis.io/topics/client-side-caching
-#
-# When tracking is enabled for a client, all the read only queries are assumed
-# to be cached: this will force Redis to store information in the invalidation
-# table. When keys are modified, such information is flushed away, and
-# invalidation messages are sent to the clients. However if the workload is
-# heavily dominated by reads, Redis could use more and more memory in order
-# to track the keys fetched by many clients.
-#
-# For this reason it is possible to configure a maximum fill value for the
-# invalidation table. By default it is set to 1M of keys, and once this limit
-# is reached, Redis will start to evict keys in the invalidation table
-# even if they were not modified, just to reclaim memory: this will in turn
-# force the clients to invalidate the cached values. Basically the table
-# maximum size is a trade off between the memory you want to spend server
-# side to track information about who cached what, and the ability of clients
-# to retain cached objects in memory.
-#
-# If you set the value to 0, it means there are no limits, and Redis will
-# retain as many keys as needed in the invalidation table.
-# In the "stats" INFO section, you can find information about the number of
-# keys in the invalidation table at every given moment.
-#
-# Note: when key tracking is used in broadcasting mode, no memory is used
-# in the server side so this setting is useless.
-#
-# tracking-table-max-keys 1000000
-
-################################## SECURITY ###################################
-
-# Warning: since Redis is pretty fast, an outside user can try up to
-# 1 million passwords per second against a modern box. This means that you
-# should use very strong passwords, otherwise they will be very easy to break.
-# Note that because the password is really a shared secret between the client
-# and the server, and should not be memorized by any human, the password
-# can be easily a long string from /dev/urandom or whatever, so by using a
-# long and unguessable password no brute force attack will be possible.
-
-# Redis ACL users are defined in the following format:
-#
-#   user <username> ... acl rules ...
-#
-# For example:
-#
-#   user worker +@list +@connection ~jobs:* on >ffa9203c493aa99
-#
-# The special username "default" is used for new connections. If this user
-# has the "nopass" rule, then new connections will be immediately authenticated
-# as the "default" user without the need of any password provided via the
-# AUTH command. Otherwise if the "default" user is not flagged with "nopass"
-# the connections will start in not authenticated state, and will require
-# AUTH (or the HELLO command AUTH option) in order to be authenticated and
-# start to work.
-#
-# The ACL rules that describe what a user can do are the following:
-#
-#  on           Enable the user: it is possible to authenticate as this user.
-#  off          Disable the user: it's no longer possible to authenticate
-#               with this user, however the already authenticated connections
-#               will still work.
-#  +<command>   Allow the execution of that command
-#  -<command>   Disallow the execution of that command
-#  +@<category> Allow the execution of all the commands in such category
-#               with valid categories are like @admin, @set, @sortedset, ...
-#               and so forth, see the full list in the server.c file where
-#               the Redis command table is described and defined.
-#               The special category @all means all the commands, but currently
-#               present in the server, and that will be loaded in the future
-#               via modules.
-#  +<command>|subcommand    Allow a specific subcommand of an otherwise
-#                           disabled command. Note that this form is not
-#                           allowed as negative like -DEBUG|SEGFAULT, but
-#                           only additive starting with "+".
-#  allcommands  Alias for +@all. Note that it implies the ability to execute
-#               all the future commands loaded via the modules system.
-#  nocommands   Alias for -@all.
-#  ~<pattern>   Add a pattern of keys that can be mentioned as part of
-#               commands. For instance ~* allows all the keys. The pattern
-#               is a glob-style pattern like the one of KEYS.
-#               It is possible to specify multiple patterns.
-#  allkeys      Alias for ~*
-#  resetkeys    Flush the list of allowed keys patterns.
-#  ><password>  Add this password to the list of valid password for the user.
-#               For example >mypass will add "mypass" to the list.
-#               This directive clears the "nopass" flag (see later).
-#  <<password>  Remove this password from the list of valid passwords.
-#  nopass       All the set passwords of the user are removed, and the user
-#               is flagged as requiring no password: it means that every
-#               password will work against this user. If this directive is
-#               used for the default user, every new connection will be
-#               immediately authenticated with the default user without
-#               any explicit AUTH command required. Note that the "resetpass"
-#               directive will clear this condition.
-#  resetpass    Flush the list of allowed passwords. Moreover removes the
-#               "nopass" status. After "resetpass" the user has no associated
-#               passwords and there is no way to authenticate without adding
-#               some password (or setting it as "nopass" later).
-#  reset        Performs the following actions: resetpass, resetkeys, off,
-#               -@all. The user returns to the same state it has immediately
-#               after its creation.
-#
-# ACL rules can be specified in any order: for instance you can start with
-# passwords, then flags, or key patterns. However note that the additive
-# and subtractive rules will CHANGE MEANING depending on the ordering.
-# For instance see the following example:
-#
-#   user alice on +@all -DEBUG ~* >somepassword
-#
-# This will allow "alice" to use all the commands with the exception of the
-# DEBUG command, since +@all added all the commands to the set of the commands
-# alice can use, and later DEBUG was removed. However if we invert the order
-# of two ACL rules the result will be different:
-#
-#   user alice on -DEBUG +@all ~* >somepassword
-#
-# Now DEBUG was removed when alice had yet no commands in the set of allowed
-# commands, later all the commands are added, so the user will be able to
-# execute everything.
-#
-# Basically ACL rules are processed left-to-right.
-#
-# For more information about ACL configuration please refer to
-# the Redis web site at https://redis.io/topics/acl
-
-# ACL LOG
-#
-# The ACL Log tracks failed commands and authentication events associated
-# with ACLs. The ACL Log is useful to troubleshoot failed commands blocked
-# by ACLs. The ACL Log is stored in memory. You can reclaim memory with
-# ACL LOG RESET. Define the maximum entry length of the ACL Log below.
-acllog-max-len 128
-
-# Using an external ACL file
-#
-# Instead of configuring users here in this file, it is possible to use
-# a stand-alone file just listing users. The two methods cannot be mixed:
-# if you configure users here and at the same time you activate the external
-# ACL file, the server will refuse to start.
-#
-# The format of the external ACL user file is exactly the same as the
-# format that is used inside redis.conf to describe users.
-#
-# aclfile /etc/redis/users.acl
-
-# IMPORTANT NOTE: starting with Redis 6 "requirepass" is just a compatibility
-# layer on top of the new ACL system. The option effect will be just setting
-# the password for the default user. Clients will still authenticate using
-# AUTH <password> as usually, or more explicitly with AUTH default <password>
-# if they follow the new protocol: both will work.
-#
-# requirepass foobared
-
-# Command renaming (DEPRECATED).
-#
-# ------------------------------------------------------------------------
-# WARNING: avoid using this option if possible. Instead use ACLs to remove
-# commands from the default user, and put them only in some admin user you
-# create for administrative purposes.
-# ------------------------------------------------------------------------
-#
-# It is possible to change the name of dangerous commands in a shared
-# environment. For instance the CONFIG command may be renamed into something
-# hard to guess so that it will still be available for internal-use tools
-# but not available for general clients.
-#
-# Example:
-#
-# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
-#
-# It is also possible to completely kill a command by renaming it into
-# an empty string:
-#
-# rename-command CONFIG ""
-#
-# Please note that changing the name of commands that are logged into the
-# AOF file or transmitted to replicas may cause problems.
-
-################################### CLIENTS ####################################
-
-# Set the max number of connected clients at the same time. By default
-# this limit is set to 10000 clients, however if the Redis server is not
-# able to configure the process file limit to allow for the specified limit
-# the max number of allowed clients is set to the current file limit
-# minus 32 (as Redis reserves a few file descriptors for internal uses).
-#
-# Once the limit is reached Redis will close all the new connections sending
-# an error 'max number of clients reached'.
-#
-# IMPORTANT: When Redis Cluster is used, the max number of connections is also
-# shared with the cluster bus: every node in the cluster will use two
-# connections, one incoming and another outgoing. It is important to size the
-# limit accordingly in case of very large clusters.
-#
-# maxclients 10000
-
-############################## MEMORY MANAGEMENT ################################
-
-# Set a memory usage limit to the specified amount of bytes.
-# When the memory limit is reached Redis will try to remove keys
-# according to the eviction policy selected (see maxmemory-policy).
-#
-# If Redis can't remove keys according to the policy, or if the policy is
-# set to 'noeviction', Redis will start to reply with errors to commands
-# that would use more memory, like SET, LPUSH, and so on, and will continue
-# to reply to read-only commands like GET.
-#
-# This option is usually useful when using Redis as an LRU or LFU cache, or to
-# set a hard memory limit for an instance (using the 'noeviction' policy).
-#
-# WARNING: If you have replicas attached to an instance with maxmemory on,
-# the size of the output buffers needed to feed the replicas are subtracted
-# from the used memory count, so that network problems / resyncs will
-# not trigger a loop where keys are evicted, and in turn the output
-# buffer of replicas is full with DELs of keys evicted triggering the deletion
-# of more keys, and so forth until the database is completely emptied.
-#
-# In short... if you have replicas attached it is suggested that you set a lower
-# limit for maxmemory so that there is some free RAM on the system for replica
-# output buffers (but this is not needed if the policy is 'noeviction').
-#
-# maxmemory <bytes>
-
-# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
-# is reached. You can select one from the following behaviors:
-#
-# volatile-lru -> Evict using approximated LRU, only keys with an expire set.
-# allkeys-lru -> Evict any key using approximated LRU.
-# volatile-lfu -> Evict using approximated LFU, only keys with an expire set.
-# allkeys-lfu -> Evict any key using approximated LFU.
-# volatile-random -> Remove a random key having an expire set.
-# allkeys-random -> Remove a random key, any key.
-# volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
-# noeviction -> Don't evict anything, just return an error on write operations.
-#
-# LRU means Least Recently Used
-# LFU means Least Frequently Used
-#
-# Both LRU, LFU and volatile-ttl are implemented using approximated
-# randomized algorithms.
-#
-# Note: with any of the above policies, Redis will return an error on write
-#       operations, when there are no suitable keys for eviction.
-#
-#       At the date of writing these commands are: set setnx setex append
-#       incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
-#       sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
-#       zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
-#       getset mset msetnx exec sort
-#
-# The default is:
-#
-# maxmemory-policy noeviction
-
-# LRU, LFU and minimal TTL algorithms are not precise algorithms but approximated
-# algorithms (in order to save memory), so you can tune it for speed or
-# accuracy. By default Redis will check five keys and pick the one that was
-# used least recently, you can change the sample size using the following
-# configuration directive.
-#
-# The default of 5 produces good enough results. 10 Approximates very closely
-# true LRU but costs more CPU. 3 is faster but not very accurate.
-#
-# maxmemory-samples 5
-
-# Starting from Redis 5, by default a replica will ignore its maxmemory setting
-# (unless it is promoted to master after a failover or manually). It means
-# that the eviction of keys will be just handled by the master, sending the
-# DEL commands to the replica as keys evict in the master side.
-#
-# This behavior ensures that masters and replicas stay consistent, and is usually
-# what you want, however if your replica is writable, or you want the replica
-# to have a different memory setting, and you are sure all the writes performed
-# to the replica are idempotent, then you may change this default (but be sure
-# to understand what you are doing).
-#
-# Note that since the replica by default does not evict, it may end using more
-# memory than the one set via maxmemory (there are certain buffers that may
-# be larger on the replica, or data structures may sometimes take more memory
-# and so forth). So make sure you monitor your replicas and make sure they
-# have enough memory to never hit a real out-of-memory condition before the
-# master hits the configured maxmemory setting.
-#
-# replica-ignore-maxmemory yes
-
-# Redis reclaims expired keys in two ways: upon access when those keys are
-# found to be expired, and also in background, in what is called the
-# "active expire key". The key space is slowly and interactively scanned
-# looking for expired keys to reclaim, so that it is possible to free memory
-# of keys that are expired and will never be accessed again in a short time.
-#
-# The default effort of the expire cycle will try to avoid having more than
-# ten percent of expired keys still in memory, and will try to avoid consuming
-# more than 25% of total memory and to add latency to the system. However
-# it is possible to increase the expire "effort" that is normally set to
-# "1", to a greater value, up to the value "10". At its maximum value the
-# system will use more CPU, longer cycles (and technically may introduce
-# more latency), and will tolerate less already expired keys still present
-# in the system. It's a tradeoff between memory, CPU and latency.
-#
-# active-expire-effort 1
-
-############################# LAZY FREEING ####################################
-
-# Redis has two primitives to delete keys. One is called DEL and is a blocking
-# deletion of the object. It means that the server stops processing new commands
-# in order to reclaim all the memory associated with an object in a synchronous
-# way. If the key deleted is associated with a small object, the time needed
-# in order to execute the DEL command is very small and comparable to most other
-# O(1) or O(log_N) commands in Redis. However if the key is associated with an
-# aggregated value containing millions of elements, the server can block for
-# a long time (even seconds) in order to complete the operation.
-#
-# For the above reasons Redis also offers non blocking deletion primitives
-# such as UNLINK (non blocking DEL) and the ASYNC option of FLUSHALL and
-# FLUSHDB commands, in order to reclaim memory in background. Those commands
-# are executed in constant time. Another thread will incrementally free the
-# object in the background as fast as possible.
-#
-# DEL, UNLINK and ASYNC option of FLUSHALL and FLUSHDB are user-controlled.
-# It's up to the design of the application to understand when it is a good
-# idea to use one or the other. However the Redis server sometimes has to
-# delete keys or flush the whole database as a side effect of other operations.
-# Specifically Redis deletes objects independently of a user call in the
-# following scenarios:
-#
-# 1) On eviction, because of the maxmemory and maxmemory policy configurations,
-#    in order to make room for new data, without going over the specified
-#    memory limit.
-# 2) Because of expire: when a key with an associated time to live (see the
-#    EXPIRE command) must be deleted from memory.
-# 3) Because of a side effect of a command that stores data on a key that may
-#    already exist. For example the RENAME command may delete the old key
-#    content when it is replaced with another one. Similarly SUNIONSTORE
-#    or SORT with STORE option may delete existing keys. The SET command
-#    itself removes any old content of the specified key in order to replace
-#    it with the specified string.
-# 4) During replication, when a replica performs a full resynchronization with
-#    its master, the content of the whole database is removed in order to
-#    load the RDB file just transferred.
-#
-# In all the above cases the default is to delete objects in a blocking way,
-# like if DEL was called. However you can configure each case specifically
-# in order to instead release memory in a non-blocking way like if UNLINK
-# was called, using the following configuration directives.
-
-lazyfree-lazy-eviction no
-lazyfree-lazy-expire no
-lazyfree-lazy-server-del no
-replica-lazy-flush no
-
-# It is also possible, for the case when to replace the user code DEL calls
-# with UNLINK calls is not easy, to modify the default behavior of the DEL
-# command to act exactly like UNLINK, using the following configuration
-# directive:
-
-lazyfree-lazy-user-del no
-
-################################ THREADED I/O #################################
-
-# Redis is mostly single threaded, however there are certain threaded
-# operations such as UNLINK, slow I/O accesses and other things that are
-# performed on side threads.
-#
-# Now it is also possible to handle Redis clients socket reads and writes
-# in different I/O threads. Since especially writing is so slow, normally
-# Redis users use pipelining in order to speed up the Redis performances per
-# core, and spawn multiple instances in order to scale more. Using I/O
-# threads it is possible to easily speedup two times Redis without resorting
-# to pipelining nor sharding of the instance.
-#
-# By default threading is disabled, we suggest enabling it only in machines
-# that have at least 4 or more cores, leaving at least one spare core.
-# Using more than 8 threads is unlikely to help much. We also recommend using
-# threaded I/O only if you actually have performance problems, with Redis
-# instances being able to use a quite big percentage of CPU time, otherwise
-# there is no point in using this feature.
-#
-# So for instance if you have a four cores boxes, try to use 2 or 3 I/O
-# threads, if you have a 8 cores, try to use 6 threads. In order to
-# enable I/O threads use the following configuration directive:
-#
-# io-threads 4
-#
-# Setting io-threads to 1 will just use the main thread as usual.
-# When I/O threads are enabled, we only use threads for writes, that is
-# to thread the write(2) syscall and transfer the client buffers to the
-# socket. However it is also possible to enable threading of reads and
-# protocol parsing using the following configuration directive, by setting
-# it to yes:
-#
-# io-threads-do-reads no
-#
-# Usually threading reads doesn't help much.
-#
-# NOTE 1: This configuration directive cannot be changed at runtime via
-# CONFIG SET. Aso this feature currently does not work when SSL is
-# enabled.
-#
-# NOTE 2: If you want to test the Redis speedup using redis-benchmark, make
-# sure you also run the benchmark itself in threaded mode, using the
-# --threads option to match the number of Redis threads, otherwise you'll not
-# be able to notice the improvements.
-
-############################ KERNEL OOM CONTROL ##############################
-
-# On Linux, it is possible to hint the kernel OOM killer on what processes
-# should be killed first when out of memory.
-#
-# Enabling this feature makes Redis actively control the oom_score_adj value
-# for all its processes, depending on their role. The default scores will
-# attempt to have background child processes killed before all others, and
-# replicas killed before masters.
-#
-# Redis supports three options:
-#
-# no:       Don't make changes to oom-score-adj (default).
-# yes:      Alias to "relative" see below.
-# absolute: Values in oom-score-adj-values are written as is to the kernel.
-# relative: Values are used relative to the initial value of oom_score_adj when
-#           the server starts and are then clamped to a range of -1000 to 1000.
-#           Because typically the initial value is 0, they will often match the
-#           absolute values.
-oom-score-adj no
-
-# When oom-score-adj is used, this directive controls the specific values used
-# for master, replica and background child processes. Values range -2000 to
-# 2000 (higher means more likely to be killed).
-#
-# Unprivileged processes (not root, and without CAP_SYS_RESOURCE capabilities)
-# can freely increase their value, but not decrease it below its initial
-# settings. This means that setting oom-score-adj to "relative" and setting the
-# oom-score-adj-values to positive values will always succeed.
-oom-score-adj-values 0 200 800
-
-############################## APPEND ONLY MODE ###############################
-
-# By default Redis asynchronously dumps the dataset on disk. This mode is
-# good enough in many applications, but an issue with the Redis process or
-# a power outage may result into a few minutes of writes lost (depending on
-# the configured save points).
-#
-# The Append Only File is an alternative persistence mode that provides
-# much better durability. For instance using the default data fsync policy
-# (see later in the config file) Redis can lose just one second of writes in a
-# dramatic event like a server power outage, or a single write if something
-# wrong with the Redis process itself happens, but the operating system is
-# still running correctly.
-#
-# AOF and RDB persistence can be enabled at the same time without problems.
-# If the AOF is enabled on startup Redis will load the AOF, that is the file
-# with the better durability guarantees.
-#
-# Please check http://redis.io/topics/persistence for more information.
-
-appendonly no
-
-# The name of the append only file (default: "appendonly.aof")
-
-appendfilename "appendonly.aof"
-
-# The fsync() call tells the Operating System to actually write data on disk
-# instead of waiting for more data in the output buffer. Some OS will really flush
-# data on disk, some other OS will just try to do it ASAP.
-#
-# Redis supports three different modes:
-#
-# no: don't fsync, just let the OS flush the data when it wants. Faster.
-# always: fsync after every write to the append only log. Slow, Safest.
-# everysec: fsync only one time every second. Compromise.
-#
-# The default is "everysec", as that's usually the right compromise between
-# speed and data safety. It's up to you to understand if you can relax this to
-# "no" that will let the operating system flush the output buffer when
-# it wants, for better performances (but if you can live with the idea of
-# some data loss consider the default persistence mode that's snapshotting),
-# or on the contrary, use "always" that's very slow but a bit safer than
-# everysec.
-#
-# More details please check the following article:
-# http://antirez.com/post/redis-persistence-demystified.html
-#
-# If unsure, use "everysec".
-
-# appendfsync always
-appendfsync everysec
-# appendfsync no
-
-# When the AOF fsync policy is set to always or everysec, and a background
-# saving process (a background save or AOF log background rewriting) is
-# performing a lot of I/O against the disk, in some Linux configurations
-# Redis may block too long on the fsync() call. Note that there is no fix for
-# this currently, as even performing fsync in a different thread will block
-# our synchronous write(2) call.
-#
-# In order to mitigate this problem it's possible to use the following option
-# that will prevent fsync() from being called in the main process while a
-# BGSAVE or BGREWRITEAOF is in progress.
-#
-# This means that while another child is saving, the durability of Redis is
-# the same as "appendfsync none". In practical terms, this means that it is
-# possible to lose up to 30 seconds of log in the worst scenario (with the
-# default Linux settings).
-#
-# If you have latency problems turn this to "yes". Otherwise leave it as
-# "no" that is the safest pick from the point of view of durability.
-
-no-appendfsync-on-rewrite no
-
-# Automatic rewrite of the append only file.
-# Redis is able to automatically rewrite the log file implicitly calling
-# BGREWRITEAOF when the AOF log size grows by the specified percentage.
-#
-# This is how it works: Redis remembers the size of the AOF file after the
-# latest rewrite (if no rewrite has happened since the restart, the size of
-# the AOF at startup is used).
-#
-# This base size is compared to the current size. If the current size is
-# bigger than the specified percentage, the rewrite is triggered. Also
-# you need to specify a minimal size for the AOF file to be rewritten, this
-# is useful to avoid rewriting the AOF file even if the percentage increase
-# is reached but it is still pretty small.
-#
-# Specify a percentage of zero in order to disable the automatic AOF
-# rewrite feature.
-
-auto-aof-rewrite-percentage 100
-auto-aof-rewrite-min-size 64mb
-
-# An AOF file may be found to be truncated at the end during the Redis
-# startup process, when the AOF data gets loaded back into memory.
-# This may happen when the system where Redis is running
-# crashes, especially when an ext4 filesystem is mounted without the
-# data=ordered option (however this can't happen when Redis itself
-# crashes or aborts but the operating system still works correctly).
-#
-# Redis can either exit with an error when this happens, or load as much
-# data as possible (the default now) and start if the AOF file is found
-# to be truncated at the end. The following option controls this behavior.
-#
-# If aof-load-truncated is set to yes, a truncated AOF file is loaded and
-# the Redis server starts emitting a log to inform the user of the event.
-# Otherwise if the option is set to no, the server aborts with an error
-# and refuses to start. When the option is set to no, the user requires
-# to fix the AOF file using the "redis-check-aof" utility before to restart
-# the server.
-#
-# Note that if the AOF file will be found to be corrupted in the middle
-# the server will still exit with an error. This option only applies when
-# Redis will try to read more data from the AOF file but not enough bytes
-# will be found.
-aof-load-truncated yes
-
-# When rewriting the AOF file, Redis is able to use an RDB preamble in the
-# AOF file for faster rewrites and recoveries. When this option is turned
-# on the rewritten AOF file is composed of two different stanzas:
-#
-#   [RDB file][AOF tail]
-#
-# When loading, Redis recognizes that the AOF file starts with the "REDIS"
-# string and loads the prefixed RDB file, then continues loading the AOF
-# tail.
-aof-use-rdb-preamble yes
-
-################################ LUA SCRIPTING  ###############################
-
-# Max execution time of a Lua script in milliseconds.
-#
-# If the maximum execution time is reached Redis will log that a script is
-# still in execution after the maximum allowed time and will start to
-# reply to queries with an error.
-#
-# When a long running script exceeds the maximum execution time only the
-# SCRIPT KILL and SHUTDOWN NOSAVE commands are available. The first can be
-# used to stop a script that did not yet call any write commands. The second
-# is the only way to shut down the server in the case a write command was
-# already issued by the script but the user doesn't want to wait for the natural
-# termination of the script.
-#
-# Set it to 0 or a negative value for unlimited execution without warnings.
-lua-time-limit 5000
-
-################################ REDIS CLUSTER  ###############################
-
-# Normal Redis instances can't be part of a Redis Cluster; only nodes that are
-# started as cluster nodes can. In order to start a Redis instance as a
-# cluster node enable the cluster support uncommenting the following:
-#
-# cluster-enabled yes
-
-# Every cluster node has a cluster configuration file. This file is not
-# intended to be edited by hand. It is created and updated by Redis nodes.
-# Every Redis Cluster node requires a different cluster configuration file.
-# Make sure that instances running in the same system do not have
-# overlapping cluster configuration file names.
-#
-# cluster-config-file nodes-6379.conf
-
-# Cluster node timeout is the amount of milliseconds a node must be unreachable
-# for it to be considered in failure state.
-# Most other internal time limits are a multiple of the node timeout.
-#
-# cluster-node-timeout 15000
-
-# A replica of a failing master will avoid to start a failover if its data
-# looks too old.
-#
-# There is no simple way for a replica to actually have an exact measure of
-# its "data age", so the following two checks are performed:
-#
-# 1) If there are multiple replicas able to failover, they exchange messages
-#    in order to try to give an advantage to the replica with the best
-#    replication offset (more data from the master processed).
-#    Replicas will try to get their rank by offset, and apply to the start
-#    of the failover a delay proportional to their rank.
-#
-# 2) Every single replica computes the time of the last interaction with
-#    its master. This can be the last ping or command received (if the master
-#    is still in the "connected" state), or the time that elapsed since the
-#    disconnection with the master (if the replication link is currently down).
-#    If the last interaction is too old, the replica will not try to failover
-#    at all.
-#
-# The point "2" can be tuned by user. Specifically a replica will not perform
-# the failover if, since the last interaction with the master, the time
-# elapsed is greater than:
-#
-#   (node-timeout * cluster-replica-validity-factor) + repl-ping-replica-period
-#
-# So for example if node-timeout is 30 seconds, and the cluster-replica-validity-factor
-# is 10, and assuming a default repl-ping-replica-period of 10 seconds, the
-# replica will not try to failover if it was not able to talk with the master
-# for longer than 310 seconds.
-#
-# A large cluster-replica-validity-factor may allow replicas with too old data to failover
-# a master, while a too small value may prevent the cluster from being able to
-# elect a replica at all.
-#
-# For maximum availability, it is possible to set the cluster-replica-validity-factor
-# to a value of 0, which means, that replicas will always try to failover the
-# master regardless of the last time they interacted with the master.
-# (However they'll always try to apply a delay proportional to their
-# offset rank).
-#
-# Zero is the only value able to guarantee that when all the partitions heal
-# the cluster will always be able to continue.
-#
-# cluster-replica-validity-factor 10
-
-# Cluster replicas are able to migrate to orphaned masters, that are masters
-# that are left without working replicas. This improves the cluster ability
-# to resist to failures as otherwise an orphaned master can't be failed over
-# in case of failure if it has no working replicas.
-#
-# Replicas migrate to orphaned masters only if there are still at least a
-# given number of other working replicas for their old master. This number
-# is the "migration barrier". A migration barrier of 1 means that a replica
-# will migrate only if there is at least 1 other working replica for its master
-# and so forth. It usually reflects the number of replicas you want for every
-# master in your cluster.
-#
-# Default is 1 (replicas migrate only if their masters remain with at least
-# one replica). To disable migration just set it to a very large value.
-# A value of 0 can be set but is useful only for debugging and dangerous
-# in production.
-#
-# cluster-migration-barrier 1
-
-# By default Redis Cluster nodes stop accepting queries if they detect there
-# is at least a hash slot uncovered (no available node is serving it).
-# This way if the cluster is partially down (for example a range of hash slots
-# are no longer covered) all the cluster becomes, eventually, unavailable.
-# It automatically returns available as soon as all the slots are covered again.
-#
-# However sometimes you want the subset of the cluster which is working,
-# to continue to accept queries for the part of the key space that is still
-# covered. In order to do so, just set the cluster-require-full-coverage
-# option to no.
-#
-# cluster-require-full-coverage yes
-
-# This option, when set to yes, prevents replicas from trying to failover its
-# master during master failures. However the master can still perform a
-# manual failover, if forced to do so.
-#
-# This is useful in different scenarios, especially in the case of multiple
-# data center operations, where we want one side to never be promoted if not
-# in the case of a total DC failure.
-#
-# cluster-replica-no-failover no
-
-# This option, when set to yes, allows nodes to serve read traffic while the
-# the cluster is in a down state, as long as it believes it owns the slots.
-#
-# This is useful for two cases.  The first case is for when an application
-# doesn't require consistency of data during node failures or network partitions.
-# One example of this is a cache, where as long as the node has the data it
-# should be able to serve it.
-#
-# The second use case is for configurations that don't meet the recommended
-# three shards but want to enable cluster mode and scale later. A
-# master outage in a 1 or 2 shard configuration causes a read/write outage to the
-# entire cluster without this option set, with it set there is only a write outage.
-# Without a quorum of masters, slot ownership will not change automatically.
-#
-# cluster-allow-reads-when-down no
-
-# In order to setup your cluster make sure to read the documentation
-# available at http://redis.io web site.
-
-########################## CLUSTER DOCKER/NAT support  ########################
-
-# In certain deployments, Redis Cluster nodes address discovery fails, because
-# addresses are NAT-ted or because ports are forwarded (the typical case is
-# Docker and other containers).
-#
-# In order to make Redis Cluster working in such environments, a static
-# configuration where each node knows its public address is needed. The
-# following two options are used for this scope, and are:
-#
-# * cluster-announce-ip
-# * cluster-announce-port
-# * cluster-announce-bus-port
-#
-# Each instructs the node about its address, client port, and cluster message
-# bus port. The information is then published in the header of the bus packets
-# so that other nodes will be able to correctly map the address of the node
-# publishing the information.
-#
-# If the above options are not used, the normal Redis Cluster auto-detection
-# will be used instead.
-#
-# Note that when remapped, the bus port may not be at the fixed offset of
-# clients port + 10000, so you can specify any port and bus-port depending
-# on how they get remapped. If the bus-port is not set, a fixed offset of
-# 10000 will be used as usual.
-#
-# Example:
-#
-# cluster-announce-ip 10.1.1.5
-# cluster-announce-port 6379
-# cluster-announce-bus-port 6380
-
-################################## SLOW LOG ###################################
-
-# The Redis Slow Log is a system to log queries that exceeded a specified
-# execution time. The execution time does not include the I/O operations
-# like talking with the client, sending the reply and so forth,
-# but just the time needed to actually execute the command (this is the only
-# stage of command execution where the thread is blocked and can not serve
-# other requests in the meantime).
-#
-# You can configure the slow log with two parameters: one tells Redis
-# what is the execution time, in microseconds, to exceed in order for the
-# command to get logged, and the other parameter is the length of the
-# slow log. When a new command is logged the oldest one is removed from the
-# queue of logged commands.
-
-# The following time is expressed in microseconds, so 1000000 is equivalent
-# to one second. Note that a negative number disables the slow log, while
-# a value of zero forces the logging of every command.
-slowlog-log-slower-than 10000
-
-# There is no limit to this length. Just be aware that it will consume memory.
-# You can reclaim memory used by the slow log with SLOWLOG RESET.
-slowlog-max-len 128
-
-################################ LATENCY MONITOR ##############################
-
-# The Redis latency monitoring subsystem samples different operations
-# at runtime in order to collect data related to possible sources of
-# latency of a Redis instance.
-#
-# Via the LATENCY command this information is available to the user that can
-# print graphs and obtain reports.
-#
-# The system only logs operations that were performed in a time equal or
-# greater than the amount of milliseconds specified via the
-# latency-monitor-threshold configuration directive. When its value is set
-# to zero, the latency monitor is turned off.
-#
-# By default latency monitoring is disabled since it is mostly not needed
-# if you don't have latency issues, and collecting data has a performance
-# impact, that while very small, can be measured under big load. Latency
-# monitoring can easily be enabled at runtime using the command
-# "CONFIG SET latency-monitor-threshold <milliseconds>" if needed.
-latency-monitor-threshold 0
-
-############################# EVENT NOTIFICATION ##############################
-
-# Redis can notify Pub/Sub clients about events happening in the key space.
-# This feature is documented at http://redis.io/topics/notifications
-#
-# For instance if keyspace events notification is enabled, and a client
-# performs a DEL operation on key "foo" stored in the Database 0, two
-# messages will be published via Pub/Sub:
-#
-# PUBLISH __keyspace@0__:foo del
-# PUBLISH __keyevent@0__:del foo
-#
-# It is possible to select the events that Redis will notify among a set
-# of classes. Every class is identified by a single character:
-#
-#  K     Keyspace events, published with __keyspace@<db>__ prefix.
-#  E     Keyevent events, published with __keyevent@<db>__ prefix.
-#  g     Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ...
-#  $     String commands
-#  l     List commands
-#  s     Set commands
-#  h     Hash commands
-#  z     Sorted set commands
-#  x     Expired events (events generated every time a key expires)
-#  e     Evicted events (events generated when a key is evicted for maxmemory)
-#  t     Stream commands
-#  m     Key-miss events (Note: It is not included in the 'A' class)
-#  A     Alias for g$lshzxet, so that the "AKE" string means all the events
-#        (Except key-miss events which are excluded from 'A' due to their
-#         unique nature).
-#
-#  The "notify-keyspace-events" takes as argument a string that is composed
-#  of zero or multiple characters. The empty string means that notifications
-#  are disabled.
-#
-#  Example: to enable list and generic events, from the point of view of the
-#           event name, use:
-#
-#  notify-keyspace-events Elg
-#
-#  Example 2: to get the stream of the expired keys subscribing to channel
-#             name __keyevent@0__:expired use:
-#
-#  notify-keyspace-events Ex
-#
-#  By default all notifications are disabled because most users don't need
-#  this feature and the feature has some overhead. Note that if you don't
-#  specify at least one of K or E, no events will be delivered.
-notify-keyspace-events ""
-
-############################### GOPHER SERVER #################################
-
-# Redis contains an implementation of the Gopher protocol, as specified in
-# the RFC 1436 (https://www.ietf.org/rfc/rfc1436.txt).
-#
-# The Gopher protocol was very popular in the late '90s. It is an alternative
-# to the web, and the implementation both server and client side is so simple
-# that the Redis server has just 100 lines of code in order to implement this
-# support.
-#
-# What do you do with Gopher nowadays? Well Gopher never *really* died, and
-# lately there is a movement in order for the Gopher more hierarchical content
-# composed of just plain text documents to be resurrected. Some want a simpler
-# internet, others believe that the mainstream internet became too much
-# controlled, and it's cool to create an alternative space for people that
-# want a bit of fresh air.
-#
-# Anyway for the 10nth birthday of the Redis, we gave it the Gopher protocol
-# as a gift.
-#
-# --- HOW IT WORKS? ---
-#
-# The Redis Gopher support uses the inline protocol of Redis, and specifically
-# two kind of inline requests that were anyway illegal: an empty request
-# or any request that starts with "/" (there are no Redis commands starting
-# with such a slash). Normal RESP2/RESP3 requests are completely out of the
-# path of the Gopher protocol implementation and are served as usual as well.
-#
-# If you open a connection to Redis when Gopher is enabled and send it
-# a string like "/foo", if there is a key named "/foo" it is served via the
-# Gopher protocol.
-#
-# In order to create a real Gopher "hole" (the name of a Gopher site in Gopher
-# talking), you likely need a script like the following:
-#
-#   https://github.com/antirez/gopher2redis
-#
-# --- SECURITY WARNING ---
-#
-# If you plan to put Redis on the internet in a publicly accessible address
-# to server Gopher pages MAKE SURE TO SET A PASSWORD to the instance.
-# Once a password is set:
-#
-#   1. The Gopher server (when enabled, not by default) will still serve
-#      content via Gopher.
-#   2. However other commands cannot be called before the client will
-#      authenticate.
-#
-# So use the 'requirepass' option to protect your instance.
-#
-# Note that Gopher is not currently supported when 'io-threads-do-reads'
-# is enabled.
-#
-# To enable Gopher support, uncomment the following line and set the option
-# from no (the default) to yes.
-#
-# gopher-enabled no
-
-############################### ADVANCED CONFIG ###############################
-
-# Hashes are encoded using a memory efficient data structure when they have a
-# small number of entries, and the biggest entry does not exceed a given
-# threshold. These thresholds can be configured using the following directives.
-hash-max-ziplist-entries 512
-hash-max-ziplist-value 64
-
-# Lists are also encoded in a special way to save a lot of space.
-# The number of entries allowed per internal list node can be specified
-# as a fixed maximum size or a maximum number of elements.
-# For a fixed maximum size, use -5 through -1, meaning:
-# -5: max size: 64 Kb  <-- not recommended for normal workloads
-# -4: max size: 32 Kb  <-- not recommended
-# -3: max size: 16 Kb  <-- probably not recommended
-# -2: max size: 8 Kb   <-- good
-# -1: max size: 4 Kb   <-- good
-# Positive numbers mean store up to _exactly_ that number of elements
-# per list node.
-# The highest performing option is usually -2 (8 Kb size) or -1 (4 Kb size),
-# but if your use case is unique, adjust the settings as necessary.
-list-max-ziplist-size -2
-
-# Lists may also be compressed.
-# Compress depth is the number of quicklist ziplist nodes from *each* side of
-# the list to *exclude* from compression.  The head and tail of the list
-# are always uncompressed for fast push/pop operations.  Settings are:
-# 0: disable all list compression
-# 1: depth 1 means "don't start compressing until after 1 node into the list,
-#    going from either the head or tail"
-#    So: [head]->node->node->...->node->[tail]
-#    [head], [tail] will always be uncompressed; inner nodes will compress.
-# 2: [head]->[next]->node->node->...->node->[prev]->[tail]
-#    2 here means: don't compress head or head->next or tail->prev or tail,
-#    but compress all nodes between them.
-# 3: [head]->[next]->[next]->node->node->...->node->[prev]->[prev]->[tail]
-# etc.
-list-compress-depth 0
-
-# Sets have a special encoding in just one case: when a set is composed
-# of just strings that happen to be integers in radix 10 in the range
-# of 64 bit signed integers.
-# The following configuration setting sets the limit in the size of the
-# set in order to use this special memory saving encoding.
-set-max-intset-entries 512
-
-# Similarly to hashes and lists, sorted sets are also specially encoded in
-# order to save a lot of space. This encoding is only used when the length and
-# elements of a sorted set are below the following limits:
-zset-max-ziplist-entries 128
-zset-max-ziplist-value 64
-
-# HyperLogLog sparse representation bytes limit. The limit includes the
-# 16 bytes header. When an HyperLogLog using the sparse representation crosses
-# this limit, it is converted into the dense representation.
-#
-# A value greater than 16000 is totally useless, since at that point the
-# dense representation is more memory efficient.
-#
-# The suggested value is ~ 3000 in order to have the benefits of
-# the space efficient encoding without slowing down too much PFADD,
-# which is O(N) with the sparse encoding. The value can be raised to
-# ~ 10000 when CPU is not a concern, but space is, and the data set is
-# composed of many HyperLogLogs with cardinality in the 0 - 15000 range.
-hll-sparse-max-bytes 3000
-
-# Streams macro node max size / items. The stream data structure is a radix
-# tree of big nodes that encode multiple items inside. Using this configuration
-# it is possible to configure how big a single node can be in bytes, and the
-# maximum number of items it may contain before switching to a new node when
-# appending new stream entries. If any of the following settings are set to
-# zero, the limit is ignored, so for instance it is possible to set just a
-# max entires limit by setting max-bytes to 0 and max-entries to the desired
-# value.
-stream-node-max-bytes 4096
-stream-node-max-entries 100
-
-# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in
-# order to help rehashing the main Redis hash table (the one mapping top-level
-# keys to values). The hash table implementation Redis uses (see dict.c)
-# performs a lazy rehashing: the more operation you run into a hash table
-# that is rehashing, the more rehashing "steps" are performed, so if the
-# server is idle the rehashing is never complete and some more memory is used
-# by the hash table.
-#
-# The default is to use this millisecond 10 times every second in order to
-# actively rehash the main dictionaries, freeing memory when possible.
-#
-# If unsure:
-# use "activerehashing no" if you have hard latency requirements and it is
-# not a good thing in your environment that Redis can reply from time to time
-# to queries with 2 milliseconds delay.
-#
-# use "activerehashing yes" if you don't have such hard requirements but
-# want to free memory asap when possible.
-activerehashing yes
-
-# The client output buffer limits can be used to force disconnection of clients
-# that are not reading data from the server fast enough for some reason (a
-# common reason is that a Pub/Sub client can't consume messages as fast as the
-# publisher can produce them).
-#
-# The limit can be set differently for the three different classes of clients:
-#
-# normal -> normal clients including MONITOR clients
-# replica  -> replica clients
-# pubsub -> clients subscribed to at least one pubsub channel or pattern
-#
-# The syntax of every client-output-buffer-limit directive is the following:
-#
-# client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>
-#
-# A client is immediately disconnected once the hard limit is reached, or if
-# the soft limit is reached and remains reached for the specified number of
-# seconds (continuously).
-# So for instance if the hard limit is 32 megabytes and the soft limit is
-# 16 megabytes / 10 seconds, the client will get disconnected immediately
-# if the size of the output buffers reach 32 megabytes, but will also get
-# disconnected if the client reaches 16 megabytes and continuously overcomes
-# the limit for 10 seconds.
-#
-# By default normal clients are not limited because they don't receive data
-# without asking (in a push way), but just after a request, so only
-# asynchronous clients may create a scenario where data is requested faster
-# than it can read.
-#
-# Instead there is a default limit for pubsub and replica clients, since
-# subscribers and replicas receive data in a push fashion.
-#
-# Both the hard or the soft limit can be disabled by setting them to zero.
-client-output-buffer-limit normal 0 0 0
-client-output-buffer-limit replica 256mb 64mb 60
-client-output-buffer-limit pubsub 32mb 8mb 60
-
-# Client query buffers accumulate new commands. They are limited to a fixed
-# amount by default in order to avoid that a protocol desynchronization (for
-# instance due to a bug in the client) will lead to unbound memory usage in
-# the query buffer. However you can configure it here if you have very special
-# needs, such us huge multi/exec requests or alike.
-#
-# client-query-buffer-limit 1gb
-
-# In the Redis protocol, bulk requests, that are, elements representing single
-# strings, are normally limited to 512 mb. However you can change this limit
-# here, but must be 1mb or greater
-#
-# proto-max-bulk-len 512mb
-
-# Redis calls an internal function to perform many background tasks, like
-# closing connections of clients in timeout, purging expired keys that are
-# never requested, and so forth.
-#
-# Not all tasks are performed with the same frequency, but Redis checks for
-# tasks to perform according to the specified "hz" value.
-#
-# By default "hz" is set to 10. Raising the value will use more CPU when
-# Redis is idle, but at the same time will make Redis more responsive when
-# there are many keys expiring at the same time, and timeouts may be
-# handled with more precision.
-#
-# The range is between 1 and 500, however a value over 100 is usually not
-# a good idea. Most users should use the default of 10 and raise this up to
-# 100 only in environments where very low latency is required.
-hz 10
-
-# Normally it is useful to have an HZ value which is proportional to the
-# number of clients connected. This is useful in order, for instance, to
-# avoid too many clients are processed for each background task invocation
-# in order to avoid latency spikes.
-#
-# Since the default HZ value by default is conservatively set to 10, Redis
-# offers, and enables by default, the ability to use an adaptive HZ value
-# which will temporarily raise when there are many connected clients.
-#
-# When dynamic HZ is enabled, the actual configured HZ will be used
-# as a baseline, but multiples of the configured HZ value will be actually
-# used as needed once more clients are connected. In this way an idle
-# instance will use very little CPU time while a busy instance will be
-# more responsive.
-dynamic-hz yes
-
-# When a child rewrites the AOF file, if the following option is enabled
-# the file will be fsync-ed every 32 MB of data generated. This is useful
-# in order to commit the file to the disk more incrementally and avoid
-# big latency spikes.
-aof-rewrite-incremental-fsync yes
-
-# When redis saves RDB file, if the following option is enabled
-# the file will be fsync-ed every 32 MB of data generated. This is useful
-# in order to commit the file to the disk more incrementally and avoid
-# big latency spikes.
-rdb-save-incremental-fsync yes
-
-# Redis LFU eviction (see maxmemory setting) can be tuned. However it is a good
-# idea to start with the default settings and only change them after investigating
-# how to improve the performances and how the keys LFU change over time, which
-# is possible to inspect via the OBJECT FREQ command.
-#
-# There are two tunable parameters in the Redis LFU implementation: the
-# counter logarithm factor and the counter decay time. It is important to
-# understand what the two parameters mean before changing them.
-#
-# The LFU counter is just 8 bits per key, it's maximum value is 255, so Redis
-# uses a probabilistic increment with logarithmic behavior. Given the value
-# of the old counter, when a key is accessed, the counter is incremented in
-# this way:
-#
-# 1. A random number R between 0 and 1 is extracted.
-# 2. A probability P is calculated as 1/(old_value*lfu_log_factor+1).
-# 3. The counter is incremented only if R < P.
-#
-# The default lfu-log-factor is 10. This is a table of how the frequency
-# counter changes with a different number of accesses with different
-# logarithmic factors:
-#
-# +--------+------------+------------+------------+------------+------------+
-# | factor | 100 hits   | 1000 hits  | 100K hits  | 1M hits    | 10M hits   |
-# +--------+------------+------------+------------+------------+------------+
-# | 0      | 104        | 255        | 255        | 255        | 255        |
-# +--------+------------+------------+------------+------------+------------+
-# | 1      | 18         | 49         | 255        | 255        | 255        |
-# +--------+------------+------------+------------+------------+------------+
-# | 10     | 10         | 18         | 142        | 255        | 255        |
-# +--------+------------+------------+------------+------------+------------+
-# | 100    | 8          | 11         | 49         | 143        | 255        |
-# +--------+------------+------------+------------+------------+------------+
-#
-# NOTE: The above table was obtained by running the following commands:
-#
-#   redis-benchmark -n 1000000 incr foo
-#   redis-cli object freq foo
-#
-# NOTE 2: The counter initial value is 5 in order to give new objects a chance
-# to accumulate hits.
-#
-# The counter decay time is the time, in minutes, that must elapse in order
-# for the key counter to be divided by two (or decremented if it has a value
-# less <= 10).
-#
-# The default value for the lfu-decay-time is 1. A special value of 0 means to
-# decay the counter every time it happens to be scanned.
-#
-# lfu-log-factor 10
-# lfu-decay-time 1
-
-########################### ACTIVE DEFRAGMENTATION #######################
-#
-# What is active defragmentation?
-# -------------------------------
-#
-# Active (online) defragmentation allows a Redis server to compact the
-# spaces left between small allocations and deallocations of data in memory,
-# thus allowing to reclaim back memory.
-#
-# Fragmentation is a natural process that happens with every allocator (but
-# less so with Jemalloc, fortunately) and certain workloads. Normally a server
-# restart is needed in order to lower the fragmentation, or at least to flush
-# away all the data and create it again. However thanks to this feature
-# implemented by Oran Agra for Redis 4.0 this process can happen at runtime
-# in a "hot" way, while the server is running.
-#
-# Basically when the fragmentation is over a certain level (see the
-# configuration options below) Redis will start to create new copies of the
-# values in contiguous memory regions by exploiting certain specific Jemalloc
-# features (in order to understand if an allocation is causing fragmentation
-# and to allocate it in a better place), and at the same time, will release the
-# old copies of the data. This process, repeated incrementally for all the keys
-# will cause the fragmentation to drop back to normal values.
-#
-# Important things to understand:
-#
-# 1. This feature is disabled by default, and only works if you compiled Redis
-#    to use the copy of Jemalloc we ship with the source code of Redis.
-#    This is the default with Linux builds.
-#
-# 2. You never need to enable this feature if you don't have fragmentation
-#    issues.
-#
-# 3. Once you experience fragmentation, you can enable this feature when
-#    needed with the command "CONFIG SET activedefrag yes".
-#
-# The configuration parameters are able to fine tune the behavior of the
-# defragmentation process. If you are not sure about what they mean it is
-# a good idea to leave the defaults untouched.
-
-# Enabled active defragmentation
-# activedefrag no
-
-# Minimum amount of fragmentation waste to start active defrag
-# active-defrag-ignore-bytes 100mb
-
-# Minimum percentage of fragmentation to start active defrag
-# active-defrag-threshold-lower 10
-
-# Maximum percentage of fragmentation at which we use maximum effort
-# active-defrag-threshold-upper 100
-
-# Minimal effort for defrag in CPU percentage, to be used when the lower
-# threshold is reached
-# active-defrag-cycle-min 1
-
-# Maximal effort for defrag in CPU percentage, to be used when the upper
-# threshold is reached
-# active-defrag-cycle-max 25
-
-# Maximum number of set/hash/zset/list fields that will be processed from
-# the main dictionary scan
-# active-defrag-max-scan-fields 1000
-
-# Jemalloc background thread for purging will be enabled by default
-jemalloc-bg-thread yes
-
-# It is possible to pin different threads and processes of Redis to specific
-# CPUs in your system, in order to maximize the performances of the server.
-# This is useful both in order to pin different Redis threads in different
-# CPUs, but also in order to make sure that multiple Redis instances running
-# in the same host will be pinned to different CPUs.
-#
-# Normally you can do this using the "taskset" command, however it is also
-# possible to this via Redis configuration directly, both in Linux and FreeBSD.
-#
-# You can pin the server/IO threads, bio threads, aof rewrite child process, and
-# the bgsave child process. The syntax to specify the cpu list is the same as
-# the taskset command:
-#
-# Set redis server/io threads to cpu affinity 0,2,4,6:
-# server_cpulist 0-7:2
-#
-# Set bio threads to cpu affinity 1,3:
-# bio_cpulist 1,3
-#
-# Set aof rewrite child process to cpu affinity 8,9,10,11:
-# aof_rewrite_cpulist 8-11
-#
-# Set bgsave child process to cpu affinity 1,10,11
-# bgsave_cpulist 1,10-11
-
-# In some cases redis will emit warnings and even refuse to start if it detects
-# that the system is in bad state, it is possible to suppress these warnings
-# by setting the following config which takes a space delimited list of warnings
-# to suppress
-#
-# ignore-warnings ARM64-COW-BUG
+# Redis configuration file example.
+requirepass 123456
+#
+# Note that in order to read the configuration file, Redis must be
+# started with the file path as first argument:
+#
+# ./redis-server /path/to/redis.conf
+
+# Note on units: when memory size is needed, it is possible to specify
+# it in the usual form of 1k 5GB 4M and so forth:
+#
+# 1k => 1000 bytes
+# 1kb => 1024 bytes
+# 1m => 1000000 bytes
+# 1mb => 1024*1024 bytes
+# 1g => 1000000000 bytes
+# 1gb => 1024*1024*1024 bytes
+#
+# units are case insensitive so 1GB 1Gb 1gB are all the same.
+################################## INCLUDES ###################################
+
+# Include one or more other config files here.  This is useful if you
+# have a standard template that goes to all Redis servers but also need
+# to customize a few per-server settings.  Include files can include
+# other files, so use this wisely.
+#
+# Note that option "include" won't be rewritten by command "CONFIG REWRITE"
+# from admin or Redis Sentinel. Since Redis always uses the last processed
+# line as value of a configuration directive, you'd better put includes
+# at the beginning of this file to avoid overwriting config change at runtime.
+#
+# If instead you are interested in using includes to override configuration
+# options, it is better to use include as the last line.
+#
+# include /path/to/local.conf
+# include /path/to/other.conf
+
+################################## MODULES #####################################
+
+# Load modules at startup. If the server is not able to load modules
+# it will abort. It is possible to use multiple loadmodule directives.
+#
+# loadmodule /path/to/my_module.so
+# loadmodule /path/to/other_module.so
+
+################################## NETWORK #####################################
+
+# By default, if no "bind" configuration directive is specified, Redis listens
+# for connections from all available network interfaces on the host machine.
+# It is possible to listen to just one or multiple selected interfaces using
+# the "bind" configuration directive, followed by one or more IP addresses.
+#
+# Examples:
+#
+# bind 192.168.1.100 10.0.0.1
+# bind 127.0.0.1 ::1
+#
+# ~~~ WARNING ~~~ If the computer running Redis is directly exposed to the
+# internet, binding to all the interfaces is dangerous and will expose the
+# instance to everybody on the internet. So by default we uncomment the
+# following bind directive, that will force Redis to listen only on the
+# IPv4 loopback interface address (this means Redis will only be able to
+# accept client connections from the same host that it is running on).
+#
+# IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES
+# JUST COMMENT OUT THE FOLLOWING LINE.
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+bind 0.0.0.0
+
+# Protected mode is a layer of security protection, in order to avoid that
+# Redis instances left open on the internet are accessed and exploited.
+#
+# When protected mode is on and if:
+#
+# 1) The server is not binding explicitly to a set of addresses using the
+#    "bind" directive.
+# 2) No password is configured.
+#
+# The server only accepts connections from clients connecting from the
+# IPv4 and IPv6 loopback addresses 127.0.0.1 and ::1, and from Unix domain
+# sockets.
+#
+# By default protected mode is enabled. You should disable it only if
+# you are sure you want clients from other hosts to connect to Redis
+# even if no authentication is configured, nor a specific set of interfaces
+# are explicitly listed using the "bind" directive.
+protected-mode no
+
+# Accept connections on the specified port, default is 6379 (IANA #815344).
+# If port 0 is specified Redis will not listen on a TCP socket.
+port 6379
+
+# TCP listen() backlog.
+#
+# In high requests-per-second environments you need a high backlog in order
+# to avoid slow clients connection issues. Note that the Linux kernel
+# will silently truncate it to the value of /proc/sys/net/core/somaxconn so
+# make sure to raise both the value of somaxconn and tcp_max_syn_backlog
+# in order to get the desired effect.
+tcp-backlog 511
+
+# Unix socket.
+#
+# Specify the path for the Unix socket that will be used to listen for
+# incoming connections. There is no default, so Redis will not listen
+# on a unix socket when not specified.
+#
+unixsocket /run/redis/redis.sock
+unixsocketperm 770
+
+# Close the connection after a client is idle for N seconds (0 to disable)
+timeout 0
+
+# TCP keepalive.
+#
+# If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence
+# of communication. This is useful for two reasons:
+#
+# 1) Detect dead peers.
+# 2) Force network equipment in the middle to consider the connection to be
+#    alive.
+#
+# On Linux, the specified value (in seconds) is the period used to send ACKs.
+# Note that to close the connection the double of the time is needed.
+# On other kernels the period depends on the kernel configuration.
+#
+# A reasonable value for this option is 300 seconds, which is the new
+# Redis default starting with Redis 3.2.1.
+tcp-keepalive 300
+
+################################# TLS/SSL #####################################
+
+# By default, TLS/SSL is disabled. To enable it, the "tls-port" configuration
+# directive can be used to define TLS-listening ports. To enable TLS on the
+# default port, use:
+#
+# port 0
+# tls-port 6379
+
+# Configure a X.509 certificate and private key to use for authenticating the
+# server to connected clients, masters or cluster peers.  These files should be
+# PEM formatted.
+#
+# tls-cert-file redis.crt
+# tls-key-file redis.key
+
+# Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange:
+#
+# tls-dh-params-file redis.dh
+
+# Configure a CA certificate(s) bundle or directory to authenticate TLS/SSL
+# clients and peers.  Redis requires an explicit configuration of at least one
+# of these, and will not implicitly use the system wide configuration.
+#
+# tls-ca-cert-file ca.crt
+# tls-ca-cert-dir /etc/ssl/certs
+
+# By default, clients (including replica servers) on a TLS port are required
+# to authenticate using valid client side certificates.
+#
+# If "no" is specified, client certificates are not required and not accepted.
+# If "optional" is specified, client certificates are accepted and must be
+# valid if provided, but are not required.
+#
+# tls-auth-clients no
+# tls-auth-clients optional
+
+# By default, a Redis replica does not attempt to establish a TLS connection
+# with its master.
+#
+# Use the following directive to enable TLS on replication links.
+#
+# tls-replication yes
+
+# By default, the Redis Cluster bus uses a plain TCP connection. To enable
+# TLS for the bus protocol, use the following directive:
+#
+# tls-cluster yes
+
+# Explicitly specify TLS versions to support. Allowed values are case insensitive
+# and include "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" (OpenSSL >= 1.1.1) or
+# any combination. To enable only TLSv1.2 and TLSv1.3, use:
+#
+# tls-protocols "TLSv1.2 TLSv1.3"
+
+# Configure allowed ciphers.  See the ciphers(1ssl) manpage for more information
+# about the syntax of this string.
+#
+# Note: this configuration applies only to <= TLSv1.2.
+#
+# tls-ciphers DEFAULT:!MEDIUM
+
+# Configure allowed TLSv1.3 ciphersuites.  See the ciphers(1ssl) manpage for more
+# information about the syntax of this string, and specifically for TLSv1.3
+# ciphersuites.
+#
+# tls-ciphersuites TLS_CHACHA20_POLY1305_SHA256
+
+# When choosing a cipher, use the server's preference instead of the client
+# preference. By default, the server follows the client's preference.
+#
+# tls-prefer-server-ciphers yes
+
+# By default, TLS session caching is enabled to allow faster and less expensive
+# reconnections by clients that support it. Use the following directive to disable
+# caching.
+#
+# tls-session-caching no
+
+# Change the default number of TLS sessions cached. A zero value sets the cache
+# to unlimited size. The default size is 20480.
+#
+# tls-session-cache-size 5000
+
+# Change the default timeout of cached TLS sessions. The default timeout is 300
+# seconds.
+#
+# tls-session-cache-timeout 60
+
+################################# GENERAL #####################################
+
+# If you run Redis from upstart or systemd, Redis can interact with your
+# supervision tree. Options:
+#   supervised no      - no supervision interaction
+#   supervised upstart - signal upstart by putting Redis into SIGSTOP mode
+#                        requires "expect stop" in your upstart job config
+#   supervised systemd - signal systemd by writing READY=1 to $NOTIFY_SOCKET
+#   supervised auto    - detect upstart or systemd method based on
+#                        UPSTART_JOB or NOTIFY_SOCKET environment variables
+# Note: these supervision methods only signal "process is ready."
+#       They do not enable continuous pings back to your supervisor.
+supervised no
+
+# Specify the server verbosity level.
+# This can be one of:
+# debug (a lot of information, useful for development/testing)
+# verbose (many rarely useful info, but not a mess like the debug level)
+# notice (moderately verbose, what you want in production probably)
+# warning (only very important / critical messages are logged)
+loglevel notice
+
+# Specify the log file name. Also the empty string can be used to force
+# Redis to log on the standard output. Note that if you use standard
+# output for logging but daemonize, logs will be sent to /dev/null
+logfile /var/log/redis/redis.log
+
+# To enable logging to the system logger, just set 'syslog-enabled' to yes,
+# and optionally update the other syslog parameters to suit your needs.
+# syslog-enabled no
+
+# Specify the syslog identity.
+# syslog-ident redis
+
+# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7.
+# syslog-facility local0
+
+# Set the number of databases. The default database is DB 0, you can select
+# a different one on a per-connection basis using SELECT <dbid> where
+# dbid is a number between 0 and 'databases'-1
+databases 16
+
+# By default Redis shows an ASCII art logo only when started to log to the
+# standard output and if the standard output is a TTY. Basically this means
+# that normally a logo is displayed only in interactive sessions.
+#
+# However it is possible to force the pre-4.0 behavior and always show a
+# ASCII art logo in startup logs by setting the following option to yes.
+always-show-logo no
+
+################################ SNAPSHOTTING  ################################
+#
+# Save the DB on disk:
+#
+#   save <seconds> <changes>
+#
+#   Will save the DB if both the given number of seconds and the given
+#   number of write operations against the DB occurred.
+#
+#   In the example below the behavior will be to save:
+#   after 900 sec (15 min) if at least 1 key changed
+#   after 300 sec (5 min) if at least 10 keys changed
+#   after 60 sec if at least 10000 keys changed
+#
+#   Note: you can disable saving completely by commenting out all "save" lines.
+#
+#   It is also possible to remove all the previously configured save
+#   points by adding a save directive with a single empty string argument
+#   like in the following example:
+#
+#   save ""
+
+save 900 1
+save 300 10
+save 60 10000
+
+# By default Redis will stop accepting writes if RDB snapshots are enabled
+# (at least one save point) and the latest background save failed.
+# This will make the user aware (in a hard way) that data is not persisting
+# on disk properly, otherwise chances are that no one will notice and some
+# disaster will happen.
+#
+# If the background saving process will start working again Redis will
+# automatically allow writes again.
+#
+# However if you have setup your proper monitoring of the Redis server
+# and persistence, you may want to disable this feature so that Redis will
+# continue to work as usual even if there are problems with disk,
+# permissions, and so forth.
+stop-writes-on-bgsave-error yes
+
+# Compress string objects using LZF when dump .rdb databases?
+# By default compression is enabled as it's almost always a win.
+# If you want to save some CPU in the saving child set it to 'no' but
+# the dataset will likely be bigger if you have compressible values or keys.
+rdbcompression yes
+
+# Since version 5 of RDB a CRC64 checksum is placed at the end of the file.
+# This makes the format more resistant to corruption but there is a performance
+# hit to pay (around 10%) when saving and loading RDB files, so you can disable it
+# for maximum performances.
+#
+# RDB files created with checksum disabled have a checksum of zero that will
+# tell the loading code to skip the check.
+rdbchecksum yes
+
+# The filename where to dump the DB
+dbfilename dump.rdb
+
+# Remove RDB files used by replication in instances without persistence
+# enabled. By default this option is disabled, however there are environments
+# where for regulations or other security concerns, RDB files persisted on
+# disk by masters in order to feed replicas, or stored on disk by replicas
+# in order to load them for the initial synchronization, should be deleted
+# ASAP. Note that this option ONLY WORKS in instances that have both AOF
+# and RDB persistence disabled, otherwise is completely ignored.
+#
+# An alternative (and sometimes better) way to obtain the same effect is
+# to use diskless replication on both master and replicas instances. However
+# in the case of replicas, diskless is not always an option.
+rdb-del-sync-files no
+
+# The working directory.
+#
+# The DB will be written inside this directory, with the filename specified
+# above using the 'dbfilename' configuration directive.
+#
+# The Append Only File will also be created inside this directory.
+#
+# Note that you must specify a directory here, not a file name.
+dir /var/lib/redis
+
+################################# REPLICATION #################################
+
+# Master-Replica replication. Use replicaof to make a Redis instance a copy of
+# another Redis server. A few things to understand ASAP about Redis replication.
+#
+#   +------------------+      +---------------+
+#   |      Master      | ---> |    Replica    |
+#   | (receive writes) |      |  (exact copy) |
+#   +------------------+      +---------------+
+#
+# 1) Redis replication is asynchronous, but you can configure a master to
+#    stop accepting writes if it appears to be not connected with at least
+#    a given number of replicas.
+# 2) Redis replicas are able to perform a partial resynchronization with the
+#    master if the replication link is lost for a relatively small amount of
+#    time. You may want to configure the replication backlog size (see the next
+#    sections of this file) with a sensible value depending on your needs.
+# 3) Replication is automatic and does not need user intervention. After a
+#    network partition replicas automatically try to reconnect to masters
+#    and resynchronize with them.
+#
+# replicaof <masterip> <masterport>
+
+# If the master is password protected (using the "requirepass" configuration
+# directive below) it is possible to tell the replica to authenticate before
+# starting the replication synchronization process, otherwise the master will
+# refuse the replica request.
+#
+# masterauth <master-password>
+#
+# However this is not enough if you are using Redis ACLs (for Redis version
+# 6 or greater), and the default user is not capable of running the PSYNC
+# command and/or other commands needed for replication. In this case it's
+# better to configure a special user to use with replication, and specify the
+# masteruser configuration as such:
+#
+# masteruser <username>
+#
+# When masteruser is specified, the replica will authenticate against its
+# master using the new AUTH form: AUTH <username> <password>.
+
+# When a replica loses its connection with the master, or when the replication
+# is still in progress, the replica can act in two different ways:
+#
+# 1) if replica-serve-stale-data is set to 'yes' (the default) the replica will
+#    still reply to client requests, possibly with out of date data, or the
+#    data set may just be empty if this is the first synchronization.
+#
+# 2) If replica-serve-stale-data is set to 'no' the replica will reply with
+#    an error "SYNC with master in progress" to all commands except:
+#    INFO, REPLICAOF, AUTH, PING, SHUTDOWN, REPLCONF, ROLE, CONFIG, SUBSCRIBE,
+#    UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, PUBSUB, COMMAND, POST,
+#    HOST and LATENCY.
+#
+replica-serve-stale-data yes
+
+# You can configure a replica instance to accept writes or not. Writing against
+# a replica instance may be useful to store some ephemeral data (because data
+# written on a replica will be easily deleted after resync with the master) but
+# may also cause problems if clients are writing to it because of a
+# misconfiguration.
+#
+# Since Redis 2.6 by default replicas are read-only.
+#
+# Note: read only replicas are not designed to be exposed to untrusted clients
+# on the internet. It's just a protection layer against misuse of the instance.
+# Still a read only replica exports by default all the administrative commands
+# such as CONFIG, DEBUG, and so forth. To a limited extent you can improve
+# security of read only replicas using 'rename-command' to shadow all the
+# administrative / dangerous commands.
+replica-read-only yes
+
+# Replication SYNC strategy: disk or socket.
+#
+# New replicas and reconnecting replicas that are not able to continue the
+# replication process just receiving differences, need to do what is called a
+# "full synchronization". An RDB file is transmitted from the master to the
+# replicas.
+#
+# The transmission can happen in two different ways:
+#
+# 1) Disk-backed: The Redis master creates a new process that writes the RDB
+#                 file on disk. Later the file is transferred by the parent
+#                 process to the replicas incrementally.
+# 2) Diskless: The Redis master creates a new process that directly writes the
+#              RDB file to replica sockets, without touching the disk at all.
+#
+# With disk-backed replication, while the RDB file is generated, more replicas
+# can be queued and served with the RDB file as soon as the current child
+# producing the RDB file finishes its work. With diskless replication instead
+# once the transfer starts, new replicas arriving will be queued and a new
+# transfer will start when the current one terminates.
+#
+# When diskless replication is used, the master waits a configurable amount of
+# time (in seconds) before starting the transfer in the hope that multiple
+# replicas will arrive and the transfer can be parallelized.
+#
+# With slow disks and fast (large bandwidth) networks, diskless replication
+# works better.
+repl-diskless-sync no
+
+# When diskless replication is enabled, it is possible to configure the delay
+# the server waits in order to spawn the child that transfers the RDB via socket
+# to the replicas.
+#
+# This is important since once the transfer starts, it is not possible to serve
+# new replicas arriving, that will be queued for the next RDB transfer, so the
+# server waits a delay in order to let more replicas arrive.
+#
+# The delay is specified in seconds, and by default is 5 seconds. To disable
+# it entirely just set it to 0 seconds and the transfer will start ASAP.
+repl-diskless-sync-delay 5
+
+# -----------------------------------------------------------------------------
+# WARNING: RDB diskless load is experimental. Since in this setup the replica
+# does not immediately store an RDB on disk, it may cause data loss during
+# failovers. RDB diskless load + Redis modules not handling I/O reads may also
+# cause Redis to abort in case of I/O errors during the initial synchronization
+# stage with the master. Use only if your do what you are doing.
+# -----------------------------------------------------------------------------
+#
+# Replica can load the RDB it reads from the replication link directly from the
+# socket, or store the RDB to a file and read that file after it was completely
+# received from the master.
+#
+# In many cases the disk is slower than the network, and storing and loading
+# the RDB file may increase replication time (and even increase the master's
+# Copy on Write memory and salve buffers).
+# However, parsing the RDB file directly from the socket may mean that we have
+# to flush the contents of the current database before the full rdb was
+# received. For this reason we have the following options:
+#
+# "disabled"    - Don't use diskless load (store the rdb file to the disk first)
+# "on-empty-db" - Use diskless load only when it is completely safe.
+# "swapdb"      - Keep a copy of the current db contents in RAM while parsing
+#                 the data directly from the socket. note that this requires
+#                 sufficient memory, if you don't have it, you risk an OOM kill.
+repl-diskless-load disabled
+
+# Replicas send PINGs to server in a predefined interval. It's possible to
+# change this interval with the repl_ping_replica_period option. The default
+# value is 10 seconds.
+#
+# repl-ping-replica-period 10
+
+# The following option sets the replication timeout for:
+#
+# 1) Bulk transfer I/O during SYNC, from the point of view of replica.
+# 2) Master timeout from the point of view of replicas (data, pings).
+# 3) Replica timeout from the point of view of masters (REPLCONF ACK pings).
+#
+# It is important to make sure that this value is greater than the value
+# specified for repl-ping-replica-period otherwise a timeout will be detected
+# every time there is low traffic between the master and the replica. The default
+# value is 60 seconds.
+#
+# repl-timeout 60
+
+# Disable TCP_NODELAY on the replica socket after SYNC?
+#
+# If you select "yes" Redis will use a smaller number of TCP packets and
+# less bandwidth to send data to replicas. But this can add a delay for
+# the data to appear on the replica side, up to 40 milliseconds with
+# Linux kernels using a default configuration.
+#
+# If you select "no" the delay for data to appear on the replica side will
+# be reduced but more bandwidth will be used for replication.
+#
+# By default we optimize for low latency, but in very high traffic conditions
+# or when the master and replicas are many hops away, turning this to "yes" may
+# be a good idea.
+repl-disable-tcp-nodelay no
+
+# Set the replication backlog size. The backlog is a buffer that accumulates
+# replica data when replicas are disconnected for some time, so that when a
+# replica wants to reconnect again, often a full resync is not needed, but a
+# partial resync is enough, just passing the portion of data the replica
+# missed while disconnected.
+#
+# The bigger the replication backlog, the longer the replica can endure the
+# disconnect and later be able to perform a partial resynchronization.
+#
+# The backlog is only allocated if there is at least one replica connected.
+#
+# repl-backlog-size 1mb
+
+# After a master has no connected replicas for some time, the backlog will be
+# freed. The following option configures the amount of seconds that need to
+# elapse, starting from the time the last replica disconnected, for the backlog
+# buffer to be freed.
+#
+# Note that replicas never free the backlog for timeout, since they may be
+# promoted to masters later, and should be able to correctly "partially
+# resynchronize" with other replicas: hence they should always accumulate backlog.
+#
+# A value of 0 means to never release the backlog.
+#
+# repl-backlog-ttl 3600
+
+# The replica priority is an integer number published by Redis in the INFO
+# output. It is used by Redis Sentinel in order to select a replica to promote
+# into a master if the master is no longer working correctly.
+#
+# A replica with a low priority number is considered better for promotion, so
+# for instance if there are three replicas with priority 10, 100, 25 Sentinel
+# will pick the one with priority 10, that is the lowest.
+#
+# However a special priority of 0 marks the replica as not able to perform the
+# role of master, so a replica with priority of 0 will never be selected by
+# Redis Sentinel for promotion.
+#
+# By default the priority is 100.
+replica-priority 100
+
+# It is possible for a master to stop accepting writes if there are less than
+# N replicas connected, having a lag less or equal than M seconds.
+#
+# The N replicas need to be in "online" state.
+#
+# The lag in seconds, that must be <= the specified value, is calculated from
+# the last ping received from the replica, that is usually sent every second.
+#
+# This option does not GUARANTEE that N replicas will accept the write, but
+# will limit the window of exposure for lost writes in case not enough replicas
+# are available, to the specified number of seconds.
+#
+# For example to require at least 3 replicas with a lag <= 10 seconds use:
+#
+# min-replicas-to-write 3
+# min-replicas-max-lag 10
+#
+# Setting one or the other to 0 disables the feature.
+#
+# By default min-replicas-to-write is set to 0 (feature disabled) and
+# min-replicas-max-lag is set to 10.
+
+# A Redis master is able to list the address and port of the attached
+# replicas in different ways. For example the "INFO replication" section
+# offers this information, which is used, among other tools, by
+# Redis Sentinel in order to discover replica instances.
+# Another place where this info is available is in the output of the
+# "ROLE" command of a master.
+#
+# The listed IP address and port normally reported by a replica is
+# obtained in the following way:
+#
+#   IP: The address is auto detected by checking the peer address
+#   of the socket used by the replica to connect with the master.
+#
+#   Port: The port is communicated by the replica during the replication
+#   handshake, and is normally the port that the replica is using to
+#   listen for connections.
+#
+# However when port forwarding or Network Address Translation (NAT) is
+# used, the replica may actually be reachable via different IP and port
+# pairs. The following two options can be used by a replica in order to
+# report to its master a specific set of IP and port, so that both INFO
+# and ROLE will report those values.
+#
+# There is no need to use both the options if you need to override just
+# the port or the IP address.
+#
+# replica-announce-ip 5.5.5.5
+# replica-announce-port 1234
+
+############################### KEYS TRACKING #################################
+
+# Redis implements server assisted support for client side caching of values.
+# This is implemented using an invalidation table that remembers, using
+# 16 millions of slots, what clients may have certain subsets of keys. In turn
+# this is used in order to send invalidation messages to clients. Please
+# check this page to understand more about the feature:
+#
+#   https://redis.io/topics/client-side-caching
+#
+# When tracking is enabled for a client, all the read only queries are assumed
+# to be cached: this will force Redis to store information in the invalidation
+# table. When keys are modified, such information is flushed away, and
+# invalidation messages are sent to the clients. However if the workload is
+# heavily dominated by reads, Redis could use more and more memory in order
+# to track the keys fetched by many clients.
+#
+# For this reason it is possible to configure a maximum fill value for the
+# invalidation table. By default it is set to 1M of keys, and once this limit
+# is reached, Redis will start to evict keys in the invalidation table
+# even if they were not modified, just to reclaim memory: this will in turn
+# force the clients to invalidate the cached values. Basically the table
+# maximum size is a trade off between the memory you want to spend server
+# side to track information about who cached what, and the ability of clients
+# to retain cached objects in memory.
+#
+# If you set the value to 0, it means there are no limits, and Redis will
+# retain as many keys as needed in the invalidation table.
+# In the "stats" INFO section, you can find information about the number of
+# keys in the invalidation table at every given moment.
+#
+# Note: when key tracking is used in broadcasting mode, no memory is used
+# in the server side so this setting is useless.
+#
+# tracking-table-max-keys 1000000
+
+################################## SECURITY ###################################
+
+# Warning: since Redis is pretty fast, an outside user can try up to
+# 1 million passwords per second against a modern box. This means that you
+# should use very strong passwords, otherwise they will be very easy to break.
+# Note that because the password is really a shared secret between the client
+# and the server, and should not be memorized by any human, the password
+# can be easily a long string from /dev/urandom or whatever, so by using a
+# long and unguessable password no brute force attack will be possible.
+
+# Redis ACL users are defined in the following format:
+#
+#   user <username> ... acl rules ...
+#
+# For example:
+#
+#   user worker +@list +@connection ~jobs:* on >ffa9203c493aa99
+#
+# The special username "default" is used for new connections. If this user
+# has the "nopass" rule, then new connections will be immediately authenticated
+# as the "default" user without the need of any password provided via the
+# AUTH command. Otherwise if the "default" user is not flagged with "nopass"
+# the connections will start in not authenticated state, and will require
+# AUTH (or the HELLO command AUTH option) in order to be authenticated and
+# start to work.
+#
+# The ACL rules that describe what a user can do are the following:
+#
+#  on           Enable the user: it is possible to authenticate as this user.
+#  off          Disable the user: it's no longer possible to authenticate
+#               with this user, however the already authenticated connections
+#               will still work.
+#  +<command>   Allow the execution of that command
+#  -<command>   Disallow the execution of that command
+#  +@<category> Allow the execution of all the commands in such category
+#               with valid categories are like @admin, @set, @sortedset, ...
+#               and so forth, see the full list in the server.c file where
+#               the Redis command table is described and defined.
+#               The special category @all means all the commands, but currently
+#               present in the server, and that will be loaded in the future
+#               via modules.
+#  +<command>|subcommand    Allow a specific subcommand of an otherwise
+#                           disabled command. Note that this form is not
+#                           allowed as negative like -DEBUG|SEGFAULT, but
+#                           only additive starting with "+".
+#  allcommands  Alias for +@all. Note that it implies the ability to execute
+#               all the future commands loaded via the modules system.
+#  nocommands   Alias for -@all.
+#  ~<pattern>   Add a pattern of keys that can be mentioned as part of
+#               commands. For instance ~* allows all the keys. The pattern
+#               is a glob-style pattern like the one of KEYS.
+#               It is possible to specify multiple patterns.
+#  allkeys      Alias for ~*
+#  resetkeys    Flush the list of allowed keys patterns.
+#  ><password>  Add this password to the list of valid password for the user.
+#               For example >mypass will add "mypass" to the list.
+#               This directive clears the "nopass" flag (see later).
+#  <<password>  Remove this password from the list of valid passwords.
+#  nopass       All the set passwords of the user are removed, and the user
+#               is flagged as requiring no password: it means that every
+#               password will work against this user. If this directive is
+#               used for the default user, every new connection will be
+#               immediately authenticated with the default user without
+#               any explicit AUTH command required. Note that the "resetpass"
+#               directive will clear this condition.
+#  resetpass    Flush the list of allowed passwords. Moreover removes the
+#               "nopass" status. After "resetpass" the user has no associated
+#               passwords and there is no way to authenticate without adding
+#               some password (or setting it as "nopass" later).
+#  reset        Performs the following actions: resetpass, resetkeys, off,
+#               -@all. The user returns to the same state it has immediately
+#               after its creation.
+#
+# ACL rules can be specified in any order: for instance you can start with
+# passwords, then flags, or key patterns. However note that the additive
+# and subtractive rules will CHANGE MEANING depending on the ordering.
+# For instance see the following example:
+#
+#   user alice on +@all -DEBUG ~* >somepassword
+#
+# This will allow "alice" to use all the commands with the exception of the
+# DEBUG command, since +@all added all the commands to the set of the commands
+# alice can use, and later DEBUG was removed. However if we invert the order
+# of two ACL rules the result will be different:
+#
+#   user alice on -DEBUG +@all ~* >somepassword
+#
+# Now DEBUG was removed when alice had yet no commands in the set of allowed
+# commands, later all the commands are added, so the user will be able to
+# execute everything.
+#
+# Basically ACL rules are processed left-to-right.
+#
+# For more information about ACL configuration please refer to
+# the Redis web site at https://redis.io/topics/acl
+
+# ACL LOG
+#
+# The ACL Log tracks failed commands and authentication events associated
+# with ACLs. The ACL Log is useful to troubleshoot failed commands blocked
+# by ACLs. The ACL Log is stored in memory. You can reclaim memory with
+# ACL LOG RESET. Define the maximum entry length of the ACL Log below.
+acllog-max-len 128
+
+# Using an external ACL file
+#
+# Instead of configuring users here in this file, it is possible to use
+# a stand-alone file just listing users. The two methods cannot be mixed:
+# if you configure users here and at the same time you activate the external
+# ACL file, the server will refuse to start.
+#
+# The format of the external ACL user file is exactly the same as the
+# format that is used inside redis.conf to describe users.
+#
+# aclfile /etc/redis/users.acl
+
+# IMPORTANT NOTE: starting with Redis 6 "requirepass" is just a compatibility
+# layer on top of the new ACL system. The option effect will be just setting
+# the password for the default user. Clients will still authenticate using
+# AUTH <password> as usually, or more explicitly with AUTH default <password>
+# if they follow the new protocol: both will work.
+#
+# requirepass foobared
+
+# Command renaming (DEPRECATED).
+#
+# ------------------------------------------------------------------------
+# WARNING: avoid using this option if possible. Instead use ACLs to remove
+# commands from the default user, and put them only in some admin user you
+# create for administrative purposes.
+# ------------------------------------------------------------------------
+#
+# It is possible to change the name of dangerous commands in a shared
+# environment. For instance the CONFIG command may be renamed into something
+# hard to guess so that it will still be available for internal-use tools
+# but not available for general clients.
+#
+# Example:
+#
+# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
+#
+# It is also possible to completely kill a command by renaming it into
+# an empty string:
+#
+# rename-command CONFIG ""
+#
+# Please note that changing the name of commands that are logged into the
+# AOF file or transmitted to replicas may cause problems.
+
+################################### CLIENTS ####################################
+
+# Set the max number of connected clients at the same time. By default
+# this limit is set to 10000 clients, however if the Redis server is not
+# able to configure the process file limit to allow for the specified limit
+# the max number of allowed clients is set to the current file limit
+# minus 32 (as Redis reserves a few file descriptors for internal uses).
+#
+# Once the limit is reached Redis will close all the new connections sending
+# an error 'max number of clients reached'.
+#
+# IMPORTANT: When Redis Cluster is used, the max number of connections is also
+# shared with the cluster bus: every node in the cluster will use two
+# connections, one incoming and another outgoing. It is important to size the
+# limit accordingly in case of very large clusters.
+#
+# maxclients 10000
+
+############################## MEMORY MANAGEMENT ################################
+
+# Set a memory usage limit to the specified amount of bytes.
+# When the memory limit is reached Redis will try to remove keys
+# according to the eviction policy selected (see maxmemory-policy).
+#
+# If Redis can't remove keys according to the policy, or if the policy is
+# set to 'noeviction', Redis will start to reply with errors to commands
+# that would use more memory, like SET, LPUSH, and so on, and will continue
+# to reply to read-only commands like GET.
+#
+# This option is usually useful when using Redis as an LRU or LFU cache, or to
+# set a hard memory limit for an instance (using the 'noeviction' policy).
+#
+# WARNING: If you have replicas attached to an instance with maxmemory on,
+# the size of the output buffers needed to feed the replicas are subtracted
+# from the used memory count, so that network problems / resyncs will
+# not trigger a loop where keys are evicted, and in turn the output
+# buffer of replicas is full with DELs of keys evicted triggering the deletion
+# of more keys, and so forth until the database is completely emptied.
+#
+# In short... if you have replicas attached it is suggested that you set a lower
+# limit for maxmemory so that there is some free RAM on the system for replica
+# output buffers (but this is not needed if the policy is 'noeviction').
+#
+# maxmemory <bytes>
+
+# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
+# is reached. You can select one from the following behaviors:
+#
+# volatile-lru -> Evict using approximated LRU, only keys with an expire set.
+# allkeys-lru -> Evict any key using approximated LRU.
+# volatile-lfu -> Evict using approximated LFU, only keys with an expire set.
+# allkeys-lfu -> Evict any key using approximated LFU.
+# volatile-random -> Remove a random key having an expire set.
+# allkeys-random -> Remove a random key, any key.
+# volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
+# noeviction -> Don't evict anything, just return an error on write operations.
+#
+# LRU means Least Recently Used
+# LFU means Least Frequently Used
+#
+# Both LRU, LFU and volatile-ttl are implemented using approximated
+# randomized algorithms.
+#
+# Note: with any of the above policies, Redis will return an error on write
+#       operations, when there are no suitable keys for eviction.
+#
+#       At the date of writing these commands are: set setnx setex append
+#       incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
+#       sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
+#       zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
+#       getset mset msetnx exec sort
+#
+# The default is:
+#
+# maxmemory-policy noeviction
+
+# LRU, LFU and minimal TTL algorithms are not precise algorithms but approximated
+# algorithms (in order to save memory), so you can tune it for speed or
+# accuracy. By default Redis will check five keys and pick the one that was
+# used least recently, you can change the sample size using the following
+# configuration directive.
+#
+# The default of 5 produces good enough results. 10 Approximates very closely
+# true LRU but costs more CPU. 3 is faster but not very accurate.
+#
+# maxmemory-samples 5
+
+# Starting from Redis 5, by default a replica will ignore its maxmemory setting
+# (unless it is promoted to master after a failover or manually). It means
+# that the eviction of keys will be just handled by the master, sending the
+# DEL commands to the replica as keys evict in the master side.
+#
+# This behavior ensures that masters and replicas stay consistent, and is usually
+# what you want, however if your replica is writable, or you want the replica
+# to have a different memory setting, and you are sure all the writes performed
+# to the replica are idempotent, then you may change this default (but be sure
+# to understand what you are doing).
+#
+# Note that since the replica by default does not evict, it may end using more
+# memory than the one set via maxmemory (there are certain buffers that may
+# be larger on the replica, or data structures may sometimes take more memory
+# and so forth). So make sure you monitor your replicas and make sure they
+# have enough memory to never hit a real out-of-memory condition before the
+# master hits the configured maxmemory setting.
+#
+# replica-ignore-maxmemory yes
+
+# Redis reclaims expired keys in two ways: upon access when those keys are
+# found to be expired, and also in background, in what is called the
+# "active expire key". The key space is slowly and interactively scanned
+# looking for expired keys to reclaim, so that it is possible to free memory
+# of keys that are expired and will never be accessed again in a short time.
+#
+# The default effort of the expire cycle will try to avoid having more than
+# ten percent of expired keys still in memory, and will try to avoid consuming
+# more than 25% of total memory and to add latency to the system. However
+# it is possible to increase the expire "effort" that is normally set to
+# "1", to a greater value, up to the value "10". At its maximum value the
+# system will use more CPU, longer cycles (and technically may introduce
+# more latency), and will tolerate less already expired keys still present
+# in the system. It's a tradeoff between memory, CPU and latency.
+#
+# active-expire-effort 1
+
+############################# LAZY FREEING ####################################
+
+# Redis has two primitives to delete keys. One is called DEL and is a blocking
+# deletion of the object. It means that the server stops processing new commands
+# in order to reclaim all the memory associated with an object in a synchronous
+# way. If the key deleted is associated with a small object, the time needed
+# in order to execute the DEL command is very small and comparable to most other
+# O(1) or O(log_N) commands in Redis. However if the key is associated with an
+# aggregated value containing millions of elements, the server can block for
+# a long time (even seconds) in order to complete the operation.
+#
+# For the above reasons Redis also offers non blocking deletion primitives
+# such as UNLINK (non blocking DEL) and the ASYNC option of FLUSHALL and
+# FLUSHDB commands, in order to reclaim memory in background. Those commands
+# are executed in constant time. Another thread will incrementally free the
+# object in the background as fast as possible.
+#
+# DEL, UNLINK and ASYNC option of FLUSHALL and FLUSHDB are user-controlled.
+# It's up to the design of the application to understand when it is a good
+# idea to use one or the other. However the Redis server sometimes has to
+# delete keys or flush the whole database as a side effect of other operations.
+# Specifically Redis deletes objects independently of a user call in the
+# following scenarios:
+#
+# 1) On eviction, because of the maxmemory and maxmemory policy configurations,
+#    in order to make room for new data, without going over the specified
+#    memory limit.
+# 2) Because of expire: when a key with an associated time to live (see the
+#    EXPIRE command) must be deleted from memory.
+# 3) Because of a side effect of a command that stores data on a key that may
+#    already exist. For example the RENAME command may delete the old key
+#    content when it is replaced with another one. Similarly SUNIONSTORE
+#    or SORT with STORE option may delete existing keys. The SET command
+#    itself removes any old content of the specified key in order to replace
+#    it with the specified string.
+# 4) During replication, when a replica performs a full resynchronization with
+#    its master, the content of the whole database is removed in order to
+#    load the RDB file just transferred.
+#
+# In all the above cases the default is to delete objects in a blocking way,
+# like if DEL was called. However you can configure each case specifically
+# in order to instead release memory in a non-blocking way like if UNLINK
+# was called, using the following configuration directives.
+
+lazyfree-lazy-eviction no
+lazyfree-lazy-expire no
+lazyfree-lazy-server-del no
+replica-lazy-flush no
+
+# It is also possible, for the case when to replace the user code DEL calls
+# with UNLINK calls is not easy, to modify the default behavior of the DEL
+# command to act exactly like UNLINK, using the following configuration
+# directive:
+
+lazyfree-lazy-user-del no
+
+################################ THREADED I/O #################################
+
+# Redis is mostly single threaded, however there are certain threaded
+# operations such as UNLINK, slow I/O accesses and other things that are
+# performed on side threads.
+#
+# Now it is also possible to handle Redis clients socket reads and writes
+# in different I/O threads. Since especially writing is so slow, normally
+# Redis users use pipelining in order to speed up the Redis performances per
+# core, and spawn multiple instances in order to scale more. Using I/O
+# threads it is possible to easily speedup two times Redis without resorting
+# to pipelining nor sharding of the instance.
+#
+# By default threading is disabled, we suggest enabling it only in machines
+# that have at least 4 or more cores, leaving at least one spare core.
+# Using more than 8 threads is unlikely to help much. We also recommend using
+# threaded I/O only if you actually have performance problems, with Redis
+# instances being able to use a quite big percentage of CPU time, otherwise
+# there is no point in using this feature.
+#
+# So for instance if you have a four cores boxes, try to use 2 or 3 I/O
+# threads, if you have a 8 cores, try to use 6 threads. In order to
+# enable I/O threads use the following configuration directive:
+#
+# io-threads 4
+#
+# Setting io-threads to 1 will just use the main thread as usual.
+# When I/O threads are enabled, we only use threads for writes, that is
+# to thread the write(2) syscall and transfer the client buffers to the
+# socket. However it is also possible to enable threading of reads and
+# protocol parsing using the following configuration directive, by setting
+# it to yes:
+#
+# io-threads-do-reads no
+#
+# Usually threading reads doesn't help much.
+#
+# NOTE 1: This configuration directive cannot be changed at runtime via
+# CONFIG SET. Aso this feature currently does not work when SSL is
+# enabled.
+#
+# NOTE 2: If you want to test the Redis speedup using redis-benchmark, make
+# sure you also run the benchmark itself in threaded mode, using the
+# --threads option to match the number of Redis threads, otherwise you'll not
+# be able to notice the improvements.
+
+############################ KERNEL OOM CONTROL ##############################
+
+# On Linux, it is possible to hint the kernel OOM killer on what processes
+# should be killed first when out of memory.
+#
+# Enabling this feature makes Redis actively control the oom_score_adj value
+# for all its processes, depending on their role. The default scores will
+# attempt to have background child processes killed before all others, and
+# replicas killed before masters.
+#
+# Redis supports three options:
+#
+# no:       Don't make changes to oom-score-adj (default).
+# yes:      Alias to "relative" see below.
+# absolute: Values in oom-score-adj-values are written as is to the kernel.
+# relative: Values are used relative to the initial value of oom_score_adj when
+#           the server starts and are then clamped to a range of -1000 to 1000.
+#           Because typically the initial value is 0, they will often match the
+#           absolute values.
+oom-score-adj no
+
+# When oom-score-adj is used, this directive controls the specific values used
+# for master, replica and background child processes. Values range -2000 to
+# 2000 (higher means more likely to be killed).
+#
+# Unprivileged processes (not root, and without CAP_SYS_RESOURCE capabilities)
+# can freely increase their value, but not decrease it below its initial
+# settings. This means that setting oom-score-adj to "relative" and setting the
+# oom-score-adj-values to positive values will always succeed.
+oom-score-adj-values 0 200 800
+
+############################## APPEND ONLY MODE ###############################
+
+# By default Redis asynchronously dumps the dataset on disk. This mode is
+# good enough in many applications, but an issue with the Redis process or
+# a power outage may result into a few minutes of writes lost (depending on
+# the configured save points).
+#
+# The Append Only File is an alternative persistence mode that provides
+# much better durability. For instance using the default data fsync policy
+# (see later in the config file) Redis can lose just one second of writes in a
+# dramatic event like a server power outage, or a single write if something
+# wrong with the Redis process itself happens, but the operating system is
+# still running correctly.
+#
+# AOF and RDB persistence can be enabled at the same time without problems.
+# If the AOF is enabled on startup Redis will load the AOF, that is the file
+# with the better durability guarantees.
+#
+# Please check http://redis.io/topics/persistence for more information.
+
+appendonly no
+
+# The name of the append only file (default: "appendonly.aof")
+
+appendfilename "appendonly.aof"
+
+# The fsync() call tells the Operating System to actually write data on disk
+# instead of waiting for more data in the output buffer. Some OS will really flush
+# data on disk, some other OS will just try to do it ASAP.
+#
+# Redis supports three different modes:
+#
+# no: don't fsync, just let the OS flush the data when it wants. Faster.
+# always: fsync after every write to the append only log. Slow, Safest.
+# everysec: fsync only one time every second. Compromise.
+#
+# The default is "everysec", as that's usually the right compromise between
+# speed and data safety. It's up to you to understand if you can relax this to
+# "no" that will let the operating system flush the output buffer when
+# it wants, for better performances (but if you can live with the idea of
+# some data loss consider the default persistence mode that's snapshotting),
+# or on the contrary, use "always" that's very slow but a bit safer than
+# everysec.
+#
+# More details please check the following article:
+# http://antirez.com/post/redis-persistence-demystified.html
+#
+# If unsure, use "everysec".
+
+# appendfsync always
+appendfsync everysec
+# appendfsync no
+
+# When the AOF fsync policy is set to always or everysec, and a background
+# saving process (a background save or AOF log background rewriting) is
+# performing a lot of I/O against the disk, in some Linux configurations
+# Redis may block too long on the fsync() call. Note that there is no fix for
+# this currently, as even performing fsync in a different thread will block
+# our synchronous write(2) call.
+#
+# In order to mitigate this problem it's possible to use the following option
+# that will prevent fsync() from being called in the main process while a
+# BGSAVE or BGREWRITEAOF is in progress.
+#
+# This means that while another child is saving, the durability of Redis is
+# the same as "appendfsync none". In practical terms, this means that it is
+# possible to lose up to 30 seconds of log in the worst scenario (with the
+# default Linux settings).
+#
+# If you have latency problems turn this to "yes". Otherwise leave it as
+# "no" that is the safest pick from the point of view of durability.
+
+no-appendfsync-on-rewrite no
+
+# Automatic rewrite of the append only file.
+# Redis is able to automatically rewrite the log file implicitly calling
+# BGREWRITEAOF when the AOF log size grows by the specified percentage.
+#
+# This is how it works: Redis remembers the size of the AOF file after the
+# latest rewrite (if no rewrite has happened since the restart, the size of
+# the AOF at startup is used).
+#
+# This base size is compared to the current size. If the current size is
+# bigger than the specified percentage, the rewrite is triggered. Also
+# you need to specify a minimal size for the AOF file to be rewritten, this
+# is useful to avoid rewriting the AOF file even if the percentage increase
+# is reached but it is still pretty small.
+#
+# Specify a percentage of zero in order to disable the automatic AOF
+# rewrite feature.
+
+auto-aof-rewrite-percentage 100
+auto-aof-rewrite-min-size 64mb
+
+# An AOF file may be found to be truncated at the end during the Redis
+# startup process, when the AOF data gets loaded back into memory.
+# This may happen when the system where Redis is running
+# crashes, especially when an ext4 filesystem is mounted without the
+# data=ordered option (however this can't happen when Redis itself
+# crashes or aborts but the operating system still works correctly).
+#
+# Redis can either exit with an error when this happens, or load as much
+# data as possible (the default now) and start if the AOF file is found
+# to be truncated at the end. The following option controls this behavior.
+#
+# If aof-load-truncated is set to yes, a truncated AOF file is loaded and
+# the Redis server starts emitting a log to inform the user of the event.
+# Otherwise if the option is set to no, the server aborts with an error
+# and refuses to start. When the option is set to no, the user requires
+# to fix the AOF file using the "redis-check-aof" utility before to restart
+# the server.
+#
+# Note that if the AOF file will be found to be corrupted in the middle
+# the server will still exit with an error. This option only applies when
+# Redis will try to read more data from the AOF file but not enough bytes
+# will be found.
+aof-load-truncated yes
+
+# When rewriting the AOF file, Redis is able to use an RDB preamble in the
+# AOF file for faster rewrites and recoveries. When this option is turned
+# on the rewritten AOF file is composed of two different stanzas:
+#
+#   [RDB file][AOF tail]
+#
+# When loading, Redis recognizes that the AOF file starts with the "REDIS"
+# string and loads the prefixed RDB file, then continues loading the AOF
+# tail.
+aof-use-rdb-preamble yes
+
+################################ LUA SCRIPTING  ###############################
+
+# Max execution time of a Lua script in milliseconds.
+#
+# If the maximum execution time is reached Redis will log that a script is
+# still in execution after the maximum allowed time and will start to
+# reply to queries with an error.
+#
+# When a long running script exceeds the maximum execution time only the
+# SCRIPT KILL and SHUTDOWN NOSAVE commands are available. The first can be
+# used to stop a script that did not yet call any write commands. The second
+# is the only way to shut down the server in the case a write command was
+# already issued by the script but the user doesn't want to wait for the natural
+# termination of the script.
+#
+# Set it to 0 or a negative value for unlimited execution without warnings.
+lua-time-limit 5000
+
+################################ REDIS CLUSTER  ###############################
+
+# Normal Redis instances can't be part of a Redis Cluster; only nodes that are
+# started as cluster nodes can. In order to start a Redis instance as a
+# cluster node enable the cluster support uncommenting the following:
+#
+# cluster-enabled yes
+
+# Every cluster node has a cluster configuration file. This file is not
+# intended to be edited by hand. It is created and updated by Redis nodes.
+# Every Redis Cluster node requires a different cluster configuration file.
+# Make sure that instances running in the same system do not have
+# overlapping cluster configuration file names.
+#
+# cluster-config-file nodes-6379.conf
+
+# Cluster node timeout is the amount of milliseconds a node must be unreachable
+# for it to be considered in failure state.
+# Most other internal time limits are a multiple of the node timeout.
+#
+# cluster-node-timeout 15000
+
+# A replica of a failing master will avoid to start a failover if its data
+# looks too old.
+#
+# There is no simple way for a replica to actually have an exact measure of
+# its "data age", so the following two checks are performed:
+#
+# 1) If there are multiple replicas able to failover, they exchange messages
+#    in order to try to give an advantage to the replica with the best
+#    replication offset (more data from the master processed).
+#    Replicas will try to get their rank by offset, and apply to the start
+#    of the failover a delay proportional to their rank.
+#
+# 2) Every single replica computes the time of the last interaction with
+#    its master. This can be the last ping or command received (if the master
+#    is still in the "connected" state), or the time that elapsed since the
+#    disconnection with the master (if the replication link is currently down).
+#    If the last interaction is too old, the replica will not try to failover
+#    at all.
+#
+# The point "2" can be tuned by user. Specifically a replica will not perform
+# the failover if, since the last interaction with the master, the time
+# elapsed is greater than:
+#
+#   (node-timeout * cluster-replica-validity-factor) + repl-ping-replica-period
+#
+# So for example if node-timeout is 30 seconds, and the cluster-replica-validity-factor
+# is 10, and assuming a default repl-ping-replica-period of 10 seconds, the
+# replica will not try to failover if it was not able to talk with the master
+# for longer than 310 seconds.
+#
+# A large cluster-replica-validity-factor may allow replicas with too old data to failover
+# a master, while a too small value may prevent the cluster from being able to
+# elect a replica at all.
+#
+# For maximum availability, it is possible to set the cluster-replica-validity-factor
+# to a value of 0, which means, that replicas will always try to failover the
+# master regardless of the last time they interacted with the master.
+# (However they'll always try to apply a delay proportional to their
+# offset rank).
+#
+# Zero is the only value able to guarantee that when all the partitions heal
+# the cluster will always be able to continue.
+#
+# cluster-replica-validity-factor 10
+
+# Cluster replicas are able to migrate to orphaned masters, that are masters
+# that are left without working replicas. This improves the cluster ability
+# to resist to failures as otherwise an orphaned master can't be failed over
+# in case of failure if it has no working replicas.
+#
+# Replicas migrate to orphaned masters only if there are still at least a
+# given number of other working replicas for their old master. This number
+# is the "migration barrier". A migration barrier of 1 means that a replica
+# will migrate only if there is at least 1 other working replica for its master
+# and so forth. It usually reflects the number of replicas you want for every
+# master in your cluster.
+#
+# Default is 1 (replicas migrate only if their masters remain with at least
+# one replica). To disable migration just set it to a very large value.
+# A value of 0 can be set but is useful only for debugging and dangerous
+# in production.
+#
+# cluster-migration-barrier 1
+
+# By default Redis Cluster nodes stop accepting queries if they detect there
+# is at least a hash slot uncovered (no available node is serving it).
+# This way if the cluster is partially down (for example a range of hash slots
+# are no longer covered) all the cluster becomes, eventually, unavailable.
+# It automatically returns available as soon as all the slots are covered again.
+#
+# However sometimes you want the subset of the cluster which is working,
+# to continue to accept queries for the part of the key space that is still
+# covered. In order to do so, just set the cluster-require-full-coverage
+# option to no.
+#
+# cluster-require-full-coverage yes
+
+# This option, when set to yes, prevents replicas from trying to failover its
+# master during master failures. However the master can still perform a
+# manual failover, if forced to do so.
+#
+# This is useful in different scenarios, especially in the case of multiple
+# data center operations, where we want one side to never be promoted if not
+# in the case of a total DC failure.
+#
+# cluster-replica-no-failover no
+
+# This option, when set to yes, allows nodes to serve read traffic while the
+# the cluster is in a down state, as long as it believes it owns the slots.
+#
+# This is useful for two cases.  The first case is for when an application
+# doesn't require consistency of data during node failures or network partitions.
+# One example of this is a cache, where as long as the node has the data it
+# should be able to serve it.
+#
+# The second use case is for configurations that don't meet the recommended
+# three shards but want to enable cluster mode and scale later. A
+# master outage in a 1 or 2 shard configuration causes a read/write outage to the
+# entire cluster without this option set, with it set there is only a write outage.
+# Without a quorum of masters, slot ownership will not change automatically.
+#
+# cluster-allow-reads-when-down no
+
+# In order to setup your cluster make sure to read the documentation
+# available at http://redis.io web site.
+
+########################## CLUSTER DOCKER/NAT support  ########################
+
+# In certain deployments, Redis Cluster nodes address discovery fails, because
+# addresses are NAT-ted or because ports are forwarded (the typical case is
+# Docker and other containers).
+#
+# In order to make Redis Cluster working in such environments, a static
+# configuration where each node knows its public address is needed. The
+# following two options are used for this scope, and are:
+#
+# * cluster-announce-ip
+# * cluster-announce-port
+# * cluster-announce-bus-port
+#
+# Each instructs the node about its address, client port, and cluster message
+# bus port. The information is then published in the header of the bus packets
+# so that other nodes will be able to correctly map the address of the node
+# publishing the information.
+#
+# If the above options are not used, the normal Redis Cluster auto-detection
+# will be used instead.
+#
+# Note that when remapped, the bus port may not be at the fixed offset of
+# clients port + 10000, so you can specify any port and bus-port depending
+# on how they get remapped. If the bus-port is not set, a fixed offset of
+# 10000 will be used as usual.
+#
+# Example:
+#
+# cluster-announce-ip 10.1.1.5
+# cluster-announce-port 6379
+# cluster-announce-bus-port 6380
+
+################################## SLOW LOG ###################################
+
+# The Redis Slow Log is a system to log queries that exceeded a specified
+# execution time. The execution time does not include the I/O operations
+# like talking with the client, sending the reply and so forth,
+# but just the time needed to actually execute the command (this is the only
+# stage of command execution where the thread is blocked and can not serve
+# other requests in the meantime).
+#
+# You can configure the slow log with two parameters: one tells Redis
+# what is the execution time, in microseconds, to exceed in order for the
+# command to get logged, and the other parameter is the length of the
+# slow log. When a new command is logged the oldest one is removed from the
+# queue of logged commands.
+
+# The following time is expressed in microseconds, so 1000000 is equivalent
+# to one second. Note that a negative number disables the slow log, while
+# a value of zero forces the logging of every command.
+slowlog-log-slower-than 10000
+
+# There is no limit to this length. Just be aware that it will consume memory.
+# You can reclaim memory used by the slow log with SLOWLOG RESET.
+slowlog-max-len 128
+
+################################ LATENCY MONITOR ##############################
+
+# The Redis latency monitoring subsystem samples different operations
+# at runtime in order to collect data related to possible sources of
+# latency of a Redis instance.
+#
+# Via the LATENCY command this information is available to the user that can
+# print graphs and obtain reports.
+#
+# The system only logs operations that were performed in a time equal or
+# greater than the amount of milliseconds specified via the
+# latency-monitor-threshold configuration directive. When its value is set
+# to zero, the latency monitor is turned off.
+#
+# By default latency monitoring is disabled since it is mostly not needed
+# if you don't have latency issues, and collecting data has a performance
+# impact, that while very small, can be measured under big load. Latency
+# monitoring can easily be enabled at runtime using the command
+# "CONFIG SET latency-monitor-threshold <milliseconds>" if needed.
+latency-monitor-threshold 0
+
+############################# EVENT NOTIFICATION ##############################
+
+# Redis can notify Pub/Sub clients about events happening in the key space.
+# This feature is documented at http://redis.io/topics/notifications
+#
+# For instance if keyspace events notification is enabled, and a client
+# performs a DEL operation on key "foo" stored in the Database 0, two
+# messages will be published via Pub/Sub:
+#
+# PUBLISH __keyspace@0__:foo del
+# PUBLISH __keyevent@0__:del foo
+#
+# It is possible to select the events that Redis will notify among a set
+# of classes. Every class is identified by a single character:
+#
+#  K     Keyspace events, published with __keyspace@<db>__ prefix.
+#  E     Keyevent events, published with __keyevent@<db>__ prefix.
+#  g     Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ...
+#  $     String commands
+#  l     List commands
+#  s     Set commands
+#  h     Hash commands
+#  z     Sorted set commands
+#  x     Expired events (events generated every time a key expires)
+#  e     Evicted events (events generated when a key is evicted for maxmemory)
+#  t     Stream commands
+#  m     Key-miss events (Note: It is not included in the 'A' class)
+#  A     Alias for g$lshzxet, so that the "AKE" string means all the events
+#        (Except key-miss events which are excluded from 'A' due to their
+#         unique nature).
+#
+#  The "notify-keyspace-events" takes as argument a string that is composed
+#  of zero or multiple characters. The empty string means that notifications
+#  are disabled.
+#
+#  Example: to enable list and generic events, from the point of view of the
+#           event name, use:
+#
+#  notify-keyspace-events Elg
+#
+#  Example 2: to get the stream of the expired keys subscribing to channel
+#             name __keyevent@0__:expired use:
+#
+#  notify-keyspace-events Ex
+#
+#  By default all notifications are disabled because most users don't need
+#  this feature and the feature has some overhead. Note that if you don't
+#  specify at least one of K or E, no events will be delivered.
+notify-keyspace-events ""
+
+############################### GOPHER SERVER #################################
+
+# Redis contains an implementation of the Gopher protocol, as specified in
+# the RFC 1436 (https://www.ietf.org/rfc/rfc1436.txt).
+#
+# The Gopher protocol was very popular in the late '90s. It is an alternative
+# to the web, and the implementation both server and client side is so simple
+# that the Redis server has just 100 lines of code in order to implement this
+# support.
+#
+# What do you do with Gopher nowadays? Well Gopher never *really* died, and
+# lately there is a movement in order for the Gopher more hierarchical content
+# composed of just plain text documents to be resurrected. Some want a simpler
+# internet, others believe that the mainstream internet became too much
+# controlled, and it's cool to create an alternative space for people that
+# want a bit of fresh air.
+#
+# Anyway for the 10nth birthday of the Redis, we gave it the Gopher protocol
+# as a gift.
+#
+# --- HOW IT WORKS? ---
+#
+# The Redis Gopher support uses the inline protocol of Redis, and specifically
+# two kind of inline requests that were anyway illegal: an empty request
+# or any request that starts with "/" (there are no Redis commands starting
+# with such a slash). Normal RESP2/RESP3 requests are completely out of the
+# path of the Gopher protocol implementation and are served as usual as well.
+#
+# If you open a connection to Redis when Gopher is enabled and send it
+# a string like "/foo", if there is a key named "/foo" it is served via the
+# Gopher protocol.
+#
+# In order to create a real Gopher "hole" (the name of a Gopher site in Gopher
+# talking), you likely need a script like the following:
+#
+#   https://github.com/antirez/gopher2redis
+#
+# --- SECURITY WARNING ---
+#
+# If you plan to put Redis on the internet in a publicly accessible address
+# to server Gopher pages MAKE SURE TO SET A PASSWORD to the instance.
+# Once a password is set:
+#
+#   1. The Gopher server (when enabled, not by default) will still serve
+#      content via Gopher.
+#   2. However other commands cannot be called before the client will
+#      authenticate.
+#
+# So use the 'requirepass' option to protect your instance.
+#
+# Note that Gopher is not currently supported when 'io-threads-do-reads'
+# is enabled.
+#
+# To enable Gopher support, uncomment the following line and set the option
+# from no (the default) to yes.
+#
+# gopher-enabled no
+
+############################### ADVANCED CONFIG ###############################
+
+# Hashes are encoded using a memory efficient data structure when they have a
+# small number of entries, and the biggest entry does not exceed a given
+# threshold. These thresholds can be configured using the following directives.
+hash-max-ziplist-entries 512
+hash-max-ziplist-value 64
+
+# Lists are also encoded in a special way to save a lot of space.
+# The number of entries allowed per internal list node can be specified
+# as a fixed maximum size or a maximum number of elements.
+# For a fixed maximum size, use -5 through -1, meaning:
+# -5: max size: 64 Kb  <-- not recommended for normal workloads
+# -4: max size: 32 Kb  <-- not recommended
+# -3: max size: 16 Kb  <-- probably not recommended
+# -2: max size: 8 Kb   <-- good
+# -1: max size: 4 Kb   <-- good
+# Positive numbers mean store up to _exactly_ that number of elements
+# per list node.
+# The highest performing option is usually -2 (8 Kb size) or -1 (4 Kb size),
+# but if your use case is unique, adjust the settings as necessary.
+list-max-ziplist-size -2
+
+# Lists may also be compressed.
+# Compress depth is the number of quicklist ziplist nodes from *each* side of
+# the list to *exclude* from compression.  The head and tail of the list
+# are always uncompressed for fast push/pop operations.  Settings are:
+# 0: disable all list compression
+# 1: depth 1 means "don't start compressing until after 1 node into the list,
+#    going from either the head or tail"
+#    So: [head]->node->node->...->node->[tail]
+#    [head], [tail] will always be uncompressed; inner nodes will compress.
+# 2: [head]->[next]->node->node->...->node->[prev]->[tail]
+#    2 here means: don't compress head or head->next or tail->prev or tail,
+#    but compress all nodes between them.
+# 3: [head]->[next]->[next]->node->node->...->node->[prev]->[prev]->[tail]
+# etc.
+list-compress-depth 0
+
+# Sets have a special encoding in just one case: when a set is composed
+# of just strings that happen to be integers in radix 10 in the range
+# of 64 bit signed integers.
+# The following configuration setting sets the limit in the size of the
+# set in order to use this special memory saving encoding.
+set-max-intset-entries 512
+
+# Similarly to hashes and lists, sorted sets are also specially encoded in
+# order to save a lot of space. This encoding is only used when the length and
+# elements of a sorted set are below the following limits:
+zset-max-ziplist-entries 128
+zset-max-ziplist-value 64
+
+# HyperLogLog sparse representation bytes limit. The limit includes the
+# 16 bytes header. When an HyperLogLog using the sparse representation crosses
+# this limit, it is converted into the dense representation.
+#
+# A value greater than 16000 is totally useless, since at that point the
+# dense representation is more memory efficient.
+#
+# The suggested value is ~ 3000 in order to have the benefits of
+# the space efficient encoding without slowing down too much PFADD,
+# which is O(N) with the sparse encoding. The value can be raised to
+# ~ 10000 when CPU is not a concern, but space is, and the data set is
+# composed of many HyperLogLogs with cardinality in the 0 - 15000 range.
+hll-sparse-max-bytes 3000
+
+# Streams macro node max size / items. The stream data structure is a radix
+# tree of big nodes that encode multiple items inside. Using this configuration
+# it is possible to configure how big a single node can be in bytes, and the
+# maximum number of items it may contain before switching to a new node when
+# appending new stream entries. If any of the following settings are set to
+# zero, the limit is ignored, so for instance it is possible to set just a
+# max entires limit by setting max-bytes to 0 and max-entries to the desired
+# value.
+stream-node-max-bytes 4096
+stream-node-max-entries 100
+
+# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in
+# order to help rehashing the main Redis hash table (the one mapping top-level
+# keys to values). The hash table implementation Redis uses (see dict.c)
+# performs a lazy rehashing: the more operation you run into a hash table
+# that is rehashing, the more rehashing "steps" are performed, so if the
+# server is idle the rehashing is never complete and some more memory is used
+# by the hash table.
+#
+# The default is to use this millisecond 10 times every second in order to
+# actively rehash the main dictionaries, freeing memory when possible.
+#
+# If unsure:
+# use "activerehashing no" if you have hard latency requirements and it is
+# not a good thing in your environment that Redis can reply from time to time
+# to queries with 2 milliseconds delay.
+#
+# use "activerehashing yes" if you don't have such hard requirements but
+# want to free memory asap when possible.
+activerehashing yes
+
+# The client output buffer limits can be used to force disconnection of clients
+# that are not reading data from the server fast enough for some reason (a
+# common reason is that a Pub/Sub client can't consume messages as fast as the
+# publisher can produce them).
+#
+# The limit can be set differently for the three different classes of clients:
+#
+# normal -> normal clients including MONITOR clients
+# replica  -> replica clients
+# pubsub -> clients subscribed to at least one pubsub channel or pattern
+#
+# The syntax of every client-output-buffer-limit directive is the following:
+#
+# client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>
+#
+# A client is immediately disconnected once the hard limit is reached, or if
+# the soft limit is reached and remains reached for the specified number of
+# seconds (continuously).
+# So for instance if the hard limit is 32 megabytes and the soft limit is
+# 16 megabytes / 10 seconds, the client will get disconnected immediately
+# if the size of the output buffers reach 32 megabytes, but will also get
+# disconnected if the client reaches 16 megabytes and continuously overcomes
+# the limit for 10 seconds.
+#
+# By default normal clients are not limited because they don't receive data
+# without asking (in a push way), but just after a request, so only
+# asynchronous clients may create a scenario where data is requested faster
+# than it can read.
+#
+# Instead there is a default limit for pubsub and replica clients, since
+# subscribers and replicas receive data in a push fashion.
+#
+# Both the hard or the soft limit can be disabled by setting them to zero.
+client-output-buffer-limit normal 0 0 0
+client-output-buffer-limit replica 256mb 64mb 60
+client-output-buffer-limit pubsub 32mb 8mb 60
+
+# Client query buffers accumulate new commands. They are limited to a fixed
+# amount by default in order to avoid that a protocol desynchronization (for
+# instance due to a bug in the client) will lead to unbound memory usage in
+# the query buffer. However you can configure it here if you have very special
+# needs, such us huge multi/exec requests or alike.
+#
+# client-query-buffer-limit 1gb
+
+# In the Redis protocol, bulk requests, that are, elements representing single
+# strings, are normally limited to 512 mb. However you can change this limit
+# here, but must be 1mb or greater
+#
+# proto-max-bulk-len 512mb
+
+# Redis calls an internal function to perform many background tasks, like
+# closing connections of clients in timeout, purging expired keys that are
+# never requested, and so forth.
+#
+# Not all tasks are performed with the same frequency, but Redis checks for
+# tasks to perform according to the specified "hz" value.
+#
+# By default "hz" is set to 10. Raising the value will use more CPU when
+# Redis is idle, but at the same time will make Redis more responsive when
+# there are many keys expiring at the same time, and timeouts may be
+# handled with more precision.
+#
+# The range is between 1 and 500, however a value over 100 is usually not
+# a good idea. Most users should use the default of 10 and raise this up to
+# 100 only in environments where very low latency is required.
+hz 10
+
+# Normally it is useful to have an HZ value which is proportional to the
+# number of clients connected. This is useful in order, for instance, to
+# avoid too many clients are processed for each background task invocation
+# in order to avoid latency spikes.
+#
+# Since the default HZ value by default is conservatively set to 10, Redis
+# offers, and enables by default, the ability to use an adaptive HZ value
+# which will temporarily raise when there are many connected clients.
+#
+# When dynamic HZ is enabled, the actual configured HZ will be used
+# as a baseline, but multiples of the configured HZ value will be actually
+# used as needed once more clients are connected. In this way an idle
+# instance will use very little CPU time while a busy instance will be
+# more responsive.
+dynamic-hz yes
+
+# When a child rewrites the AOF file, if the following option is enabled
+# the file will be fsync-ed every 32 MB of data generated. This is useful
+# in order to commit the file to the disk more incrementally and avoid
+# big latency spikes.
+aof-rewrite-incremental-fsync yes
+
+# When redis saves RDB file, if the following option is enabled
+# the file will be fsync-ed every 32 MB of data generated. This is useful
+# in order to commit the file to the disk more incrementally and avoid
+# big latency spikes.
+rdb-save-incremental-fsync yes
+
+# Redis LFU eviction (see maxmemory setting) can be tuned. However it is a good
+# idea to start with the default settings and only change them after investigating
+# how to improve the performances and how the keys LFU change over time, which
+# is possible to inspect via the OBJECT FREQ command.
+#
+# There are two tunable parameters in the Redis LFU implementation: the
+# counter logarithm factor and the counter decay time. It is important to
+# understand what the two parameters mean before changing them.
+#
+# The LFU counter is just 8 bits per key, it's maximum value is 255, so Redis
+# uses a probabilistic increment with logarithmic behavior. Given the value
+# of the old counter, when a key is accessed, the counter is incremented in
+# this way:
+#
+# 1. A random number R between 0 and 1 is extracted.
+# 2. A probability P is calculated as 1/(old_value*lfu_log_factor+1).
+# 3. The counter is incremented only if R < P.
+#
+# The default lfu-log-factor is 10. This is a table of how the frequency
+# counter changes with a different number of accesses with different
+# logarithmic factors:
+#
+# +--------+------------+------------+------------+------------+------------+
+# | factor | 100 hits   | 1000 hits  | 100K hits  | 1M hits    | 10M hits   |
+# +--------+------------+------------+------------+------------+------------+
+# | 0      | 104        | 255        | 255        | 255        | 255        |
+# +--------+------------+------------+------------+------------+------------+
+# | 1      | 18         | 49         | 255        | 255        | 255        |
+# +--------+------------+------------+------------+------------+------------+
+# | 10     | 10         | 18         | 142        | 255        | 255        |
+# +--------+------------+------------+------------+------------+------------+
+# | 100    | 8          | 11         | 49         | 143        | 255        |
+# +--------+------------+------------+------------+------------+------------+
+#
+# NOTE: The above table was obtained by running the following commands:
+#
+#   redis-benchmark -n 1000000 incr foo
+#   redis-cli object freq foo
+#
+# NOTE 2: The counter initial value is 5 in order to give new objects a chance
+# to accumulate hits.
+#
+# The counter decay time is the time, in minutes, that must elapse in order
+# for the key counter to be divided by two (or decremented if it has a value
+# less <= 10).
+#
+# The default value for the lfu-decay-time is 1. A special value of 0 means to
+# decay the counter every time it happens to be scanned.
+#
+# lfu-log-factor 10
+# lfu-decay-time 1
+
+########################### ACTIVE DEFRAGMENTATION #######################
+#
+# What is active defragmentation?
+# -------------------------------
+#
+# Active (online) defragmentation allows a Redis server to compact the
+# spaces left between small allocations and deallocations of data in memory,
+# thus allowing to reclaim back memory.
+#
+# Fragmentation is a natural process that happens with every allocator (but
+# less so with Jemalloc, fortunately) and certain workloads. Normally a server
+# restart is needed in order to lower the fragmentation, or at least to flush
+# away all the data and create it again. However thanks to this feature
+# implemented by Oran Agra for Redis 4.0 this process can happen at runtime
+# in a "hot" way, while the server is running.
+#
+# Basically when the fragmentation is over a certain level (see the
+# configuration options below) Redis will start to create new copies of the
+# values in contiguous memory regions by exploiting certain specific Jemalloc
+# features (in order to understand if an allocation is causing fragmentation
+# and to allocate it in a better place), and at the same time, will release the
+# old copies of the data. This process, repeated incrementally for all the keys
+# will cause the fragmentation to drop back to normal values.
+#
+# Important things to understand:
+#
+# 1. This feature is disabled by default, and only works if you compiled Redis
+#    to use the copy of Jemalloc we ship with the source code of Redis.
+#    This is the default with Linux builds.
+#
+# 2. You never need to enable this feature if you don't have fragmentation
+#    issues.
+#
+# 3. Once you experience fragmentation, you can enable this feature when
+#    needed with the command "CONFIG SET activedefrag yes".
+#
+# The configuration parameters are able to fine tune the behavior of the
+# defragmentation process. If you are not sure about what they mean it is
+# a good idea to leave the defaults untouched.
+
+# Enabled active defragmentation
+# activedefrag no
+
+# Minimum amount of fragmentation waste to start active defrag
+# active-defrag-ignore-bytes 100mb
+
+# Minimum percentage of fragmentation to start active defrag
+# active-defrag-threshold-lower 10
+
+# Maximum percentage of fragmentation at which we use maximum effort
+# active-defrag-threshold-upper 100
+
+# Minimal effort for defrag in CPU percentage, to be used when the lower
+# threshold is reached
+# active-defrag-cycle-min 1
+
+# Maximal effort for defrag in CPU percentage, to be used when the upper
+# threshold is reached
+# active-defrag-cycle-max 25
+
+# Maximum number of set/hash/zset/list fields that will be processed from
+# the main dictionary scan
+# active-defrag-max-scan-fields 1000
+
+# Jemalloc background thread for purging will be enabled by default
+jemalloc-bg-thread yes
+
+# It is possible to pin different threads and processes of Redis to specific
+# CPUs in your system, in order to maximize the performances of the server.
+# This is useful both in order to pin different Redis threads in different
+# CPUs, but also in order to make sure that multiple Redis instances running
+# in the same host will be pinned to different CPUs.
+#
+# Normally you can do this using the "taskset" command, however it is also
+# possible to this via Redis configuration directly, both in Linux and FreeBSD.
+#
+# You can pin the server/IO threads, bio threads, aof rewrite child process, and
+# the bgsave child process. The syntax to specify the cpu list is the same as
+# the taskset command:
+#
+# Set redis server/io threads to cpu affinity 0,2,4,6:
+# server_cpulist 0-7:2
+#
+# Set bio threads to cpu affinity 1,3:
+# bio_cpulist 1,3
+#
+# Set aof rewrite child process to cpu affinity 8,9,10,11:
+# aof_rewrite_cpulist 8-11
+#
+# Set bgsave child process to cpu affinity 1,10,11
+# bgsave_cpulist 1,10-11
+
+# In some cases redis will emit warnings and even refuse to start if it detects
+# that the system is in bad state, it is possible to suppress these warnings
+# by setting the following config which takes a space delimited list of warnings
+# to suppress
+#
+# ignore-warnings ARM64-COW-BUG

+ 440 - 440
docker/www.conf

@@ -1,440 +1,440 @@
-; Start a new pool named 'www'.
-; the variable $pool can be used in any directive and will be replaced by the
-; pool name ('www' here)
-[www]
-
-; Per pool prefix
-; It only applies on the following directives:
-; - 'access.log'
-; - 'slowlog'
-; - 'listen' (unixsocket)
-; - 'chroot'
-; - 'chdir'
-; - 'php_values'
-; - 'php_admin_values'
-; When not set, the global prefix (or /usr) applies instead.
-; Note: This directive can also be relative to the global prefix.
-; Default Value: none
-;prefix = /path/to/pools/$pool
-
-; Unix user/group of processes
-; Note: The user is mandatory. If the group is not set, the default user's group
-;       will be used.
-user = nobody
-group = nobody
-
-; The address on which to accept FastCGI requests.
-; Valid syntaxes are:
-;   'ip.add.re.ss:port'    - to listen on a TCP socket to a specific IPv4 address on
-;                            a specific port;
-;   '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on
-;                            a specific port;
-;   'port'                 - to listen on a TCP socket to all addresses
-;                            (IPv6 and IPv4-mapped) on a specific port;
-;   '/path/to/unix/socket' - to listen on a unix socket.
-; Note: This value is mandatory.
-listen.mode = 0666
-listen = /tmp/php-cgi-74.sock
-
-; Set listen(2) backlog.
-; Default Value: 511 (-1 on FreeBSD and OpenBSD)
-;listen.backlog = 511
-
-; Set permissions for unix socket, if one is used. In Linux, read/write
-; permissions must be set in order to allow connections from a web server. Many
-; BSD-derived systems allow connections regardless of permissions. The owner
-; and group can be specified either by name or by their numeric IDs.
-; Default Values: user and group are set as the running user
-;                 mode is set to 0660
-;listen.owner = nobody
-;listen.group = nobody
-;listen.mode = 0660
-; When POSIX Access Control Lists are supported you can set them using
-; these options, value is a comma separated list of user/group names.
-; When set, listen.owner and listen.group are ignored
-;listen.acl_users =
-;listen.acl_groups =
-
-; List of addresses (IPv4/IPv6) of FastCGI clients which are allowed to connect.
-; Equivalent to the FCGI_WEB_SERVER_ADDRS environment variable in the original
-; PHP FCGI (5.2.2+). Makes sense only with a tcp listening socket. Each address
-; must be separated by a comma. If this value is left blank, connections will be
-; accepted from any ip address.
-; Default Value: any
-;listen.allowed_clients = 127.0.0.1
-
-; Specify the nice(2) priority to apply to the pool processes (only if set)
-; The value can vary from -19 (highest priority) to 20 (lower priority)
-; Note: - It will only work if the FPM master process is launched as root
-;       - The pool processes will inherit the master process priority
-;         unless it specified otherwise
-; Default Value: no set
-; process.priority = -19
-
-; Set the process dumpable flag (PR_SET_DUMPABLE prctl) even if the process user
-; or group is differrent than the master process user. It allows to create process
-; core dump and ptrace the process for the pool user.
-; Default Value: no
-; process.dumpable = yes
-
-; Choose how the process manager will control the number of child processes.
-; Possible Values:
-;   static  - a fixed number (pm.max_children) of child processes;
-;   dynamic - the number of child processes are set dynamically based on the
-;             following directives. With this process management, there will be
-;             always at least 1 children.
-;             pm.max_children      - the maximum number of children that can
-;                                    be alive at the same time.
-;             pm.start_servers     - the number of children created on startup.
-;             pm.min_spare_servers - the minimum number of children in 'idle'
-;                                    state (waiting to process). If the number
-;                                    of 'idle' processes is less than this
-;                                    number then some children will be created.
-;             pm.max_spare_servers - the maximum number of children in 'idle'
-;                                    state (waiting to process). If the number
-;                                    of 'idle' processes is greater than this
-;                                    number then some children will be killed.
-;  ondemand - no children are created at startup. Children will be forked when
-;             new requests will connect. The following parameter are used:
-;             pm.max_children           - the maximum number of children that
-;                                         can be alive at the same time.
-;             pm.process_idle_timeout   - The number of seconds after which
-;                                         an idle process will be killed.
-; Note: This value is mandatory.
-pm = dynamic
-
-; The number of child processes to be created when pm is set to 'static' and the
-; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'.
-; This value sets the limit on the number of simultaneous requests that will be
-; served. Equivalent to the ApacheMaxClients directive with mpm_prefork.
-; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP
-; CGI. The below defaults are based on a server without much resources. Don't
-; forget to tweak pm.* to fit your needs.
-; Note: Used when pm is set to 'static', 'dynamic' or 'ondemand'
-; Note: This value is mandatory.
-pm.max_children = 5
-
-; The number of child processes created on startup.
-; Note: Used only when pm is set to 'dynamic'
-; Default Value: (min_spare_servers + max_spare_servers) / 2
-pm.start_servers = 2
-
-; The desired minimum number of idle server processes.
-; Note: Used only when pm is set to 'dynamic'
-; Note: Mandatory when pm is set to 'dynamic'
-pm.min_spare_servers = 1
-
-; The desired maximum number of idle server processes.
-; Note: Used only when pm is set to 'dynamic'
-; Note: Mandatory when pm is set to 'dynamic'
-pm.max_spare_servers = 3
-
-; The number of seconds after which an idle process will be killed.
-; Note: Used only when pm is set to 'ondemand'
-; Default Value: 10s
-;pm.process_idle_timeout = 10s;
-
-; The number of requests each child process should execute before respawning.
-; This can be useful to work around memory leaks in 3rd party libraries. For
-; endless request processing specify '0'. Equivalent to PHP_FCGI_MAX_REQUESTS.
-; Default Value: 0
-;pm.max_requests = 500
-
-; The URI to view the FPM status page. If this value is not set, no URI will be
-; recognized as a status page. It shows the following informations:
-;   pool                 - the name of the pool;
-;   process manager      - static, dynamic or ondemand;
-;   start time           - the date and time FPM has started;
-;   start since          - number of seconds since FPM has started;
-;   accepted conn        - the number of request accepted by the pool;
-;   listen queue         - the number of request in the queue of pending
-;                          connections (see backlog in listen(2));
-;   max listen queue     - the maximum number of requests in the queue
-;                          of pending connections since FPM has started;
-;   listen queue len     - the size of the socket queue of pending connections;
-;   idle processes       - the number of idle processes;
-;   active processes     - the number of active processes;
-;   total processes      - the number of idle + active processes;
-;   max active processes - the maximum number of active processes since FPM
-;                          has started;
-;   max children reached - number of times, the process limit has been reached,
-;                          when pm tries to start more children (works only for
-;                          pm 'dynamic' and 'ondemand');
-; Value are updated in real time.
-; Example output:
-;   pool:                 www
-;   process manager:      static
-;   start time:           01/Jul/2011:17:53:49 +0200
-;   start since:          62636
-;   accepted conn:        190460
-;   listen queue:         0
-;   max listen queue:     1
-;   listen queue len:     42
-;   idle processes:       4
-;   active processes:     11
-;   total processes:      15
-;   max active processes: 12
-;   max children reached: 0
-;
-; By default the status page output is formatted as text/plain. Passing either
-; 'html', 'xml' or 'json' in the query string will return the corresponding
-; output syntax. Example:
-;   http://www.foo.bar/status
-;   http://www.foo.bar/status?json
-;   http://www.foo.bar/status?html
-;   http://www.foo.bar/status?xml
-;
-; By default the status page only outputs short status. Passing 'full' in the
-; query string will also return status for each pool process.
-; Example:
-;   http://www.foo.bar/status?full
-;   http://www.foo.bar/status?json&full
-;   http://www.foo.bar/status?html&full
-;   http://www.foo.bar/status?xml&full
-; The Full status returns for each process:
-;   pid                  - the PID of the process;
-;   state                - the state of the process (Idle, Running, ...);
-;   start time           - the date and time the process has started;
-;   start since          - the number of seconds since the process has started;
-;   requests             - the number of requests the process has served;
-;   request duration     - the duration in µs of the requests;
-;   request method       - the request method (GET, POST, ...);
-;   request URI          - the request URI with the query string;
-;   content length       - the content length of the request (only with POST);
-;   user                 - the user (PHP_AUTH_USER) (or '-' if not set);
-;   script               - the main script called (or '-' if not set);
-;   last request cpu     - the %cpu the last request consumed
-;                          it's always 0 if the process is not in Idle state
-;                          because CPU calculation is done when the request
-;                          processing has terminated;
-;   last request memory  - the max amount of memory the last request consumed
-;                          it's always 0 if the process is not in Idle state
-;                          because memory calculation is done when the request
-;                          processing has terminated;
-; If the process is in Idle state, then informations are related to the
-; last request the process has served. Otherwise informations are related to
-; the current request being served.
-; Example output:
-;   ************************
-;   pid:                  31330
-;   state:                Running
-;   start time:           01/Jul/2011:17:53:49 +0200
-;   start since:          63087
-;   requests:             12808
-;   request duration:     1250261
-;   request method:       GET
-;   request URI:          /test_mem.php?N=10000
-;   content length:       0
-;   user:                 -
-;   script:               /home/fat/web/docs/php/test_mem.php
-;   last request cpu:     0.00
-;   last request memory:  0
-;
-; Note: There is a real-time FPM status monitoring sample web page available
-;       It's available in: /usr/share/php7/fpm/status.html
-;
-; Note: The value must start with a leading slash (/). The value can be
-;       anything, but it may not be a good idea to use the .php extension or it
-;       may conflict with a real PHP file.
-; Default Value: not set
-;pm.status_path = /status
-
-; The ping URI to call the monitoring page of FPM. If this value is not set, no
-; URI will be recognized as a ping page. This could be used to test from outside
-; that FPM is alive and responding, or to
-; - create a graph of FPM availability (rrd or such);
-; - remove a server from a group if it is not responding (load balancing);
-; - trigger alerts for the operating team (24/7).
-; Note: The value must start with a leading slash (/). The value can be
-;       anything, but it may not be a good idea to use the .php extension or it
-;       may conflict with a real PHP file.
-; Default Value: not set
-;ping.path = /ping
-
-; This directive may be used to customize the response of a ping request. The
-; response is formatted as text/plain with a 200 response code.
-; Default Value: pong
-;ping.response = pong
-
-; The access log file
-; Default: not set
-;access.log = log/php7/$pool.access.log
-
-; The access log format.
-; The following syntax is allowed
-;  %%: the '%' character
-;  %C: %CPU used by the request
-;      it can accept the following format:
-;      - %{user}C for user CPU only
-;      - %{system}C for system CPU only
-;      - %{total}C  for user + system CPU (default)
-;  %d: time taken to serve the request
-;      it can accept the following format:
-;      - %{seconds}d (default)
-;      - %{miliseconds}d
-;      - %{mili}d
-;      - %{microseconds}d
-;      - %{micro}d
-;  %e: an environment variable (same as $_ENV or $_SERVER)
-;      it must be associated with embraces to specify the name of the env
-;      variable. Some exemples:
-;      - server specifics like: %{REQUEST_METHOD}e or %{SERVER_PROTOCOL}e
-;      - HTTP headers like: %{HTTP_HOST}e or %{HTTP_USER_AGENT}e
-;  %f: script filename
-;  %l: content-length of the request (for POST request only)
-;  %m: request method
-;  %M: peak of memory allocated by PHP
-;      it can accept the following format:
-;      - %{bytes}M (default)
-;      - %{kilobytes}M
-;      - %{kilo}M
-;      - %{megabytes}M
-;      - %{mega}M
-;  %n: pool name
-;  %o: output header
-;      it must be associated with embraces to specify the name of the header:
-;      - %{Content-Type}o
-;      - %{X-Powered-By}o
-;      - %{Transfert-Encoding}o
-;      - ....
-;  %p: PID of the child that serviced the request
-;  %P: PID of the parent of the child that serviced the request
-;  %q: the query string
-;  %Q: the '?' character if query string exists
-;  %r: the request URI (without the query string, see %q and %Q)
-;  %R: remote IP address
-;  %s: status (response code)
-;  %t: server time the request was received
-;      it can accept a strftime(3) format:
-;      %d/%b/%Y:%H:%M:%S %z (default)
-;      The strftime(3) format must be encapsuled in a %{<strftime_format>}t tag
-;      e.g. for a ISO8601 formatted timestring, use: %{%Y-%m-%dT%H:%M:%S%z}t
-;  %T: time the log has been written (the request has finished)
-;      it can accept a strftime(3) format:
-;      %d/%b/%Y:%H:%M:%S %z (default)
-;      The strftime(3) format must be encapsuled in a %{<strftime_format>}t tag
-;      e.g. for a ISO8601 formatted timestring, use: %{%Y-%m-%dT%H:%M:%S%z}t
-;  %u: remote user
-;
-; Default: "%R - %u %t \"%m %r\" %s"
-;access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{mili}d %{kilo}M %C%%"
-
-; The log file for slow requests
-; Default Value: not set
-; Note: slowlog is mandatory if request_slowlog_timeout is set
-;slowlog = log/php7/$pool.slow.log
-
-; The timeout for serving a single request after which a PHP backtrace will be
-; dumped to the 'slowlog' file. A value of '0s' means 'off'.
-; Available units: s(econds)(default), m(inutes), h(ours), or d(ays)
-; Default Value: 0
-;request_slowlog_timeout = 0
-
-; Depth of slow log stack trace.
-; Default Value: 20
-;request_slowlog_trace_depth = 20
-
-; The timeout for serving a single request after which the worker process will
-; be killed. This option should be used when the 'max_execution_time' ini option
-; does not stop script execution for some reason. A value of '0' means 'off'.
-; Available units: s(econds)(default), m(inutes), h(ours), or d(ays)
-; Default Value: 0
-;request_terminate_timeout = 0
-
-; The timeout set by 'request_terminate_timeout' ini option is not engaged after
-; application calls 'fastcgi_finish_request' or when application has finished and
-; shutdown functions are being called (registered via register_shutdown_function).
-; This option will enable timeout limit to be applied unconditionally
-; even in such cases.
-; Default Value: no
-;request_terminate_timeout_track_finished = no
-
-; Set open file descriptor rlimit.
-; Default Value: system defined value
-;rlimit_files = 1024
-
-; Set max core size rlimit.
-; Possible Values: 'unlimited' or an integer greater or equal to 0
-; Default Value: system defined value
-;rlimit_core = 0
-
-; Chroot to this directory at the start. This value must be defined as an
-; absolute path. When this value is not set, chroot is not used.
-; Note: you can prefix with '$prefix' to chroot to the pool prefix or one
-; of its subdirectories. If the pool prefix is not set, the global prefix
-; will be used instead.
-; Note: chrooting is a great security feature and should be used whenever
-;       possible. However, all PHP paths will be relative to the chroot
-;       (error_log, sessions.save_path, ...).
-; Default Value: not set
-;chroot =
-
-; Chdir to this directory at the start.
-; Note: relative path can be used.
-; Default Value: current directory or / when chroot
-;chdir = /var/www
-
-; Redirect worker stdout and stderr into main error log. If not set, stdout and
-; stderr will be redirected to /dev/null according to FastCGI specs.
-; Note: on highloaded environement, this can cause some delay in the page
-; process time (several ms).
-; Default Value: no
-;catch_workers_output = yes
-
-; Decorate worker output with prefix and suffix containing information about
-; the child that writes to the log and if stdout or stderr is used as well as
-; log level and time. This options is used only if catch_workers_output is yes.
-; Settings to "no" will output data as written to the stdout or stderr.
-; Default value: yes
-;decorate_workers_output = no
-
-; Clear environment in FPM workers
-; Prevents arbitrary environment variables from reaching FPM worker processes
-; by clearing the environment in workers before env vars specified in this
-; pool configuration are added.
-; Setting to "no" will make all environment variables available to PHP code
-; via getenv(), $_ENV and $_SERVER.
-; Default Value: yes
-;clear_env = no
-
-; Limits the extensions of the main script FPM will allow to parse. This can
-; prevent configuration mistakes on the web server side. You should only limit
-; FPM to .php extensions to prevent malicious users to use other extensions to
-; execute php code.
-; Note: set an empty value to allow all extensions.
-; Default Value: .php
-;security.limit_extensions = .php .php3 .php4 .php5 .php7
-
-; Pass environment variables like LD_LIBRARY_PATH. All $VARIABLEs are taken from
-; the current environment.
-; Default Value: clean env
-;env[HOSTNAME] = $HOSTNAME
-;env[PATH] = /usr/local/bin:/usr/bin:/bin
-;env[TMP] = /tmp
-;env[TMPDIR] = /tmp
-;env[TEMP] = /tmp
-
-; Additional php.ini defines, specific to this pool of workers. These settings
-; overwrite the values previously defined in the php.ini. The directives are the
-; same as the PHP SAPI:
-;   php_value/php_flag             - you can set classic ini defines which can
-;                                    be overwritten from PHP call 'ini_set'.
-;   php_admin_value/php_admin_flag - these directives won't be overwritten by
-;                                     PHP call 'ini_set'
-; For php_*flag, valid values are on, off, 1, 0, true, false, yes or no.
-
-; Defining 'extension' will load the corresponding shared extension from
-; extension_dir. Defining 'disable_functions' or 'disable_classes' will not
-; overwrite previously defined php.ini values, but will append the new value
-; instead.
-
-; Note: path INI options can be relative and will be expanded with the prefix
-; (pool, global or /usr)
-
-; Default Value: nothing is defined by default except the values in php.ini and
-;                specified at startup with the -d argument
-;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f www@my.domain.com
-;php_flag[display_errors] = off
-;php_admin_value[error_log] = /var/log/php7/$pool.error.log
-;php_admin_flag[log_errors] = on
-;php_admin_value[memory_limit] = 32M
+; Start a new pool named 'www'.
+; the variable $pool can be used in any directive and will be replaced by the
+; pool name ('www' here)
+[www]
+
+; Per pool prefix
+; It only applies on the following directives:
+; - 'access.log'
+; - 'slowlog'
+; - 'listen' (unixsocket)
+; - 'chroot'
+; - 'chdir'
+; - 'php_values'
+; - 'php_admin_values'
+; When not set, the global prefix (or /usr) applies instead.
+; Note: This directive can also be relative to the global prefix.
+; Default Value: none
+;prefix = /path/to/pools/$pool
+
+; Unix user/group of processes
+; Note: The user is mandatory. If the group is not set, the default user's group
+;       will be used.
+user = nobody
+group = nobody
+
+; The address on which to accept FastCGI requests.
+; Valid syntaxes are:
+;   'ip.add.re.ss:port'    - to listen on a TCP socket to a specific IPv4 address on
+;                            a specific port;
+;   '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on
+;                            a specific port;
+;   'port'                 - to listen on a TCP socket to all addresses
+;                            (IPv6 and IPv4-mapped) on a specific port;
+;   '/path/to/unix/socket' - to listen on a unix socket.
+; Note: This value is mandatory.
+listen.mode = 0666
+listen = /tmp/php-cgi-74.sock
+
+; Set listen(2) backlog.
+; Default Value: 511 (-1 on FreeBSD and OpenBSD)
+;listen.backlog = 511
+
+; Set permissions for unix socket, if one is used. In Linux, read/write
+; permissions must be set in order to allow connections from a web server. Many
+; BSD-derived systems allow connections regardless of permissions. The owner
+; and group can be specified either by name or by their numeric IDs.
+; Default Values: user and group are set as the running user
+;                 mode is set to 0660
+;listen.owner = nobody
+;listen.group = nobody
+;listen.mode = 0660
+; When POSIX Access Control Lists are supported you can set them using
+; these options, value is a comma separated list of user/group names.
+; When set, listen.owner and listen.group are ignored
+;listen.acl_users =
+;listen.acl_groups =
+
+; List of addresses (IPv4/IPv6) of FastCGI clients which are allowed to connect.
+; Equivalent to the FCGI_WEB_SERVER_ADDRS environment variable in the original
+; PHP FCGI (5.2.2+). Makes sense only with a tcp listening socket. Each address
+; must be separated by a comma. If this value is left blank, connections will be
+; accepted from any ip address.
+; Default Value: any
+;listen.allowed_clients = 127.0.0.1
+
+; Specify the nice(2) priority to apply to the pool processes (only if set)
+; The value can vary from -19 (highest priority) to 20 (lower priority)
+; Note: - It will only work if the FPM master process is launched as root
+;       - The pool processes will inherit the master process priority
+;         unless it specified otherwise
+; Default Value: no set
+; process.priority = -19
+
+; Set the process dumpable flag (PR_SET_DUMPABLE prctl) even if the process user
+; or group is differrent than the master process user. It allows to create process
+; core dump and ptrace the process for the pool user.
+; Default Value: no
+; process.dumpable = yes
+
+; Choose how the process manager will control the number of child processes.
+; Possible Values:
+;   static  - a fixed number (pm.max_children) of child processes;
+;   dynamic - the number of child processes are set dynamically based on the
+;             following directives. With this process management, there will be
+;             always at least 1 children.
+;             pm.max_children      - the maximum number of children that can
+;                                    be alive at the same time.
+;             pm.start_servers     - the number of children created on startup.
+;             pm.min_spare_servers - the minimum number of children in 'idle'
+;                                    state (waiting to process). If the number
+;                                    of 'idle' processes is less than this
+;                                    number then some children will be created.
+;             pm.max_spare_servers - the maximum number of children in 'idle'
+;                                    state (waiting to process). If the number
+;                                    of 'idle' processes is greater than this
+;                                    number then some children will be killed.
+;  ondemand - no children are created at startup. Children will be forked when
+;             new requests will connect. The following parameter are used:
+;             pm.max_children           - the maximum number of children that
+;                                         can be alive at the same time.
+;             pm.process_idle_timeout   - The number of seconds after which
+;                                         an idle process will be killed.
+; Note: This value is mandatory.
+pm = dynamic
+
+; The number of child processes to be created when pm is set to 'static' and the
+; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'.
+; This value sets the limit on the number of simultaneous requests that will be
+; served. Equivalent to the ApacheMaxClients directive with mpm_prefork.
+; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP
+; CGI. The below defaults are based on a server without much resources. Don't
+; forget to tweak pm.* to fit your needs.
+; Note: Used when pm is set to 'static', 'dynamic' or 'ondemand'
+; Note: This value is mandatory.
+pm.max_children = 5
+
+; The number of child processes created on startup.
+; Note: Used only when pm is set to 'dynamic'
+; Default Value: (min_spare_servers + max_spare_servers) / 2
+pm.start_servers = 2
+
+; The desired minimum number of idle server processes.
+; Note: Used only when pm is set to 'dynamic'
+; Note: Mandatory when pm is set to 'dynamic'
+pm.min_spare_servers = 1
+
+; The desired maximum number of idle server processes.
+; Note: Used only when pm is set to 'dynamic'
+; Note: Mandatory when pm is set to 'dynamic'
+pm.max_spare_servers = 3
+
+; The number of seconds after which an idle process will be killed.
+; Note: Used only when pm is set to 'ondemand'
+; Default Value: 10s
+;pm.process_idle_timeout = 10s;
+
+; The number of requests each child process should execute before respawning.
+; This can be useful to work around memory leaks in 3rd party libraries. For
+; endless request processing specify '0'. Equivalent to PHP_FCGI_MAX_REQUESTS.
+; Default Value: 0
+;pm.max_requests = 500
+
+; The URI to view the FPM status page. If this value is not set, no URI will be
+; recognized as a status page. It shows the following informations:
+;   pool                 - the name of the pool;
+;   process manager      - static, dynamic or ondemand;
+;   start time           - the date and time FPM has started;
+;   start since          - number of seconds since FPM has started;
+;   accepted conn        - the number of request accepted by the pool;
+;   listen queue         - the number of request in the queue of pending
+;                          connections (see backlog in listen(2));
+;   max listen queue     - the maximum number of requests in the queue
+;                          of pending connections since FPM has started;
+;   listen queue len     - the size of the socket queue of pending connections;
+;   idle processes       - the number of idle processes;
+;   active processes     - the number of active processes;
+;   total processes      - the number of idle + active processes;
+;   max active processes - the maximum number of active processes since FPM
+;                          has started;
+;   max children reached - number of times, the process limit has been reached,
+;                          when pm tries to start more children (works only for
+;                          pm 'dynamic' and 'ondemand');
+; Value are updated in real time.
+; Example output:
+;   pool:                 www
+;   process manager:      static
+;   start time:           01/Jul/2011:17:53:49 +0200
+;   start since:          62636
+;   accepted conn:        190460
+;   listen queue:         0
+;   max listen queue:     1
+;   listen queue len:     42
+;   idle processes:       4
+;   active processes:     11
+;   total processes:      15
+;   max active processes: 12
+;   max children reached: 0
+;
+; By default the status page output is formatted as text/plain. Passing either
+; 'html', 'xml' or 'json' in the query string will return the corresponding
+; output syntax. Example:
+;   http://www.foo.bar/status
+;   http://www.foo.bar/status?json
+;   http://www.foo.bar/status?html
+;   http://www.foo.bar/status?xml
+;
+; By default the status page only outputs short status. Passing 'full' in the
+; query string will also return status for each pool process.
+; Example:
+;   http://www.foo.bar/status?full
+;   http://www.foo.bar/status?json&full
+;   http://www.foo.bar/status?html&full
+;   http://www.foo.bar/status?xml&full
+; The Full status returns for each process:
+;   pid                  - the PID of the process;
+;   state                - the state of the process (Idle, Running, ...);
+;   start time           - the date and time the process has started;
+;   start since          - the number of seconds since the process has started;
+;   requests             - the number of requests the process has served;
+;   request duration     - the duration in µs of the requests;
+;   request method       - the request method (GET, POST, ...);
+;   request URI          - the request URI with the query string;
+;   content length       - the content length of the request (only with POST);
+;   user                 - the user (PHP_AUTH_USER) (or '-' if not set);
+;   script               - the main script called (or '-' if not set);
+;   last request cpu     - the %cpu the last request consumed
+;                          it's always 0 if the process is not in Idle state
+;                          because CPU calculation is done when the request
+;                          processing has terminated;
+;   last request memory  - the max amount of memory the last request consumed
+;                          it's always 0 if the process is not in Idle state
+;                          because memory calculation is done when the request
+;                          processing has terminated;
+; If the process is in Idle state, then informations are related to the
+; last request the process has served. Otherwise informations are related to
+; the current request being served.
+; Example output:
+;   ************************
+;   pid:                  31330
+;   state:                Running
+;   start time:           01/Jul/2011:17:53:49 +0200
+;   start since:          63087
+;   requests:             12808
+;   request duration:     1250261
+;   request method:       GET
+;   request URI:          /test_mem.php?N=10000
+;   content length:       0
+;   user:                 -
+;   script:               /home/fat/web/docs/php/test_mem.php
+;   last request cpu:     0.00
+;   last request memory:  0
+;
+; Note: There is a real-time FPM status monitoring sample web page available
+;       It's available in: /usr/share/php7/fpm/status.html
+;
+; Note: The value must start with a leading slash (/). The value can be
+;       anything, but it may not be a good idea to use the .php extension or it
+;       may conflict with a real PHP file.
+; Default Value: not set
+;pm.status_path = /status
+
+; The ping URI to call the monitoring page of FPM. If this value is not set, no
+; URI will be recognized as a ping page. This could be used to test from outside
+; that FPM is alive and responding, or to
+; - create a graph of FPM availability (rrd or such);
+; - remove a server from a group if it is not responding (load balancing);
+; - trigger alerts for the operating team (24/7).
+; Note: The value must start with a leading slash (/). The value can be
+;       anything, but it may not be a good idea to use the .php extension or it
+;       may conflict with a real PHP file.
+; Default Value: not set
+;ping.path = /ping
+
+; This directive may be used to customize the response of a ping request. The
+; response is formatted as text/plain with a 200 response code.
+; Default Value: pong
+;ping.response = pong
+
+; The access log file
+; Default: not set
+;access.log = log/php7/$pool.access.log
+
+; The access log format.
+; The following syntax is allowed
+;  %%: the '%' character
+;  %C: %CPU used by the request
+;      it can accept the following format:
+;      - %{user}C for user CPU only
+;      - %{system}C for system CPU only
+;      - %{total}C  for user + system CPU (default)
+;  %d: time taken to serve the request
+;      it can accept the following format:
+;      - %{seconds}d (default)
+;      - %{miliseconds}d
+;      - %{mili}d
+;      - %{microseconds}d
+;      - %{micro}d
+;  %e: an environment variable (same as $_ENV or $_SERVER)
+;      it must be associated with embraces to specify the name of the env
+;      variable. Some exemples:
+;      - server specifics like: %{REQUEST_METHOD}e or %{SERVER_PROTOCOL}e
+;      - HTTP headers like: %{HTTP_HOST}e or %{HTTP_USER_AGENT}e
+;  %f: script filename
+;  %l: content-length of the request (for POST request only)
+;  %m: request method
+;  %M: peak of memory allocated by PHP
+;      it can accept the following format:
+;      - %{bytes}M (default)
+;      - %{kilobytes}M
+;      - %{kilo}M
+;      - %{megabytes}M
+;      - %{mega}M
+;  %n: pool name
+;  %o: output header
+;      it must be associated with embraces to specify the name of the header:
+;      - %{Content-Type}o
+;      - %{X-Powered-By}o
+;      - %{Transfert-Encoding}o
+;      - ....
+;  %p: PID of the child that serviced the request
+;  %P: PID of the parent of the child that serviced the request
+;  %q: the query string
+;  %Q: the '?' character if query string exists
+;  %r: the request URI (without the query string, see %q and %Q)
+;  %R: remote IP address
+;  %s: status (response code)
+;  %t: server time the request was received
+;      it can accept a strftime(3) format:
+;      %d/%b/%Y:%H:%M:%S %z (default)
+;      The strftime(3) format must be encapsuled in a %{<strftime_format>}t tag
+;      e.g. for a ISO8601 formatted timestring, use: %{%Y-%m-%dT%H:%M:%S%z}t
+;  %T: time the log has been written (the request has finished)
+;      it can accept a strftime(3) format:
+;      %d/%b/%Y:%H:%M:%S %z (default)
+;      The strftime(3) format must be encapsuled in a %{<strftime_format>}t tag
+;      e.g. for a ISO8601 formatted timestring, use: %{%Y-%m-%dT%H:%M:%S%z}t
+;  %u: remote user
+;
+; Default: "%R - %u %t \"%m %r\" %s"
+;access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{mili}d %{kilo}M %C%%"
+
+; The log file for slow requests
+; Default Value: not set
+; Note: slowlog is mandatory if request_slowlog_timeout is set
+;slowlog = log/php7/$pool.slow.log
+
+; The timeout for serving a single request after which a PHP backtrace will be
+; dumped to the 'slowlog' file. A value of '0s' means 'off'.
+; Available units: s(econds)(default), m(inutes), h(ours), or d(ays)
+; Default Value: 0
+;request_slowlog_timeout = 0
+
+; Depth of slow log stack trace.
+; Default Value: 20
+;request_slowlog_trace_depth = 20
+
+; The timeout for serving a single request after which the worker process will
+; be killed. This option should be used when the 'max_execution_time' ini option
+; does not stop script execution for some reason. A value of '0' means 'off'.
+; Available units: s(econds)(default), m(inutes), h(ours), or d(ays)
+; Default Value: 0
+;request_terminate_timeout = 0
+
+; The timeout set by 'request_terminate_timeout' ini option is not engaged after
+; application calls 'fastcgi_finish_request' or when application has finished and
+; shutdown functions are being called (registered via register_shutdown_function).
+; This option will enable timeout limit to be applied unconditionally
+; even in such cases.
+; Default Value: no
+;request_terminate_timeout_track_finished = no
+
+; Set open file descriptor rlimit.
+; Default Value: system defined value
+;rlimit_files = 1024
+
+; Set max core size rlimit.
+; Possible Values: 'unlimited' or an integer greater or equal to 0
+; Default Value: system defined value
+;rlimit_core = 0
+
+; Chroot to this directory at the start. This value must be defined as an
+; absolute path. When this value is not set, chroot is not used.
+; Note: you can prefix with '$prefix' to chroot to the pool prefix or one
+; of its subdirectories. If the pool prefix is not set, the global prefix
+; will be used instead.
+; Note: chrooting is a great security feature and should be used whenever
+;       possible. However, all PHP paths will be relative to the chroot
+;       (error_log, sessions.save_path, ...).
+; Default Value: not set
+;chroot =
+
+; Chdir to this directory at the start.
+; Note: relative path can be used.
+; Default Value: current directory or / when chroot
+;chdir = /var/www
+
+; Redirect worker stdout and stderr into main error log. If not set, stdout and
+; stderr will be redirected to /dev/null according to FastCGI specs.
+; Note: on highloaded environement, this can cause some delay in the page
+; process time (several ms).
+; Default Value: no
+;catch_workers_output = yes
+
+; Decorate worker output with prefix and suffix containing information about
+; the child that writes to the log and if stdout or stderr is used as well as
+; log level and time. This options is used only if catch_workers_output is yes.
+; Settings to "no" will output data as written to the stdout or stderr.
+; Default value: yes
+;decorate_workers_output = no
+
+; Clear environment in FPM workers
+; Prevents arbitrary environment variables from reaching FPM worker processes
+; by clearing the environment in workers before env vars specified in this
+; pool configuration are added.
+; Setting to "no" will make all environment variables available to PHP code
+; via getenv(), $_ENV and $_SERVER.
+; Default Value: yes
+;clear_env = no
+
+; Limits the extensions of the main script FPM will allow to parse. This can
+; prevent configuration mistakes on the web server side. You should only limit
+; FPM to .php extensions to prevent malicious users to use other extensions to
+; execute php code.
+; Note: set an empty value to allow all extensions.
+; Default Value: .php
+;security.limit_extensions = .php .php3 .php4 .php5 .php7
+
+; Pass environment variables like LD_LIBRARY_PATH. All $VARIABLEs are taken from
+; the current environment.
+; Default Value: clean env
+;env[HOSTNAME] = $HOSTNAME
+;env[PATH] = /usr/local/bin:/usr/bin:/bin
+;env[TMP] = /tmp
+;env[TMPDIR] = /tmp
+;env[TEMP] = /tmp
+
+; Additional php.ini defines, specific to this pool of workers. These settings
+; overwrite the values previously defined in the php.ini. The directives are the
+; same as the PHP SAPI:
+;   php_value/php_flag             - you can set classic ini defines which can
+;                                    be overwritten from PHP call 'ini_set'.
+;   php_admin_value/php_admin_flag - these directives won't be overwritten by
+;                                     PHP call 'ini_set'
+; For php_*flag, valid values are on, off, 1, 0, true, false, yes or no.
+
+; Defining 'extension' will load the corresponding shared extension from
+; extension_dir. Defining 'disable_functions' or 'disable_classes' will not
+; overwrite previously defined php.ini values, but will append the new value
+; instead.
+
+; Note: path INI options can be relative and will be expanded with the prefix
+; (pool, global or /usr)
+
+; Default Value: nothing is defined by default except the values in php.ini and
+;                specified at startup with the -d argument
+;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f www@my.domain.com
+;php_flag[display_errors] = off
+;php_admin_value[error_log] = /var/log/php7/$pool.error.log
+;php_admin_flag[log_errors] = on
+;php_admin_value[memory_limit] = 32M

+ 18 - 18
extend/Axios.php

@@ -1,19 +1,19 @@
-<?php
-
-
-class Axios
-{
-    public static function http(): \GuzzleHttp\Client
-    {
-        return new \GuzzleHttp\Client(['verify' => false]);
-    }
-
-    public static function toJson($content = "")
-    {
-        try {
-            return json_decode($content, true);
-        } catch (\Exception $e) {
-            return false;
-        }
-    }
+<?php
+
+
+class Axios
+{
+    public static function http(): \GuzzleHttp\Client
+    {
+        return new \GuzzleHttp\Client(['verify' => false]);
+    }
+
+    public static function toJson($content = "")
+    {
+        try {
+            return json_decode($content, true);
+        } catch (\Exception $e) {
+            return false;
+        }
+    }
 }

+ 27 - 27
extend/Mail.php

@@ -1,27 +1,27 @@
-<?php
-
-use Nette\Mail\Message;
-use Nette\Mail\SmtpMailer;
-use \app\model\SettingModel;
-
-class Mail
-{
-    public static function send($to = "", $text = ""): bool
-    {
-        $mail = new Message;
-        $send_mail = SettingModel::Config('smtp_email');
-        $mail->setFrom(SettingModel::Config('title', '') . " <$send_mail>")
-            ->addTo($to)
-            ->setSubject(SettingModel::Config('title', '') . '动态令牌')
-            ->setHtmlBody($text);
-        $mailer = new SmtpMailer([
-            'port' => SettingModel::Config('smtp_port'),
-            'host' => SettingModel::Config('smtp_host'),
-            'username' => SettingModel::Config('smtp_email'),
-            'password' => SettingModel::Config('smtp_password'),
-            'secure' => 'ssl',
-        ]);
-        $mailer->send($mail);
-        return true;
-    }
-}
+<?php
+
+use Nette\Mail\Message;
+use Nette\Mail\SmtpMailer;
+use \app\model\SettingModel;
+
+class Mail
+{
+    public static function send($to = "", $text = ""): bool
+    {
+        $mail = new Message;
+        $send_mail = SettingModel::Config('smtp_email');
+        $mail->setFrom(SettingModel::Config('title', '') . " <$send_mail>")
+            ->addTo($to)
+            ->setSubject(SettingModel::Config('title', '') . '动态令牌')
+            ->setHtmlBody($text);
+        $mailer = new SmtpMailer([
+            'port' => SettingModel::Config('smtp_port'),
+            'host' => SettingModel::Config('smtp_host'),
+            'username' => SettingModel::Config('smtp_email'),
+            'password' => SettingModel::Config('smtp_password'),
+            'secure' => 'ssl',
+        ]);
+        $mailer->send($mail);
+        return true;
+    }
+}

+ 73 - 73
extend/NetworkSpeedMonitor.php

@@ -1,73 +1,73 @@
-<?php
-
-class NetworkSpeedMonitor
-{
-
-//获取内存使用情况
-    public static function getMemoryUsage(): array
-    {
-        $memoryInfo = shell_exec('free -b');
-        if ($memoryInfo !== false) {
-            $lines = explode("\n", $memoryInfo);
-            $memoryData = preg_split('/\s+/', $lines[1]);
-            $totalMemory = $memoryData[1];
-            $usedMemory = $memoryData[2];
-            return ['total' => (double)$totalMemory, 'used' => (double)$usedMemory, 'percentage' => (double)number_format($usedMemory / $totalMemory * 100, 2)];
-        } else {
-            return ['total' => 0, 'used' => 0, 'percentage' => 0];
-        }
-    }
-
-
-    /**
-     * 格式化字节数为更人性化的显示方式
-     *
-     * @param int $bytes 字节数
-     * @return string 格式化后的字符串
-     */
-    public static function formatBytes(int $bytes): string
-    {
-        $units = array('Byte', 'KB', 'MB', 'GB', 'TB');
-        $index = 0;
-        while ($bytes >= 1024 && $index < count($units) - 1) {
-            $bytes /= 1024;
-            $index++;
-        }
-        return round($bytes, 2) . ' ' . $units[$index];
-    }
-
-    static function getDiskData(): array
-    {
-        $df_output = shell_exec('df -h');
-        $lines = explode("\n", $df_output);
-        $disk_usage = array();
-        for ($i = 1; $i < count($lines); $i++) {
-            if (empty(trim($lines[$i]))) {
-                continue;
-            }
-            // 分割每一行的数据,以空格作为分隔符
-            $data = preg_split('/\s+/', $lines[$i]);
-            // 提取所需的信息,例如文件系统路径、总大小、已用空间、可用空间、使用率
-            $filesystem = $data[0];
-            $total_size = $data[1];
-            $used_space = $data[2];
-            $available_space = $data[3];
-            $usage_percent = $data[4];
-            $mounts = $data[5];
-            if ($mounts == "/") {
-                // 将信息存储到关联数组中
-                $disk_usage[] = array(
-                    'filesystem' => $filesystem,
-                    'total_size' => $total_size,
-                    'used_space' => $used_space,
-                    'available_space' => $available_space,
-                    'usage_percent' => (double)preg_replace("#%#", '', $usage_percent), //去掉内容中的%,
-                    'mounted'=>$mounts
-                );
-            }
-        }
-        return $disk_usage;
-    }
-}
-
-
+<?php
+
+class NetworkSpeedMonitor
+{
+
+//获取内存使用情况
+    public static function getMemoryUsage(): array
+    {
+        $memoryInfo = shell_exec('free -b');
+        if ($memoryInfo !== false) {
+            $lines = explode("\n", $memoryInfo);
+            $memoryData = preg_split('/\s+/', $lines[1]);
+            $totalMemory = $memoryData[1];
+            $usedMemory = $memoryData[2];
+            return ['total' => (double)$totalMemory, 'used' => (double)$usedMemory, 'percentage' => (double)number_format($usedMemory / $totalMemory * 100, 2)];
+        } else {
+            return ['total' => 0, 'used' => 0, 'percentage' => 0];
+        }
+    }
+
+
+    /**
+     * 格式化字节数为更人性化的显示方式
+     *
+     * @param int $bytes 字节数
+     * @return string 格式化后的字符串
+     */
+    public static function formatBytes(int $bytes): string
+    {
+        $units = array('Byte', 'KB', 'MB', 'GB', 'TB');
+        $index = 0;
+        while ($bytes >= 1024 && $index < count($units) - 1) {
+            $bytes /= 1024;
+            $index++;
+        }
+        return round($bytes, 2) . ' ' . $units[$index];
+    }
+
+    static function getDiskData(): array
+    {
+        $df_output = shell_exec('df -h');
+        $lines = explode("\n", $df_output);
+        $disk_usage = array();
+        for ($i = 1; $i < count($lines); $i++) {
+            if (empty(trim($lines[$i]))) {
+                continue;
+            }
+            // 分割每一行的数据,以空格作为分隔符
+            $data = preg_split('/\s+/', $lines[$i]);
+            // 提取所需的信息,例如文件系统路径、总大小、已用空间、可用空间、使用率
+            $filesystem = $data[0];
+            $total_size = $data[1];
+            $used_space = $data[2];
+            $available_space = $data[3];
+            $usage_percent = $data[4];
+            $mounts = $data[5];
+            if ($mounts == "/") {
+                // 将信息存储到关联数组中
+                $disk_usage[] = array(
+                    'filesystem' => $filesystem,
+                    'total_size' => $total_size,
+                    'used_space' => $used_space,
+                    'available_space' => $available_space,
+                    'usage_percent' => (double)preg_replace("#%#", '', $usage_percent), //去掉内容中的%,
+                    'mounted'=>$mounts
+                );
+            }
+        }
+        return $disk_usage;
+    }
+}
+
+

+ 47 - 47
extend/PluginStaticSystem.php

@@ -1,48 +1,48 @@
-<?php
-
-class PluginStaticSystem
-{
-    function index($dir, $file)
-    {
-        $file = preg_replace("#\.\.#", "", $file);
-        $file = plugins_path("static/" . $file);
-        if (file_exists($file)) {
-            return download($file)->force(false)->mimeType(self::mimeType($file))->expire(60*60*24*7);
-        }
-        return response('', 404);
-    }
-
-    static function mimeType($ext): string
-    {
-        $ext = pathinfo($ext);
-        if ($ext['extension']) {
-            $ext = $ext["extension"];
-            $type = array(
-                'css' => 'text/css',
-                'js' => 'text/javascript',
-                'woff' => 'font/woff',
-                'ttf' => 'font/truetype',
-                'ico' => 'image/x-icon',
-                'jpg' => 'image/jpeg',
-                'png' => 'image/png',
-                'webp' => 'image/webp',
-                'gif' => 'image/gif',
-                'svg' => 'image/svg+xml',
-                'json'=> 'application/json',
-                'pdf' => 'application/pdf',
-                'doc' => 'application/msword',
-                'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
-                'xls' => 'application/vnd.ms-excel',
-                'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
-                'zip' => 'application/zip',
-                'rar' => 'application/x-rar-compressed',
-                'txt' => 'text/plain',
-                'html' => 'text/html',
-            );
-            if (isset($type[$ext])) {
-                return $type[$ext];
-            }
-        }
-        return '';
-    }
+<?php
+
+class PluginStaticSystem
+{
+    function index($dir, $file)
+    {
+        $file = preg_replace("#\.\.#", "", $file);
+        $file = plugins_path("static/" . $file);
+        if (file_exists($file)) {
+            return download($file)->force(false)->mimeType(self::mimeType($file))->expire(60*60*24*7);
+        }
+        return response('', 404);
+    }
+
+    static function mimeType($ext): string
+    {
+        $ext = pathinfo($ext);
+        if ($ext['extension']) {
+            $ext = $ext["extension"];
+            $type = array(
+                'css' => 'text/css',
+                'js' => 'text/javascript',
+                'woff' => 'font/woff',
+                'ttf' => 'font/truetype',
+                'ico' => 'image/x-icon',
+                'jpg' => 'image/jpeg',
+                'png' => 'image/png',
+                'webp' => 'image/webp',
+                'gif' => 'image/gif',
+                'svg' => 'image/svg+xml',
+                'json'=> 'application/json',
+                'pdf' => 'application/pdf',
+                'doc' => 'application/msword',
+                'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+                'xls' => 'application/vnd.ms-excel',
+                'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+                'zip' => 'application/zip',
+                'rar' => 'application/x-rar-compressed',
+                'txt' => 'text/plain',
+                'html' => 'text/html',
+            );
+            if (isset($type[$ext])) {
+                return $type[$ext];
+            }
+        }
+        return '';
+    }
 }

+ 175 - 175
extend/PluginsInstall.php

@@ -1,176 +1,176 @@
-<?php
-
-ini_set('max_execution_time', 0);
-ini_set('memory_limit', '500M');
-
-class PluginsInstall
-{
-    protected string $archiveFile = '';//升级文件地址
-    protected string $extractPath = '';//解压目录地址
-    protected string $root_path = '';//程序根目录
-    public string $download = '';//升级zip文件下载地址
-    protected string $directory = '';//插件目录名称
-    protected string $update_sql = '';//升级sql文件地址
-
-    //构造方法初始化一些数据
-    function __construct($info)
-    {
-        $this->archiveFile = runtime_path() . $info['name_en'] . '.zip';
-        $this->extractPath = runtime_path();
-        $this->root_path = root_path() . 'plugins/';
-        if (!is_dir($this->root_path)) {//不存在插件目录则创建
-            mkdir($this->root_path, 0777, true);
-        }
-        $this->download = $info['download'];
-        $this->directory = $info['name_en'];
-        if (isset($info['update_sql']) && $info['update_sql']) {
-            $this->update_sql = $info['update_sql'];
-        }
-    }
-
-    //运行入口
-    function run()
-    {
-        return $this->startUpgrade();
-    }
-
-    //新的进程启动升级
-    private function startUpgrade()
-    {
-        //如果有程序代码的更新资源则更新程序代码
-        if (strlen($this->download) > 1) {
-            //如果有遗留的解压资源则删除
-            $this->deleteDirectory("{$this->extractPath}{$this->directory}");
-            //如果存在旧的升级包则删除
-            $this->delZip();
-            //下载远程更新包
-            if (!$this->fileDownload()) {
-                abort(0, "资源下载失败");
-            }
-            //解压升级包
-            if (!$this->unzip($this->archiveFile, $this->extractPath)) {
-                $this->delZip();
-                abort(0, '升级资源包解压失败');
-            }
-            //拷贝覆盖
-            $this->copy();
-            //删除下载的更新包
-            $this->delZip();
-            //更新完后的一些操作
-            if (mb_strlen($this->update_sql) > 0) {
-                $this->updateSql();
-            }
-        }
-        //退出
-        return true;
-    }
-
-    private function fileDownload(): bool
-    {
-        try {
-            $f = fopen($this->download, 'r');
-            $w = fopen($this->archiveFile, 'wb+');
-            do {
-                $a = fread($f, 1024);
-                fwrite($w, $a);
-            } while ($a);
-            fclose($w);
-            fclose($f);
-        } catch (ErrorException $e) {
-            return false;
-        }
-        return true;
-    }
-
-    //删除升级包
-    function delZip()
-    {
-        if (file_exists($this->archiveFile)) {
-            unlink($this->archiveFile);
-        }
-    }
-
-    //解压
-    private function unzip($archiveFile, $extractPath): bool
-    {
-        $zip = new ZipArchive();
-        if ($zip->open($archiveFile) === TRUE) {
-            $zip->extractTo($extractPath, null);
-            $zip->close();
-        } else {
-            return false;
-        }
-        return true;
-    }
-
-    //升级的数据库
-    function updateSql()
-    {
-        $f = fopen($this->update_sql, 'r');
-        $sql = '';
-        do {
-            $sqlTmp = fread($f, 1024);
-            $sql = $sql . $sqlTmp;
-        } while ($sqlTmp);
-        fclose($f);
-        // 解析SQL文件内容并执行
-        $sql_statements = explode(';', trim($sql));
-        foreach ($sql_statements as $sql_statement) {
-            if (!empty($sql_statement)) {
-                try {
-                    \think\facade\Db::query($sql_statement);
-                } catch (Exception $e) {
-
-                }
-            }
-        }
-    }
-
-    //递归删除目录
-    function deleteDirectory($dir)
-    {
-        if (!is_dir($dir)) {
-            return;
-        }
-        $files = scandir($dir);
-        foreach ($files as $file) {
-            if ($file != '.' && $file != '..') {
-                if (is_dir("$dir/$file")) {
-                    $this->deleteDirectory("$dir/$file");
-                } else {
-                    unlink("$dir/$file");
-                }
-            }
-        }
-        rmdir($dir);
-    }
-
-    // 递归复制目录及其内容
-    function copyDir($source, $dest)
-    {
-        if (!is_dir($dest)) {
-            mkdir($dest, 0777, true);
-        }
-        $files = scandir($source);
-        foreach ($files as $file) {
-            if ($file !== '.' && $file !== '..') {
-                $src = $source . '/' . $file;
-                $dst = $dest . '/' . $file;
-                if (is_dir($src)) {
-                    $this->copyDir($src, $dst);
-                } else {
-                    copy($src, $dst);
-                }
-            }
-        }
-    }
-
-    //覆盖原来的程序
-    private function copy()
-    {
-        //移动覆盖
-        $this->copyDir("{$this->extractPath}{$this->directory}/", "{$this->root_path}{$this->directory}");
-        //删除解压目录
-        $this->deleteDirectory("{$this->extractPath}{$this->directory}");
-    }
+<?php
+
+ini_set('max_execution_time', 0);
+ini_set('memory_limit', '500M');
+
+class PluginsInstall
+{
+    protected string $archiveFile = '';//升级文件地址
+    protected string $extractPath = '';//解压目录地址
+    protected string $root_path = '';//程序根目录
+    public string $download = '';//升级zip文件下载地址
+    protected string $directory = '';//插件目录名称
+    protected string $update_sql = '';//升级sql文件地址
+
+    //构造方法初始化一些数据
+    function __construct($info)
+    {
+        $this->archiveFile = runtime_path() . $info['name_en'] . '.zip';
+        $this->extractPath = runtime_path();
+        $this->root_path = root_path() . 'plugins/';
+        if (!is_dir($this->root_path)) {//不存在插件目录则创建
+            mkdir($this->root_path, 0777, true);
+        }
+        $this->download = $info['download'];
+        $this->directory = $info['name_en'];
+        if (isset($info['update_sql']) && $info['update_sql']) {
+            $this->update_sql = $info['update_sql'];
+        }
+    }
+
+    //运行入口
+    function run()
+    {
+        return $this->startUpgrade();
+    }
+
+    //新的进程启动升级
+    private function startUpgrade()
+    {
+        //如果有程序代码的更新资源则更新程序代码
+        if (strlen($this->download) > 1) {
+            //如果有遗留的解压资源则删除
+            $this->deleteDirectory("{$this->extractPath}{$this->directory}");
+            //如果存在旧的升级包则删除
+            $this->delZip();
+            //下载远程更新包
+            if (!$this->fileDownload()) {
+                abort(0, "资源下载失败");
+            }
+            //解压升级包
+            if (!$this->unzip($this->archiveFile, $this->extractPath)) {
+                $this->delZip();
+                abort(0, '升级资源包解压失败');
+            }
+            //拷贝覆盖
+            $this->copy();
+            //删除下载的更新包
+            $this->delZip();
+            //更新完后的一些操作
+            if (mb_strlen($this->update_sql) > 0) {
+                $this->updateSql();
+            }
+        }
+        //退出
+        return true;
+    }
+
+    private function fileDownload(): bool
+    {
+        try {
+            $f = fopen($this->download, 'r');
+            $w = fopen($this->archiveFile, 'wb+');
+            do {
+                $a = fread($f, 1024);
+                fwrite($w, $a);
+            } while ($a);
+            fclose($w);
+            fclose($f);
+        } catch (ErrorException $e) {
+            return false;
+        }
+        return true;
+    }
+
+    //删除升级包
+    function delZip()
+    {
+        if (file_exists($this->archiveFile)) {
+            unlink($this->archiveFile);
+        }
+    }
+
+    //解压
+    private function unzip($archiveFile, $extractPath): bool
+    {
+        $zip = new ZipArchive();
+        if ($zip->open($archiveFile) === TRUE) {
+            $zip->extractTo($extractPath, null);
+            $zip->close();
+        } else {
+            return false;
+        }
+        return true;
+    }
+
+    //升级的数据库
+    function updateSql()
+    {
+        $f = fopen($this->update_sql, 'r');
+        $sql = '';
+        do {
+            $sqlTmp = fread($f, 1024);
+            $sql = $sql . $sqlTmp;
+        } while ($sqlTmp);
+        fclose($f);
+        // 解析SQL文件内容并执行
+        $sql_statements = explode(';', trim($sql));
+        foreach ($sql_statements as $sql_statement) {
+            if (!empty($sql_statement)) {
+                try {
+                    \think\facade\Db::query($sql_statement);
+                } catch (Exception $e) {
+
+                }
+            }
+        }
+    }
+
+    //递归删除目录
+    function deleteDirectory($dir)
+    {
+        if (!is_dir($dir)) {
+            return;
+        }
+        $files = scandir($dir);
+        foreach ($files as $file) {
+            if ($file != '.' && $file != '..') {
+                if (is_dir("$dir/$file")) {
+                    $this->deleteDirectory("$dir/$file");
+                } else {
+                    unlink("$dir/$file");
+                }
+            }
+        }
+        rmdir($dir);
+    }
+
+    // 递归复制目录及其内容
+    function copyDir($source, $dest)
+    {
+        if (!is_dir($dest)) {
+            mkdir($dest, 0777, true);
+        }
+        $files = scandir($source);
+        foreach ($files as $file) {
+            if ($file !== '.' && $file !== '..') {
+                $src = $source . '/' . $file;
+                $dst = $dest . '/' . $file;
+                if (is_dir($src)) {
+                    $this->copyDir($src, $dst);
+                } else {
+                    copy($src, $dst);
+                }
+            }
+        }
+    }
+
+    //覆盖原来的程序
+    private function copy()
+    {
+        //移动覆盖
+        $this->copyDir("{$this->extractPath}{$this->directory}/", "{$this->root_path}{$this->directory}");
+        //删除解压目录
+        $this->deleteDirectory("{$this->extractPath}{$this->directory}");
+    }
 }

+ 177 - 177
extend/Upgrade2.php

@@ -1,178 +1,178 @@
-<?php
-ini_set('max_execution_time', 0);
-ini_set('memory_limit', '500M');
-class Upgrade2
-{
-    protected string $archiveFile = "";//升级文件地址
-    protected string $extractPath = "";//解压目录地址
-    protected string $root_path = "";//程序根目录
-    public string $update_download_url = "";//升级zip文件下载地址
-    public string $update_sql_url = "";//升级sql脚本文件地址
-    public string $update_script = "";//升级后执行的脚本地址
-
-    //构造方法初始化一些数据
-    function __construct($update_download_url = null, $update_sql_url = null, $update_script = null)
-    {
-        $this->archiveFile = runtime_path() . 'mtab.zip';
-        $this->extractPath = runtime_path();
-        $this->root_path = root_path();
-        if ($update_download_url !== null) {
-            $this->update_download_url = $update_download_url;
-        }
-        if ($update_sql_url !== null) {
-            $this->update_sql_url = $update_sql_url;
-        }
-        if ($update_script !== null) {
-            $this->update_script = $update_script;
-        }
-    }
-
-    //运行入口
-    function run(): bool
-    {
-        return $this->startUpgrade();
-    }
-
-    //新的进程启动升级
-    private function startUpgrade()
-    {
-        //如果有程序代码的更新资源则更新程序代码
-        if (strlen($this->update_download_url) > 1) {
-            //如果有遗留的解压资源则删除
-            $this->deleteDirectory("{$this->extractPath}mtab");
-            //如果存在旧的升级包则删除
-            $this->delZip();
-            //下载远程更新包
-            if(!$this->fileDownload()){
-                abort(0, '资源下载失败');
-            }
-            //解压升级包
-            if (!$this->unzip($this->archiveFile, $this->extractPath)) {
-                $this->delZip();
-                abort(0, '升级资源包解压失败');
-            }
-            $this->deleteDirectory(public_path().'dist/');//删除旧的网站文件
-            //拷贝覆盖
-            $this->copy();
-            //删除下载的更新包
-            $this->delZip();
-            //更新完后的一些操作
-        }
-        //如果有数据库的更新资源则更新程序代码
-        if (strlen($this->update_sql_url) > 1) {
-            $this->updateSql();
-        }
-        //退出
-        return true;
-    }
-
-    private function fileDownload(): bool
-    {
-        try {
-            $f = fopen($this->update_download_url, 'r');
-            $w = fopen($this->archiveFile, 'wb+');
-            do {
-                $a = fread($f, 1024);
-                fwrite($w, $a);
-            } while ($a);
-            fclose($w);
-            fclose($f);
-        } catch (ErrorException $e) {
-            return false;
-        }
-        return true;
-    }
-
-    //删除升级包
-    function delZip()
-    {
-        if (file_exists($this->archiveFile)) {
-            unlink($this->archiveFile);
-        }
-    }
-
-    //解压
-    private function unzip($archiveFile, $extractPath): bool
-    {
-        $zip = new ZipArchive();
-        if ($zip->open($archiveFile) === TRUE) {
-            $zip->extractTo($extractPath, null);
-            $zip->close();
-        } else {
-            return false;
-        }
-        return true;
-    }
-
-    //升级的数据库
-    function updateSql()
-    {
-        $f = fopen($this->update_sql_url, 'r');
-        $sql = "";
-        do {
-            $sqlTmp = fread($f, 1024);
-            $sql = $sql . $sqlTmp;
-        } while ($sqlTmp);
-        fclose($f);
-        // 解析SQL文件内容并执行
-        $sql_statements = explode(';', trim($sql));
-        foreach ($sql_statements as $sql_statement) {
-            if (!empty($sql_statement)) {
-                try {
-                    \think\facade\Db::execute($sql_statement);
-                } catch (Exception $e) {
-
-                }
-            }
-        }
-    }
-
-    //递归删除目录
-    function deleteDirectory($dir)
-    {
-        if (!is_dir($dir)) {
-            return;
-        }
-        $files = scandir($dir);
-        foreach ($files as $file) {
-            if ($file != '.' && $file != '..') {
-                if (is_dir("$dir/$file")) {
-                    $this->deleteDirectory("$dir/$file");
-                } else {
-                    unlink("$dir/$file");
-                }
-            }
-        }
-        rmdir($dir);
-    }
-
-    // 递归复制目录及其内容
-    function copyDir($source, $dest)
-    {
-        if (!is_dir($dest)) {
-            mkdir($dest, 0777, true);
-        }
-        $files = scandir($source);
-        foreach ($files as $file) {
-            if ($file !== '.' && $file !== '..') {
-                $src = $source . '/' . $file;
-                $dst = $dest . '/' . $file;
-                if (is_dir($src)) {
-                    $this->copyDir($src, $dst);
-                } else {
-                    copy($src, $dst);
-                }
-            }
-        }
-    }
-
-    //覆盖原来的程序
-    private function copy()
-    {
-        //移动覆盖
-        $this->copyDir("{$this->extractPath}mtab/", "{$this->root_path}");
-        //删除解压目录
-        $this->deleteDirectory("{$this->extractPath}mtab");
-    }
-
+<?php
+ini_set('max_execution_time', 0);
+ini_set('memory_limit', '500M');
+class Upgrade2
+{
+    protected string $archiveFile = "";//升级文件地址
+    protected string $extractPath = "";//解压目录地址
+    protected string $root_path = "";//程序根目录
+    public string $update_download_url = "";//升级zip文件下载地址
+    public string $update_sql_url = "";//升级sql脚本文件地址
+    public string $update_script = "";//升级后执行的脚本地址
+
+    //构造方法初始化一些数据
+    function __construct($update_download_url = null, $update_sql_url = null, $update_script = null)
+    {
+        $this->archiveFile = runtime_path() . 'mtab.zip';
+        $this->extractPath = runtime_path();
+        $this->root_path = root_path();
+        if ($update_download_url !== null) {
+            $this->update_download_url = $update_download_url;
+        }
+        if ($update_sql_url !== null) {
+            $this->update_sql_url = $update_sql_url;
+        }
+        if ($update_script !== null) {
+            $this->update_script = $update_script;
+        }
+    }
+
+    //运行入口
+    function run(): bool
+    {
+        return $this->startUpgrade();
+    }
+
+    //新的进程启动升级
+    private function startUpgrade()
+    {
+        //如果有程序代码的更新资源则更新程序代码
+        if (strlen($this->update_download_url) > 1) {
+            //如果有遗留的解压资源则删除
+            $this->deleteDirectory("{$this->extractPath}mtab");
+            //如果存在旧的升级包则删除
+            $this->delZip();
+            //下载远程更新包
+            if(!$this->fileDownload()){
+                abort(0, '资源下载失败');
+            }
+            //解压升级包
+            if (!$this->unzip($this->archiveFile, $this->extractPath)) {
+                $this->delZip();
+                abort(0, '升级资源包解压失败');
+            }
+            $this->deleteDirectory(public_path().'dist/');//删除旧的网站文件
+            //拷贝覆盖
+            $this->copy();
+            //删除下载的更新包
+            $this->delZip();
+            //更新完后的一些操作
+        }
+        //如果有数据库的更新资源则更新程序代码
+        if (strlen($this->update_sql_url) > 1) {
+            $this->updateSql();
+        }
+        //退出
+        return true;
+    }
+
+    private function fileDownload(): bool
+    {
+        try {
+            $f = fopen($this->update_download_url, 'r');
+            $w = fopen($this->archiveFile, 'wb+');
+            do {
+                $a = fread($f, 1024);
+                fwrite($w, $a);
+            } while ($a);
+            fclose($w);
+            fclose($f);
+        } catch (ErrorException $e) {
+            return false;
+        }
+        return true;
+    }
+
+    //删除升级包
+    function delZip()
+    {
+        if (file_exists($this->archiveFile)) {
+            unlink($this->archiveFile);
+        }
+    }
+
+    //解压
+    private function unzip($archiveFile, $extractPath): bool
+    {
+        $zip = new ZipArchive();
+        if ($zip->open($archiveFile) === TRUE) {
+            $zip->extractTo($extractPath, null);
+            $zip->close();
+        } else {
+            return false;
+        }
+        return true;
+    }
+
+    //升级的数据库
+    function updateSql()
+    {
+        $f = fopen($this->update_sql_url, 'r');
+        $sql = "";
+        do {
+            $sqlTmp = fread($f, 1024);
+            $sql = $sql . $sqlTmp;
+        } while ($sqlTmp);
+        fclose($f);
+        // 解析SQL文件内容并执行
+        $sql_statements = explode(';', trim($sql));
+        foreach ($sql_statements as $sql_statement) {
+            if (!empty($sql_statement)) {
+                try {
+                    \think\facade\Db::execute($sql_statement);
+                } catch (Exception $e) {
+
+                }
+            }
+        }
+    }
+
+    //递归删除目录
+    function deleteDirectory($dir)
+    {
+        if (!is_dir($dir)) {
+            return;
+        }
+        $files = scandir($dir);
+        foreach ($files as $file) {
+            if ($file != '.' && $file != '..') {
+                if (is_dir("$dir/$file")) {
+                    $this->deleteDirectory("$dir/$file");
+                } else {
+                    unlink("$dir/$file");
+                }
+            }
+        }
+        rmdir($dir);
+    }
+
+    // 递归复制目录及其内容
+    function copyDir($source, $dest)
+    {
+        if (!is_dir($dest)) {
+            mkdir($dest, 0777, true);
+        }
+        $files = scandir($source);
+        foreach ($files as $file) {
+            if ($file !== '.' && $file !== '..') {
+                $src = $source . '/' . $file;
+                $dst = $dest . '/' . $file;
+                if (is_dir($src)) {
+                    $this->copyDir($src, $dst);
+                } else {
+                    copy($src, $dst);
+                }
+            }
+        }
+    }
+
+    //覆盖原来的程序
+    private function copy()
+    {
+        //移动覆盖
+        $this->copyDir("{$this->extractPath}mtab/", "{$this->root_path}");
+        //删除解压目录
+        $this->deleteDirectory("{$this->extractPath}mtab");
+    }
+
 }

+ 433 - 433
install.sql

@@ -1,434 +1,434 @@
-# 创建Card数据表
-
-create table if not exists card
-(
-    id int auto_increment
-        primary key
-) comment '卡片数据表';
-
-
-alter table card
-    add name varchar(200) null;
-
-alter table card
-    add name_en varchar(200) null;
-
-alter table card
-    add status int default 0 null;
-
-alter table card
-    add version int default 0 null;
-
-alter table card
-    add tips varchar(255) null comment '说明';
-
-alter table card
-    add create_time datetime null comment '添加时间';
-
-alter table card
-    add src text null comment 'logo';
-
-alter table card
-    add url varchar(255) null comment '卡片地址';
-
-alter table card
-    add `window` varchar(255) null comment '窗口地址';
-
-alter table card
-    add update_time datetime null;
-
-alter table card
-    add install_num int default 0 null;
-
-alter table card
-    add setting varchar(200) null comment '设置页面的url';
-
-alter table card
-    add dict_option longtext null comment '配置的参数';
-
-
-#创建config数据表
-
-create table if not exists config
-(
-    user_id int null
-) comment '用户配置数据表';
-
-create index config_user_id_index
-    on config (user_id);
-
-alter table config
-    add config longtext null;
-
-# 创建file数据表
-
-create table if not exists file
-(
-    id bigint auto_increment
-        primary key
-)
-    comment '文件';
-
-alter table file
-    add path varchar(255) null;
-
-alter table file
-    add user_id int null;
-
-alter table file
-    add create_time datetime null;
-
-alter table file
-    add size double default 0 null comment '尺寸';
-
-alter table file
-    add mime_type varchar(100) null comment '文件类型';
-
-
-#创建history数据表
-
-create table if not exists history
-(
-    id bigint auto_increment
-        primary key,
-
-    constraint history_id_uindex
-        unique (id)
-)
-    comment 'link历史数据';
-
-alter table history
-    add user_id int null;
-
-alter table history
-    add link longtext null;
-
-alter table history
-    add create_time datetime null comment '创建时间';
-
-
-#创建link数据表
-
-create table if not exists link
-(
-    user_id int null
-);
-
-create index link_user_id_index
-    on link (user_id);
-
-
-alter table link
-    add update_time datetime null comment '更新时间';
-
-alter table link
-    add link longtext null;
-
-
-#创建link_folder数据表
-
-create table if not exists link_folder
-(
-    id int auto_increment comment 'id'
-        primary key
-)
-    comment '标签链接分类';
-
-alter table link_folder
-    add name varchar(50) null comment '分类名称';
-
-alter table link_folder
-    add sort int default 0 null;
-
-
-#创建link_store数据表
-
-create table if not exists linkstore
-(
-    id int auto_increment
-        primary key,
-    constraint linkStore_id_uindex
-        unique (id)
-);
-
-alter table linkstore
-    add name varchar(255) null;
-
-alter table linkstore
-    add src varchar(255) null;
-
-alter table linkstore
-    add url varchar(255) null;
-
-alter table linkstore
-    add type varchar(20) default 'icon' null;
-
-alter table linkstore
-    add size varchar(20) default '1x1' null;
-
-alter table linkstore
-    add create_time datetime null;
-
-alter table linkstore
-    add hot bigint default 0 null;
-
-alter table linkstore
-    add area varchar(20) default '' null comment '专区';
-
-alter table linkstore
-    add tips varchar(255) null comment '介绍';
-
-alter table linkstore
-    add domain varchar(255) null;
-
-alter table linkstore
-    add app int default 0 null comment '是否app';
-
-alter table linkstore
-    add install_num int default 0 null comment '安装量';
-
-alter table linkstore
-    add bgColor varchar(30) null comment '背景颜色';
-
-alter table linkstore
-    add vip int default 0 null comment '是否会员可见 0所有人 1=会员';
-
-alter table linkstore
-    add custom text null comment '自定义配置';
-
-alter table linkstore
-    add user_id int null comment '用户id';
-
-alter table linkstore
-    add status int default 1 null comment '状态 1=展示 0=待审核';
-
-
-#创建note数据表
-
-create table if not exists note
-(
-    id bigint auto_increment
-        primary key,
-    constraint note_id_uindex
-        unique (id)
-);
-
-alter table note
-    add user_id bigint null;
-
-alter table note
-    add title varchar(50) null;
-
-alter table note
-    add text text null;
-
-alter table note
-    add create_time datetime null;
-
-alter table note
-    add update_time datetime null;
-
-alter table note
-    add weight int default 0 null;
-
-create index note_user_id_index
-    on note (user_id);
-
-#创建search_engine数据表
-
-create table if not exists search_engine
-(
-    id int auto_increment
-        primary key
-)
-    comment '搜索引擎';
-
-alter table search_engine
-    add name varchar(50) null comment '名称';
-
-alter table search_engine
-    add icon varchar(255) null comment '图标 128x128';
-
-alter table search_engine
-    add url varchar(255) null comment '跳转url';
-
-alter table search_engine
-    add sort int default 0 null comment '排序';
-
-alter table search_engine
-    add create_time datetime null comment '添加时间';
-
-alter table search_engine
-    add status int default 0 null comment '状态 0=关闭 1=启用';
-
-alter table search_engine
-    add tips varchar(250) null comment '搜索引擎介绍';
-
-
-#创建setting表
-
-create table if not exists setting
-(
-    `keys` varchar(200) not null
-        primary key
-);
-
-alter table setting
-    add value text null;
-
-#创建tabbar数据表
-
-create table if not exists tabbar
-(
-    user_id int null
-)
-    comment '用户页脚信息';
-
-alter table tabbar
-    add tabs longtext null;
-
-alter table tabbar
-    add update_time datetime null;
-
-#创建token表
-
-create table if not exists token
-(
-    id bigint auto_increment
-        primary key,
-    constraint token_id_uindex
-        unique (id)
-);
-
-alter table token
-    add user_id int null;
-
-alter table token
-    add token tinytext null;
-
-alter table token
-    add create_time int null;
-
-alter table token
-    add ip tinytext null;
-
-alter table token
-    add user_agent tinytext null;
-
-alter table token
-    add access_token varchar(200) null comment 'qq的令牌';
-
-
-#创建user表
-
-create table if not exists user
-(
-    id int auto_increment
-        primary key,
-    constraint user_id_uindex
-        unique (id)
-);
-
-alter table user
-    add avatar varchar(255) null comment '头像';
-
-alter table user
-    add mail varchar(50) null;
-
-alter table user
-    add password tinytext null;
-
-alter table user
-    add create_time datetime null;
-
-alter table user
-    add login_ip varchar(100) null comment '登录IP';
-
-alter table user
-    add register_ip varchar(100) null comment '注册IP';
-
-alter table user
-    add manager int default 0 null;
-
-alter table user
-    add login_fail_count int default 0 null;
-
-alter table user
-    add login_time datetime null comment '登录时间';
-
-alter table user
-    add qq_open_id varchar(200) null comment 'qq开放平台Id';
-
-alter table user
-    add nickname varchar(200) null comment '昵称';
-
-alter table user
-    add status int default 0 null comment '用户账号状态 0正常 1冻结';
-
-#创建user_search_engine表
-
-create table if not exists user_search_engine
-(
-    user_id int not null
-        primary key,
-    constraint user_search_engine_pk
-        unique (user_id)
-)
-    comment '用户搜索引擎同步表';
-
-alter table user_search_engine
-    add list longtext null;
-
-
-#创建wallpaper表
-
-create table if not exists wallpaper
-(
-    id int auto_increment
-        primary key
-);
-
-alter table wallpaper
-    add type int null comment '1=folder;0=assets';
-
-alter table wallpaper
-    add folder int null comment '0';
-
-alter table wallpaper
-    add mime int default 0 null comment '文件类型0=images,1=video';
-
-alter table wallpaper
-    add url text null comment '图片地址';
-
-alter table wallpaper
-    add cover text null comment '封面';
-
-alter table wallpaper
-    add create_time datetime null;
-
-alter table wallpaper
-    add name varchar(200) null comment '标题';
-
-alter table wallpaper
-    add sort int default 999 null;
-
-##创建结束
-
-#补充内容 针对<5.7数据库使用longtext
-
-alter table history
-    modify link longtext null;
-
-alter table link
-    modify link longtext null;
-
-alter table config
-    modify config longtext null;
-
-alter table tabbar
-    modify tabs longtext null;
-
-alter table user_search_engine
-    modify list longtext null;
-
+# 创建Card数据表
+
+create table if not exists card
+(
+    id int auto_increment
+        primary key
+) comment '卡片数据表';
+
+
+alter table card
+    add name varchar(200) null;
+
+alter table card
+    add name_en varchar(200) null;
+
+alter table card
+    add status int default 0 null;
+
+alter table card
+    add version int default 0 null;
+
+alter table card
+    add tips varchar(255) null comment '说明';
+
+alter table card
+    add create_time datetime null comment '添加时间';
+
+alter table card
+    add src text null comment 'logo';
+
+alter table card
+    add url varchar(255) null comment '卡片地址';
+
+alter table card
+    add `window` varchar(255) null comment '窗口地址';
+
+alter table card
+    add update_time datetime null;
+
+alter table card
+    add install_num int default 0 null;
+
+alter table card
+    add setting varchar(200) null comment '设置页面的url';
+
+alter table card
+    add dict_option longtext null comment '配置的参数';
+
+
+#创建config数据表
+
+create table if not exists config
+(
+    user_id int null
+) comment '用户配置数据表';
+
+create index config_user_id_index
+    on config (user_id);
+
+alter table config
+    add config longtext null;
+
+# 创建file数据表
+
+create table if not exists file
+(
+    id bigint auto_increment
+        primary key
+)
+    comment '文件';
+
+alter table file
+    add path varchar(255) null;
+
+alter table file
+    add user_id int null;
+
+alter table file
+    add create_time datetime null;
+
+alter table file
+    add size double default 0 null comment '尺寸';
+
+alter table file
+    add mime_type varchar(100) null comment '文件类型';
+
+
+#创建history数据表
+
+create table if not exists history
+(
+    id bigint auto_increment
+        primary key,
+
+    constraint history_id_uindex
+        unique (id)
+)
+    comment 'link历史数据';
+
+alter table history
+    add user_id int null;
+
+alter table history
+    add link longtext null;
+
+alter table history
+    add create_time datetime null comment '创建时间';
+
+
+#创建link数据表
+
+create table if not exists link
+(
+    user_id int null
+);
+
+create index link_user_id_index
+    on link (user_id);
+
+
+alter table link
+    add update_time datetime null comment '更新时间';
+
+alter table link
+    add link longtext null;
+
+
+#创建link_folder数据表
+
+create table if not exists link_folder
+(
+    id int auto_increment comment 'id'
+        primary key
+)
+    comment '标签链接分类';
+
+alter table link_folder
+    add name varchar(50) null comment '分类名称';
+
+alter table link_folder
+    add sort int default 0 null;
+
+
+#创建link_store数据表
+
+create table if not exists linkstore
+(
+    id int auto_increment
+        primary key,
+    constraint linkStore_id_uindex
+        unique (id)
+);
+
+alter table linkstore
+    add name varchar(255) null;
+
+alter table linkstore
+    add src varchar(255) null;
+
+alter table linkstore
+    add url varchar(255) null;
+
+alter table linkstore
+    add type varchar(20) default 'icon' null;
+
+alter table linkstore
+    add size varchar(20) default '1x1' null;
+
+alter table linkstore
+    add create_time datetime null;
+
+alter table linkstore
+    add hot bigint default 0 null;
+
+alter table linkstore
+    add area varchar(20) default '' null comment '专区';
+
+alter table linkstore
+    add tips varchar(255) null comment '介绍';
+
+alter table linkstore
+    add domain varchar(255) null;
+
+alter table linkstore
+    add app int default 0 null comment '是否app';
+
+alter table linkstore
+    add install_num int default 0 null comment '安装量';
+
+alter table linkstore
+    add bgColor varchar(30) null comment '背景颜色';
+
+alter table linkstore
+    add vip int default 0 null comment '是否会员可见 0所有人 1=会员';
+
+alter table linkstore
+    add custom text null comment '自定义配置';
+
+alter table linkstore
+    add user_id int null comment '用户id';
+
+alter table linkstore
+    add status int default 1 null comment '状态 1=展示 0=待审核';
+
+
+#创建note数据表
+
+create table if not exists note
+(
+    id bigint auto_increment
+        primary key,
+    constraint note_id_uindex
+        unique (id)
+);
+
+alter table note
+    add user_id bigint null;
+
+alter table note
+    add title varchar(50) null;
+
+alter table note
+    add text text null;
+
+alter table note
+    add create_time datetime null;
+
+alter table note
+    add update_time datetime null;
+
+alter table note
+    add weight int default 0 null;
+
+create index note_user_id_index
+    on note (user_id);
+
+#创建search_engine数据表
+
+create table if not exists search_engine
+(
+    id int auto_increment
+        primary key
+)
+    comment '搜索引擎';
+
+alter table search_engine
+    add name varchar(50) null comment '名称';
+
+alter table search_engine
+    add icon varchar(255) null comment '图标 128x128';
+
+alter table search_engine
+    add url varchar(255) null comment '跳转url';
+
+alter table search_engine
+    add sort int default 0 null comment '排序';
+
+alter table search_engine
+    add create_time datetime null comment '添加时间';
+
+alter table search_engine
+    add status int default 0 null comment '状态 0=关闭 1=启用';
+
+alter table search_engine
+    add tips varchar(250) null comment '搜索引擎介绍';
+
+
+#创建setting表
+
+create table if not exists setting
+(
+    `keys` varchar(200) not null
+        primary key
+);
+
+alter table setting
+    add value text null;
+
+#创建tabbar数据表
+
+create table if not exists tabbar
+(
+    user_id int null
+)
+    comment '用户页脚信息';
+
+alter table tabbar
+    add tabs longtext null;
+
+alter table tabbar
+    add update_time datetime null;
+
+#创建token表
+
+create table if not exists token
+(
+    id bigint auto_increment
+        primary key,
+    constraint token_id_uindex
+        unique (id)
+);
+
+alter table token
+    add user_id int null;
+
+alter table token
+    add token tinytext null;
+
+alter table token
+    add create_time int null;
+
+alter table token
+    add ip tinytext null;
+
+alter table token
+    add user_agent tinytext null;
+
+alter table token
+    add access_token varchar(200) null comment 'qq的令牌';
+
+
+#创建user表
+
+create table if not exists user
+(
+    id int auto_increment
+        primary key,
+    constraint user_id_uindex
+        unique (id)
+);
+
+alter table user
+    add avatar varchar(255) null comment '头像';
+
+alter table user
+    add mail varchar(50) null;
+
+alter table user
+    add password tinytext null;
+
+alter table user
+    add create_time datetime null;
+
+alter table user
+    add login_ip varchar(100) null comment '登录IP';
+
+alter table user
+    add register_ip varchar(100) null comment '注册IP';
+
+alter table user
+    add manager int default 0 null;
+
+alter table user
+    add login_fail_count int default 0 null;
+
+alter table user
+    add login_time datetime null comment '登录时间';
+
+alter table user
+    add qq_open_id varchar(200) null comment 'qq开放平台Id';
+
+alter table user
+    add nickname varchar(200) null comment '昵称';
+
+alter table user
+    add status int default 0 null comment '用户账号状态 0正常 1冻结';
+
+#创建user_search_engine表
+
+create table if not exists user_search_engine
+(
+    user_id int not null
+        primary key,
+    constraint user_search_engine_pk
+        unique (user_id)
+)
+    comment '用户搜索引擎同步表';
+
+alter table user_search_engine
+    add list longtext null;
+
+
+#创建wallpaper表
+
+create table if not exists wallpaper
+(
+    id int auto_increment
+        primary key
+);
+
+alter table wallpaper
+    add type int null comment '1=folder;0=assets';
+
+alter table wallpaper
+    add folder int null comment '0';
+
+alter table wallpaper
+    add mime int default 0 null comment '文件类型0=images,1=video';
+
+alter table wallpaper
+    add url text null comment '图片地址';
+
+alter table wallpaper
+    add cover text null comment '封面';
+
+alter table wallpaper
+    add create_time datetime null;
+
+alter table wallpaper
+    add name varchar(200) null comment '标题';
+
+alter table wallpaper
+    add sort int default 999 null;
+
+##创建结束
+
+#补充内容 针对<5.7数据库使用longtext
+
+alter table history
+    modify link longtext null;
+
+alter table link
+    modify link longtext null;
+
+alter table config
+    modify config longtext null;
+
+alter table tabbar
+    modify tabs longtext null;
+
+alter table user_search_engine
+    modify list longtext null;
+
 #补充结束

+ 108 - 108
nginx.conf

@@ -1,108 +1,108 @@
-# /etc/nginx/nginx.conf
-
-user nginx;
-
-# Set number of worker processes automatically based on number of CPU cores.
-worker_processes auto;
-
-# Enables the use of JIT for regular expressions to speed-up their processing.
-pcre_jit on;
-
-# Configures default error logger.
-error_log /var/log/nginx/error.log warn;
-
-# Includes files with directives to load dynamic modules.
-include /etc/nginx/modules/*.conf;
-
-
-events {
-	# The maximum number of simultaneous connections that can be opened by
-	# a worker process.
-	worker_connections 1024;
-}
-
-http {
-	# Includes mapping of file name extensions to MIME types of responses
-	# and defines the default type.
-	include /etc/nginx/mime.types;
-	default_type application/octet-stream;
-
-	# Name servers used to resolve names of upstream servers into addresses.
-	# It's also needed when using tcpsocket and udpsocket in Lua modules.
-	#resolver 1.1.1.1 1.0.0.1 2606:4700:4700::1111 2606:4700:4700::1001;
-
-	# Don't tell nginx version to the clients. Default is 'on'.
-	server_tokens off;
-
-	# Specifies the maximum accepted body size of a client request, as
-	# indicated by the request header Content-Length. If the stated content
-	# length is greater than this size, then the client receives the HTTP
-	# error code 413. Set to 0 to disable. Default is '1m'.
-	client_max_body_size 1m;
-
-	# Sendfile copies data between one FD and other from within the kernel,
-	# which is more efficient than read() + write(). Default is off.
-	sendfile on;
-
-	# Causes nginx to attempt to send its HTTP response head in one packet,
-	# instead of using partial frames. Default is 'off'.
-	tcp_nopush on;
-
-
-	# Enables the specified protocols. Default is TLSv1 TLSv1.1 TLSv1.2.
-	# TIP: If you're not obligated to support ancient clients, remove TLSv1.1.
-	ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
-
-	# Path of the file with Diffie-Hellman parameters for EDH ciphers.
-	# TIP: Generate with: `openssl dhparam -out /etc/ssl/nginx/dh2048.pem 2048`
-	#ssl_dhparam /etc/ssl/nginx/dh2048.pem;
-
-	# Specifies that our cipher suits should be preferred over client ciphers.
-	# Default is 'off'.
-	ssl_prefer_server_ciphers on;
-
-	# Enables a shared SSL cache with size that can hold around 8000 sessions.
-	# Default is 'none'.
-	ssl_session_cache shared:SSL:2m;
-
-	# Specifies a time during which a client may reuse the session parameters.
-	# Default is '5m'.
-	ssl_session_timeout 1h;
-
-	# Disable TLS session tickets (they are insecure). Default is 'on'.
-	ssl_session_tickets off;
-
-
-	# Enable gzipping of responses.
-	#gzip on;
-
-	# Set the Vary HTTP header as defined in the RFC 2616. Default is 'off'.
-	gzip_vary on;
-
-
-	# Helper variable for proxying websockets.
-	map $http_upgrade $connection_upgrade {
-		default upgrade;
-		'' close;
-	}
-
-
-	# Specifies the main log format.
-	log_format main '$remote_addr - $remote_user [$time_local] "$request" '
-			'$status $body_bytes_sent "$http_referer" '
-			'"$http_user_agent" "$http_x_forwarded_for"';
-
-	# Sets the path, format, and configuration for a buffered log write.
-	access_log /var/log/nginx/access.log main;
-
-
-	# Includes virtual hosts configs.
-	include /etc/nginx/http.d/*.conf;
-
-	# WARNING: Don't use this directory for virtual hosts anymore.
-	# This include will be moved to the root context in Alpine 3.14.
-	#include /etc/nginx/conf.d/*.conf;
-}
-
-# TIP: Uncomment if you use stream module.
-#include /etc/nginx/stream.conf;
+# /etc/nginx/nginx.conf
+
+user nginx;
+
+# Set number of worker processes automatically based on number of CPU cores.
+worker_processes auto;
+
+# Enables the use of JIT for regular expressions to speed-up their processing.
+pcre_jit on;
+
+# Configures default error logger.
+error_log /var/log/nginx/error.log warn;
+
+# Includes files with directives to load dynamic modules.
+include /etc/nginx/modules/*.conf;
+
+
+events {
+	# The maximum number of simultaneous connections that can be opened by
+	# a worker process.
+	worker_connections 1024;
+}
+
+http {
+	# Includes mapping of file name extensions to MIME types of responses
+	# and defines the default type.
+	include /etc/nginx/mime.types;
+	default_type application/octet-stream;
+
+	# Name servers used to resolve names of upstream servers into addresses.
+	# It's also needed when using tcpsocket and udpsocket in Lua modules.
+	#resolver 1.1.1.1 1.0.0.1 2606:4700:4700::1111 2606:4700:4700::1001;
+
+	# Don't tell nginx version to the clients. Default is 'on'.
+	server_tokens off;
+
+	# Specifies the maximum accepted body size of a client request, as
+	# indicated by the request header Content-Length. If the stated content
+	# length is greater than this size, then the client receives the HTTP
+	# error code 413. Set to 0 to disable. Default is '1m'.
+	client_max_body_size 1m;
+
+	# Sendfile copies data between one FD and other from within the kernel,
+	# which is more efficient than read() + write(). Default is off.
+	sendfile on;
+
+	# Causes nginx to attempt to send its HTTP response head in one packet,
+	# instead of using partial frames. Default is 'off'.
+	tcp_nopush on;
+
+
+	# Enables the specified protocols. Default is TLSv1 TLSv1.1 TLSv1.2.
+	# TIP: If you're not obligated to support ancient clients, remove TLSv1.1.
+	ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
+
+	# Path of the file with Diffie-Hellman parameters for EDH ciphers.
+	# TIP: Generate with: `openssl dhparam -out /etc/ssl/nginx/dh2048.pem 2048`
+	#ssl_dhparam /etc/ssl/nginx/dh2048.pem;
+
+	# Specifies that our cipher suits should be preferred over client ciphers.
+	# Default is 'off'.
+	ssl_prefer_server_ciphers on;
+
+	# Enables a shared SSL cache with size that can hold around 8000 sessions.
+	# Default is 'none'.
+	ssl_session_cache shared:SSL:2m;
+
+	# Specifies a time during which a client may reuse the session parameters.
+	# Default is '5m'.
+	ssl_session_timeout 1h;
+
+	# Disable TLS session tickets (they are insecure). Default is 'on'.
+	ssl_session_tickets off;
+
+
+	# Enable gzipping of responses.
+	#gzip on;
+
+	# Set the Vary HTTP header as defined in the RFC 2616. Default is 'off'.
+	gzip_vary on;
+
+
+	# Helper variable for proxying websockets.
+	map $http_upgrade $connection_upgrade {
+		default upgrade;
+		'' close;
+	}
+
+
+	# Specifies the main log format.
+	log_format main '$remote_addr - $remote_user [$time_local] "$request" '
+			'$status $body_bytes_sent "$http_referer" '
+			'"$http_user_agent" "$http_x_forwarded_for"';
+
+	# Sets the path, format, and configuration for a buffered log write.
+	access_log /var/log/nginx/access.log main;
+
+
+	# Includes virtual hosts configs.
+	include /etc/nginx/http.d/*.conf;
+
+	# WARNING: Don't use this directory for virtual hosts anymore.
+	# This include will be moved to the root context in Alpine 3.14.
+	#include /etc/nginx/conf.d/*.conf;
+}
+
+# TIP: Uncomment if you use stream module.
+#include /etc/nginx/stream.conf;

+ 4 - 4
nginx.rewrite

@@ -1,5 +1,5 @@
-location ~^/ {
-    if (!-e $request_filename){
-        rewrite  ^(.*)$  /index.php?s=$1  last;   break;
-    }
+location ~^/ {
+    if (!-e $request_filename){
+        rewrite  ^(.*)$  /index.php?s=$1  last;   break;
+    }
 }

File diff suppressed because it is too large
+ 0 - 79
public/404.html


+ 0 - 17
public/dist/index.html

@@ -1,17 +0,0 @@
-<!doctype html>
-<html lang="zh">
-<head>
-    <meta charset="UTF-8"/>
-    <link href="{$favicon}" rel="icon"/>
-    <meta name="version" content="{$version}">
-    <meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1">
-    <title>{$title}</title>{$customHead|raw}
-    <meta content="{$keywords}" name="keywords"/>
-    <meta content="{$description}" name="description"/>
-    <script type="module" crossorigin src="/dist/assets/index.1716208563220.js"></script>
-    <link rel="stylesheet" href="/dist/assets/index.17162085632204.css">
-</head>
-<body style="background-color:#fff">
-<div id="app"></div>
-</body>
-</html>

+ 15 - 15
public/index.php

@@ -1,15 +1,15 @@
-<?php
-
-namespace think;
-define('app_version', '2.0.1');
-define('app_version_code', 201);
-require __DIR__ . '/../vendor/autoload.php';
-
-// 执行HTTP应用并响应
-$http = (new App())->http;
-
-$response = $http->run();
-
-$response->send();
-
-$http->end($response);
+<?php
+
+namespace think;
+define('app_version', '2.0.2');
+define('app_version_code', 202);
+require __DIR__ . '/../vendor/autoload.php';
+
+// 执行HTTP应用并响应
+$http = (new App())->http;
+
+$response = $http->run();
+
+$response->send();
+
+$http->end($response);

+ 429 - 429
public/install.php

@@ -1,429 +1,429 @@
-<?php
-function params($key, $default_value = '')
-{
-    return $_POST[$key] ?? $default_value;
-}
-
-$run = true;
-if (file_exists('./installed.lock')) {//如果没有安装的就提示安装
-    header('Location: /');
-    $run = false;
-    return false;//阻止后续执行
-}
-if (!$run) {
-    exit();
-}
-// 获取当前PHP版本
-$phpVersion = phpversion();
-// 检查是否大于7.4
-$php_version = false;
-if (version_compare($phpVersion, '7.4', '>')) {
-    $php_version = true;
-}
-$fileinfo_ext = false;
-if (extension_loaded('fileinfo')) {
-    $fileinfo_ext = true;
-}
-$zip_ext = false;
-if (extension_loaded('zip')) {
-    $zip_ext = true;
-}
-$mysqli_ext = false;
-if (extension_loaded('mysqli')) {
-    $mysqli_ext = true;
-}
-$curl_ext = false;
-if (extension_loaded('curl')) {
-    $curl_ext = true;
-}
-// 连接数据库
-$servername = 'localhost';
-$db_username = params('db_username', false);
-$db_password = params('db_password', false);
-$db_host = params('db_host', '');
-$db_port = params('db_port', 3306);
-$table_name = params('table_name', '');
-$admin_email = params('admin_email', '');
-$admin_password = params('admin_password', '');
-$database_type = params('database_type', 1);//1=全新安装,2=使用已存在数据库不安装数据库
-$error = false;
-$conn = null;
-$status = false;
-
-function isDatabaseVersionValid($conn): bool
-{
-    global $error;
-    $serverInfo = mysqli_get_server_info($conn);
-    if (strpos($serverInfo, 'MariaDB') !== false) {
-        return true;
-        // preg_match('/^(\d+\.\d+\.\d+)/', $serverInfo, $matches);
-        // $mariaDbVersion = $matches[1];
-        // if (version_compare(trim($mariaDbVersion), '10.0.0', '>=')) {//验证MariaDB数据库版本是否大于10.2.3
-        //     return true;
-        // }else{
-        //     $error = '<div style="text-align: center">数据库相关错误,详细信息如下</div>' . "<div style='margin-top:15px;text-align: center'>MariaDB版本低于10.0.0,请升级MariaDB版本至10.0.0及以上!</div>";
-        //     return false;
-        // }
-    }
-    if (version_compare($serverInfo, '5.7', '>=')) {//验证数据库版本是否大于5.7
-        return true;
-    }
-    $error = '<div style="text-align: center">数据库相关错误,详细信息如下</div>' . "<div style='margin-top:15px;text-align: center'>Mysql数据库版本低于5.7,请升级Mysql数据库至5.7及以上!</div>";
-    return false;
-}
-
-
-if ($db_username && $php_version && $fileinfo_ext && $curl_ext && $zip_ext) {
-    $conn = new mysqli($db_host, $db_username, $db_password, null, $db_port);
-    if ($conn->connect_error) {
-        $error = '<div style="text-align: center">数据库相关错误,详细信息如下</div>' . "<div style='margin-top:15px;text-align: center'>{$conn->connect_error}</div>";
-    } else if (!isDatabaseVersionValid($conn)) {
-
-    } else {
-        if ($database_type == 1) {//全新安装
-            $sql = "DROP DATABASE $table_name";//删除原来的
-            $conn->query($sql);
-            $sql = "CREATE DATABASE $table_name";//创建新的
-            if ($conn->query($sql) !== TRUE) {
-                $error = '数据表创建失败';
-            }
-            $conn = new mysqli($db_host, $db_username, $db_password, $table_name, $db_port);
-            //数据库的格式内容数据
-            $sql_file_content = file_get_contents('../install.sql');
-            // 解析SQL文件内容并执行
-            $sql_statements = explode(';', trim($sql_file_content));
-            foreach ($sql_statements as $sql_statement) {
-                if (!empty($sql_statement)) {
-                    $conn->query($sql_statement);
-                }
-            }
-            //默认的一些基础数据
-            $sql_file_content = file_get_contents('../defaultData.sql');
-            // 解析SQL文件内容并执行
-            $sql_statements = explode(';', trim($sql_file_content));
-            foreach ($sql_statements as $sql_statement) {
-                if (!empty($sql_statement)) {
-                    $conn->query($sql_statement);
-                }
-            }
-            $admin_password = md5($admin_password);
-            //添加默认管理员
-            $AdminSql = ("
-                    INSERT INTO user (mail, password, create_time, login_ip, register_ip, manager, login_fail_count, login_time)
-                    VALUES ('$admin_email', '$admin_password', null, null, null, 1, DEFAULT, null);
-                 ");
-            $conn->query($AdminSql);
-            $conn->close();
-            file_put_contents('./installed.lock', 'installed');
-            $status = true;
-        }
-    }
-}
-if ($status) {
-    $env = <<<EOF
-APP_DEBUG = false
-
-[APP]
-
-[DATABASE]
-TYPE = mysql
-HOSTNAME = {$db_host}
-DATABASE = {$table_name}
-USERNAME = {$db_username}
-PASSWORD = {$db_password}
-HOSTPORT =  {$db_port}
-CHARSET = utf8mb4
-DEBUG = false
-
-[CACHE]
-DRIVER = file
-
-EOF;
-    file_put_contents('../.env', $env);
-}
-
-?>
-
-<?php if ($status === false) { ?>
-    <!DOCTYPE html>
-    <html lang="zh">
-    <head>
-        <title>mTab新标签页安装页面</title>
-        <meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'>
-        <style>
-            body {
-                font-family: Arial, sans-serif;
-                background: url("static/background.jpeg") no-repeat center/cover;
-            }
-
-            *::-webkit-scrollbar {
-                display: none;
-            }
-
-            * {
-                scrollbar-width: none;
-                -ms-overflow-style: none;
-            }
-
-            form {
-                max-width: 900px;
-                margin: 0 auto 100px;
-                background-color: #fff;
-                padding: 20px 20px 30px 20px;
-                border-radius: 12px;
-                box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
-            }
-
-            label {
-                display: block;
-                margin-bottom: 10px;
-                font-weight: bold;
-                margin-top: 15px;
-            }
-
-            input[type='text'], input[type='password'], input[type='number'] {
-                text-indent: 15px;
-                width: calc(100% - 8px);
-                height: 45px;
-                line-height: 30px;
-                border: 2px solid transparent;
-                border-radius: 10px;
-                outline: none;
-                background-color: #f3f3f3;
-                color: #0d0c22;
-                transition: .5s ease;
-                font-size: 16px;
-            }
-
-            input[type='submit'] {
-                width: 100%;
-                background-color: rgb(255, 171, 84);
-                color: #fff;
-                border-radius: 0.9em;
-                border: none;
-                padding: 0.8em 1.2em 0.8em 1em;
-                transition: all ease-in-out 0.2s;
-                font-size: 16px;
-            }
-
-            .input:focus, input:hover {
-                outline: none;
-                border-color: rgba(255, 171, 84, 0.85);
-                background-color: #fff;
-                box-shadow: 0 0 0 5px rgba(253, 224, 99, 0.3);
-            }
-
-            input[type='submit']:hover {
-                background-color: rgb(255, 171, 84);
-            }
-
-            #error-popup {
-                position: fixed;
-                left: 0;
-                right: 0;
-                top: 0;
-                bottom: 0;
-                margin: auto;
-                width: 500px;
-                height: fit-content;
-                padding: 10px 20px 20px;
-                background-color: rgb(255, 101, 2);
-                color: #fff;
-                border-radius: 12px;
-                justify-content: center;
-                z-index: 9999;
-            }
-        </style>
-        <link rel='icon' href='/static/mtab.png'>
-    </head>
-    <body>
-    <?php if ($error) { ?>
-        <div id='error-popup'>
-            <div style='text-align: center'><h2>错误提示</h2></div>
-            <p style='text-align: center;font-size: 18px'><?php echo $error ?></p>
-        </div>
-
-        <script>
-            setTimeout(function () {
-                document.querySelector("#error-popup").style.display = "none";
-            }, 5000);
-        </script>
-        }
-    <?php } ?>
-    <h1 style="text-align: center;color: #fff">mTab书签安装程序</h1>
-    <form method='post' action='install.php'>
-        <div style="font-size: 25px;font-weight: bold;margin-bottom: 15px;">
-            请优先授权程序可执行权限(755及以上的权限),并检查并安装以下php扩展
-        </div>
-        <div style="margin-bottom: 30px;display: flex;flex-wrap: wrap;gap:15px 40px;">
-            <b>
-                php版本>7.4
-                <?php if ($php_version) { ?>
-                    <span style='color: limegreen'>✔</span>
-                <?php } else { ?>
-                    <span style='color: red'>✘</span>
-                <?php } ?>
-            </b>
-            <b>
-                fileinfo扩展
-                <?php if ($fileinfo_ext) { ?>
-                    <span style='color: limegreen'>✔</span>
-                <?php } else { ?>
-                    <span style='color: red'>✘</span>
-                <?php } ?>
-            </b>
-            <b>
-                zip扩展
-                <?php if ($zip_ext) { ?>
-                    <span style='color: limegreen'>✔</span>
-                <?php } else { ?>
-                    <span style='color: red'>✘</span>
-                <?php } ?>
-            </b>
-            <b>
-                curl扩展
-                <?php if ($curl_ext) { ?>
-                    <span style='color: limegreen'>✔</span>
-                <?php } else { ?>
-                    <span style='color: red'>✘</span>
-                <?php } ?>
-            </b>
-            <b>
-                mysqli扩展
-                <?php if ($mysqli_ext) { ?>
-                    <span style='color: limegreen'>✔</span>
-                <?php } else { ?>
-                    <span style='color: red'>✘</span>
-                <?php } ?>
-            </b>
-        </div>
-        <label for='db_host'>mysql数据库地址: <span style='font-size: 13px;color: #1d5cdc'>Mysql数据库版本必须大于等于5.7及以上,内存大于6G推荐Mysql8,小于推荐Mysql5.7</span></label>
-        <input value="<?php echo $db_host; ?>"
-               placeholder="本地一般是127.0.0.1,docker部署请勿填写127.0.0.1,请使用内网ip或docker容器网关ip或者能到达数据库服务的ip"
-               type='text' name='db_host' id='db_host'
-               required><br>
-        <label for='db_port'>mysql数据库端口号:</label>
-        <input type='number' value="<?php echo $db_port; ?>" placeholder='默认 3306' name='db_port' id='db_port'
-               required><br>
-        <label for='db_username'>mysql数据库用户名:<span style="font-size: 13px;color: #1d5cdc">前提是当前用户名有数据库的控制权限,并且允许访问来源权限是当前服务的IP,或者是 %(代表任何来源)</span></label>
-        <input type='text' placeholder="请输入数据库用户名" value="<?php echo $db_username; ?>" name='db_username'
-               id='db_username' required><br>
-        <label for='db_password'>mysql数据库密码:</label>
-        <input type='text' name='db_password' value="<?php echo $db_password; ?>" placeholder="请输入数据库密码"
-               id='db_password' required><br>
-        <label for='table_name'>mysql数据库名称:</label>
-        <input type='text' value="<?php echo $table_name; ?>" placeholder="请输入创建的数据库名称" name='table_name'
-               id='table_name' required><br>
-
-        <label for='redis_port'>管理员邮箱账号:</label>
-        <input type='text' value="<?php echo $admin_email; ?>" placeholder='请输入邮箱,用于默认的管理员账号登录使用'
-               name='admin_email'
-               id='redis_port'
-               required><br>
-        <label for='redis_port'>管理员密码:</label>
-        <input type='text' value="<?php echo $admin_password; ?>" placeholder='请设置管理员账号密码'
-               name='admin_password'
-               id='redis_port'
-               required><br>
-        <label for='redis_port'>数据库安装其他选项</label>
-        <label for='install_other'></label>
-        <label>
-            <input checked type='radio' name='database_type' value='1' required>
-            全新安装(如果数据库存在则删除原来的数据库,重新安装)
-        </label>
-        <label>
-            <input type='radio' name='database_type' value='2' required>
-            使用已存在数据库(不会覆盖数据库,仅安装代码,注意的是数据库的数据表要和最新版本的程序的库一致,否则使用旧版本的数据库表<b
-                    style="color: red">却</b>安装最新版的代码,否则导致有些服务异常)
-        </label>
-        <input type='submit' value='安装' style="margin-top: 30px">
-        <div style='margin-top: 30px;font-size: 14px;line-height: 24px;display: flex;flex-direction: column;align-items: center;text-align: center'>
-            <b style="font-size: 18px">温馨提示</b>如果您在安装阶段出现问题或对安装方式(特别是Nas部署用户)不知如何操作,可联系我们为您提供解决方法或辅助您安装,本服务不收费
-            <a target='_blank'
-               style='text-decoration: none;color: #ffffff;padding: 5px 15px;background: #1e9fff;border-radius: 30px;margin-top: 10px;'
-               href='https://mtab.cc'>点我跳转至官网,点击右下角客服即可联系</a>
-        </div>
-    </form>
-
-    </body>
-    </html>
-<?php } else { ?>
-    <!DOCTYPE html>
-    <html lang="zh">
-    <head>
-        <meta charset="UTF-8">
-        <title>网站安装完毕</title>
-        <style>
-            body {
-                font-family: Arial, sans-serif;
-                background-color: #fff;
-                text-align: center;
-                margin: 0;
-                padding: 0;
-            }
-
-            .container {
-                background-color: #fff;
-                padding: 20px;
-                border-radius: 10px;
-                box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
-                margin: 50px auto;
-                max-width: 800px;
-            }
-
-            h1 {
-                color: #333;
-            }
-
-            p {
-                color: #666;
-                font-size: 18px;
-            }
-
-            .btn-container {
-                margin-top: 20px;
-            }
-
-            .btn {
-                display: inline-block;
-                padding: 10px 20px;
-                margin-right: 10px;
-                background-color: rgb(255, 171, 84);
-                color: #fff;
-                text-decoration: none;
-                border-radius: 5px;
-                transition: background-color 0.3s;
-            }
-
-            .btn:hover {
-                background-color: rgb(255, 147, 38);
-            }
-        </style>
-        <link rel='icon' href='favicon.png'>
-    </head>
-    <body>
-    <div class='container'>
-        <h1 style="align-content: center">mTab书签安装完毕</h1>
-        <div style="display: flex;justify-content: center">
-            <svg style="width: 100px" t='1694607796889' class='icon' viewBox='0 0 1024 1024' version='1.1'
-                 xmlns='http://www.w3.org/2000/svg'
-                 p-id='40460' width='128' height='128'>
-                <path d='M512 0C230.4 0 0 230.4 0 512c0 281.6 230.4 512 512 512 281.6 0 512-230.4 512-512C1024 230.4 793.6 0 512 0zM512 960c-249.6 0-448-204.8-448-448 0-249.6 204.8-448 448-448 249.6 0 448 198.4 448 448C960 761.6 761.6 960 512 960zM691.2 339.2 454.4 576 332.8 454.4c-19.2-19.2-51.2-19.2-76.8 0C243.2 480 243.2 512 262.4 531.2l153.6 153.6c19.2 19.2 51.2 19.2 70.4 0l51.2-51.2 224-224c19.2-19.2 25.6-51.2 0-70.4C742.4 320 710.4 320 691.2 339.2z'
-                      fill='#54E283' p-id='40461'></path>
-            </svg>
-        </div>
-        <p>欢迎使用mTab书签,<br>点击下方按钮跳转到首页。</p>
-        <div class='btn-container'>
-            <a class='btn' href='/'>进入首页</a>
-        </div>
-        <p>后台进入方式,需要用管理员账户登录客户端<br/></p>
-        <p><b>鼠标在桌面右击打开菜单->点击设置->个人中心->登录管理员的账号</b><br/>
-            <b>
-                ->再次进入个人中心即可看到->管理后台->进入即可</b></p>
-        <p>这是一个多用户的书签导航程序,用户之间数据是隔离的不受干扰</p>
-        <p>可以使用鼠标右键在桌面点击呼出菜单。</p>
-        <p>很多功能就在鼠标右键菜单内。别怪我没告诉你哟hahaha~</p>
-    </div>
-    </body>
-    </html>
-<?php } ?>
+<?php
+function params($key, $default_value = '')
+{
+    return $_POST[$key] ?? $default_value;
+}
+
+$run = true;
+if (file_exists('./installed.lock')) {//如果没有安装的就提示安装
+    header('Location: /');
+    $run = false;
+    return false;//阻止后续执行
+}
+if (!$run) {
+    exit();
+}
+// 获取当前PHP版本
+$phpVersion = phpversion();
+// 检查是否大于7.4
+$php_version = false;
+if (version_compare($phpVersion, '7.4', '>')) {
+    $php_version = true;
+}
+$fileinfo_ext = false;
+if (extension_loaded('fileinfo')) {
+    $fileinfo_ext = true;
+}
+$zip_ext = false;
+if (extension_loaded('zip')) {
+    $zip_ext = true;
+}
+$mysqli_ext = false;
+if (extension_loaded('mysqli')) {
+    $mysqli_ext = true;
+}
+$curl_ext = false;
+if (extension_loaded('curl')) {
+    $curl_ext = true;
+}
+// 连接数据库
+$servername = 'localhost';
+$db_username = params('db_username', false);
+$db_password = params('db_password', false);
+$db_host = params('db_host', '');
+$db_port = params('db_port', 3306);
+$table_name = params('table_name', '');
+$admin_email = params('admin_email', '');
+$admin_password = params('admin_password', '');
+$database_type = params('database_type', 1);//1=全新安装,2=使用已存在数据库不安装数据库
+$error = false;
+$conn = null;
+$status = false;
+
+function isDatabaseVersionValid($conn): bool
+{
+    global $error;
+    $serverInfo = mysqli_get_server_info($conn);
+    if (strpos($serverInfo, 'MariaDB') !== false) {
+        return true;
+        // preg_match('/^(\d+\.\d+\.\d+)/', $serverInfo, $matches);
+        // $mariaDbVersion = $matches[1];
+        // if (version_compare(trim($mariaDbVersion), '10.0.0', '>=')) {//验证MariaDB数据库版本是否大于10.2.3
+        //     return true;
+        // }else{
+        //     $error = '<div style="text-align: center">数据库相关错误,详细信息如下</div>' . "<div style='margin-top:15px;text-align: center'>MariaDB版本低于10.0.0,请升级MariaDB版本至10.0.0及以上!</div>";
+        //     return false;
+        // }
+    }
+    if (version_compare($serverInfo, '5.7', '>=')) {//验证数据库版本是否大于5.7
+        return true;
+    }
+    $error = '<div style="text-align: center">数据库相关错误,详细信息如下</div>' . "<div style='margin-top:15px;text-align: center'>Mysql数据库版本低于5.7,请升级Mysql数据库至5.7及以上!</div>";
+    return false;
+}
+
+
+if ($db_username && $php_version && $fileinfo_ext && $curl_ext && $zip_ext) {
+    $conn = new mysqli($db_host, $db_username, $db_password, null, $db_port);
+    if ($conn->connect_error) {
+        $error = '<div style="text-align: center">数据库相关错误,详细信息如下</div>' . "<div style='margin-top:15px;text-align: center'>{$conn->connect_error}</div>";
+    } else if (!isDatabaseVersionValid($conn)) {
+
+    } else {
+        if ($database_type == 1) {//全新安装
+            $sql = "DROP DATABASE $table_name";//删除原来的
+            $conn->query($sql);
+            $sql = "CREATE DATABASE $table_name";//创建新的
+            if ($conn->query($sql) !== TRUE) {
+                $error = '数据表创建失败';
+            }
+            $conn = new mysqli($db_host, $db_username, $db_password, $table_name, $db_port);
+            //数据库的格式内容数据
+            $sql_file_content = file_get_contents('../install.sql');
+            // 解析SQL文件内容并执行
+            $sql_statements = explode(';', trim($sql_file_content));
+            foreach ($sql_statements as $sql_statement) {
+                if (!empty($sql_statement)) {
+                    $conn->query($sql_statement);
+                }
+            }
+            //默认的一些基础数据
+            $sql_file_content = file_get_contents('../defaultData.sql');
+            // 解析SQL文件内容并执行
+            $sql_statements = explode(';', trim($sql_file_content));
+            foreach ($sql_statements as $sql_statement) {
+                if (!empty($sql_statement)) {
+                    $conn->query($sql_statement);
+                }
+            }
+            $admin_password = md5($admin_password);
+            //添加默认管理员
+            $AdminSql = ("
+                    INSERT INTO user (mail, password, create_time, login_ip, register_ip, manager, login_fail_count, login_time)
+                    VALUES ('$admin_email', '$admin_password', null, null, null, 1, DEFAULT, null);
+                 ");
+            $conn->query($AdminSql);
+            $conn->close();
+            file_put_contents('./installed.lock', 'installed');
+            $status = true;
+        }
+    }
+}
+if ($status) {
+    $env = <<<EOF
+APP_DEBUG = false
+
+[APP]
+
+[DATABASE]
+TYPE = mysql
+HOSTNAME = {$db_host}
+DATABASE = {$table_name}
+USERNAME = {$db_username}
+PASSWORD = {$db_password}
+HOSTPORT =  {$db_port}
+CHARSET = utf8mb4
+DEBUG = false
+
+[CACHE]
+DRIVER = file
+
+EOF;
+    file_put_contents('../.env', $env);
+}
+
+?>
+
+<?php if ($status === false) { ?>
+    <!DOCTYPE html>
+    <html lang="zh">
+    <head>
+        <title>mTab新标签页安装页面</title>
+        <meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'>
+        <style>
+            body {
+                font-family: Arial, sans-serif;
+                background: url("static/background.jpeg") no-repeat center/cover;
+            }
+
+            *::-webkit-scrollbar {
+                display: none;
+            }
+
+            * {
+                scrollbar-width: none;
+                -ms-overflow-style: none;
+            }
+
+            form {
+                max-width: 900px;
+                margin: 0 auto 100px;
+                background-color: #fff;
+                padding: 20px 20px 30px 20px;
+                border-radius: 12px;
+                box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+            }
+
+            label {
+                display: block;
+                margin-bottom: 10px;
+                font-weight: bold;
+                margin-top: 15px;
+            }
+
+            input[type='text'], input[type='password'], input[type='number'] {
+                text-indent: 15px;
+                width: calc(100% - 8px);
+                height: 45px;
+                line-height: 30px;
+                border: 2px solid transparent;
+                border-radius: 10px;
+                outline: none;
+                background-color: #f3f3f3;
+                color: #0d0c22;
+                transition: .5s ease;
+                font-size: 16px;
+            }
+
+            input[type='submit'] {
+                width: 100%;
+                background-color: rgb(255, 171, 84);
+                color: #fff;
+                border-radius: 0.9em;
+                border: none;
+                padding: 0.8em 1.2em 0.8em 1em;
+                transition: all ease-in-out 0.2s;
+                font-size: 16px;
+            }
+
+            .input:focus, input:hover {
+                outline: none;
+                border-color: rgba(255, 171, 84, 0.85);
+                background-color: #fff;
+                box-shadow: 0 0 0 5px rgba(253, 224, 99, 0.3);
+            }
+
+            input[type='submit']:hover {
+                background-color: rgb(255, 171, 84);
+            }
+
+            #error-popup {
+                position: fixed;
+                left: 0;
+                right: 0;
+                top: 0;
+                bottom: 0;
+                margin: auto;
+                width: 500px;
+                height: fit-content;
+                padding: 10px 20px 20px;
+                background-color: rgb(255, 101, 2);
+                color: #fff;
+                border-radius: 12px;
+                justify-content: center;
+                z-index: 9999;
+            }
+        </style>
+        <link rel='icon' href='/static/mtab.png'>
+    </head>
+    <body>
+    <?php if ($error) { ?>
+        <div id='error-popup'>
+            <div style='text-align: center'><h2>错误提示</h2></div>
+            <p style='text-align: center;font-size: 18px'><?php echo $error ?></p>
+        </div>
+
+        <script>
+            setTimeout(function () {
+                document.querySelector("#error-popup").style.display = "none";
+            }, 5000);
+        </script>
+        }
+    <?php } ?>
+    <h1 style="text-align: center;color: #fff">mTab书签安装程序</h1>
+    <form method='post' action='install.php'>
+        <div style="font-size: 25px;font-weight: bold;margin-bottom: 15px;">
+            请优先授权程序可执行权限(755及以上的权限),并检查并安装以下php扩展
+        </div>
+        <div style="margin-bottom: 30px;display: flex;flex-wrap: wrap;gap:15px 40px;">
+            <b>
+                php版本>7.4
+                <?php if ($php_version) { ?>
+                    <span style='color: limegreen'>✔</span>
+                <?php } else { ?>
+                    <span style='color: red'>✘</span>
+                <?php } ?>
+            </b>
+            <b>
+                fileinfo扩展
+                <?php if ($fileinfo_ext) { ?>
+                    <span style='color: limegreen'>✔</span>
+                <?php } else { ?>
+                    <span style='color: red'>✘</span>
+                <?php } ?>
+            </b>
+            <b>
+                zip扩展
+                <?php if ($zip_ext) { ?>
+                    <span style='color: limegreen'>✔</span>
+                <?php } else { ?>
+                    <span style='color: red'>✘</span>
+                <?php } ?>
+            </b>
+            <b>
+                curl扩展
+                <?php if ($curl_ext) { ?>
+                    <span style='color: limegreen'>✔</span>
+                <?php } else { ?>
+                    <span style='color: red'>✘</span>
+                <?php } ?>
+            </b>
+            <b>
+                mysqli扩展
+                <?php if ($mysqli_ext) { ?>
+                    <span style='color: limegreen'>✔</span>
+                <?php } else { ?>
+                    <span style='color: red'>✘</span>
+                <?php } ?>
+            </b>
+        </div>
+        <label for='db_host'>mysql数据库地址: <span style='font-size: 13px;color: #1d5cdc'>Mysql数据库版本必须大于等于5.7及以上,内存大于6G推荐Mysql8,小于推荐Mysql5.7</span></label>
+        <input value="<?php echo $db_host; ?>"
+               placeholder="本地一般是127.0.0.1,docker部署请勿填写127.0.0.1,请使用内网ip或docker容器网关ip或者能到达数据库服务的ip"
+               type='text' name='db_host' id='db_host'
+               required><br>
+        <label for='db_port'>mysql数据库端口号:</label>
+        <input type='number' value="<?php echo $db_port; ?>" placeholder='默认 3306' name='db_port' id='db_port'
+               required><br>
+        <label for='db_username'>mysql数据库用户名:<span style="font-size: 13px;color: #1d5cdc">前提是当前用户名有数据库的控制权限,并且允许访问来源权限是当前服务的IP,或者是 %(代表任何来源)</span></label>
+        <input type='text' placeholder="请输入数据库用户名" value="<?php echo $db_username; ?>" name='db_username'
+               id='db_username' required><br>
+        <label for='db_password'>mysql数据库密码:</label>
+        <input type='text' name='db_password' value="<?php echo $db_password; ?>" placeholder="请输入数据库密码"
+               id='db_password' required><br>
+        <label for='table_name'>mysql数据库名称:</label>
+        <input type='text' value="<?php echo $table_name; ?>" placeholder="请输入创建的数据库名称" name='table_name'
+               id='table_name' required><br>
+
+        <label for='redis_port'>管理员邮箱账号:</label>
+        <input type='text' value="<?php echo $admin_email; ?>" placeholder='请输入邮箱,用于默认的管理员账号登录使用'
+               name='admin_email'
+               id='redis_port'
+               required><br>
+        <label for='redis_port'>管理员密码:</label>
+        <input type='text' value="<?php echo $admin_password; ?>" placeholder='请设置管理员账号密码'
+               name='admin_password'
+               id='redis_port'
+               required><br>
+        <label for='redis_port'>数据库安装其他选项</label>
+        <label for='install_other'></label>
+        <label>
+            <input checked type='radio' name='database_type' value='1' required>
+            全新安装(如果数据库存在则删除原来的数据库,重新安装)
+        </label>
+        <label>
+            <input type='radio' name='database_type' value='2' required>
+            使用已存在数据库(不会覆盖数据库,仅安装代码,注意的是数据库的数据表要和最新版本的程序的库一致,否则使用旧版本的数据库表<b
+                    style="color: red">却</b>安装最新版的代码,否则导致有些服务异常)
+        </label>
+        <input type='submit' value='安装' style="margin-top: 30px">
+        <div style='margin-top: 30px;font-size: 14px;line-height: 24px;display: flex;flex-direction: column;align-items: center;text-align: center'>
+            <b style="font-size: 18px">温馨提示</b>如果您在安装阶段出现问题或对安装方式(特别是Nas部署用户)不知如何操作,可联系我们为您提供解决方法或辅助您安装,本服务不收费
+            <a target='_blank'
+               style='text-decoration: none;color: #ffffff;padding: 5px 15px;background: #1e9fff;border-radius: 30px;margin-top: 10px;'
+               href='https://mtab.cc'>点我跳转至官网,点击右下角客服即可联系</a>
+        </div>
+    </form>
+
+    </body>
+    </html>
+<?php } else { ?>
+    <!DOCTYPE html>
+    <html lang="zh">
+    <head>
+        <meta charset="UTF-8">
+        <title>网站安装完毕</title>
+        <style>
+            body {
+                font-family: Arial, sans-serif;
+                background-color: #fff;
+                text-align: center;
+                margin: 0;
+                padding: 0;
+            }
+
+            .container {
+                background-color: #fff;
+                padding: 20px;
+                border-radius: 10px;
+                box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
+                margin: 50px auto;
+                max-width: 800px;
+            }
+
+            h1 {
+                color: #333;
+            }
+
+            p {
+                color: #666;
+                font-size: 18px;
+            }
+
+            .btn-container {
+                margin-top: 20px;
+            }
+
+            .btn {
+                display: inline-block;
+                padding: 10px 20px;
+                margin-right: 10px;
+                background-color: rgb(255, 171, 84);
+                color: #fff;
+                text-decoration: none;
+                border-radius: 5px;
+                transition: background-color 0.3s;
+            }
+
+            .btn:hover {
+                background-color: rgb(255, 147, 38);
+            }
+        </style>
+        <link rel='icon' href='favicon.png'>
+    </head>
+    <body>
+    <div class='container'>
+        <h1 style="align-content: center">mTab书签安装完毕</h1>
+        <div style="display: flex;justify-content: center">
+            <svg style="width: 100px" t='1694607796889' class='icon' viewBox='0 0 1024 1024' version='1.1'
+                 xmlns='http://www.w3.org/2000/svg'
+                 p-id='40460' width='128' height='128'>
+                <path d='M512 0C230.4 0 0 230.4 0 512c0 281.6 230.4 512 512 512 281.6 0 512-230.4 512-512C1024 230.4 793.6 0 512 0zM512 960c-249.6 0-448-204.8-448-448 0-249.6 204.8-448 448-448 249.6 0 448 198.4 448 448C960 761.6 761.6 960 512 960zM691.2 339.2 454.4 576 332.8 454.4c-19.2-19.2-51.2-19.2-76.8 0C243.2 480 243.2 512 262.4 531.2l153.6 153.6c19.2 19.2 51.2 19.2 70.4 0l51.2-51.2 224-224c19.2-19.2 25.6-51.2 0-70.4C742.4 320 710.4 320 691.2 339.2z'
+                      fill='#54E283' p-id='40461'></path>
+            </svg>
+        </div>
+        <p>欢迎使用mTab书签,<br>点击下方按钮跳转到首页。</p>
+        <div class='btn-container'>
+            <a class='btn' href='/'>进入首页</a>
+        </div>
+        <p>后台进入方式,需要用管理员账户登录客户端<br/></p>
+        <p><b>鼠标在桌面右击打开菜单->点击设置->个人中心->登录管理员的账号</b><br/>
+            <b>
+                ->再次进入个人中心即可看到->管理后台->进入即可</b></p>
+        <p>这是一个多用户的书签导航程序,用户之间数据是隔离的不受干扰</p>
+        <p>可以使用鼠标右键在桌面点击呼出菜单。</p>
+        <p>很多功能就在鼠标右键菜单内。别怪我没告诉你哟hahaha~</p>
+    </div>
+    </body>
+    </html>
+<?php } ?>

+ 2 - 2
public/robots.txt

@@ -1,2 +1,2 @@
-User-agent: *
-Disallow:
+User-agent: *
+Disallow:

+ 19 - 19
public/router.php

@@ -1,19 +1,19 @@
-<?php
-// +----------------------------------------------------------------------
-// | ThinkPHP [ WE CAN DO IT JUST THINK ]
-// +----------------------------------------------------------------------
-// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
-// +----------------------------------------------------------------------
-// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
-// +----------------------------------------------------------------------
-// | Author: liu21st <liu21st@gmail.com>
-// +----------------------------------------------------------------------
-// $Id$
-
-if (is_file($_SERVER["DOCUMENT_ROOT"] . $_SERVER["SCRIPT_NAME"])) {
-    return false;
-} else {
-    $_SERVER["SCRIPT_FILENAME"] = __DIR__ . '/index.php';
-
-    require __DIR__ . "/index.php";
-}
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+// $Id$
+
+if (is_file($_SERVER["DOCUMENT_ROOT"] . $_SERVER["SCRIPT_NAME"])) {
+    return false;
+} else {
+    $_SERVER["SCRIPT_FILENAME"] = __DIR__ . '/index.php';
+
+    require __DIR__ . "/index.php";
+}

BIN
public/static/aliyun.png


+ 53 - 53
public/static/css/card.css

@@ -1,54 +1,54 @@
-/* 兼容所有浏览器,隐藏滚动条 */
-body {
-    scrollbar-width: none;
-    overflow: hidden;
-}
-
-* {
-    padding: 0;
-    margin: 0;
-}
-
-/* 针对WebKit浏览器(如Chrome、Safari等)添加以下样式 */
-*::-webkit-scrollbar {
-    display: none;
-}
-
-*::-webkit-scrollbar-track {
-    display: none;
-}
-
-*::-webkit-scrollbar-thumb {
-    display: none;
-}
-
-*::-webkit-scrollbar-corner {
-    display: none;
-}
-
-#think_page_trace {
-    display: none;
-}
-
-.f-1x1 {
-    display: none;
-}
-
-@media screen and (max-width: 200px) {
-    .f-2x2\:hidden {
-        display: none !important;
-    }
-}
-
-@media screen and (max-width: 100px) {
-    .f-1x1\:hidden {
-        display: none !important;
-    }
-    .f-1x1 {
-        width: 100%;
-        height: 100%;
-        display: flex;
-        justify-content: center;
-        align-items: center;
-    }
+/* 兼容所有浏览器,隐藏滚动条 */
+body {
+    scrollbar-width: none;
+    overflow: hidden;
+}
+
+* {
+    padding: 0;
+    margin: 0;
+}
+
+/* 针对WebKit浏览器(如Chrome、Safari等)添加以下样式 */
+*::-webkit-scrollbar {
+    display: none;
+}
+
+*::-webkit-scrollbar-track {
+    display: none;
+}
+
+*::-webkit-scrollbar-thumb {
+    display: none;
+}
+
+*::-webkit-scrollbar-corner {
+    display: none;
+}
+
+#think_page_trace {
+    display: none;
+}
+
+.f-1x1 {
+    display: none;
+}
+
+@media screen and (max-width: 200px) {
+    .f-2x2\:hidden {
+        display: none !important;
+    }
+}
+
+@media screen and (max-width: 100px) {
+    .f-1x1\:hidden {
+        display: none !important;
+    }
+    .f-1x1 {
+        width: 100%;
+        height: 100%;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+    }
 }

+ 751 - 751
public/static/defaultTab.json

@@ -1,752 +1,752 @@
-{
-  "link": [
-    {
-      "id": "EA3D2091-3C2E-4F6B-83BE-0609B5F16024",
-      "app": 0,
-      "pid": null,
-      "src": "/static/aliyun.svg",
-      "url": "https://www.aliyun.com/",
-      "name": "阿里云",
-      "size": "1x1",
-      "sort": 26,
-      "type": "icon",
-      "bgColor": "rgba(255, 106, 0, 1)",
-      "pageGroup": "",
-      "form": "link"
-    },
-    {
-      "id": "45154338-89c4-41b1-ae9f-4508078f4c7b",
-      "app": 0,
-      "pid": null,
-      "src": "/static/mtab.png",
-      "url": "https://mtab.cc",
-      "form": "link",
-      "name": "Mtab书签",
-      "size": "1x1",
-      "sort": 1,
-      "type": "icon",
-      "bgColor": "#fff",
-      "pageGroup": ""
-    },
-    {
-      "id": "f730b603-6514-414a-8165-7d5a679e12db",
-      "app": 0,
-      "pid": null,
-      "src": "/static/mcecy.png",
-      "url": "https://blog.mcecy.com/",
-      "form": "link",
-      "name": "小涂博客",
-      "size": "1x1",
-      "sort": 0,
-      "type": "icon",
-      "bgColor": "#fff",
-      "pageGroup": ""
-    },
-    {
-      "id": "D3AB9DDF-EB1A-47AD-B9B5-824A367C7A61",
-      "app": 0,
-      "pid": null,
-      "src": "/static/huawei.png",
-      "url": "https://www.huaweicloud.com/",
-      "name": "华为云",
-      "size": "1x1",
-      "sort": 22,
-      "type": "icon",
-      "bgColor": "#fff",
-      "pageGroup": "",
-      "form": "link"
-    },
-    {
-      "id": "5C3099FE-4A6E-4DA9-85B2-F20E1205347C",
-      "app": 0,
-      "pid": null,
-      "src": "/static/github.png",
-      "url": "https://github.com/",
-      "name": "GitHub",
-      "size": "1x1",
-      "sort": 11,
-      "type": "icon",
-      "bgColor": "rgba(0, 0, 0, 1)",
-      "pageGroup": ""
-    },
-    {
-      "id": "C4E35210-C3CB-44DB-9D32-96FDA9116EB1",
-      "app": 0,
-      "pid": null,
-      "src": "/static/weibo.png",
-      "url": "http://weibo.com/",
-      "form": "link",
-      "name": "微博",
-      "size": "1x1",
-      "sort": 6,
-      "type": "icon",
-      "bgColor": "#fff",
-      "pageGroup": ""
-    },
-    {
-      "id": "E97A6A65-6744-40F5-B097-44BBCB68FA86",
-      "app": 0,
-      "pid": null,
-      "src": "/static/tencentcloud.png",
-      "url": "https://cloud.tencent.com/",
-      "name": "腾讯云",
-      "size": "1x1",
-      "sort": 27,
-      "type": "icon",
-      "bgColor": "rgba(43, 48, 59, 1)",
-      "pageGroup": "",
-      "form": "link"
-    },
-    {
-      "id": "679EFDC3-1A8F-473C-94B1-DE94C3C4C561",
-      "app": 0,
-      "pid": null,
-      "src": "/static/txsp.png",
-      "url": "https://v.qq.com/channel/choice?channel_2022=1",
-      "form": "link",
-      "name": "腾讯视频",
-      "size": "1x2",
-      "sort": 14,
-      "type": "icon",
-      "bgColor": "#fff",
-      "pageGroup": ""
-    },
-    {
-      "id": "E0F115F4-B6E4-46A2-92CE-FF49329EF0E1",
-      "app": 0,
-      "pid": null,
-      "src": "/static/tsy.png",
-      "url": "https://www.tsyvps.com/aff/IRYIGFMX",
-      "form": "link",
-      "name": "蓝易云",
-      "size": "1x1",
-      "sort": 21,
-      "type": "icon",
-      "bgColor": "#fff",
-      "pageGroup": ""
-    },
-    {
-      "id": "983BA64F-D710-46B5-B3B9-8AC1C37C3A5E",
-      "app": 0,
-      "pid": null,
-      "src": "/static/bilibili.png",
-      "url": "https://bilibili.com",
-      "form": "link",
-      "name": "Bilibili",
-      "size": "1x2",
-      "sort": 16,
-      "type": "icon",
-      "bgColor": "#fff",
-      "pageGroup": ""
-    },
-    {
-      "id": "F743D0A2-49BE-42BF-8CC1-EE98FFD20B48",
-      "app": 0,
-      "pid": null,
-      "src": "/static/imgurl.png",
-      "url": "https://imgurl.ink",
-      "form": "link",
-      "name": "ImgUrl图床",
-      "size": "1x1",
-      "sort": 4,
-      "type": "icon",
-      "pageGroup": ""
-    },
-    {
-      "id": "4F9137E3-5701-4F6F-A8F4-C3F131FD9429",
-      "app": 1,
-      "pid": null,
-      "src": "/static/huoshanfanyi.png",
-      "url": "https://translate.volcengine.com/translate",
-      "form": "link",
-      "name": "火山翻译",
-      "size": "1x1",
-      "sort": 31,
-      "type": "icon",
-      "bgColor": "#fff",
-      "pageGroup": ""
-    },
-    {
-      "id": "9589E037-8EE3-4B1C-95E8-F419E9187357",
-      "app": 1,
-      "pid": null,
-      "src": "/static/note.png",
-      "url": "/noteApp",
-      "name": "记事本",
-      "size": "1x1",
-      "sort": 33,
-      "type": "icon",
-      "bgColor": "rgba(252, 141, 0, 1)",
-      "pageGroup": "",
-      "form": "link"
-    },
-    {
-      "id": "9F256401-0FF9-4DCE-980E-082F1C4DA118",
-      "app": 0,
-      "pid": null,
-      "src": "/static/addIco.png",
-      "url": "tab://addicon",
-      "name": "添加标签",
-      "size": "1x1",
-      "sort": 29,
-      "type": "icon",
-      "bgColor": "rgba(255, 255, 255, 1)",
-      "pageGroup": ""
-    },
-    {
-      "id": "033E9038-B97C-4894-83D2-6E030FAB83C6",
-      "app": 0,
-      "pid": null,
-      "src": "/static/setting.png",
-      "url": "tab://setting",
-      "form": "link",
-      "name": "设置",
-      "size": "1x1",
-      "sort": 34,
-      "type": "icon",
-      "bgColor": "rgba(149, 168, 184, 1)",
-      "pageGroup": ""
-    },
-    {
-      "id": "4A7E7020-690E-4955-86F4-2D9B35CFAF8B",
-      "app": 1,
-      "pid": null,
-      "src": "/static/webTerm.svg",
-      "url": "https://ssh.mtab.cc",
-      "form": "link",
-      "name": "WebTerm",
-      "size": "1x1",
-      "sort": 32,
-      "tips": "WebTerm",
-      "type": "icon",
-      "custom": {
-        "immersion": 0,
-        "width": 1200,
-        "height": 700,
-        "controllerColor": "#FFFFFF",
-        "maximize": 1,
-        "minimization": 1
-      },
-      "bgColor": "rgba(0, 0, 0, 0.79)",
-      "origin_id": 114,
-      "pageGroup": ""
-    },
-    {
-      "id": "5D022341-A5B4-43F5-BF1E-0783F4778676",
-      "app": 0,
-      "pid": null,
-      "src": "/static/jsdesign.svg",
-      "url": "https://js.design/",
-      "form": "link",
-      "name": "即时设计",
-      "size": "1x1",
-      "sort": 8,
-      "tips": "即时设计 - 可实时协作的专业 UI 设计工具",
-      "type": "icon",
-      "custom": null,
-      "bgColor": "rgba(244, 71, 71, 1)",
-      "origin_id": 93,
-      "pageGroup": ""
-    },
-    {
-      "id": "835c280d-be17-460d-b45d-b8d0a4d832db",
-      "app": 0,
-      "src": "/static/defLinkLogo/yisuyun.png",
-      "url": "https://www.yisu.com/",
-      "name": "亿速云",
-      "size": "1x1",
-      "sort": 25,
-      "type": "icon",
-      "bgColor": "#fff",
-      "pageGroup": "",
-      "pid": null,
-      "form": "link"
-    },
-    {
-      "id": "5d179bcc-55cb-4e39-9d14-ae9ee5758370",
-      "app": 0,
-      "pid": "AD879898-8CEA-4980-9798-F799A9A4AF7F",
-      "src": "https://download.mtab.cc/friendLinkLogo/sayai-logo.png",
-      "url": "https://sayai.cc",
-      "form": "link",
-      "name": "SayAi",
-      "size": "1x1",
-      "sort": 4,
-      "type": "icon",
-      "bgColor": "#fff",
-      "pageGroup": ""
-    },
-    {
-      "id": "666f1878-e285-41f6-9bf1-d0893a1b0cba",
-      "app": 0,
-      "src": "/static/defLinkLogo/baiduyun.svg",
-      "url": "https://cloud.baidu.com/",
-      "name": "百度智能云",
-      "size": "1x1",
-      "sort": 24,
-      "type": "icon",
-      "bgColor": "#fff",
-      "pageGroup": "",
-      "pid": null,
-      "form": "link"
-    },
-    {
-      "id": "3768e010-c2eb-4bf6-9fd6-2e221b0dd1e3",
-      "app": 0,
-      "src": "/static/defLinkLogo/jingdongyun.svg",
-      "url": "https://www.jdcloud.com/",
-      "name": "京东云",
-      "size": "1x1",
-      "sort": 23,
-      "type": "icon",
-      "bgColor": "#fff",
-      "pageGroup": "",
-      "pid": null,
-      "form": "link"
-    },
-    {
-      "id": "51a6914b-d310-4b35-b911-5d2bc203a714",
-      "app": 0,
-      "pid": "AD879898-8CEA-4980-9798-F799A9A4AF7F",
-      "src": "https://download.mtab.cc/friendLinkLogo/el9.png",
-      "url": "https://blog.el9.cn",
-      "form": "link",
-      "name": "布兰德",
-      "size": "1x1",
-      "sort": 5,
-      "type": "icon",
-      "bgColor": "#fff",
-      "pageGroup": ""
-    },
-    {
-      "id": "6ee9674c-4a8b-4d23-bb4d-d7eefec2c792",
-      "app": 0,
-      "pid": null,
-      "src": "/static/defLinkLogo/iqy.svg",
-      "url": "https://www.iqiyi.com/",
-      "form": "link",
-      "name": "爱奇艺",
-      "size": "1x2",
-      "sort": 17,
-      "type": "icon",
-      "bgColor": "rgba(66, 189, 86, 1)",
-      "pageGroup": ""
-    },
-    {
-      "id": "caeec76a-250e-4ce1-b69f-51a843c79c58",
-      "app": 0,
-      "pid": null,
-      "src": "/static/defLinkLogo/youku.svg",
-      "url": "https://www.youku.com/",
-      "form": "link",
-      "name": "优酷视频",
-      "size": "1x2",
-      "sort": 19,
-      "type": "icon",
-      "bgColor": "#fff",
-      "pageGroup": ""
-    },
-    {
-      "id": "de5c2249-1413-4abb-802a-c3f680698979",
-      "app": 0,
-      "pid": null,
-      "src": "/static/defLinkLogo/mangguo.svg",
-      "url": "https://www.mgtv.com/",
-      "name": "芒果TV",
-      "size": "1x1",
-      "sort": 5,
-      "type": "icon",
-      "bgColor": "#fff",
-      "pageGroup": "",
-      "form": "link"
-    },
-    {
-      "id": "f9b77d71-2db2-4d38-b72f-84c7b08b1afa",
-      "app": 0,
-      "pid": null,
-      "src": "/static/defLinkLogo/migu.svg",
-      "url": "https://www.miguvideo.com/",
-      "name": "咪咕视频",
-      "size": "1x1",
-      "sort": 2,
-      "type": "icon",
-      "bgColor": "#fff",
-      "pageGroup": "",
-      "form": "link"
-    },
-    {
-      "id": "d7207735-83c9-4060-8ae5-539d52c810e9",
-      "app": 0,
-      "pid": null,
-      "src": "/static/defLinkLogo/leshi.svg",
-      "url": "https://www.le.com/",
-      "name": "乐视视频",
-      "size": "1x1",
-      "sort": 12,
-      "type": "icon",
-      "bgColor": "#fff",
-      "pageGroup": "",
-      "form": "link"
-    },
-    {
-      "id": "86ae5293-32d7-40de-98a2-9e195f15ca2b",
-      "app": 0,
-      "pid": null,
-      "src": "/static/defLinkLogo/webos.png",
-      "url": "https://os.tenfell.cn/",
-      "name": "Web os",
-      "size": "1x1",
-      "sort": 9,
-      "type": "icon",
-      "bgColor": null,
-      "pageGroup": "",
-      "form": "link"
-    },
-    {
-      "id": "3e6d48dd-8b31-41d9-8487-a123407c5aae",
-      "app": 0,
-      "pid": null,
-      "src": "/static/defLinkLogo/acfun.svg",
-      "url": "https://www.acfun.cn/",
-      "form": "link",
-      "name": "AcFun",
-      "size": "1x2",
-      "sort": 15,
-      "type": "icon",
-      "bgColor": "rgba(250, 99, 110, 1)",
-      "pageGroup": ""
-    },
-    {
-      "id": "007d6c7d-06e3-46ad-8257-48ae63b830f8",
-      "name": "抖音",
-      "src": "/static/defLinkLogo/douyin.svg",
-      "url": "https://www.douyin.com/",
-      "size": "1x2",
-      "type": "icon",
-      "sort": 18,
-      "app": 0,
-      "bgColor": "rgba(17, 17, 17, 1)",
-      "pageGroup": "",
-      "form": "link",
-      "pid": null
-    },
-    {
-      "id": "994e5a5f-5ea2-4af6-8e63-86867a1422ad",
-      "name": "快手",
-      "src": "/static/defLinkLogo/kuaishou.svg",
-      "url": "https://www.kuaishou.com/new-reco",
-      "size": "1x2",
-      "type": "icon",
-      "sort": 20,
-      "app": 0,
-      "bgColor": "rgba(255, 74, 8, 1)",
-      "pageGroup": "",
-      "pid": null,
-      "form": "link"
-    },
-    {
-      "id": "fd9c04a9-e499-44af-add3-a7b63356cd81",
-      "name": "QQ音乐",
-      "src": "/static/defLinkLogo/qqmusic.svg",
-      "url": "https://y.qq.com/",
-      "size": "1x1",
-      "type": "icon",
-      "sort": 3,
-      "app": 0,
-      "bgColor": "#fff",
-      "pageGroup": "",
-      "pid": null,
-      "form": "link"
-    },
-    {
-      "id": "431fb677-da74-442a-8fa3-98738402b604",
-      "name": "网易云音乐",
-      "src": "/static/defLinkLogo/wangyiyun.svg",
-      "url": "https://music.163.com/",
-      "size": "1x1",
-      "type": "icon",
-      "sort": 13,
-      "app": 0,
-      "bgColor": "rgba(234, 62, 60, 1)",
-      "pageGroup": "",
-      "pid": null,
-      "form": "link"
-    },
-    {
-      "id": "73866702-487c-475b-b56b-8da63f067c56",
-      "name": "Youtube",
-      "src": "/static/defLinkLogo/youtube.svg",
-      "url": "https://www.youtube.com/",
-      "size": "1x1",
-      "type": "icon",
-      "sort": 7,
-      "app": 0,
-      "bgColor": "#fff",
-      "pageGroup": "",
-      "pid": null,
-      "form": "link"
-    },
-    {
-      "sort": 30,
-      "id": "3C7C4FB1-5D91-4B44-93A2-3FC06A27DAEE",
-      "name": "壁纸",
-      "size": "1x1",
-      "src": "/static/backgroundIco.png",
-      "url": "tab://background",
-      "type": "icon",
-      "app": 0,
-      "tips": "设置自己的个性首页背景",
-      "bgColor": "rgba(255,255,255,1)",
-      "pageGroup": "",
-      "pid": null,
-      "form": "link"
-    },
-    {
-      "id": "08798f97-0e68-40fb-8440-83540efacfc9",
-      "name": "子幽博客",
-      "src": "https://download.mtab.cc/friendLinkLogo/liukuai.png",
-      "url": "https://blog.liukuan.cc/",
-      "size": "1x1",
-      "type": "icon",
-      "sort": 3,
-      "app": 0,
-      "bgColor": "#fff",
-      "pageGroup": "",
-      "form": "link",
-      "pid": "AD879898-8CEA-4980-9798-F799A9A4AF7F"
-    },
-    {
-      "id": "a5b425d1-4c2f-4979-aeaf-68531e052a4e",
-      "name": "学习时间-综合性学习服务平台",
-      "src": "https://download.mtab.cc/friendLinkLogo/learningtimes.png",
-      "url": "https://learningtimes.cn",
-      "size": "1x1",
-      "type": "icon",
-      "sort": 1,
-      "app": 0,
-      "bgColor": null,
-      "pageGroup": "",
-      "pid": "AD879898-8CEA-4980-9798-F799A9A4AF7F",
-      "form": "link"
-    },
-    {
-      "id": "93024f66-0561-4d85-a527-c10897cde7f4",
-      "name": "大师兄导航",
-      "src": "https://download.mtab.cc/friendLinkLogo/dsx.png",
-      "url": "https://dsxdh.com/",
-      "size": "1x1",
-      "type": "icon",
-      "sort": 0,
-      "app": 0,
-      "bgColor": "rgba(0,0,0,0)",
-      "pageGroup": "",
-      "pid": "AD879898-8CEA-4980-9798-F799A9A4AF7F",
-      "form": "link"
-    },
-    {
-      "id": "8b70f87f-65ce-4533-9ca1-1ed815943bb4",
-      "name": "码云Gitee",
-      "src": "/static/defLinkLogo/gitee.svg",
-      "url": "https://gitee.com",
-      "size": "1x1",
-      "type": "icon",
-      "sort": 10,
-      "app": 0,
-      "bgColor": "rgba(199, 29, 35, 1)",
-      "pageGroup": "",
-      "form": "link",
-      "pid": null
-    },
-    {
-      "id": "3497447a-bd77-4d4e-a9a6-99452a93f99c",
-      "name": "旅者Bin",
-      "src": "https://download.mtab.cc/friendLinkLogo/101jc.png",
-      "url": "https://blog.101jc.com/archives",
-      "size": "1x1",
-      "type": "icon",
-      "sort": 2,
-      "app": 0,
-      "bgColor": "#fff",
-      "pageGroup": "",
-      "form": "link",
-      "pid": "AD879898-8CEA-4980-9798-F799A9A4AF7F"
-    },
-    {
-      "id": "AD879898-8CEA-4980-9798-F799A9A4AF7F",
-      "size": "1x1",
-      "component": "iconGroup",
-      "name": "友情推荐",
-      "type": "component",
-      "url": "iconGroup",
-      "pageGroup": "",
-      "children": [],
-      "pid": null,
-      "sort": 28
-    },
-    {
-      "id": "0c706332-0417-4512-a1c7-02813106b4d5",
-      "name": "王先生",
-      "src": "https://download.mtab.cc/friendLinkLogo/wangxiansheng.jpg",
-      "url": "https://www.wangxiansheng.com",
-      "size": "1x1",
-      "type": "icon",
-      "sort": 6,
-      "app": 0,
-      "bgColor": null,
-      "pageGroup": "",
-      "pid": "AD879898-8CEA-4980-9798-F799A9A4AF7F"
-    }
-  ],
-  "tabbar": [
-    {
-      "id": "45154338-89c4-41b1-ae9f-4508078f4c7b",
-      "app": 0,
-      "pid": null,
-      "src": "/static/mtab.png",
-      "url": "https://mtab.cc",
-      "name": "Mtab书签",
-      "size": "1x1",
-      "sort": 0,
-      "type": "icon"
-    },
-    {
-      "id": "4F9137E3-5701-4F6F-A8F4-C3F131FD9429",
-      "app": 1,
-      "pid": null,
-      "src": "/static/huoshanfanyi.png",
-      "url": "https://translate.volcengine.com/translate",
-      "name": "火山翻译",
-      "size": "1x1",
-      "sort": 4,
-      "type": "icon"
-    },
-    {
-      "id": "9589E037-8EE3-4B1C-95E8-F419E9187357",
-      "app": 1,
-      "pid": null,
-      "src": "/static/note.png",
-      "url": "/noteApp",
-      "name": "记事本",
-      "size": "1x1",
-      "sort": 5,
-      "type": "icon"
-    },
-    {
-      "id": "D3AB9DDF-EB1A-47AD-B9B5-824A367C7A61",
-      "app": 0,
-      "pid": null,
-      "src": "/static/huawei.png",
-      "url": "https://www.huaweicloud.com/",
-      "name": "华为云",
-      "size": "1x1",
-      "sort": 1,
-      "type": "icon"
-    },
-    {
-      "id": "5C3099FE-4A6E-4DA9-85B2-F20E1205347C",
-      "app": 0,
-      "pid": null,
-      "src": "/static/github.png",
-      "url": "https://github.com/",
-      "name": "GitHub",
-      "size": "1x1",
-      "sort": 2,
-      "type": "icon"
-    },
-    {
-      "id": "C4E35210-C3CB-44DB-9D32-96FDA9116EB1",
-      "app": 0,
-      "pid": null,
-      "src": "/static/weibo.png",
-      "url": "http://weibo.com/",
-      "name": "微博",
-      "size": "1x1",
-      "sort": 3,
-      "type": "icon"
-    },
-    {
-      "id": "4A7E7020-690E-4955-86F4-2D9B35CFAF8B",
-      "app": 1,
-      "pid": null,
-      "src": "/static/webTerm.svg",
-      "url": "https://ssh.mtab.cc",
-      "form": "tabbar",
-      "name": "WebTerm",
-      "size": "1x2",
-      "sort": 10,
-      "tips": "WebTerm",
-      "type": "icon",
-      "custom": {
-        "width": 1200,
-        "height": 700,
-        "maximize": 1,
-        "immersion": 0,
-        "minimization": 1,
-        "controllerColor": "#000000"
-      },
-      "bgColor": "rgba(0, 0, 0, 0.79)",
-      "origin_id": 114,
-      "pageGroup": ""
-    },
-    {
-      "id": "033E9038-B97C-4894-83D2-6E030FAB83C6",
-      "app": 0,
-      "pid": null,
-      "src": "/static/setting.png",
-      "url": "tab://setting",
-      "name": "设置",
-      "size": "1x1",
-      "sort": 8,
-      "type": "icon"
-    },
-    {
-      "id": "f730b603-6514-414a-8165-7d5a679e12db",
-      "app": 0,
-      "pid": null,
-      "src": "/static/mcecy.png",
-      "url": "https://blog.mcecy.com/",
-      "form": "link",
-      "name": "IT博客",
-      "size": "1x1",
-      "sort": 0,
-      "type": "icon",
-      "bgColor": "#fff",
-      "pageGroup": ""
-    }
-  ],
-  "config": {
-    "openType": {
-      "searchOpen": true,
-      "linkOpen": true,
-      "autofocus": true,
-      "searchLink": true,
-      "searchRecommend": true,
-      "tabbar": true
-    },
-    "theme": {
-      "backgroundImage": "/static/background.jpeg",
-      "backgroundMime": 0,
-      "blur": 0,
-      "timeColor": "#fff",
-      "tabbar": true,
-      "iconWidth": 60,
-      "iconBg": false,
-      "LinkTitle": false,
-      "iconRadius": 10,
-      "CompactMode": false,
-      "nameColor": "#fff",
-      "opacity": 0.1,
-      "colsGap": 35,
-      "pageGroup": true,
-      "pageGroupStatus": false,
-      "timeView": true,
-      "timeWeek": true,
-      "timeGanZhi": true,
-      "timeSecond": true,
-      "timeMonthDay": true,
-      "timeLunar": true,
-      "time24": true,
-      "maxColumn":14
-    }
-  }
+{
+  "link": [
+    {
+      "id": "EA3D2091-3C2E-4F6B-83BE-0609B5F16024",
+      "app": 0,
+      "pid": null,
+      "src": "/static/aliyun.svg",
+      "url": "https://www.aliyun.com/",
+      "name": "阿里云",
+      "size": "1x1",
+      "sort": 26,
+      "type": "icon",
+      "bgColor": "rgba(255, 106, 0, 1)",
+      "pageGroup": "",
+      "form": "link"
+    },
+    {
+      "id": "45154338-89c4-41b1-ae9f-4508078f4c7b",
+      "app": 0,
+      "pid": null,
+      "src": "/static/mtab.png",
+      "url": "https://mtab.cc",
+      "form": "link",
+      "name": "Mtab书签",
+      "size": "1x1",
+      "sort": 1,
+      "type": "icon",
+      "bgColor": "#fff",
+      "pageGroup": ""
+    },
+    {
+      "id": "f730b603-6514-414a-8165-7d5a679e12db",
+      "app": 0,
+      "pid": null,
+      "src": "/static/mcecy.png",
+      "url": "https://blog.mcecy.com/",
+      "form": "link",
+      "name": "小涂博客",
+      "size": "1x1",
+      "sort": 0,
+      "type": "icon",
+      "bgColor": "#fff",
+      "pageGroup": ""
+    },
+    {
+      "id": "D3AB9DDF-EB1A-47AD-B9B5-824A367C7A61",
+      "app": 0,
+      "pid": null,
+      "src": "/static/huawei.png",
+      "url": "https://www.huaweicloud.com/",
+      "name": "华为云",
+      "size": "1x1",
+      "sort": 22,
+      "type": "icon",
+      "bgColor": "#fff",
+      "pageGroup": "",
+      "form": "link"
+    },
+    {
+      "id": "5C3099FE-4A6E-4DA9-85B2-F20E1205347C",
+      "app": 0,
+      "pid": null,
+      "src": "/static/github.png",
+      "url": "https://github.com/",
+      "name": "GitHub",
+      "size": "1x1",
+      "sort": 11,
+      "type": "icon",
+      "bgColor": "rgba(0, 0, 0, 1)",
+      "pageGroup": ""
+    },
+    {
+      "id": "C4E35210-C3CB-44DB-9D32-96FDA9116EB1",
+      "app": 0,
+      "pid": null,
+      "src": "/static/weibo.png",
+      "url": "http://weibo.com/",
+      "form": "link",
+      "name": "微博",
+      "size": "1x1",
+      "sort": 6,
+      "type": "icon",
+      "bgColor": "#fff",
+      "pageGroup": ""
+    },
+    {
+      "id": "E97A6A65-6744-40F5-B097-44BBCB68FA86",
+      "app": 0,
+      "pid": null,
+      "src": "/static/tencentcloud.png",
+      "url": "https://cloud.tencent.com/",
+      "name": "腾讯云",
+      "size": "1x1",
+      "sort": 27,
+      "type": "icon",
+      "bgColor": "rgba(43, 48, 59, 1)",
+      "pageGroup": "",
+      "form": "link"
+    },
+    {
+      "id": "679EFDC3-1A8F-473C-94B1-DE94C3C4C561",
+      "app": 0,
+      "pid": null,
+      "src": "/static/txsp.png",
+      "url": "https://v.qq.com/channel/choice?channel_2022=1",
+      "form": "link",
+      "name": "腾讯视频",
+      "size": "1x2",
+      "sort": 14,
+      "type": "icon",
+      "bgColor": "#fff",
+      "pageGroup": ""
+    },
+    {
+      "id": "E0F115F4-B6E4-46A2-92CE-FF49329EF0E1",
+      "app": 0,
+      "pid": null,
+      "src": "/static/tsy.png",
+      "url": "https://www.tsyvps.com/aff/IRYIGFMX",
+      "form": "link",
+      "name": "蓝易云",
+      "size": "1x1",
+      "sort": 21,
+      "type": "icon",
+      "bgColor": "#fff",
+      "pageGroup": ""
+    },
+    {
+      "id": "983BA64F-D710-46B5-B3B9-8AC1C37C3A5E",
+      "app": 0,
+      "pid": null,
+      "src": "/static/bilibili.png",
+      "url": "https://bilibili.com",
+      "form": "link",
+      "name": "Bilibili",
+      "size": "1x2",
+      "sort": 16,
+      "type": "icon",
+      "bgColor": "#fff",
+      "pageGroup": ""
+    },
+    {
+      "id": "F743D0A2-49BE-42BF-8CC1-EE98FFD20B48",
+      "app": 0,
+      "pid": null,
+      "src": "/static/imgurl.png",
+      "url": "https://imgurl.ink",
+      "form": "link",
+      "name": "ImgUrl图床",
+      "size": "1x1",
+      "sort": 4,
+      "type": "icon",
+      "pageGroup": ""
+    },
+    {
+      "id": "4F9137E3-5701-4F6F-A8F4-C3F131FD9429",
+      "app": 1,
+      "pid": null,
+      "src": "/static/huoshanfanyi.png",
+      "url": "https://translate.volcengine.com/translate",
+      "form": "link",
+      "name": "火山翻译",
+      "size": "1x1",
+      "sort": 31,
+      "type": "icon",
+      "bgColor": "#fff",
+      "pageGroup": ""
+    },
+    {
+      "id": "9589E037-8EE3-4B1C-95E8-F419E9187357",
+      "app": 1,
+      "pid": null,
+      "src": "/static/note.png",
+      "url": "/noteApp",
+      "name": "记事本",
+      "size": "1x1",
+      "sort": 33,
+      "type": "icon",
+      "bgColor": "rgba(252, 141, 0, 1)",
+      "pageGroup": "",
+      "form": "link"
+    },
+    {
+      "id": "9F256401-0FF9-4DCE-980E-082F1C4DA118",
+      "app": 0,
+      "pid": null,
+      "src": "/static/addIco.png",
+      "url": "tab://addicon",
+      "name": "添加标签",
+      "size": "1x1",
+      "sort": 29,
+      "type": "icon",
+      "bgColor": "rgba(255, 255, 255, 1)",
+      "pageGroup": ""
+    },
+    {
+      "id": "033E9038-B97C-4894-83D2-6E030FAB83C6",
+      "app": 0,
+      "pid": null,
+      "src": "/static/setting.png",
+      "url": "tab://setting",
+      "form": "link",
+      "name": "设置",
+      "size": "1x1",
+      "sort": 34,
+      "type": "icon",
+      "bgColor": "rgba(149, 168, 184, 1)",
+      "pageGroup": ""
+    },
+    {
+      "id": "4A7E7020-690E-4955-86F4-2D9B35CFAF8B",
+      "app": 1,
+      "pid": null,
+      "src": "/static/webTerm.svg",
+      "url": "https://ssh.mtab.cc",
+      "form": "link",
+      "name": "WebTerm",
+      "size": "1x1",
+      "sort": 32,
+      "tips": "WebTerm",
+      "type": "icon",
+      "custom": {
+        "immersion": 0,
+        "width": 1200,
+        "height": 700,
+        "controllerColor": "#FFFFFF",
+        "maximize": 1,
+        "minimization": 1
+      },
+      "bgColor": "rgba(0, 0, 0, 0.79)",
+      "origin_id": 114,
+      "pageGroup": ""
+    },
+    {
+      "id": "5D022341-A5B4-43F5-BF1E-0783F4778676",
+      "app": 0,
+      "pid": null,
+      "src": "/static/jsdesign.svg",
+      "url": "https://js.design/",
+      "form": "link",
+      "name": "即时设计",
+      "size": "1x1",
+      "sort": 8,
+      "tips": "即时设计 - 可实时协作的专业 UI 设计工具",
+      "type": "icon",
+      "custom": null,
+      "bgColor": "rgba(244, 71, 71, 1)",
+      "origin_id": 93,
+      "pageGroup": ""
+    },
+    {
+      "id": "835c280d-be17-460d-b45d-b8d0a4d832db",
+      "app": 0,
+      "src": "/static/defLinkLogo/yisuyun.png",
+      "url": "https://www.yisu.com/",
+      "name": "亿速云",
+      "size": "1x1",
+      "sort": 25,
+      "type": "icon",
+      "bgColor": "#fff",
+      "pageGroup": "",
+      "pid": null,
+      "form": "link"
+    },
+    {
+      "id": "5d179bcc-55cb-4e39-9d14-ae9ee5758370",
+      "app": 0,
+      "pid": "AD879898-8CEA-4980-9798-F799A9A4AF7F",
+      "src": "https://download.mtab.cc/friendLinkLogo/sayai-logo.png",
+      "url": "https://sayai.cc",
+      "form": "link",
+      "name": "SayAi",
+      "size": "1x1",
+      "sort": 4,
+      "type": "icon",
+      "bgColor": "#fff",
+      "pageGroup": ""
+    },
+    {
+      "id": "666f1878-e285-41f6-9bf1-d0893a1b0cba",
+      "app": 0,
+      "src": "/static/defLinkLogo/baiduyun.svg",
+      "url": "https://cloud.baidu.com/",
+      "name": "百度智能云",
+      "size": "1x1",
+      "sort": 24,
+      "type": "icon",
+      "bgColor": "#fff",
+      "pageGroup": "",
+      "pid": null,
+      "form": "link"
+    },
+    {
+      "id": "3768e010-c2eb-4bf6-9fd6-2e221b0dd1e3",
+      "app": 0,
+      "src": "/static/defLinkLogo/jingdongyun.svg",
+      "url": "https://www.jdcloud.com/",
+      "name": "京东云",
+      "size": "1x1",
+      "sort": 23,
+      "type": "icon",
+      "bgColor": "#fff",
+      "pageGroup": "",
+      "pid": null,
+      "form": "link"
+    },
+    {
+      "id": "51a6914b-d310-4b35-b911-5d2bc203a714",
+      "app": 0,
+      "pid": "AD879898-8CEA-4980-9798-F799A9A4AF7F",
+      "src": "https://download.mtab.cc/friendLinkLogo/el9.png",
+      "url": "https://blog.el9.cn",
+      "form": "link",
+      "name": "布兰德",
+      "size": "1x1",
+      "sort": 5,
+      "type": "icon",
+      "bgColor": "#fff",
+      "pageGroup": ""
+    },
+    {
+      "id": "6ee9674c-4a8b-4d23-bb4d-d7eefec2c792",
+      "app": 0,
+      "pid": null,
+      "src": "/static/defLinkLogo/iqy.svg",
+      "url": "https://www.iqiyi.com/",
+      "form": "link",
+      "name": "爱奇艺",
+      "size": "1x2",
+      "sort": 17,
+      "type": "icon",
+      "bgColor": "rgba(66, 189, 86, 1)",
+      "pageGroup": ""
+    },
+    {
+      "id": "caeec76a-250e-4ce1-b69f-51a843c79c58",
+      "app": 0,
+      "pid": null,
+      "src": "/static/defLinkLogo/youku.svg",
+      "url": "https://www.youku.com/",
+      "form": "link",
+      "name": "优酷视频",
+      "size": "1x2",
+      "sort": 19,
+      "type": "icon",
+      "bgColor": "#fff",
+      "pageGroup": ""
+    },
+    {
+      "id": "de5c2249-1413-4abb-802a-c3f680698979",
+      "app": 0,
+      "pid": null,
+      "src": "/static/defLinkLogo/mangguo.svg",
+      "url": "https://www.mgtv.com/",
+      "name": "芒果TV",
+      "size": "1x1",
+      "sort": 5,
+      "type": "icon",
+      "bgColor": "#fff",
+      "pageGroup": "",
+      "form": "link"
+    },
+    {
+      "id": "f9b77d71-2db2-4d38-b72f-84c7b08b1afa",
+      "app": 0,
+      "pid": null,
+      "src": "/static/defLinkLogo/migu.svg",
+      "url": "https://www.miguvideo.com/",
+      "name": "咪咕视频",
+      "size": "1x1",
+      "sort": 2,
+      "type": "icon",
+      "bgColor": "#fff",
+      "pageGroup": "",
+      "form": "link"
+    },
+    {
+      "id": "d7207735-83c9-4060-8ae5-539d52c810e9",
+      "app": 0,
+      "pid": null,
+      "src": "/static/defLinkLogo/leshi.svg",
+      "url": "https://www.le.com/",
+      "name": "乐视视频",
+      "size": "1x1",
+      "sort": 12,
+      "type": "icon",
+      "bgColor": "#fff",
+      "pageGroup": "",
+      "form": "link"
+    },
+    {
+      "id": "86ae5293-32d7-40de-98a2-9e195f15ca2b",
+      "app": 0,
+      "pid": null,
+      "src": "/static/defLinkLogo/webos.png",
+      "url": "https://os.tenfell.cn/",
+      "name": "Web os",
+      "size": "1x1",
+      "sort": 9,
+      "type": "icon",
+      "bgColor": null,
+      "pageGroup": "",
+      "form": "link"
+    },
+    {
+      "id": "3e6d48dd-8b31-41d9-8487-a123407c5aae",
+      "app": 0,
+      "pid": null,
+      "src": "/static/defLinkLogo/acfun.svg",
+      "url": "https://www.acfun.cn/",
+      "form": "link",
+      "name": "AcFun",
+      "size": "1x2",
+      "sort": 15,
+      "type": "icon",
+      "bgColor": "rgba(250, 99, 110, 1)",
+      "pageGroup": ""
+    },
+    {
+      "id": "007d6c7d-06e3-46ad-8257-48ae63b830f8",
+      "name": "抖音",
+      "src": "/static/defLinkLogo/douyin.svg",
+      "url": "https://www.douyin.com/",
+      "size": "1x2",
+      "type": "icon",
+      "sort": 18,
+      "app": 0,
+      "bgColor": "rgba(17, 17, 17, 1)",
+      "pageGroup": "",
+      "form": "link",
+      "pid": null
+    },
+    {
+      "id": "994e5a5f-5ea2-4af6-8e63-86867a1422ad",
+      "name": "快手",
+      "src": "/static/defLinkLogo/kuaishou.svg",
+      "url": "https://www.kuaishou.com/new-reco",
+      "size": "1x2",
+      "type": "icon",
+      "sort": 20,
+      "app": 0,
+      "bgColor": "rgba(255, 74, 8, 1)",
+      "pageGroup": "",
+      "pid": null,
+      "form": "link"
+    },
+    {
+      "id": "fd9c04a9-e499-44af-add3-a7b63356cd81",
+      "name": "QQ音乐",
+      "src": "/static/defLinkLogo/qqmusic.svg",
+      "url": "https://y.qq.com/",
+      "size": "1x1",
+      "type": "icon",
+      "sort": 3,
+      "app": 0,
+      "bgColor": "#fff",
+      "pageGroup": "",
+      "pid": null,
+      "form": "link"
+    },
+    {
+      "id": "431fb677-da74-442a-8fa3-98738402b604",
+      "name": "网易云音乐",
+      "src": "/static/defLinkLogo/wangyiyun.svg",
+      "url": "https://music.163.com/",
+      "size": "1x1",
+      "type": "icon",
+      "sort": 13,
+      "app": 0,
+      "bgColor": "rgba(234, 62, 60, 1)",
+      "pageGroup": "",
+      "pid": null,
+      "form": "link"
+    },
+    {
+      "id": "73866702-487c-475b-b56b-8da63f067c56",
+      "name": "Youtube",
+      "src": "/static/defLinkLogo/youtube.svg",
+      "url": "https://www.youtube.com/",
+      "size": "1x1",
+      "type": "icon",
+      "sort": 7,
+      "app": 0,
+      "bgColor": "#fff",
+      "pageGroup": "",
+      "pid": null,
+      "form": "link"
+    },
+    {
+      "sort": 30,
+      "id": "3C7C4FB1-5D91-4B44-93A2-3FC06A27DAEE",
+      "name": "壁纸",
+      "size": "1x1",
+      "src": "/static/backgroundIco.png",
+      "url": "tab://background",
+      "type": "icon",
+      "app": 0,
+      "tips": "设置自己的个性首页背景",
+      "bgColor": "rgba(255,255,255,1)",
+      "pageGroup": "",
+      "pid": null,
+      "form": "link"
+    },
+    {
+      "id": "08798f97-0e68-40fb-8440-83540efacfc9",
+      "name": "子幽博客",
+      "src": "https://download.mtab.cc/friendLinkLogo/liukuai.png",
+      "url": "https://blog.liukuan.cc/",
+      "size": "1x1",
+      "type": "icon",
+      "sort": 3,
+      "app": 0,
+      "bgColor": "#fff",
+      "pageGroup": "",
+      "form": "link",
+      "pid": "AD879898-8CEA-4980-9798-F799A9A4AF7F"
+    },
+    {
+      "id": "a5b425d1-4c2f-4979-aeaf-68531e052a4e",
+      "name": "学习时间-综合性学习服务平台",
+      "src": "https://download.mtab.cc/friendLinkLogo/learningtimes.png",
+      "url": "https://learningtimes.cn",
+      "size": "1x1",
+      "type": "icon",
+      "sort": 1,
+      "app": 0,
+      "bgColor": null,
+      "pageGroup": "",
+      "pid": "AD879898-8CEA-4980-9798-F799A9A4AF7F",
+      "form": "link"
+    },
+    {
+      "id": "93024f66-0561-4d85-a527-c10897cde7f4",
+      "name": "大师兄导航",
+      "src": "https://download.mtab.cc/friendLinkLogo/dsx.png",
+      "url": "https://dsxdh.com/",
+      "size": "1x1",
+      "type": "icon",
+      "sort": 0,
+      "app": 0,
+      "bgColor": "rgba(0,0,0,0)",
+      "pageGroup": "",
+      "pid": "AD879898-8CEA-4980-9798-F799A9A4AF7F",
+      "form": "link"
+    },
+    {
+      "id": "8b70f87f-65ce-4533-9ca1-1ed815943bb4",
+      "name": "码云Gitee",
+      "src": "/static/defLinkLogo/gitee.svg",
+      "url": "https://gitee.com",
+      "size": "1x1",
+      "type": "icon",
+      "sort": 10,
+      "app": 0,
+      "bgColor": "rgba(199, 29, 35, 1)",
+      "pageGroup": "",
+      "form": "link",
+      "pid": null
+    },
+    {
+      "id": "3497447a-bd77-4d4e-a9a6-99452a93f99c",
+      "name": "旅者Bin",
+      "src": "https://download.mtab.cc/friendLinkLogo/101jc.png",
+      "url": "https://blog.101jc.com/archives",
+      "size": "1x1",
+      "type": "icon",
+      "sort": 2,
+      "app": 0,
+      "bgColor": "#fff",
+      "pageGroup": "",
+      "form": "link",
+      "pid": "AD879898-8CEA-4980-9798-F799A9A4AF7F"
+    },
+    {
+      "id": "AD879898-8CEA-4980-9798-F799A9A4AF7F",
+      "size": "1x1",
+      "component": "iconGroup",
+      "name": "友情推荐",
+      "type": "component",
+      "url": "iconGroup",
+      "pageGroup": "",
+      "children": [],
+      "pid": null,
+      "sort": 28
+    },
+    {
+      "id": "0c706332-0417-4512-a1c7-02813106b4d5",
+      "name": "王先生",
+      "src": "https://download.mtab.cc/friendLinkLogo/wangxiansheng.jpg",
+      "url": "https://www.wangxiansheng.com",
+      "size": "1x1",
+      "type": "icon",
+      "sort": 6,
+      "app": 0,
+      "bgColor": null,
+      "pageGroup": "",
+      "pid": "AD879898-8CEA-4980-9798-F799A9A4AF7F"
+    }
+  ],
+  "tabbar": [
+    {
+      "id": "45154338-89c4-41b1-ae9f-4508078f4c7b",
+      "app": 0,
+      "pid": null,
+      "src": "/static/mtab.png",
+      "url": "https://mtab.cc",
+      "name": "Mtab书签",
+      "size": "1x1",
+      "sort": 0,
+      "type": "icon"
+    },
+    {
+      "id": "4F9137E3-5701-4F6F-A8F4-C3F131FD9429",
+      "app": 1,
+      "pid": null,
+      "src": "/static/huoshanfanyi.png",
+      "url": "https://translate.volcengine.com/translate",
+      "name": "火山翻译",
+      "size": "1x1",
+      "sort": 4,
+      "type": "icon"
+    },
+    {
+      "id": "9589E037-8EE3-4B1C-95E8-F419E9187357",
+      "app": 1,
+      "pid": null,
+      "src": "/static/note.png",
+      "url": "/noteApp",
+      "name": "记事本",
+      "size": "1x1",
+      "sort": 5,
+      "type": "icon"
+    },
+    {
+      "id": "D3AB9DDF-EB1A-47AD-B9B5-824A367C7A61",
+      "app": 0,
+      "pid": null,
+      "src": "/static/huawei.png",
+      "url": "https://www.huaweicloud.com/",
+      "name": "华为云",
+      "size": "1x1",
+      "sort": 1,
+      "type": "icon"
+    },
+    {
+      "id": "5C3099FE-4A6E-4DA9-85B2-F20E1205347C",
+      "app": 0,
+      "pid": null,
+      "src": "/static/github.png",
+      "url": "https://github.com/",
+      "name": "GitHub",
+      "size": "1x1",
+      "sort": 2,
+      "type": "icon"
+    },
+    {
+      "id": "C4E35210-C3CB-44DB-9D32-96FDA9116EB1",
+      "app": 0,
+      "pid": null,
+      "src": "/static/weibo.png",
+      "url": "http://weibo.com/",
+      "name": "微博",
+      "size": "1x1",
+      "sort": 3,
+      "type": "icon"
+    },
+    {
+      "id": "4A7E7020-690E-4955-86F4-2D9B35CFAF8B",
+      "app": 1,
+      "pid": null,
+      "src": "/static/webTerm.svg",
+      "url": "https://ssh.mtab.cc",
+      "form": "tabbar",
+      "name": "WebTerm",
+      "size": "1x2",
+      "sort": 10,
+      "tips": "WebTerm",
+      "type": "icon",
+      "custom": {
+        "width": 1200,
+        "height": 700,
+        "maximize": 1,
+        "immersion": 0,
+        "minimization": 1,
+        "controllerColor": "#000000"
+      },
+      "bgColor": "rgba(0, 0, 0, 0.79)",
+      "origin_id": 114,
+      "pageGroup": ""
+    },
+    {
+      "id": "033E9038-B97C-4894-83D2-6E030FAB83C6",
+      "app": 0,
+      "pid": null,
+      "src": "/static/setting.png",
+      "url": "tab://setting",
+      "name": "设置",
+      "size": "1x1",
+      "sort": 8,
+      "type": "icon"
+    },
+    {
+      "id": "f730b603-6514-414a-8165-7d5a679e12db",
+      "app": 0,
+      "pid": null,
+      "src": "/static/mcecy.png",
+      "url": "https://blog.mcecy.com/",
+      "form": "link",
+      "name": "IT博客",
+      "size": "1x1",
+      "sort": 0,
+      "type": "icon",
+      "bgColor": "#fff",
+      "pageGroup": ""
+    }
+  ],
+  "config": {
+    "openType": {
+      "searchOpen": true,
+      "linkOpen": true,
+      "autofocus": true,
+      "searchLink": true,
+      "searchRecommend": true,
+      "tabbar": true
+    },
+    "theme": {
+      "backgroundImage": "/static/background.jpeg",
+      "backgroundMime": 0,
+      "blur": 0,
+      "timeColor": "#fff",
+      "tabbar": true,
+      "iconWidth": 60,
+      "iconBg": false,
+      "LinkTitle": false,
+      "iconRadius": 10,
+      "CompactMode": false,
+      "nameColor": "#fff",
+      "opacity": 0.1,
+      "colsGap": 35,
+      "pageGroup": true,
+      "pageGroupStatus": false,
+      "timeView": true,
+      "timeWeek": true,
+      "timeGanZhi": true,
+      "timeSecond": true,
+      "timeMonthDay": true,
+      "timeLunar": true,
+      "time24": true,
+      "maxColumn":14
+    }
+  }
 }

+ 92 - 92
public/static/js/card.js

@@ -1,93 +1,93 @@
-/**
- opt:{
- name: "窗口名称",
- src: "logo,
- url: "打开的窗口地址"
- }*
- */
-let subjectArr = []
-
-
-function openCard(opt) {
-    sendMessage('openCard', opt)
-}
-
-//向书签发送事件消息
-function emitter_emit(event, data) {
-    sendMessage(event, data)
-}
-
-//监听书签发送的事件消息
-function emitter_on(event, callback) {
-    subjectArr.push({
-        subject: event,
-        callback: callback,
-        subjectType: 'on'
-    })
-}
-
-//取消监听书签发送的事件消息
-function emitter_off(event, callback) {
-    subjectArr.forEach((item, index) => {
-        if (item === event && callback === item.callback) {
-            subjectArr.splice(index, 1)
-        }
-    })
-}
-
-function emitter_once(event, callback) {
-    subjectArr.push({
-        subject: event,
-        callback: callback,
-        subjectType: 'once'
-    })
-}
-
-const sendMessage = (subject, message) => {
-    let dt = {
-        type: 'emitter',
-        message: message,
-        subject: subject,
-    }
-    window.parent.postMessage(JSON.stringify(dt), '*')
-}
-window.addEventListener("message", (event) => {
-    try {
-        const data = JSON.parse(event.data);
-        const {type = null, subject = null, message = ''} = data;
-        if (type === 'emitter') {
-            subjectArr.forEach(item => {
-                if (item.subject === subject) {
-                    if (item.callback) {
-                        item.callback(message)
-                    }
-                    if (item.subjectType === 'once') {
-                        emitter_once(item.subject, item.callback)
-                    }
-                }
-            })
-        }
-    } catch (e) {
-    }
-})
-
-
-window.addEventListener("load", () => {
-    document.body.oncontextmenu = function (event) {
-        const {x, y} = event
-        emitter_emit("cardMouseRight", {
-            left: x,
-            top: y,
-            data: {
-                id: window.name
-            }
-        });
-        return false;
-    }
-
-
-    document.body.addEventListener('mousedown', () => {
-        emitter_emit('deskTopMouseClose')
-        emitter_emit('mouseMenuClose')
-    })
+/**
+ opt:{
+ name: "窗口名称",
+ src: "logo,
+ url: "打开的窗口地址"
+ }*
+ */
+let subjectArr = []
+
+
+function openCard(opt) {
+    sendMessage('openCard', opt)
+}
+
+//向书签发送事件消息
+function emitter_emit(event, data) {
+    sendMessage(event, data)
+}
+
+//监听书签发送的事件消息
+function emitter_on(event, callback) {
+    subjectArr.push({
+        subject: event,
+        callback: callback,
+        subjectType: 'on'
+    })
+}
+
+//取消监听书签发送的事件消息
+function emitter_off(event, callback) {
+    subjectArr.forEach((item, index) => {
+        if (item === event && callback === item.callback) {
+            subjectArr.splice(index, 1)
+        }
+    })
+}
+
+function emitter_once(event, callback) {
+    subjectArr.push({
+        subject: event,
+        callback: callback,
+        subjectType: 'once'
+    })
+}
+
+const sendMessage = (subject, message) => {
+    let dt = {
+        type: 'emitter',
+        message: message,
+        subject: subject,
+    }
+    window.parent.postMessage(JSON.stringify(dt), '*')
+}
+window.addEventListener("message", (event) => {
+    try {
+        const data = JSON.parse(event.data);
+        const {type = null, subject = null, message = ''} = data;
+        if (type === 'emitter') {
+            subjectArr.forEach(item => {
+                if (item.subject === subject) {
+                    if (item.callback) {
+                        item.callback(message)
+                    }
+                    if (item.subjectType === 'once') {
+                        emitter_once(item.subject, item.callback)
+                    }
+                }
+            })
+        }
+    } catch (e) {
+    }
+})
+
+
+window.addEventListener("load", () => {
+    document.body.oncontextmenu = function (event) {
+        const {x, y} = event
+        emitter_emit("cardMouseRight", {
+            left: x,
+            top: y,
+            data: {
+                id: window.name
+            }
+        });
+        return false;
+    }
+
+
+    document.body.addEventListener('mousedown', () => {
+        emitter_emit('deskTopMouseClose')
+        emitter_emit('mouseMenuClose')
+    })
 })

File diff suppressed because it is too large
+ 1 - 1
public/static/js/jquery.min.js


+ 1 - 1
public/static/searchEngine/360.svg

@@ -1 +1 @@
-<svg t="1650420515904" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6781" width="200" height="200"><path d="M457.8816 954.2656C214.016 954.2656 15.616 755.8656 15.616 512S214.016 69.7344 457.8816 69.7344s442.2656 198.4 442.2656 442.2656-198.41024 442.2656-442.2656 442.2656z m0-742.25664c-165.41696 0-299.99104 134.57408-299.99104 299.99104S292.4544 811.99104 457.8816 811.99104 757.87264 677.41696 757.87264 512 623.29856 212.00896 457.8816 212.00896z" fill="#2CAC57" p-id="6782"></path><path d="M937.24672 849.11104m-71.13728 0a71.13728 71.13728 0 1 0 142.27456 0 71.13728 71.13728 0 1 0-142.27456 0Z" fill="#F4B122" p-id="6783"></path><path d="M457.8816 954.2656c-175.33952 0-334.2848-103.71072-404.9408-264.21248l-1.95584-4.5056c-15.4112-36.13696 1.39264-77.9264 37.51936-93.3376 36.12672-15.4112 77.9264 1.3824 93.3376 37.51936l1.29024 2.9696c47.95392 108.93312 155.79136 179.29216 274.7392 179.29216 117.5552 0 224.88064-69.21216 273.41824-176.32256 0.65536-1.44384 1.31072-2.9184 1.9456-4.39296 15.60576-36.05504 57.47712-52.6336 93.53216-37.02784 36.05504 15.60576 52.6336 57.47712 37.02784 93.53216-0.95232 2.21184-1.93536 4.4032-2.92864 6.59456-71.53664 157.88032-229.72416 259.8912-402.98496 259.8912z" fill="#F4B122" p-id="6784"></path></svg>
+<svg t="1650420515904" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6781" width="200" height="200"><path d="M457.8816 954.2656C214.016 954.2656 15.616 755.8656 15.616 512S214.016 69.7344 457.8816 69.7344s442.2656 198.4 442.2656 442.2656-198.41024 442.2656-442.2656 442.2656z m0-742.25664c-165.41696 0-299.99104 134.57408-299.99104 299.99104S292.4544 811.99104 457.8816 811.99104 757.87264 677.41696 757.87264 512 623.29856 212.00896 457.8816 212.00896z" fill="#2CAC57" p-id="6782"></path><path d="M937.24672 849.11104m-71.13728 0a71.13728 71.13728 0 1 0 142.27456 0 71.13728 71.13728 0 1 0-142.27456 0Z" fill="#F4B122" p-id="6783"></path><path d="M457.8816 954.2656c-175.33952 0-334.2848-103.71072-404.9408-264.21248l-1.95584-4.5056c-15.4112-36.13696 1.39264-77.9264 37.51936-93.3376 36.12672-15.4112 77.9264 1.3824 93.3376 37.51936l1.29024 2.9696c47.95392 108.93312 155.79136 179.29216 274.7392 179.29216 117.5552 0 224.88064-69.21216 273.41824-176.32256 0.65536-1.44384 1.31072-2.9184 1.9456-4.39296 15.60576-36.05504 57.47712-52.6336 93.53216-37.02784 36.05504 15.60576 52.6336 57.47712 37.02784 93.53216-0.95232 2.21184-1.93536 4.4032-2.92864 6.59456-71.53664 157.88032-229.72416 259.8912-402.98496 259.8912z" fill="#F4B122" p-id="6784"></path></svg>

+ 1 - 1
public/static/searchEngine/baidu.svg

@@ -1 +1 @@
-<svg t="1650420067930" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3368" width="200" height="200"><path d="M184.682 538.759c111.177-23.874 96.03-156.737 92.702-185.776-5.445-44.768-58.102-123.02-129.606-116.831-89.98 8.074-103.126 138.052-103.126 138.052-12.17 60.08 29.132 188.452 140.03 164.555zM302.746 769.86c-3.257 9.331-10.517 33.228-4.234 54.03 12.402 46.677 52.912 48.77 52.912 48.77h58.218v-142.31h-62.336c-28.016 8.354-41.535 30.157-44.56 39.51z m88.281-453.898c61.406 0 111.037-70.667 111.037-158.04C502.064 70.643 452.433 0 391.027 0c-61.312 0-111.06 70.643-111.06 157.923 0 87.373 49.77 158.04 111.06 158.04z m264.47 10.447c82.068 10.657 134.84-76.925 145.335-143.31 10.703-66.292-42.256-143.288-100.357-156.527-58.218-13.356-130.909 79.904-137.54 140.704-7.912 74.32 10.633 148.593 92.562 159.133z m201.086 390.213s-126.976-98.24-201.11-204.414C555 355.66 412.272 419.37 364.525 498.993 316.987 578.594 242.9 628.947 232.382 642.28c-10.68 13.124-153.385 90.166-121.694 230.87 31.669 140.612 142.939 137.936 142.939 137.936s81.998 8.074 177.12-13.217c95.168-21.104 177.096 5.26 177.096 5.26s222.284 74.435 283.108-68.852c60.754-143.334-34.368-217.654-34.368-217.654zM476.26 929.88H331.739c-62.406-12.449-87.257-55.03-90.398-62.29-3.072-7.376-20.802-41.604-11.425-99.845 26.968-87.257 103.87-93.516 103.87-93.516h76.926v-94.563l65.524 1V929.88z m269.146-1h-166.3c-64.453-16.614-67.455-62.407-67.455-62.407v-183.89l67.455-1.094v165.276c4.119 17.637 26.015 20.825 26.015 20.825h68.525V682.581h71.76v246.297z m235.408-490.99c0-31.76-26.387-127.394-124.23-127.394-98.008 0-111.108 90.258-111.108 154.06 0 60.894 5.142 145.894 126.883 143.195 121.788-2.7 108.455-137.936 108.455-169.86z m0 0" fill="#3245DF" p-id="3369"></path></svg>
+<svg t="1650420067930" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3368" width="200" height="200"><path d="M184.682 538.759c111.177-23.874 96.03-156.737 92.702-185.776-5.445-44.768-58.102-123.02-129.606-116.831-89.98 8.074-103.126 138.052-103.126 138.052-12.17 60.08 29.132 188.452 140.03 164.555zM302.746 769.86c-3.257 9.331-10.517 33.228-4.234 54.03 12.402 46.677 52.912 48.77 52.912 48.77h58.218v-142.31h-62.336c-28.016 8.354-41.535 30.157-44.56 39.51z m88.281-453.898c61.406 0 111.037-70.667 111.037-158.04C502.064 70.643 452.433 0 391.027 0c-61.312 0-111.06 70.643-111.06 157.923 0 87.373 49.77 158.04 111.06 158.04z m264.47 10.447c82.068 10.657 134.84-76.925 145.335-143.31 10.703-66.292-42.256-143.288-100.357-156.527-58.218-13.356-130.909 79.904-137.54 140.704-7.912 74.32 10.633 148.593 92.562 159.133z m201.086 390.213s-126.976-98.24-201.11-204.414C555 355.66 412.272 419.37 364.525 498.993 316.987 578.594 242.9 628.947 232.382 642.28c-10.68 13.124-153.385 90.166-121.694 230.87 31.669 140.612 142.939 137.936 142.939 137.936s81.998 8.074 177.12-13.217c95.168-21.104 177.096 5.26 177.096 5.26s222.284 74.435 283.108-68.852c60.754-143.334-34.368-217.654-34.368-217.654zM476.26 929.88H331.739c-62.406-12.449-87.257-55.03-90.398-62.29-3.072-7.376-20.802-41.604-11.425-99.845 26.968-87.257 103.87-93.516 103.87-93.516h76.926v-94.563l65.524 1V929.88z m269.146-1h-166.3c-64.453-16.614-67.455-62.407-67.455-62.407v-183.89l67.455-1.094v165.276c4.119 17.637 26.015 20.825 26.015 20.825h68.525V682.581h71.76v246.297z m235.408-490.99c0-31.76-26.387-127.394-124.23-127.394-98.008 0-111.108 90.258-111.108 154.06 0 60.894 5.142 145.894 126.883 143.195 121.788-2.7 108.455-137.936 108.455-169.86z m0 0" fill="#3245DF" p-id="3369"></path></svg>

+ 1 - 1
public/static/searchEngine/bing.svg

@@ -1 +1 @@
-<svg t="1650420543193" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8343" width="200" height="200"><path d="M340.5824 70.109867L102.536533 0.682667v851.217066L340.650667 643.345067V70.109867zM102.536533 851.7632l238.045867 171.6224 580.881067-340.923733V411.784533L102.536533 851.831467z" fill="#409EFF" p-id="8344"></path><path d="M409.463467 255.3856l113.732266 238.933333 138.8544 56.866134 259.413334-139.400534-506.0608-156.330666z" fill="#409EFF" p-id="8345"></path></svg>
+<svg t="1650420543193" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8343" width="200" height="200"><path d="M340.5824 70.109867L102.536533 0.682667v851.217066L340.650667 643.345067V70.109867zM102.536533 851.7632l238.045867 171.6224 580.881067-340.923733V411.784533L102.536533 851.831467z" fill="#409EFF" p-id="8344"></path><path d="M409.463467 255.3856l113.732266 238.933333 138.8544 56.866134 259.413334-139.400534-506.0608-156.330666z" fill="#409EFF" p-id="8345"></path></svg>

+ 1 - 1
public/static/searchEngine/google.svg

@@ -1 +1 @@
-<svg t="1650419987680" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2562" width="200" height="200"><path d="M214.101333 512c0-32.512 5.546667-63.701333 15.36-92.928L57.173333 290.218667A491.861333 491.861333 0 0 0 4.693333 512c0 79.701333 18.858667 154.88 52.394667 221.610667l172.202667-129.066667A290.56 290.56 0 0 1 214.101333 512" fill="#FBBC05" p-id="2563"></path><path d="M516.693333 216.192c72.106667 0 137.258667 25.002667 188.458667 65.962667L854.101333 136.533333C763.349333 59.178667 646.997333 11.392 516.693333 11.392c-202.325333 0-376.234667 113.28-459.52 278.826667l172.373334 128.853333c39.68-118.016 152.832-202.88 287.146666-202.88" fill="#EA4335" p-id="2564"></path><path d="M516.693333 807.808c-134.357333 0-247.509333-84.864-287.232-202.88l-172.288 128.853333c83.242667 165.546667 257.152 278.826667 459.52 278.826667 124.842667 0 244.053333-43.392 333.568-124.757333l-163.584-123.818667c-46.122667 28.458667-104.234667 43.776-170.026666 43.776" fill="#34A853" p-id="2565"></path><path d="M1005.397333 512c0-29.568-4.693333-61.44-11.648-91.008H516.650667V614.4h274.602666c-13.696 65.962667-51.072 116.650667-104.533333 149.632l163.541333 123.818667c93.994667-85.418667 155.136-212.650667 155.136-375.850667" fill="#4285F4" p-id="2566"></path></svg>
+<svg t="1650419987680" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2562" width="200" height="200"><path d="M214.101333 512c0-32.512 5.546667-63.701333 15.36-92.928L57.173333 290.218667A491.861333 491.861333 0 0 0 4.693333 512c0 79.701333 18.858667 154.88 52.394667 221.610667l172.202667-129.066667A290.56 290.56 0 0 1 214.101333 512" fill="#FBBC05" p-id="2563"></path><path d="M516.693333 216.192c72.106667 0 137.258667 25.002667 188.458667 65.962667L854.101333 136.533333C763.349333 59.178667 646.997333 11.392 516.693333 11.392c-202.325333 0-376.234667 113.28-459.52 278.826667l172.373334 128.853333c39.68-118.016 152.832-202.88 287.146666-202.88" fill="#EA4335" p-id="2564"></path><path d="M516.693333 807.808c-134.357333 0-247.509333-84.864-287.232-202.88l-172.288 128.853333c83.242667 165.546667 257.152 278.826667 459.52 278.826667 124.842667 0 244.053333-43.392 333.568-124.757333l-163.584-123.818667c-46.122667 28.458667-104.234667 43.776-170.026666 43.776" fill="#34A853" p-id="2565"></path><path d="M1005.397333 512c0-29.568-4.693333-61.44-11.648-91.008H516.650667V614.4h274.602666c-13.696 65.962667-51.072 116.650667-104.533333 149.632l163.541333 123.818667c93.994667-85.418667 155.136-212.650667 155.136-375.850667" fill="#4285F4" p-id="2566"></path></svg>

+ 1 - 1
public/static/searchEngine/sougou.svg

@@ -1 +1 @@
-<svg t="1650420413766" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5090" width="200" height="200"><path d="M510.050302 1009.113308a504.54878 504.54878 0 1 1 504.470793-504.54878 505.094696 505.094696 0 0 1-504.471793 504.471792z m0-980.001071a475.452291 475.452291 0 1 0 475.374303 475.452291A475.998206 475.998206 0 0 0 510.050302 29.112237z" fill="#3EA1FC" p-id="5091"></path><path d="M694.301738 913.165183c184.643375-88.538274 273.804552-237.141236 267.408544-445.965863-22.232553-344.556584-438.944951-482.863142-532.943378-268.65635-44.308131 196.187585 223.567341 174.423959 329.188966 320.45332 94.388367 133.079369 73.170656 264.443004-63.653132 394.168893z" fill="#3EA1FC" p-id="5092"></path><path d="M321.663508 96.588776C137.020133 185.12605 47.858955 333.807 54.254964 542.553638 76.487517 887.111222 493.199915 1025.41678 587.198342 811.209989c44.386119-196.187585-223.489353-174.422959-329.03299-320.374333-94.388367-133.314332-73.326632-264.755955 63.498156-394.24688z" fill="#3EA1FC" p-id="5093"></path></svg>
+<svg t="1650420413766" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5090" width="200" height="200"><path d="M510.050302 1009.113308a504.54878 504.54878 0 1 1 504.470793-504.54878 505.094696 505.094696 0 0 1-504.471793 504.471792z m0-980.001071a475.452291 475.452291 0 1 0 475.374303 475.452291A475.998206 475.998206 0 0 0 510.050302 29.112237z" fill="#3EA1FC" p-id="5091"></path><path d="M694.301738 913.165183c184.643375-88.538274 273.804552-237.141236 267.408544-445.965863-22.232553-344.556584-438.944951-482.863142-532.943378-268.65635-44.308131 196.187585 223.567341 174.423959 329.188966 320.45332 94.388367 133.079369 73.170656 264.443004-63.653132 394.168893z" fill="#3EA1FC" p-id="5092"></path><path d="M321.663508 96.588776C137.020133 185.12605 47.858955 333.807 54.254964 542.553638 76.487517 887.111222 493.199915 1025.41678 587.198342 811.209989c44.386119-196.187585-223.489353-174.422959-329.03299-320.374333-94.388367-133.314332-73.326632-264.755955 63.498156-394.24688z" fill="#3EA1FC" p-id="5093"></path></svg>

BIN
public/static/wallpaper/.DS_Store


+ 42 - 42
route/app.php

@@ -1,42 +1,42 @@
-<?php
-
-use think\facade\Route;
-Route::any('/index/all',"index/all");
-Route::any('/manager', 'index/index');
-Route::any("/privacy", "index/privacy");
-Route::any('/noteApp', "index/index");
-Route::any("/api/background$","api/background");
-Route::any("/qq_login$", "user/qq_login");
-Route::any("/", 'index/index');
-Route::any("/favicon", "index/favicon");
-Route::get("/plugins/:dir/static/[:file]", "\PluginStaticSystem@index")->pattern(['dir' => '\w+', 'file' => '[\w||\s\-].*']); //插件静态资源路由文件
-Route::any("/manifest.json", "index/manifest")->cache(60*10);
-Route::any("/searchEngine/searchEngine", "searchEngine/searchEngine");
-Route::group("/plugins", function () {
-    $pluginsDir = root_path() . "plugins/";
-    if (is_dir($pluginsDir)) {
-        $url = request()->baseUrl();
-        $urlArr = explode('/', $url);
-        $pluginsDirName = '';
-        if (isset($urlArr[2])) {
-            $pluginsDirName = $urlArr[2];
-        }
-        foreach (scandir($pluginsDir) as $item) {
-            if (mb_strtolower($item) == mb_strtolower($pluginsDirName)) {
-                $router = $pluginsDir . $item . '/route.php';
-                if (file_exists($router)) {
-                    $_ENV['plugins_dir_name'] = $pluginsDir . $item;
-                    include_once $router;
-                    break;
-                }
-            }
-        }
-    }
-    Route::miss(function () {
-        return view(app_path() . "view/cardNotFound.html")->code(200);
-    });
-});
-
-Route::options("[:s]", function () {
-    return response('', 200);
-})->cache(60 * 60);
+<?php
+
+use think\facade\Route;
+Route::any('/index/all',"index/all");
+Route::any('/manager', 'index/index');
+Route::any("/privacy", "index/privacy");
+Route::any('/noteApp', "index/index");
+Route::any("/api/background$","api/background");
+Route::any("/qq_login$", "user/qq_login");
+Route::any("/", 'index/index');
+Route::any("/favicon", "index/favicon");
+Route::get("/plugins/:dir/static/[:file]", "\PluginStaticSystem@index")->pattern(['dir' => '\w+', 'file' => '[\w||\s\-].*']); //插件静态资源路由文件
+Route::any("/manifest.json", "index/manifest")->cache(60*10);
+Route::any("/searchEngine/searchEngine", "searchEngine/searchEngine");
+Route::group("/plugins", function () {
+    $pluginsDir = root_path() . "plugins/";
+    if (is_dir($pluginsDir)) {
+        $url = request()->baseUrl();
+        $urlArr = explode('/', $url);
+        $pluginsDirName = '';
+        if (isset($urlArr[2])) {
+            $pluginsDirName = $urlArr[2];
+        }
+        foreach (scandir($pluginsDir) as $item) {
+            if (mb_strtolower($item) == mb_strtolower($pluginsDirName)) {
+                $router = $pluginsDir . $item . '/route.php';
+                if (file_exists($router)) {
+                    $_ENV['plugins_dir_name'] = $pluginsDir . $item;
+                    include_once $router;
+                    break;
+                }
+            }
+        }
+    }
+    Route::miss(function () {
+        return view(app_path() . "view/cardNotFound.html")->code(200);
+    });
+});
+
+Route::options("[:s]", function () {
+    return response('', 200);
+})->cache(60 * 60);

+ 8 - 8
think

@@ -1,9 +1,9 @@
-#!/usr/bin/env php
-<?php
-namespace think;
-// 命令行入口文件
-// 加载基础文件
-require __DIR__ . '/vendor/autoload.php';
-
-// 应用初始化
+#!/usr/bin/env php
+<?php
+namespace think;
+// 命令行入口文件
+// 加载基础文件
+require __DIR__ . '/vendor/autoload.php';
+
+// 应用初始化
 (new App())->console->run();

BIN
vendor/autoload.php


+ 1609 - 1609
vendor/composer/ClassLoader.php

@@ -1,1609 +1,1609 @@
-# Change Log
-
-Please refer to [UPGRADING](UPGRADING.md) guide for upgrading to a major version.
-
-
-## 7.7.0 - 2023-05-21
-
-### Added
-
-- Support `guzzlehttp/promises` v2
-
-
-## 7.6.1 - 2023-05-15
-
-### Fixed
-
-- Fix `SetCookie::fromString` MaxAge deprecation warning and skip invalid MaxAge values
-
-
-## 7.6.0 - 2023-05-14
-
-### Added
-
-- Support for setting the minimum TLS version in a unified way
-- Apply on request the version set in options parameters
-
-
-## 7.5.2 - 2023-05-14
-
-### Fixed
-
-- Fixed set cookie constructor validation
-- Fixed handling of files with `'0'` body
-
-### Changed
-
-- Corrected docs and default connect timeout value to 300 seconds
-
-
-## 7.5.1 - 2023-04-17
-
-### Fixed
-
-- Fixed `NO_PROXY` settings so that setting the `proxy` option to `no` overrides the env variable
-
-### Changed
-
-- Adjusted `guzzlehttp/psr7` version constraint to `^1.9.1 || ^2.4.5`
-
-
-## 7.5.0 - 2022-08-28
-
-### Added
-
-- Support PHP 8.2
-- Add request to delay closure params
-
-
-## 7.4.5 - 2022-06-20
-
-### Fixed
-
-* Fix change in port should be considered a change in origin
-* Fix `CURLOPT_HTTPAUTH` option not cleared on change of origin
-
-
-## 7.4.4 - 2022-06-09
-
-### Fixed
-
-* Fix failure to strip Authorization header on HTTP downgrade
-* Fix failure to strip the Cookie header on change in host or HTTP downgrade
-
-
-## 7.4.3 - 2022-05-25
-
-### Fixed
-
-* Fix cross-domain cookie leakage
-
-
-## 7.4.2 - 2022-03-20
-
-### Fixed
-
-- Remove curl auth on cross-domain redirects to align with the Authorization HTTP header
-- Reject non-HTTP schemes in StreamHandler
-- Set a default ssl.peer_name context in StreamHandler to allow `force_ip_resolve`
-
-
-## 7.4.1 - 2021-12-06
-
-### Changed
-
-- Replaced implicit URI to string coercion [#2946](https://github.com/guzzle/guzzle/pull/2946)
-- Allow `symfony/deprecation-contracts` version 3 [#2961](https://github.com/guzzle/guzzle/pull/2961)
-
-### Fixed
-
-- Only close curl handle if it's done [#2950](https://github.com/guzzle/guzzle/pull/2950)
-
-
-## 7.4.0 - 2021-10-18
-
-### Added
-
-- Support PHP 8.1 [#2929](https://github.com/guzzle/guzzle/pull/2929), [#2939](https://github.com/guzzle/guzzle/pull/2939)
-- Support `psr/log` version 2 and 3 [#2943](https://github.com/guzzle/guzzle/pull/2943)
-
-### Fixed
-
-- Make sure we always call `restore_error_handler()` [#2915](https://github.com/guzzle/guzzle/pull/2915)
-- Fix progress parameter type compatibility between the cURL and stream handlers [#2936](https://github.com/guzzle/guzzle/pull/2936)
-- Throw `InvalidArgumentException` when an incorrect `headers` array is provided [#2916](https://github.com/guzzle/guzzle/pull/2916), [#2942](https://github.com/guzzle/guzzle/pull/2942)
-
-### Changed
-
-- Be more strict with types [#2914](https://github.com/guzzle/guzzle/pull/2914), [#2917](https://github.com/guzzle/guzzle/pull/2917), [#2919](https://github.com/guzzle/guzzle/pull/2919), [#2945](https://github.com/guzzle/guzzle/pull/2945)
-
-
-## 7.3.0 - 2021-03-23
-
-### Added
-
-- Support for DER and P12 certificates [#2413](https://github.com/guzzle/guzzle/pull/2413)
-- Support the cURL (http://) scheme for StreamHandler proxies [#2850](https://github.com/guzzle/guzzle/pull/2850)
-- Support for `guzzlehttp/psr7:^2.0` [#2878](https://github.com/guzzle/guzzle/pull/2878)
-
-### Fixed
-
-- Handle exceptions on invalid header consistently between PHP versions and handlers [#2872](https://github.com/guzzle/guzzle/pull/2872)
-
-
-## 7.2.0 - 2020-10-10
-
-### Added
-
-- Support for PHP 8 [#2712](https://github.com/guzzle/guzzle/pull/2712), [#2715](https://github.com/guzzle/guzzle/pull/2715), [#2789](https://github.com/guzzle/guzzle/pull/2789)
-- Support passing a body summarizer to the http errors middleware [#2795](https://github.com/guzzle/guzzle/pull/2795)
-
-### Fixed
-
-- Handle exceptions during response creation [#2591](https://github.com/guzzle/guzzle/pull/2591)
-- Fix CURLOPT_ENCODING not to be overwritten [#2595](https://github.com/guzzle/guzzle/pull/2595)
-- Make sure the Request always has a body object [#2804](https://github.com/guzzle/guzzle/pull/2804)
-
-### Changed
-
-- The `TooManyRedirectsException` has a response [#2660](https://github.com/guzzle/guzzle/pull/2660)
-- Avoid "functions" from dependencies [#2712](https://github.com/guzzle/guzzle/pull/2712)
-
-### Deprecated
-
-- Using environment variable GUZZLE_CURL_SELECT_TIMEOUT [#2786](https://github.com/guzzle/guzzle/pull/2786)
-
-
-## 7.1.1 - 2020-09-30
-
-### Fixed
-
-- Incorrect EOF detection for response body streams on Windows.
-
-### Changed
-
-- We dont connect curl `sink` on HEAD requests.
-- Removed some PHP 5 workarounds
-
-
-## 7.1.0 - 2020-09-22
-
-### Added
-
-- `GuzzleHttp\MessageFormatterInterface`
-
-### Fixed
-
-- Fixed issue that caused cookies with no value not to be stored.
-- On redirects, we allow all safe methods like GET, HEAD and OPTIONS.
-- Fixed logging on empty responses.
-- Make sure MessageFormatter::format returns string
-
-### Deprecated
-
-- All functions in `GuzzleHttp` has been deprecated. Use static methods on `Utils` instead.
-- `ClientInterface::getConfig()`
-- `Client::getConfig()`
-- `Client::__call()`
-- `Utils::defaultCaBundle()`
-- `CurlFactory::LOW_CURL_VERSION_NUMBER`
-
-
-## 7.0.1 - 2020-06-27
-
-* Fix multiply defined functions fatal error [#2699](https://github.com/guzzle/guzzle/pull/2699)
-
-
-## 7.0.0 - 2020-06-27
-
-No changes since 7.0.0-rc1.
-
-
-## 7.0.0-rc1 - 2020-06-15
-
-### Changed
-
-* Use error level for logging errors in Middleware [#2629](https://github.com/guzzle/guzzle/pull/2629)
-* Disabled IDN support by default and require ext-intl to use it [#2675](https://github.com/guzzle/guzzle/pull/2675)
-
-
-## 7.0.0-beta2 - 2020-05-25
-
-### Added
-
-* Using `Utils` class instead of functions in the `GuzzleHttp` namespace. [#2546](https://github.com/guzzle/guzzle/pull/2546)
-* `ClientInterface::MAJOR_VERSION` [#2583](https://github.com/guzzle/guzzle/pull/2583)
-
-### Changed
-
-* Avoid the `getenv` function when unsafe [#2531](https://github.com/guzzle/guzzle/pull/2531)
-* Added real client methods [#2529](https://github.com/guzzle/guzzle/pull/2529)
-* Avoid functions due to global install conflicts [#2546](https://github.com/guzzle/guzzle/pull/2546)
-* Use Symfony intl-idn polyfill [#2550](https://github.com/guzzle/guzzle/pull/2550)
-* Adding methods for HTTP verbs like `Client::get()`, `Client::head()`, `Client::patch()` etc [#2529](https://github.com/guzzle/guzzle/pull/2529)
-* `ConnectException` extends `TransferException` [#2541](https://github.com/guzzle/guzzle/pull/2541)
-* Updated the default User Agent to "GuzzleHttp/7" [#2654](https://github.com/guzzle/guzzle/pull/2654)
-
-### Fixed
-
-* Various intl icu issues [#2626](https://github.com/guzzle/guzzle/pull/2626)
-
-### Removed
-
-* Pool option `pool_size` [#2528](https://github.com/guzzle/guzzle/pull/2528)
-
-
-## 7.0.0-beta1 - 2019-12-30
-
-The diff might look very big but 95% of Guzzle users will be able to upgrade without modification.
-Please see [the upgrade document](UPGRADING.md) that describes all BC breaking changes.
-
-### Added
-
-* Implement PSR-18 and dropped PHP 5 support [#2421](https://github.com/guzzle/guzzle/pull/2421) [#2474](https://github.com/guzzle/guzzle/pull/2474)
-* PHP 7 types [#2442](https://github.com/guzzle/guzzle/pull/2442) [#2449](https://github.com/guzzle/guzzle/pull/2449) [#2466](https://github.com/guzzle/guzzle/pull/2466) [#2497](https://github.com/guzzle/guzzle/pull/2497) [#2499](https://github.com/guzzle/guzzle/pull/2499)
-* IDN support for redirects [2424](https://github.com/guzzle/guzzle/pull/2424)
-
-### Changed
-
-* Dont allow passing null as third argument to `BadResponseException::__construct()` [#2427](https://github.com/guzzle/guzzle/pull/2427)
-* Use SAPI constant instead of method call [#2450](https://github.com/guzzle/guzzle/pull/2450)
-* Use native function invocation [#2444](https://github.com/guzzle/guzzle/pull/2444)
-* Better defaults for PHP installations with old ICU lib [2454](https://github.com/guzzle/guzzle/pull/2454)
-* Added visibility to all constants [#2462](https://github.com/guzzle/guzzle/pull/2462)
-* Dont allow passing `null` as URI to `Client::request()` and `Client::requestAsync()` [#2461](https://github.com/guzzle/guzzle/pull/2461)
-* Widen the exception argument to throwable [#2495](https://github.com/guzzle/guzzle/pull/2495)
-
-### Fixed
-
-* Logging when Promise rejected with a string [#2311](https://github.com/guzzle/guzzle/pull/2311)
-
-### Removed
-
-* Class `SeekException` [#2162](https://github.com/guzzle/guzzle/pull/2162)
-* `RequestException::getResponseBodySummary()` [#2425](https://github.com/guzzle/guzzle/pull/2425)
-* `CookieJar::getCookieValue()` [#2433](https://github.com/guzzle/guzzle/pull/2433)
-* `uri_template()` and `UriTemplate` [#2440](https://github.com/guzzle/guzzle/pull/2440)
-* Request options `save_to` and `exceptions` [#2464](https://github.com/guzzle/guzzle/pull/2464)
-
-
-## 6.5.2 - 2019-12-23
-
-* idn_to_ascii() fix for old PHP versions [#2489](https://github.com/guzzle/guzzle/pull/2489)
-
-
-## 6.5.1 - 2019-12-21
-
-* Better defaults for PHP installations with old ICU lib [#2454](https://github.com/guzzle/guzzle/pull/2454)
-* IDN support for redirects [#2424](https://github.com/guzzle/guzzle/pull/2424)
-
-
-## 6.5.0 - 2019-12-07
-
-* Improvement: Added support for reset internal queue in MockHandler. [#2143](https://github.com/guzzle/guzzle/pull/2143)
-* Improvement: Added support to pass arbitrary options to `curl_multi_init`. [#2287](https://github.com/guzzle/guzzle/pull/2287)
-* Fix: Gracefully handle passing `null` to the `header` option. [#2132](https://github.com/guzzle/guzzle/pull/2132)
-* Fix: `RetryMiddleware` did not do exponential delay between retires due unit mismatch. [#2132](https://github.com/guzzle/guzzle/pull/2132)
-* Fix: Prevent undefined offset when using array for ssl_key options. [#2348](https://github.com/guzzle/guzzle/pull/2348)
-* Deprecated `ClientInterface::VERSION`
-
-
-## 6.4.1 - 2019-10-23
-
-* No `guzzle.phar` was created in 6.4.0 due expired API token. This release will fix that
-* Added `parent::__construct()` to `FileCookieJar` and `SessionCookieJar`
-
-
-## 6.4.0 - 2019-10-23
-
-* Improvement: Improved error messages when using curl < 7.21.2 [#2108](https://github.com/guzzle/guzzle/pull/2108)
-* Fix: Test if response is readable before returning a summary in `RequestException::getResponseBodySummary()` [#2081](https://github.com/guzzle/guzzle/pull/2081)
-* Fix: Add support for GUZZLE_CURL_SELECT_TIMEOUT environment variable [#2161](https://github.com/guzzle/guzzle/pull/2161)
-* Improvement: Added `GuzzleHttp\Exception\InvalidArgumentException` [#2163](https://github.com/guzzle/guzzle/pull/2163)
-* Improvement: Added `GuzzleHttp\_current_time()` to use `hrtime()` if that function exists. [#2242](https://github.com/guzzle/guzzle/pull/2242)
-* Improvement: Added curl's `appconnect_time` in `TransferStats` [#2284](https://github.com/guzzle/guzzle/pull/2284)
-* Improvement: Make GuzzleException extend Throwable wherever it's available [#2273](https://github.com/guzzle/guzzle/pull/2273)
-* Fix: Prevent concurrent writes to file when saving `CookieJar` [#2335](https://github.com/guzzle/guzzle/pull/2335)
-* Improvement: Update `MockHandler` so we can test transfer time [#2362](https://github.com/guzzle/guzzle/pull/2362)
-
-
-## 6.3.3 - 2018-04-22
-
-* Fix: Default headers when decode_content is specified
-
-
-## 6.3.2 - 2018-03-26
-
-* Fix: Release process
-
-
-## 6.3.1 - 2018-03-26
-
-* Bug fix: Parsing 0 epoch expiry times in cookies [#2014](https://github.com/guzzle/guzzle/pull/2014)
-* Improvement: Better ConnectException detection [#2012](https://github.com/guzzle/guzzle/pull/2012)
-* Bug fix: Malformed domain that contains a "/" [#1999](https://github.com/guzzle/guzzle/pull/1999)
-* Bug fix: Undefined offset when a cookie has no first key-value pair [#1998](https://github.com/guzzle/guzzle/pull/1998)
-* Improvement: Support PHPUnit 6 [#1953](https://github.com/guzzle/guzzle/pull/1953)
-* Bug fix: Support empty headers [#1915](https://github.com/guzzle/guzzle/pull/1915)
-* Bug fix: Ignore case during header modifications [#1916](https://github.com/guzzle/guzzle/pull/1916)
-
-+ Minor code cleanups, documentation fixes and clarifications.
-
-
-## 6.3.0 - 2017-06-22
-
-* Feature: force IP resolution (ipv4 or ipv6) [#1608](https://github.com/guzzle/guzzle/pull/1608), [#1659](https://github.com/guzzle/guzzle/pull/1659)
-* Improvement: Don't include summary in exception message when body is empty [#1621](https://github.com/guzzle/guzzle/pull/1621)
-* Improvement: Handle `on_headers` option in MockHandler [#1580](https://github.com/guzzle/guzzle/pull/1580)
-* Improvement: Added SUSE Linux CA path [#1609](https://github.com/guzzle/guzzle/issues/1609)
-* Improvement: Use class reference for getting the name of the class instead of using hardcoded strings [#1641](https://github.com/guzzle/guzzle/pull/1641)
-* Feature: Added `read_timeout` option [#1611](https://github.com/guzzle/guzzle/pull/1611)
-* Bug fix: PHP 7.x fixes [#1685](https://github.com/guzzle/guzzle/pull/1685), [#1686](https://github.com/guzzle/guzzle/pull/1686), [#1811](https://github.com/guzzle/guzzle/pull/1811)
-* Deprecation: BadResponseException instantiation without a response [#1642](https://github.com/guzzle/guzzle/pull/1642)
-* Feature: Added NTLM auth [#1569](https://github.com/guzzle/guzzle/pull/1569)
-* Feature: Track redirect HTTP status codes [#1711](https://github.com/guzzle/guzzle/pull/1711)
-* Improvement: Check handler type during construction [#1745](https://github.com/guzzle/guzzle/pull/1745)
-* Improvement: Always include the Content-Length if there's a body [#1721](https://github.com/guzzle/guzzle/pull/1721)
-* Feature: Added convenience method to access a cookie by name [#1318](https://github.com/guzzle/guzzle/pull/1318)
-* Bug fix: Fill `CURLOPT_CAPATH` and `CURLOPT_CAINFO` properly [#1684](https://github.com/guzzle/guzzle/pull/1684)
-* Improvement:  	Use `\GuzzleHttp\Promise\rejection_for` function instead of object init [#1827](https://github.com/guzzle/guzzle/pull/1827)
-
-+ Minor code cleanups, documentation fixes and clarifications.
-
-
-## 6.2.3 - 2017-02-28
-
-* Fix deprecations with guzzle/psr7 version 1.4
-
-
-## 6.2.2 - 2016-10-08
-
-* Allow to pass nullable Response to delay callable
-* Only add scheme when host is present
-* Fix drain case where content-length is the literal string zero
-* Obfuscate in-URL credentials in exceptions
-
-
-## 6.2.1 - 2016-07-18
-
-* Address HTTP_PROXY security vulnerability, CVE-2016-5385:
-  https://httpoxy.org/
-* Fixing timeout bug with StreamHandler:
-  https://github.com/guzzle/guzzle/pull/1488
-* Only read up to `Content-Length` in PHP StreamHandler to avoid timeouts when
-  a server does not honor `Connection: close`.
-* Ignore URI fragment when sending requests.
-
-
-## 6.2.0 - 2016-03-21
-
-* Feature: added `GuzzleHttp\json_encode` and `GuzzleHttp\json_decode`.
-  https://github.com/guzzle/guzzle/pull/1389
-* Bug fix: Fix sleep calculation when waiting for delayed requests.
-  https://github.com/guzzle/guzzle/pull/1324
-* Feature: More flexible history containers.
-  https://github.com/guzzle/guzzle/pull/1373
-* Bug fix: defer sink stream opening in StreamHandler.
-  https://github.com/guzzle/guzzle/pull/1377
-* Bug fix: do not attempt to escape cookie values.
-  https://github.com/guzzle/guzzle/pull/1406
-* Feature: report original content encoding and length on decoded responses.
-  https://github.com/guzzle/guzzle/pull/1409
-* Bug fix: rewind seekable request bodies before dispatching to cURL.
-  https://github.com/guzzle/guzzle/pull/1422
-* Bug fix: provide an empty string to `http_build_query` for HHVM workaround.
-  https://github.com/guzzle/guzzle/pull/1367
-
-
-## 6.1.1 - 2015-11-22
-
-* Bug fix: Proxy::wrapSync() now correctly proxies to the appropriate handler
-  https://github.com/guzzle/guzzle/commit/911bcbc8b434adce64e223a6d1d14e9a8f63e4e4
-* Feature: HandlerStack is now more generic.
-  https://github.com/guzzle/guzzle/commit/f2102941331cda544745eedd97fc8fd46e1ee33e
-* Bug fix: setting verify to false in the StreamHandler now disables peer
-  verification. https://github.com/guzzle/guzzle/issues/1256
-* Feature: Middleware now uses an exception factory, including more error
-  context. https://github.com/guzzle/guzzle/pull/1282
-* Feature: better support for disabled functions.
-  https://github.com/guzzle/guzzle/pull/1287
-* Bug fix: fixed regression where MockHandler was not using `sink`.
-  https://github.com/guzzle/guzzle/pull/1292
-
-
-## 6.1.0 - 2015-09-08
-
-* Feature: Added the `on_stats` request option to provide access to transfer
-  statistics for requests. https://github.com/guzzle/guzzle/pull/1202
-* Feature: Added the ability to persist session cookies in CookieJars.
-  https://github.com/guzzle/guzzle/pull/1195
-* Feature: Some compatibility updates for Google APP Engine
-  https://github.com/guzzle/guzzle/pull/1216
-* Feature: Added support for NO_PROXY to prevent the use of a proxy based on
-  a simple set of rules. https://github.com/guzzle/guzzle/pull/1197
-* Feature: Cookies can now contain square brackets.
-  https://github.com/guzzle/guzzle/pull/1237
-* Bug fix: Now correctly parsing `=` inside of quotes in Cookies.
-  https://github.com/guzzle/guzzle/pull/1232
-* Bug fix: Cusotm cURL options now correctly override curl options of the
-  same name. https://github.com/guzzle/guzzle/pull/1221
-* Bug fix: Content-Type header is now added when using an explicitly provided
-  multipart body. https://github.com/guzzle/guzzle/pull/1218
-* Bug fix: Now ignoring Set-Cookie headers that have no name.
-* Bug fix: Reason phrase is no longer cast to an int in some cases in the
-  cURL handler. https://github.com/guzzle/guzzle/pull/1187
-* Bug fix: Remove the Authorization header when redirecting if the Host
-  header changes. https://github.com/guzzle/guzzle/pull/1207
-* Bug fix: Cookie path matching fixes
-  https://github.com/guzzle/guzzle/issues/1129
-* Bug fix: Fixing the cURL `body_as_string` setting
-  https://github.com/guzzle/guzzle/pull/1201
-* Bug fix: quotes are no longer stripped when parsing cookies.
-  https://github.com/guzzle/guzzle/issues/1172
-* Bug fix: `form_params` and `query` now always uses the `&` separator.
-  https://github.com/guzzle/guzzle/pull/1163
-* Bug fix: Adding a Content-Length to PHP stream wrapper requests if not set.
-  https://github.com/guzzle/guzzle/pull/1189
-
-
-## 6.0.2 - 2015-07-04
-
-* Fixed a memory leak in the curl handlers in which references to callbacks
-  were not being removed by `curl_reset`.
-* Cookies are now extracted properly before redirects.
-* Cookies now allow more character ranges.
-* Decoded Content-Encoding responses are now modified to correctly reflect
-  their state if the encoding was automatically removed by a handler. This
-  means that the `Content-Encoding` header may be removed an the
-  `Content-Length` modified to reflect the message size after removing the
-  encoding.
-* Added a more explicit error message when trying to use `form_params` and
-  `multipart` in the same request.
-* Several fixes for HHVM support.
-* Functions are now conditionally required using an additional level of
-  indirection to help with global Composer installations.
-
-
-## 6.0.1 - 2015-05-27
-
-* Fixed a bug with serializing the `query` request option where the `&`
-  separator was missing.
-* Added a better error message for when `body` is provided as an array. Please
-  use `form_params` or `multipart` instead.
-* Various doc fixes.
-
-
-## 6.0.0 - 2015-05-26
-
-* See the UPGRADING.md document for more information.
-* Added `multipart` and `form_params` request options.
-* Added `synchronous` request option.
-* Added the `on_headers` request option.
-* Fixed `expect` handling.
-* No longer adding default middlewares in the client ctor. These need to be
-  present on the provided handler in order to work.
-* Requests are no longer initiated when sending async requests with the
-  CurlMultiHandler. This prevents unexpected recursion from requests completing
-  while ticking the cURL loop.
-* Removed the semantics of setting `default` to `true`. This is no longer
-  required now that the cURL loop is not ticked for async requests.
-* Added request and response logging middleware.
-* No longer allowing self signed certificates when using the StreamHandler.
-* Ensuring that `sink` is valid if saving to a file.
-* Request exceptions now include a "handler context" which provides handler
-  specific contextual information.
-* Added `GuzzleHttp\RequestOptions` to allow request options to be applied
-  using constants.
-* `$maxHandles` has been removed from CurlMultiHandler.
-* `MultipartPostBody` is now part of the `guzzlehttp/psr7` package.
-
-
-## 5.3.0 - 2015-05-19
-
-* Mock now supports `save_to`
-* Marked `AbstractRequestEvent::getTransaction()` as public.
-* Fixed a bug in which multiple headers using different casing would overwrite
-  previous headers in the associative array.
-* Added `Utils::getDefaultHandler()`
-* Marked `GuzzleHttp\Client::getDefaultUserAgent` as deprecated.
-* URL scheme is now always lowercased.
-
-
-## 6.0.0-beta.1
-
-* Requires PHP >= 5.5
-* Updated to use PSR-7
-  * Requires immutable messages, which basically means an event based system
-    owned by a request instance is no longer possible.
-  * Utilizing the [Guzzle PSR-7 package](https://github.com/guzzle/psr7).
-  * Removed the dependency on `guzzlehttp/streams`. These stream abstractions
-    are available in the `guzzlehttp/psr7` package under the `GuzzleHttp\Psr7`
-    namespace.
-* Added middleware and handler system
-  * Replaced the Guzzle event and subscriber system with a middleware system.
-  * No longer depends on RingPHP, but rather places the HTTP handlers directly
-    in Guzzle, operating on PSR-7 messages.
-  * Retry logic is now encapsulated in `GuzzleHttp\Middleware::retry`, which
-    means the `guzzlehttp/retry-subscriber` is now obsolete.
-  * Mocking responses is now handled using `GuzzleHttp\Handler\MockHandler`.
-* Asynchronous responses
-  * No longer supports the `future` request option to send an async request.
-    Instead, use one of the `*Async` methods of a client (e.g., `requestAsync`,
-    `getAsync`, etc.).
-  * Utilizing `GuzzleHttp\Promise` instead of React's promise library to avoid
-    recursion required by chaining and forwarding react promises. See
-    https://github.com/guzzle/promises
-  * Added `requestAsync` and `sendAsync` to send request asynchronously.
-  * Added magic methods for `getAsync()`, `postAsync()`, etc. to send requests
-    asynchronously.
-* Request options
-  * POST and form updates
-    * Added the `form_fields` and `form_files` request options.
-    * Removed the `GuzzleHttp\Post` namespace.
-    * The `body` request option no longer accepts an array for POST requests.
-  * The `exceptions` request option has been deprecated in favor of the
-    `http_errors` request options.
-  * The `save_to` request option has been deprecated in favor of `sink` request
-    option.
-* Clients no longer accept an array of URI template string and variables for
-  URI variables. You will need to expand URI templates before passing them
-  into a client constructor or request method.
-* Client methods `get()`, `post()`, `put()`, `patch()`, `options()`, etc. are
-  now magic methods that will send synchronous requests.
-* Replaced `Utils.php` with plain functions in `functions.php`.
-* Removed `GuzzleHttp\Collection`.
-* Removed `GuzzleHttp\BatchResults`. Batched pool results are now returned as
-  an array.
-* Removed `GuzzleHttp\Query`. Query string handling is now handled using an
-  associative array passed into the `query` request option. The query string
-  is serialized using PHP's `http_build_query`. If you need more control, you
-  can pass the query string in as a string.
-* `GuzzleHttp\QueryParser` has been replaced with the
-  `GuzzleHttp\Psr7\parse_query`.
-
-
-## 5.2.0 - 2015-01-27
-
-* Added `AppliesHeadersInterface` to make applying headers to a request based
-  on the body more generic and not specific to `PostBodyInterface`.
-* Reduced the number of stack frames needed to send requests.
-* Nested futures are now resolved in the client rather than the RequestFsm
-* Finishing state transitions is now handled in the RequestFsm rather than the
-  RingBridge.
-* Added a guard in the Pool class to not use recursion for request retries.
-
-
-## 5.1.0 - 2014-12-19
-
-* Pool class no longer uses recursion when a request is intercepted.
-* The size of a Pool can now be dynamically adjusted using a callback.
-  See https://github.com/guzzle/guzzle/pull/943.
-* Setting a request option to `null` when creating a request with a client will
-  ensure that the option is not set. This allows you to overwrite default
-  request options on a per-request basis.
-  See https://github.com/guzzle/guzzle/pull/937.
-* Added the ability to limit which protocols are allowed for redirects by
-  specifying a `protocols` array in the `allow_redirects` request option.
-* Nested futures due to retries are now resolved when waiting for synchronous
-  responses. See https://github.com/guzzle/guzzle/pull/947.
-* `"0"` is now an allowed URI path. See
-  https://github.com/guzzle/guzzle/pull/935.
-* `Query` no longer typehints on the `$query` argument in the constructor,
-  allowing for strings and arrays.
-* Exceptions thrown in the `end` event are now correctly wrapped with Guzzle
-  specific exceptions if necessary.
-
-
-## 5.0.3 - 2014-11-03
-
-This change updates query strings so that they are treated as un-encoded values
-by default where the value represents an un-encoded value to send over the
-wire. A Query object then encodes the value before sending over the wire. This
-means that even value query string values (e.g., ":") are url encoded. This
-makes the Query class match PHP's http_build_query function. However, if you
-want to send requests over the wire using valid query string characters that do
-not need to be encoded, then you can provide a string to Url::setQuery() and
-pass true as the second argument to specify that the query string is a raw
-string that should not be parsed or encoded (unless a call to getQuery() is
-subsequently made, forcing the query-string to be converted into a Query
-object).
-
-
-## 5.0.2 - 2014-10-30
-
-* Added a trailing `\r\n` to multipart/form-data payloads. See
-  https://github.com/guzzle/guzzle/pull/871
-* Added a `GuzzleHttp\Pool::send()` convenience method to match the docs.
-* Status codes are now returned as integers. See
-  https://github.com/guzzle/guzzle/issues/881
-* No longer overwriting an existing `application/x-www-form-urlencoded` header
-  when sending POST requests, allowing for customized headers. See
-  https://github.com/guzzle/guzzle/issues/877
-* Improved path URL serialization.
-
-  * No longer double percent-encoding characters in the path or query string if
-    they are already encoded.
-  * Now properly encoding the supplied path to a URL object, instead of only
-    encoding ' ' and '?'.
-  * Note: This has been changed in 5.0.3 to now encode query string values by
-    default unless the `rawString` argument is provided when setting the query
-    string on a URL: Now allowing many more characters to be present in the
-    query string without being percent encoded. See https://tools.ietf.org/html/rfc3986#appendix-A
-
-
-## 5.0.1 - 2014-10-16
-
-Bugfix release.
-
-* Fixed an issue where connection errors still returned response object in
-  error and end events event though the response is unusable. This has been
-  corrected so that a response is not returned in the `getResponse` method of
-  these events if the response did not complete. https://github.com/guzzle/guzzle/issues/867
-* Fixed an issue where transfer statistics were not being populated in the
-  RingBridge. https://github.com/guzzle/guzzle/issues/866
-
-
-## 5.0.0 - 2014-10-12
-
-Adding support for non-blocking responses and some minor API cleanup.
-
-### New Features
-
-* Added support for non-blocking responses based on `guzzlehttp/guzzle-ring`.
-* Added a public API for creating a default HTTP adapter.
-* Updated the redirect plugin to be non-blocking so that redirects are sent
-  concurrently. Other plugins like this can now be updated to be non-blocking.
-* Added a "progress" event so that you can get upload and download progress
-  events.
-* Added `GuzzleHttp\Pool` which implements FutureInterface and transfers
-  requests concurrently using a capped pool size as efficiently as possible.
-* Added `hasListeners()` to EmitterInterface.
-* Removed `GuzzleHttp\ClientInterface::sendAll` and marked
-  `GuzzleHttp\Client::sendAll` as deprecated (it's still there, just not the
-  recommended way).
-
-### Breaking changes
-
-The breaking changes in this release are relatively minor. The biggest thing to
-look out for is that request and response objects no longer implement fluent
-interfaces.
-
-* Removed the fluent interfaces (i.e., `return $this`) from requests,
-  responses, `GuzzleHttp\Collection`, `GuzzleHttp\Url`,
-  `GuzzleHttp\Query`, `GuzzleHttp\Post\PostBody`, and
-  `GuzzleHttp\Cookie\SetCookie`. This blog post provides a good outline of
-  why I did this: https://ocramius.github.io/blog/fluent-interfaces-are-evil/.
-  This also makes the Guzzle message interfaces compatible with the current
-  PSR-7 message proposal.
-* Removed "functions.php", so that Guzzle is truly PSR-4 compliant. Except
-  for the HTTP request functions from function.php, these functions are now
-  implemented in `GuzzleHttp\Utils` using camelCase. `GuzzleHttp\json_decode`
-  moved to `GuzzleHttp\Utils::jsonDecode`. `GuzzleHttp\get_path` moved to
-  `GuzzleHttp\Utils::getPath`. `GuzzleHttp\set_path` moved to
-  `GuzzleHttp\Utils::setPath`. `GuzzleHttp\batch` should now be
-  `GuzzleHttp\Pool::batch`, which returns an `objectStorage`. Using functions.php
-  caused problems for many users: they aren't PSR-4 compliant, require an
-  explicit include, and needed an if-guard to ensure that the functions are not
-  declared multiple times.
-* Rewrote adapter layer.
-    * Removing all classes from `GuzzleHttp\Adapter`, these are now
-      implemented as callables that are stored in `GuzzleHttp\Ring\Client`.
-    * Removed the concept of "parallel adapters". Sending requests serially or
-      concurrently is now handled using a single adapter.
-    * Moved `GuzzleHttp\Adapter\Transaction` to `GuzzleHttp\Transaction`. The
-      Transaction object now exposes the request, response, and client as public
-      properties. The getters and setters have been removed.
-* Removed the "headers" event. This event was only useful for changing the
-  body a response once the headers of the response were known. You can implement
-  a similar behavior in a number of ways. One example might be to use a
-  FnStream that has access to the transaction being sent. For example, when the
-  first byte is written, you could check if the response headers match your
-  expectations, and if so, change the actual stream body that is being
-  written to.
-* Removed the `asArray` parameter from
-  `GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header
-  value as an array, then use the newly added `getHeaderAsArray()` method of
-  `MessageInterface`. This change makes the Guzzle interfaces compatible with
-  the PSR-7 interfaces.
-* `GuzzleHttp\Message\MessageFactory` no longer allows subclasses to add
-  custom request options using double-dispatch (this was an implementation
-  detail). Instead, you should now provide an associative array to the
-  constructor which is a mapping of the request option name mapping to a
-  function that applies the option value to a request.
-* Removed the concept of "throwImmediately" from exceptions and error events.
-  This control mechanism was used to stop a transfer of concurrent requests
-  from completing. This can now be handled by throwing the exception or by
-  cancelling a pool of requests or each outstanding future request individually.
-* Updated to "GuzzleHttp\Streams" 3.0.
-    * `GuzzleHttp\Stream\StreamInterface::getContents()` no longer accepts a
-      `maxLen` parameter. This update makes the Guzzle streams project
-      compatible with the current PSR-7 proposal.
-    * `GuzzleHttp\Stream\Stream::__construct`,
-      `GuzzleHttp\Stream\Stream::factory`, and
-      `GuzzleHttp\Stream\Utils::create` no longer accept a size in the second
-      argument. They now accept an associative array of options, including the
-      "size" key and "metadata" key which can be used to provide custom metadata.
-
-
-## 4.2.2 - 2014-09-08
-
-* Fixed a memory leak in the CurlAdapter when reusing cURL handles.
-* No longer using `request_fulluri` in stream adapter proxies.
-* Relative redirects are now based on the last response, not the first response.
-
-## 4.2.1 - 2014-08-19
-
-* Ensuring that the StreamAdapter does not always add a Content-Type header
-* Adding automated github releases with a phar and zip
-
-## 4.2.0 - 2014-08-17
-
-* Now merging in default options using a case-insensitive comparison.
-  Closes https://github.com/guzzle/guzzle/issues/767
-* Added the ability to automatically decode `Content-Encoding` response bodies
-  using the `decode_content` request option. This is set to `true` by default
-  to decode the response body if it comes over the wire with a
-  `Content-Encoding`. Set this value to `false` to disable decoding the
-  response content, and pass a string to provide a request `Accept-Encoding`
-  header and turn on automatic response decoding. This feature now allows you
-  to pass an `Accept-Encoding` header in the headers of a request but still
-  disable automatic response decoding.
-  Closes https://github.com/guzzle/guzzle/issues/764
-* Added the ability to throw an exception immediately when transferring
-  requests in parallel. Closes https://github.com/guzzle/guzzle/issues/760
-* Updating guzzlehttp/streams dependency to ~2.1
-* No longer utilizing the now deprecated namespaced methods from the stream
-  package.
-
-## 4.1.8 - 2014-08-14
-
-* Fixed an issue in the CurlFactory that caused setting the `stream=false`
-  request option to throw an exception.
-  See: https://github.com/guzzle/guzzle/issues/769
-* TransactionIterator now calls rewind on the inner iterator.
-  See: https://github.com/guzzle/guzzle/pull/765
-* You can now set the `Content-Type` header to `multipart/form-data`
-  when creating POST requests to force multipart bodies.
-  See https://github.com/guzzle/guzzle/issues/768
-
-## 4.1.7 - 2014-08-07
-
-* Fixed an error in the HistoryPlugin that caused the same request and response
-  to be logged multiple times when an HTTP protocol error occurs.
-* Ensuring that cURL does not add a default Content-Type when no Content-Type
-  has been supplied by the user. This prevents the adapter layer from modifying
-  the request that is sent over the wire after any listeners may have already
-  put the request in a desired state (e.g., signed the request).
-* Throwing an exception when you attempt to send requests that have the
-  "stream" set to true in parallel using the MultiAdapter.
-* Only calling curl_multi_select when there are active cURL handles. This was
-  previously changed and caused performance problems on some systems due to PHP
-  always selecting until the maximum select timeout.
-* Fixed a bug where multipart/form-data POST fields were not correctly
-  aggregated (e.g., values with "&").
-
-## 4.1.6 - 2014-08-03
-
-* Added helper methods to make it easier to represent messages as strings,
-  including getting the start line and getting headers as a string.
-
-## 4.1.5 - 2014-08-02
-
-* Automatically retrying cURL "Connection died, retrying a fresh connect"
-  errors when possible.
-* cURL implementation cleanup
-* Allowing multiple event subscriber listeners to be registered per event by
-  passing an array of arrays of listener configuration.
-
-## 4.1.4 - 2014-07-22
-
-* Fixed a bug that caused multi-part POST requests with more than one field to
-  serialize incorrectly.
-* Paths can now be set to "0"
-* `ResponseInterface::xml` now accepts a `libxml_options` option and added a
-  missing default argument that was required when parsing XML response bodies.
-* A `save_to` stream is now created lazily, which means that files are not
-  created on disk unless a request succeeds.
-
-## 4.1.3 - 2014-07-15
-
-* Various fixes to multipart/form-data POST uploads
-* Wrapping function.php in an if-statement to ensure Guzzle can be used
-  globally and in a Composer install
-* Fixed an issue with generating and merging in events to an event array
-* POST headers are only applied before sending a request to allow you to change
-  the query aggregator used before uploading
-* Added much more robust query string parsing
-* Fixed various parsing and normalization issues with URLs
-* Fixing an issue where multi-valued headers were not being utilized correctly
-  in the StreamAdapter
-
-## 4.1.2 - 2014-06-18
-
-* Added support for sending payloads with GET requests
-
-## 4.1.1 - 2014-06-08
-
-* Fixed an issue related to using custom message factory options in subclasses
-* Fixed an issue with nested form fields in a multi-part POST
-* Fixed an issue with using the `json` request option for POST requests
-* Added `ToArrayInterface` to `GuzzleHttp\Cookie\CookieJar`
-
-## 4.1.0 - 2014-05-27
-
-* Added a `json` request option to easily serialize JSON payloads.
-* Added a `GuzzleHttp\json_decode()` wrapper to safely parse JSON.
-* Added `setPort()` and `getPort()` to `GuzzleHttp\Message\RequestInterface`.
-* Added the ability to provide an emitter to a client in the client constructor.
-* Added the ability to persist a cookie session using $_SESSION.
-* Added a trait that can be used to add event listeners to an iterator.
-* Removed request method constants from RequestInterface.
-* Fixed warning when invalid request start-lines are received.
-* Updated MessageFactory to work with custom request option methods.
-* Updated cacert bundle to latest build.
-
-4.0.2 (2014-04-16)
-------------------
-
-* Proxy requests using the StreamAdapter now properly use request_fulluri (#632)
-* Added the ability to set scalars as POST fields (#628)
-
-## 4.0.1 - 2014-04-04
-
-* The HTTP status code of a response is now set as the exception code of
-  RequestException objects.
-* 303 redirects will now correctly switch from POST to GET requests.
-* The default parallel adapter of a client now correctly uses the MultiAdapter.
-* HasDataTrait now initializes the internal data array as an empty array so
-  that the toArray() method always returns an array.
-
-## 4.0.0 - 2014-03-29
-
-* For information on changes and upgrading, see:
-  https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40
-* Added `GuzzleHttp\batch()` as a convenience function for sending requests in
-  parallel without needing to write asynchronous code.
-* Restructured how events are added to `GuzzleHttp\ClientInterface::sendAll()`.
-  You can now pass a callable or an array of associative arrays where each
-  associative array contains the "fn", "priority", and "once" keys.
-
-## 4.0.0.rc-2 - 2014-03-25
-
-* Removed `getConfig()` and `setConfig()` from clients to avoid confusion
-  around whether things like base_url, message_factory, etc. should be able to
-  be retrieved or modified.
-* Added `getDefaultOption()` and `setDefaultOption()` to ClientInterface
-* functions.php functions were renamed using snake_case to match PHP idioms
-* Added support for `HTTP_PROXY`, `HTTPS_PROXY`, and
-  `GUZZLE_CURL_SELECT_TIMEOUT` environment variables
-* Added the ability to specify custom `sendAll()` event priorities
-* Added the ability to specify custom stream context options to the stream
-  adapter.
-* Added a functions.php function for `get_path()` and `set_path()`
-* CurlAdapter and MultiAdapter now use a callable to generate curl resources
-* MockAdapter now properly reads a body and emits a `headers` event
-* Updated Url class to check if a scheme and host are set before adding ":"
-  and "//". This allows empty Url (e.g., "") to be serialized as "".
-* Parsing invalid XML no longer emits warnings
-* Curl classes now properly throw AdapterExceptions
-* Various performance optimizations
-* Streams are created with the faster `Stream\create()` function
-* Marked deprecation_proxy() as internal
-* Test server is now a collection of static methods on a class
-
-## 4.0.0-rc.1 - 2014-03-15
-
-* See https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40
-
-## 3.8.1 - 2014-01-28
-
-* Bug: Always using GET requests when redirecting from a 303 response
-* Bug: CURLOPT_SSL_VERIFYHOST is now correctly set to false when setting `$certificateAuthority` to false in
-  `Guzzle\Http\ClientInterface::setSslVerification()`
-* Bug: RedirectPlugin now uses strict RFC 3986 compliance when combining a base URL with a relative URL
-* Bug: The body of a request can now be set to `"0"`
-* Sending PHP stream requests no longer forces `HTTP/1.0`
-* Adding more information to ExceptionCollection exceptions so that users have more context, including a stack trace of
-  each sub-exception
-* Updated the `$ref` attribute in service descriptions to merge over any existing parameters of a schema (rather than
-  clobbering everything).
-* Merging URLs will now use the query string object from the relative URL (thus allowing custom query aggregators)
-* Query strings are now parsed in a way that they do no convert empty keys with no value to have a dangling `=`.
-  For example `foo&bar=baz` is now correctly parsed and recognized as `foo&bar=baz` rather than `foo=&bar=baz`.
-* Now properly escaping the regular expression delimiter when matching Cookie domains.
-* Network access is now disabled when loading XML documents
-
-## 3.8.0 - 2013-12-05
-
-* Added the ability to define a POST name for a file
-* JSON response parsing now properly walks additionalProperties
-* cURL error code 18 is now retried automatically in the BackoffPlugin
-* Fixed a cURL error when URLs contain fragments
-* Fixed an issue in the BackoffPlugin retry event where it was trying to access all exceptions as if they were
-  CurlExceptions
-* CURLOPT_PROGRESS function fix for PHP 5.5 (69fcc1e)
-* Added the ability for Guzzle to work with older versions of cURL that do not support `CURLOPT_TIMEOUT_MS`
-* Fixed a bug that was encountered when parsing empty header parameters
-* UriTemplate now has a `setRegex()` method to match the docs
-* The `debug` request parameter now checks if it is truthy rather than if it exists
-* Setting the `debug` request parameter to true shows verbose cURL output instead of using the LogPlugin
-* Added the ability to combine URLs using strict RFC 3986 compliance
-* Command objects can now return the validation errors encountered by the command
-* Various fixes to cache revalidation (#437 and 29797e5)
-* Various fixes to the AsyncPlugin
-* Cleaned up build scripts
-
-## 3.7.4 - 2013-10-02
-
-* Bug fix: 0 is now an allowed value in a description parameter that has a default value (#430)
-* Bug fix: SchemaFormatter now returns an integer when formatting to a Unix timestamp
-  (see https://github.com/aws/aws-sdk-php/issues/147)
-* Bug fix: Cleaned up and fixed URL dot segment removal to properly resolve internal dots
-* Minimum PHP version is now properly specified as 5.3.3 (up from 5.3.2) (#420)
-* Updated the bundled cacert.pem (#419)
-* OauthPlugin now supports adding authentication to headers or query string (#425)
-
-## 3.7.3 - 2013-09-08
-
-* Added the ability to get the exception associated with a request/command when using `MultiTransferException` and
-  `CommandTransferException`.
-* Setting `additionalParameters` of a response to false is now honored when parsing responses with a service description
-* Schemas are only injected into response models when explicitly configured.
-* No longer guessing Content-Type based on the path of a request. Content-Type is now only guessed based on the path of
-  an EntityBody.
-* Bug fix: ChunkedIterator can now properly chunk a \Traversable as well as an \Iterator.
-* Bug fix: FilterIterator now relies on `\Iterator` instead of `\Traversable`.
-* Bug fix: Gracefully handling malformed responses in RequestMediator::writeResponseBody()
-* Bug fix: Replaced call to canCache with canCacheRequest in the CallbackCanCacheStrategy of the CachePlugin
-* Bug fix: Visiting XML attributes first before visiting XML children when serializing requests
-* Bug fix: Properly parsing headers that contain commas contained in quotes
-* Bug fix: mimetype guessing based on a filename is now case-insensitive
-
-## 3.7.2 - 2013-08-02
-
-* Bug fix: Properly URL encoding paths when using the PHP-only version of the UriTemplate expander
-  See https://github.com/guzzle/guzzle/issues/371
-* Bug fix: Cookie domains are now matched correctly according to RFC 6265
-  See https://github.com/guzzle/guzzle/issues/377
-* Bug fix: GET parameters are now used when calculating an OAuth signature
-* Bug fix: Fixed an issue with cache revalidation where the If-None-Match header was being double quoted
-* `Guzzle\Common\AbstractHasDispatcher::dispatch()` now returns the event that was dispatched
-* `Guzzle\Http\QueryString::factory()` now guesses the most appropriate query aggregator to used based on the input.
-  See https://github.com/guzzle/guzzle/issues/379
-* Added a way to add custom domain objects to service description parsing using the `operation.parse_class` event. See
-  https://github.com/guzzle/guzzle/pull/380
-* cURL multi cleanup and optimizations
-
-## 3.7.1 - 2013-07-05
-
-* Bug fix: Setting default options on a client now works
-* Bug fix: Setting options on HEAD requests now works. See #352
-* Bug fix: Moving stream factory before send event to before building the stream. See #353
-* Bug fix: Cookies no longer match on IP addresses per RFC 6265
-* Bug fix: Correctly parsing header parameters that are in `<>` and quotes
-* Added `cert` and `ssl_key` as request options
-* `Host` header can now diverge from the host part of a URL if the header is set manually
-* `Guzzle\Service\Command\LocationVisitor\Request\XmlVisitor` was rewritten to change from using SimpleXML to XMLWriter
-* OAuth parameters are only added via the plugin if they aren't already set
-* Exceptions are now thrown when a URL cannot be parsed
-* Returning `false` if `Guzzle\Http\EntityBody::getContentMd5()` fails
-* Not setting a `Content-MD5` on a command if calculating the Content-MD5 fails via the CommandContentMd5Plugin
-
-## 3.7.0 - 2013-06-10
-
-* See UPGRADING.md for more information on how to upgrade.
-* Requests now support the ability to specify an array of $options when creating a request to more easily modify a
-  request. You can pass a 'request.options' configuration setting to a client to apply default request options to
-  every request created by a client (e.g. default query string variables, headers, curl options, etc.).
-* Added a static facade class that allows you to use Guzzle with static methods and mount the class to `\Guzzle`.
-  See `Guzzle\Http\StaticClient::mount`.
-* Added `command.request_options` to `Guzzle\Service\Command\AbstractCommand` to pass request options to requests
-      created by a command (e.g. custom headers, query string variables, timeout settings, etc.).
-* Stream size in `Guzzle\Stream\PhpStreamRequestFactory` will now be set if Content-Length is returned in the
-  headers of a response
-* Added `Guzzle\Common\Collection::setPath($path, $value)` to set a value into an array using a nested key
-  (e.g. `$collection->setPath('foo/baz/bar', 'test'); echo $collection['foo']['bar']['bar'];`)
-* ServiceBuilders now support storing and retrieving arbitrary data
-* CachePlugin can now purge all resources for a given URI
-* CachePlugin can automatically purge matching cached items when a non-idempotent request is sent to a resource
-* CachePlugin now uses the Vary header to determine if a resource is a cache hit
-* `Guzzle\Http\Message\Response` now implements `\Serializable`
-* Added `Guzzle\Cache\CacheAdapterFactory::fromCache()` to more easily create cache adapters
-* `Guzzle\Service\ClientInterface::execute()` now accepts an array, single command, or Traversable
-* Fixed a bug in `Guzzle\Http\Message\Header\Link::addLink()`
-* Better handling of calculating the size of a stream in `Guzzle\Stream\Stream` using fstat() and caching the size
-* `Guzzle\Common\Exception\ExceptionCollection` now creates a more readable exception message
-* Fixing BC break: Added back the MonologLogAdapter implementation rather than extending from PsrLog so that older
-  Symfony users can still use the old version of Monolog.
-* Fixing BC break: Added the implementation back in for `Guzzle\Http\Message\AbstractMessage::getTokenizedHeader()`.
-  Now triggering an E_USER_DEPRECATED warning when used. Use `$message->getHeader()->parseParams()`.
-* Several performance improvements to `Guzzle\Common\Collection`
-* Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`:
-  createRequest, head, delete, put, patch, post, options, prepareRequest
-* Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()`
-* Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface`
-* Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to
-  `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a
-  resource, string, or EntityBody into the $options parameter to specify the download location of the response.
-* Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a
-  default `array()`
-* Added `Guzzle\Stream\StreamInterface::isRepeatable`
-* Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use
-  $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or
-  $client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))`.
-* Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use $client->getConfig()->getPath('request.options/headers')`.
-* Removed `Guzzle\Http\ClientInterface::expandTemplate()`
-* Removed `Guzzle\Http\ClientInterface::setRequestFactory()`
-* Removed `Guzzle\Http\ClientInterface::getCurlMulti()`
-* Removed `Guzzle\Http\Message\RequestInterface::canCache`
-* Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`
-* Removed `Guzzle\Http\Message\RequestInterface::isRedirect`
-* Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods.
-* You can now enable E_USER_DEPRECATED warnings to see if you are using a deprecated method by setting
-  `Guzzle\Common\Version::$emitWarnings` to true.
-* Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use
-      `$request->getResponseBody()->isRepeatable()` instead.
-* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use
-  `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
-* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use
-  `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
-* Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead.
-* Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead.
-* Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated
-* Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand.
-  These will work through Guzzle 4.0
-* Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use [request.options][params].
-* Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client.
-* Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use $client->getConfig()->getPath('request.options/headers')`.
-* Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`.
-* Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8.
-* Marked `Guzzle\Common\Collection::inject()` as deprecated.
-* Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest');`
-* CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a
-  CacheStorageInterface. These two objects and interface will be removed in a future version.
-* Always setting X-cache headers on cached responses
-* Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin
-* `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface
-  $request, Response $response);`
-* `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);`
-* `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);`
-* Added `CacheStorageInterface::purge($url)`
-* `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin
-  $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache,
-  CanCacheStrategyInterface $canCache = null)`
-* Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)`
-
-## 3.6.0 - 2013-05-29
-
-* ServiceDescription now implements ToArrayInterface
-* Added command.hidden_params to blacklist certain headers from being treated as additionalParameters
-* Guzzle can now correctly parse incomplete URLs
-* Mixed casing of headers are now forced to be a single consistent casing across all values for that header.
-* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution
-* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader().
-* Specific header implementations can be created for complex headers. When a message creates a header, it uses a
-  HeaderFactory which can map specific headers to specific header classes. There is now a Link header and
-  CacheControl header implementation.
-* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate
-* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti()
-* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in
-  Guzzle\Http\Curl\RequestMediator
-* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string.
-* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface
-* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders()
-* Removed Guzzle\Parser\ParserRegister::get(). Use getParser()
-* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser().
-* All response header helper functions return a string rather than mixing Header objects and strings inconsistently
-* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle
-  directly via interfaces
-* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist
-  but are a no-op until removed.
-* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a
-  `Guzzle\Service\Command\ArrayCommandInterface`.
-* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response
-  on a request while the request is still being transferred
-* The ability to case-insensitively search for header values
-* Guzzle\Http\Message\Header::hasExactHeader
-* Guzzle\Http\Message\Header::raw. Use getAll()
-* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object
-  instead.
-* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess
-* Added the ability to cast Model objects to a string to view debug information.
-
-## 3.5.0 - 2013-05-13
-
-* Bug: Fixed a regression so that request responses are parsed only once per oncomplete event rather than multiple times
-* Bug: Better cleanup of one-time events across the board (when an event is meant to fire once, it will now remove
-  itself from the EventDispatcher)
-* Bug: `Guzzle\Log\MessageFormatter` now properly writes "total_time" and "connect_time" values
-* Bug: Cloning an EntityEnclosingRequest now clones the EntityBody too
-* Bug: Fixed an undefined index error when parsing nested JSON responses with a sentAs parameter that reference a
-  non-existent key
-* Bug: All __call() method arguments are now required (helps with mocking frameworks)
-* Deprecating Response::getRequest() and now using a shallow clone of a request object to remove a circular reference
-  to help with refcount based garbage collection of resources created by sending a request
-* Deprecating ZF1 cache and log adapters. These will be removed in the next major version.
-* Deprecating `Response::getPreviousResponse()` (method signature still exists, but it's deprecated). Use the
-  HistoryPlugin for a history.
-* Added a `responseBody` alias for the `response_body` location
-* Refactored internals to no longer rely on Response::getRequest()
-* HistoryPlugin can now be cast to a string
-* HistoryPlugin now logs transactions rather than requests and responses to more accurately keep track of the requests
-  and responses that are sent over the wire
-* Added `getEffectiveUrl()` and `getRedirectCount()` to Response objects
-
-## 3.4.3 - 2013-04-30
-
-* Bug fix: Fixing bug introduced in 3.4.2 where redirect responses are duplicated on the final redirected response
-* Added a check to re-extract the temp cacert bundle from the phar before sending each request
-
-## 3.4.2 - 2013-04-29
-
-* Bug fix: Stream objects now work correctly with "a" and "a+" modes
-* Bug fix: Removing `Transfer-Encoding: chunked` header when a Content-Length is present
-* Bug fix: AsyncPlugin no longer forces HEAD requests
-* Bug fix: DateTime timezones are now properly handled when using the service description schema formatter
-* Bug fix: CachePlugin now properly handles stale-if-error directives when a request to the origin server fails
-* Setting a response on a request will write to the custom request body from the response body if one is specified
-* LogPlugin now writes to php://output when STDERR is undefined
-* Added the ability to set multiple POST files for the same key in a single call
-* application/x-www-form-urlencoded POSTs now use the utf-8 charset by default
-* Added the ability to queue CurlExceptions to the MockPlugin
-* Cleaned up how manual responses are queued on requests (removed "queued_response" and now using request.before_send)
-* Configuration loading now allows remote files
-
-## 3.4.1 - 2013-04-16
-
-* Large refactoring to how CurlMulti handles work. There is now a proxy that sits in front of a pool of CurlMulti
-  handles. This greatly simplifies the implementation, fixes a couple bugs, and provides a small performance boost.
-* Exceptions are now properly grouped when sending requests in parallel
-* Redirects are now properly aggregated when a multi transaction fails
-* Redirects now set the response on the original object even in the event of a failure
-* Bug fix: Model names are now properly set even when using $refs
-* Added support for PHP 5.5's CurlFile to prevent warnings with the deprecated @ syntax
-* Added support for oauth_callback in OAuth signatures
-* Added support for oauth_verifier in OAuth signatures
-* Added support to attempt to retrieve a command first literally, then ucfirst, the with inflection
-
-## 3.4.0 - 2013-04-11
-
-* Bug fix: URLs are now resolved correctly based on https://tools.ietf.org/html/rfc3986#section-5.2. #289
-* Bug fix: Absolute URLs with a path in a service description will now properly override the base URL. #289
-* Bug fix: Parsing a query string with a single PHP array value will now result in an array. #263
-* Bug fix: Better normalization of the User-Agent header to prevent duplicate headers. #264.
-* Bug fix: Added `number` type to service descriptions.
-* Bug fix: empty parameters are removed from an OAuth signature
-* Bug fix: Revalidating a cache entry prefers the Last-Modified over the Date header
-* Bug fix: Fixed "array to string" error when validating a union of types in a service description
-* Bug fix: Removed code that attempted to determine the size of a stream when data is written to the stream
-* Bug fix: Not including an `oauth_token` if the value is null in the OauthPlugin.
-* Bug fix: Now correctly aggregating successful requests and failed requests in CurlMulti when a redirect occurs.
-* The new default CURLOPT_TIMEOUT setting has been increased to 150 seconds so that Guzzle works on poor connections.
-* Added a feature to EntityEnclosingRequest::setBody() that will automatically set the Content-Type of the request if
-  the Content-Type can be determined based on the entity body or the path of the request.
-* Added the ability to overwrite configuration settings in a client when grabbing a throwaway client from a builder.
-* Added support for a PSR-3 LogAdapter.
-* Added a `command.after_prepare` event
-* Added `oauth_callback` parameter to the OauthPlugin
-* Added the ability to create a custom stream class when using a stream factory
-* Added a CachingEntityBody decorator
-* Added support for `additionalParameters` in service descriptions to define how custom parameters are serialized.
-* The bundled SSL certificate is now provided in the phar file and extracted when running Guzzle from a phar.
-* You can now send any EntityEnclosingRequest with POST fields or POST files and cURL will handle creating bodies
-* POST requests using a custom entity body are now treated exactly like PUT requests but with a custom cURL method. This
-  means that the redirect behavior of POST requests with custom bodies will not be the same as POST requests that use
-  POST fields or files (the latter is only used when emulating a form POST in the browser).
-* Lots of cleanup to CurlHandle::factory and RequestFactory::createRequest
-
-## 3.3.1 - 2013-03-10
-
-* Added the ability to create PHP streaming responses from HTTP requests
-* Bug fix: Running any filters when parsing response headers with service descriptions
-* Bug fix: OauthPlugin fixes to allow for multi-dimensional array signing, and sorting parameters before signing
-* Bug fix: Removed the adding of default empty arrays and false Booleans to responses in order to be consistent across
-  response location visitors.
-* Bug fix: Removed the possibility of creating configuration files with circular dependencies
-* RequestFactory::create() now uses the key of a POST file when setting the POST file name
-* Added xmlAllowEmpty to serialize an XML body even if no XML specific parameters are set
-
-## 3.3.0 - 2013-03-03
-
-* A large number of performance optimizations have been made
-* Bug fix: Added 'wb' as a valid write mode for streams
-* Bug fix: `Guzzle\Http\Message\Response::json()` now allows scalar values to be returned
-* Bug fix: Fixed bug in `Guzzle\Http\Message\Response` where wrapping quotes were stripped from `getEtag()`
-* BC: Removed `Guzzle\Http\Utils` class
-* BC: Setting a service description on a client will no longer modify the client's command factories.
-* BC: Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using
-  the 'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io'
-* BC: `Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getSteamType()` are no longer converted to
-  lowercase
-* Operation parameter objects are now lazy loaded internally
-* Added ErrorResponsePlugin that can throw errors for responses defined in service description operations' errorResponses
-* Added support for instantiating responseType=class responseClass classes. Classes must implement
-  `Guzzle\Service\Command\ResponseClassInterface`
-* Added support for additionalProperties for top-level parameters in responseType=model responseClasses. These
-  additional properties also support locations and can be used to parse JSON responses where the outermost part of the
-  JSON is an array
-* Added support for nested renaming of JSON models (rename sentAs to name)
-* CachePlugin
-    * Added support for stale-if-error so that the CachePlugin can now serve stale content from the cache on error
-    * Debug headers can now added to cached response in the CachePlugin
-
-## 3.2.0 - 2013-02-14
-
-* CurlMulti is no longer reused globally. A new multi object is created per-client. This helps to isolate clients.
-* URLs with no path no longer contain a "/" by default
-* Guzzle\Http\QueryString does no longer manages the leading "?". This is now handled in Guzzle\Http\Url.
-* BadResponseException no longer includes the full request and response message
-* Adding setData() to Guzzle\Service\Description\ServiceDescriptionInterface
-* Adding getResponseBody() to Guzzle\Http\Message\RequestInterface
-* Various updates to classes to use ServiceDescriptionInterface type hints rather than ServiceDescription
-* Header values can now be normalized into distinct values when multiple headers are combined with a comma separated list
-* xmlEncoding can now be customized for the XML declaration of a XML service description operation
-* Guzzle\Http\QueryString now uses Guzzle\Http\QueryAggregator\QueryAggregatorInterface objects to add custom value
-  aggregation and no longer uses callbacks
-* The URL encoding implementation of Guzzle\Http\QueryString can now be customized
-* Bug fix: Filters were not always invoked for array service description parameters
-* Bug fix: Redirects now use a target response body rather than a temporary response body
-* Bug fix: The default exponential backoff BackoffPlugin was not giving when the request threshold was exceeded
-* Bug fix: Guzzle now takes the first found value when grabbing Cache-Control directives
-
-## 3.1.2 - 2013-01-27
-
-* Refactored how operation responses are parsed. Visitors now include a before() method responsible for parsing the
-  response body. For example, the XmlVisitor now parses the XML response into an array in the before() method.
-* Fixed an issue where cURL would not automatically decompress responses when the Accept-Encoding header was sent
-* CURLOPT_SSL_VERIFYHOST is never set to 1 because it is deprecated (see 5e0ff2ef20f839e19d1eeb298f90ba3598784444)
-* Fixed a bug where redirect responses were not chained correctly using getPreviousResponse()
-* Setting default headers on a client after setting the user-agent will not erase the user-agent setting
-
-## 3.1.1 - 2013-01-20
-
-* Adding wildcard support to Guzzle\Common\Collection::getPath()
-* Adding alias support to ServiceBuilder configs
-* Adding Guzzle\Service\Resource\CompositeResourceIteratorFactory and cleaning up factory interface
-
-## 3.1.0 - 2013-01-12
-
-* BC: CurlException now extends from RequestException rather than BadResponseException
-* BC: Renamed Guzzle\Plugin\Cache\CanCacheStrategyInterface::canCache() to canCacheRequest() and added CanCacheResponse()
-* Added getData to ServiceDescriptionInterface
-* Added context array to RequestInterface::setState()
-* Bug: Removing hard dependency on the BackoffPlugin from Guzzle\Http
-* Bug: Adding required content-type when JSON request visitor adds JSON to a command
-* Bug: Fixing the serialization of a service description with custom data
-* Made it easier to deal with exceptions thrown when transferring commands or requests in parallel by providing
-  an array of successful and failed responses
-* Moved getPath from Guzzle\Service\Resource\Model to Guzzle\Common\Collection
-* Added Guzzle\Http\IoEmittingEntityBody
-* Moved command filtration from validators to location visitors
-* Added `extends` attributes to service description parameters
-* Added getModels to ServiceDescriptionInterface
-
-## 3.0.7 - 2012-12-19
-
-* Fixing phar detection when forcing a cacert to system if null or true
-* Allowing filename to be passed to `Guzzle\Http\Message\Request::setResponseBody()`
-* Cleaning up `Guzzle\Common\Collection::inject` method
-* Adding a response_body location to service descriptions
-
-## 3.0.6 - 2012-12-09
-
-* CurlMulti performance improvements
-* Adding setErrorResponses() to Operation
-* composer.json tweaks
-
-## 3.0.5 - 2012-11-18
-
-* Bug: Fixing an infinite recursion bug caused from revalidating with the CachePlugin
-* Bug: Response body can now be a string containing "0"
-* Bug: Using Guzzle inside of a phar uses system by default but now allows for a custom cacert
-* Bug: QueryString::fromString now properly parses query string parameters that contain equal signs
-* Added support for XML attributes in service description responses
-* DefaultRequestSerializer now supports array URI parameter values for URI template expansion
-* Added better mimetype guessing to requests and post files
-
-## 3.0.4 - 2012-11-11
-
-* Bug: Fixed a bug when adding multiple cookies to a request to use the correct glue value
-* Bug: Cookies can now be added that have a name, domain, or value set to "0"
-* Bug: Using the system cacert bundle when using the Phar
-* Added json and xml methods to Response to make it easier to parse JSON and XML response data into data structures
-* Enhanced cookie jar de-duplication
-* Added the ability to enable strict cookie jars that throw exceptions when invalid cookies are added
-* Added setStream to StreamInterface to actually make it possible to implement custom rewind behavior for entity bodies
-* Added the ability to create any sort of hash for a stream rather than just an MD5 hash
-
-## 3.0.3 - 2012-11-04
-
-* Implementing redirects in PHP rather than cURL
-* Added PECL URI template extension and using as default parser if available
-* Bug: Fixed Content-Length parsing of Response factory
-* Adding rewind() method to entity bodies and streams. Allows for custom rewinding of non-repeatable streams.
-* Adding ToArrayInterface throughout library
-* Fixing OauthPlugin to create unique nonce values per request
-
-## 3.0.2 - 2012-10-25
-
-* Magic methods are enabled by default on clients
-* Magic methods return the result of a command
-* Service clients no longer require a base_url option in the factory
-* Bug: Fixed an issue with URI templates where null template variables were being expanded
-
-## 3.0.1 - 2012-10-22
-
-* Models can now be used like regular collection objects by calling filter, map, etc.
-* Models no longer require a Parameter structure or initial data in the constructor
-* Added a custom AppendIterator to get around a PHP bug with the `\AppendIterator`
-
-## 3.0.0 - 2012-10-15
-
-* Rewrote service description format to be based on Swagger
-    * Now based on JSON schema
-    * Added nested input structures and nested response models
-    * Support for JSON and XML input and output models
-    * Renamed `commands` to `operations`
-    * Removed dot class notation
-    * Removed custom types
-* Broke the project into smaller top-level namespaces to be more component friendly
-* Removed support for XML configs and descriptions. Use arrays or JSON files.
-* Removed the Validation component and Inspector
-* Moved all cookie code to Guzzle\Plugin\Cookie
-* Magic methods on a Guzzle\Service\Client now return the command un-executed.
-* Calling getResult() or getResponse() on a command will lazily execute the command if needed.
-* Now shipping with cURL's CA certs and using it by default
-* Added previousResponse() method to response objects
-* No longer sending Accept and Accept-Encoding headers on every request
-* Only sending an Expect header by default when a payload is greater than 1MB
-* Added/moved client options:
-    * curl.blacklist to curl.option.blacklist
-    * Added ssl.certificate_authority
-* Added a Guzzle\Iterator component
-* Moved plugins from Guzzle\Http\Plugin to Guzzle\Plugin
-* Added a more robust backoff retry strategy (replaced the ExponentialBackoffPlugin)
-* Added a more robust caching plugin
-* Added setBody to response objects
-* Updating LogPlugin to use a more flexible MessageFormatter
-* Added a completely revamped build process
-* Cleaning up Collection class and removing default values from the get method
-* Fixed ZF2 cache adapters
-
-## 2.8.8 - 2012-10-15
-
-* Bug: Fixed a cookie issue that caused dot prefixed domains to not match where popular browsers did
-
-## 2.8.7 - 2012-09-30
-
-* Bug: Fixed config file aliases for JSON includes
-* Bug: Fixed cookie bug on a request object by using CookieParser to parse cookies on requests
-* Bug: Removing the path to a file when sending a Content-Disposition header on a POST upload
-* Bug: Hardening request and response parsing to account for missing parts
-* Bug: Fixed PEAR packaging
-* Bug: Fixed Request::getInfo
-* Bug: Fixed cases where CURLM_CALL_MULTI_PERFORM return codes were causing curl transactions to fail
-* Adding the ability for the namespace Iterator factory to look in multiple directories
-* Added more getters/setters/removers from service descriptions
-* Added the ability to remove POST fields from OAuth signatures
-* OAuth plugin now supports 2-legged OAuth
-
-## 2.8.6 - 2012-09-05
-
-* Added the ability to modify and build service descriptions
-* Added the use of visitors to apply parameters to locations in service descriptions using the dynamic command
-* Added a `json` parameter location
-* Now allowing dot notation for classes in the CacheAdapterFactory
-* Using the union of two arrays rather than an array_merge when extending service builder services and service params
-* Ensuring that a service is a string before doing strpos() checks on it when substituting services for references
-  in service builder config files.
-* Services defined in two different config files that include one another will by default replace the previously
-  defined service, but you can now create services that extend themselves and merge their settings over the previous
-* The JsonLoader now supports aliasing filenames with different filenames. This allows you to alias something like
-  '_default' with a default JSON configuration file.
-
-## 2.8.5 - 2012-08-29
-
-* Bug: Suppressed empty arrays from URI templates
-* Bug: Added the missing $options argument from ServiceDescription::factory to enable caching
-* Added support for HTTP responses that do not contain a reason phrase in the start-line
-* AbstractCommand commands are now invokable
-* Added a way to get the data used when signing an Oauth request before a request is sent
-
-## 2.8.4 - 2012-08-15
-
-* Bug: Custom delay time calculations are no longer ignored in the ExponentialBackoffPlugin
-* Added the ability to transfer entity bodies as a string rather than streamed. This gets around curl error 65. Set `body_as_string` in a request's curl options to enable.
-* Added a StreamInterface, EntityBodyInterface, and added ftell() to Guzzle\Common\Stream
-* Added an AbstractEntityBodyDecorator and a ReadLimitEntityBody decorator to transfer only a subset of a decorated stream
-* Stream and EntityBody objects will now return the file position to the previous position after a read required operation (e.g. getContentMd5())
-* Added additional response status codes
-* Removed SSL information from the default User-Agent header
-* DELETE requests can now send an entity body
-* Added an EventDispatcher to the ExponentialBackoffPlugin and added an ExponentialBackoffLogger to log backoff retries
-* Added the ability of the MockPlugin to consume mocked request bodies
-* LogPlugin now exposes request and response objects in the extras array
-
-## 2.8.3 - 2012-07-30
-
-* Bug: Fixed a case where empty POST requests were sent as GET requests
-* Bug: Fixed a bug in ExponentialBackoffPlugin that caused fatal errors when retrying an EntityEnclosingRequest that does not have a body
-* Bug: Setting the response body of a request to null after completing a request, not when setting the state of a request to new
-* Added multiple inheritance to service description commands
-* Added an ApiCommandInterface and added `getParamNames()` and `hasParam()`
-* Removed the default 2mb size cutoff from the Md5ValidatorPlugin so that it now defaults to validating everything
-* Changed CurlMulti::perform to pass a smaller timeout to CurlMulti::executeHandles
-
-## 2.8.2 - 2012-07-24
-
-* Bug: Query string values set to 0 are no longer dropped from the query string
-* Bug: A Collection object is no longer created each time a call is made to `Guzzle\Service\Command\AbstractCommand::getRequestHeaders()`
-* Bug: `+` is now treated as an encoded space when parsing query strings
-* QueryString and Collection performance improvements
-* Allowing dot notation for class paths in filters attribute of a service descriptions
-
-## 2.8.1 - 2012-07-16
-
-* Loosening Event Dispatcher dependency
-* POST redirects can now be customized using CURLOPT_POSTREDIR
-
-## 2.8.0 - 2012-07-15
-
-* BC: Guzzle\Http\Query
-    * Query strings with empty variables will always show an equal sign unless the variable is set to QueryString::BLANK (e.g. ?acl= vs ?acl)
-    * Changed isEncodingValues() and isEncodingFields() to isUrlEncoding()
-    * Changed setEncodeValues(bool) and setEncodeFields(bool) to useUrlEncoding(bool)
-    * Changed the aggregation functions of QueryString to be static methods
-    * Can now use fromString() with querystrings that have a leading ?
-* cURL configuration values can be specified in service descriptions using `curl.` prefixed parameters
-* Content-Length is set to 0 before emitting the request.before_send event when sending an empty request body
-* Cookies are no longer URL decoded by default
-* Bug: URI template variables set to null are no longer expanded
-
-## 2.7.2 - 2012-07-02
-
-* BC: Moving things to get ready for subtree splits. Moving Inflection into Common. Moving Guzzle\Http\Parser to Guzzle\Parser.
-* BC: Removing Guzzle\Common\Batch\Batch::count() and replacing it with isEmpty()
-* CachePlugin now allows for a custom request parameter function to check if a request can be cached
-* Bug fix: CachePlugin now only caches GET and HEAD requests by default
-* Bug fix: Using header glue when transferring headers over the wire
-* Allowing deeply nested arrays for composite variables in URI templates
-* Batch divisors can now return iterators or arrays
-
-## 2.7.1 - 2012-06-26
-
-* Minor patch to update version number in UA string
-* Updating build process
-
-## 2.7.0 - 2012-06-25
-
-* BC: Inflection classes moved to Guzzle\Inflection. No longer static methods. Can now inject custom inflectors into classes.
-* BC: Removed magic setX methods from commands
-* BC: Magic methods mapped to service description commands are now inflected in the command factory rather than the client __call() method
-* Verbose cURL options are no longer enabled by default. Set curl.debug to true on a client to enable.
-* Bug: Now allowing colons in a response start-line (e.g. HTTP/1.1 503 Service Unavailable: Back-end server is at capacity)
-* Guzzle\Service\Resource\ResourceIteratorApplyBatched now internally uses the Guzzle\Common\Batch namespace
-* Added Guzzle\Service\Plugin namespace and a PluginCollectionPlugin
-* Added the ability to set POST fields and files in a service description
-* Guzzle\Http\EntityBody::factory() now accepts objects with a __toString() method
-* Adding a command.before_prepare event to clients
-* Added BatchClosureTransfer and BatchClosureDivisor
-* BatchTransferException now includes references to the batch divisor and transfer strategies
-* Fixed some tests so that they pass more reliably
-* Added Guzzle\Common\Log\ArrayLogAdapter
-
-## 2.6.6 - 2012-06-10
-
-* BC: Removing Guzzle\Http\Plugin\BatchQueuePlugin
-* BC: Removing Guzzle\Service\Command\CommandSet
-* Adding generic batching system (replaces the batch queue plugin and command set)
-* Updating ZF cache and log adapters and now using ZF's composer repository
-* Bug: Setting the name of each ApiParam when creating through an ApiCommand
-* Adding result_type, result_doc, deprecated, and doc_url to service descriptions
-* Bug: Changed the default cookie header casing back to 'Cookie'
-
-## 2.6.5 - 2012-06-03
-
-* BC: Renaming Guzzle\Http\Message\RequestInterface::getResourceUri() to getResource()
-* BC: Removing unused AUTH_BASIC and AUTH_DIGEST constants from
-* BC: Guzzle\Http\Cookie is now used to manage Set-Cookie data, not Cookie data
-* BC: Renaming methods in the CookieJarInterface
-* Moving almost all cookie logic out of the CookiePlugin and into the Cookie or CookieJar implementations
-* Making the default glue for HTTP headers ';' instead of ','
-* Adding a removeValue to Guzzle\Http\Message\Header
-* Adding getCookies() to request interface.
-* Making it easier to add event subscribers to HasDispatcherInterface classes. Can now directly call addSubscriber()
-
-## 2.6.4 - 2012-05-30
-
-* BC: Cleaning up how POST files are stored in EntityEnclosingRequest objects. Adding PostFile class.
-* BC: Moving ApiCommand specific functionality from the Inspector and on to the ApiCommand
-* Bug: Fixing magic method command calls on clients
-* Bug: Email constraint only validates strings
-* Bug: Aggregate POST fields when POST files are present in curl handle
-* Bug: Fixing default User-Agent header
-* Bug: Only appending or prepending parameters in commands if they are specified
-* Bug: Not requiring response reason phrases or status codes to match a predefined list of codes
-* Allowing the use of dot notation for class namespaces when using instance_of constraint
-* Added any_match validation constraint
-* Added an AsyncPlugin
-* Passing request object to the calculateWait method of the ExponentialBackoffPlugin
-* Allowing the result of a command object to be changed
-* Parsing location and type sub values when instantiating a service description rather than over and over at runtime
-
-## 2.6.3 - 2012-05-23
-
-* [BC] Guzzle\Common\FromConfigInterface no longer requires any config options.
-* [BC] Refactoring how POST files are stored on an EntityEnclosingRequest. They are now separate from POST fields.
-* You can now use an array of data when creating PUT request bodies in the request factory.
-* Removing the requirement that HTTPS requests needed a Cache-Control: public directive to be cacheable.
-* [Http] Adding support for Content-Type in multipart POST uploads per upload
-* [Http] Added support for uploading multiple files using the same name (foo[0], foo[1])
-* Adding more POST data operations for easier manipulation of POST data.
-* You can now set empty POST fields.
-* The body of a request is only shown on EntityEnclosingRequest objects that do not use POST files.
-* Split the Guzzle\Service\Inspector::validateConfig method into two methods. One to initialize when a command is created, and one to validate.
-* CS updates
-
-## 2.6.2 - 2012-05-19
-
-* [Http] Better handling of nested scope requests in CurlMulti.  Requests are now always prepares in the send() method rather than the addRequest() method.
-
-## 2.6.1 - 2012-05-19
-
-* [BC] Removing 'path' support in service descriptions.  Use 'uri'.
-* [BC] Guzzle\Service\Inspector::parseDocBlock is now protected. Adding getApiParamsForClass() with cache.
-* [BC] Removing Guzzle\Common\NullObject.  Use https://github.com/mtdowling/NullObject if you need it.
-* [BC] Removing Guzzle\Common\XmlElement.
-* All commands, both dynamic and concrete, have ApiCommand objects.
-* Adding a fix for CurlMulti so that if all of the connections encounter some sort of curl error, then the loop exits.
-* Adding checks to EntityEnclosingRequest so that empty POST files and fields are ignored.
-* Making the method signature of Guzzle\Service\Builder\ServiceBuilder::factory more flexible.
-
-## 2.6.0 - 2012-05-15
-
-* [BC] Moving Guzzle\Service\Builder to Guzzle\Service\Builder\ServiceBuilder
-* [BC] Executing a Command returns the result of the command rather than the command
-* [BC] Moving all HTTP parsing logic to Guzzle\Http\Parsers. Allows for faster C implementations if needed.
-* [BC] Changing the Guzzle\Http\Message\Response::setProtocol() method to accept a protocol and version in separate args.
-* [BC] Moving ResourceIterator* to Guzzle\Service\Resource
-* [BC] Completely refactored ResourceIterators to iterate over a cloned command object
-* [BC] Moved Guzzle\Http\UriTemplate to Guzzle\Http\Parser\UriTemplate\UriTemplate
-* [BC] Guzzle\Guzzle is now deprecated
-* Moving Guzzle\Common\Guzzle::inject to Guzzle\Common\Collection::inject
-* Adding Guzzle\Version class to give version information about Guzzle
-* Adding Guzzle\Http\Utils class to provide getDefaultUserAgent() and getHttpDate()
-* Adding Guzzle\Curl\CurlVersion to manage caching curl_version() data
-* ServiceDescription and ServiceBuilder are now cacheable using similar configs
-* Changing the format of XML and JSON service builder configs.  Backwards compatible.
-* Cleaned up Cookie parsing
-* Trimming the default Guzzle User-Agent header
-* Adding a setOnComplete() method to Commands that is called when a command completes
-* Keeping track of requests that were mocked in the MockPlugin
-* Fixed a caching bug in the CacheAdapterFactory
-* Inspector objects can be injected into a Command object
-* Refactoring a lot of code and tests to be case insensitive when dealing with headers
-* Adding Guzzle\Http\Message\HeaderComparison for easy comparison of HTTP headers using a DSL
-* Adding the ability to set global option overrides to service builder configs
-* Adding the ability to include other service builder config files from within XML and JSON files
-* Moving the parseQuery method out of Url and on to QueryString::fromString() as a static factory method.
-
-## 2.5.0 - 2012-05-08
-
-* Major performance improvements
-* [BC] Simplifying Guzzle\Common\Collection.  Please check to see if you are using features that are now deprecated.
-* [BC] Using a custom validation system that allows a flyweight implementation for much faster validation. No longer using Symfony2 Validation component.
-* [BC] No longer supporting "{{ }}" for injecting into command or UriTemplates.  Use "{}"
-* Added the ability to passed parameters to all requests created by a client
-* Added callback functionality to the ExponentialBackoffPlugin
-* Using microtime in ExponentialBackoffPlugin to allow more granular backoff strategies.
-* Rewinding request stream bodies when retrying requests
-* Exception is thrown when JSON response body cannot be decoded
-* Added configurable magic method calls to clients and commands.  This is off by default.
-* Fixed a defect that added a hash to every parsed URL part
-* Fixed duplicate none generation for OauthPlugin.
-* Emitting an event each time a client is generated by a ServiceBuilder
-* Using an ApiParams object instead of a Collection for parameters of an ApiCommand
-* cache.* request parameters should be renamed to params.cache.*
-* Added the ability to set arbitrary curl options on requests (disable_wire, progress, etc.). See CurlHandle.
-* Added the ability to disable type validation of service descriptions
-* ServiceDescriptions and ServiceBuilders are now Serializable
+# Change Log
+
+Please refer to [UPGRADING](UPGRADING.md) guide for upgrading to a major version.
+
+
+## 7.7.0 - 2023-05-21
+
+### Added
+
+- Support `guzzlehttp/promises` v2
+
+
+## 7.6.1 - 2023-05-15
+
+### Fixed
+
+- Fix `SetCookie::fromString` MaxAge deprecation warning and skip invalid MaxAge values
+
+
+## 7.6.0 - 2023-05-14
+
+### Added
+
+- Support for setting the minimum TLS version in a unified way
+- Apply on request the version set in options parameters
+
+
+## 7.5.2 - 2023-05-14
+
+### Fixed
+
+- Fixed set cookie constructor validation
+- Fixed handling of files with `'0'` body
+
+### Changed
+
+- Corrected docs and default connect timeout value to 300 seconds
+
+
+## 7.5.1 - 2023-04-17
+
+### Fixed
+
+- Fixed `NO_PROXY` settings so that setting the `proxy` option to `no` overrides the env variable
+
+### Changed
+
+- Adjusted `guzzlehttp/psr7` version constraint to `^1.9.1 || ^2.4.5`
+
+
+## 7.5.0 - 2022-08-28
+
+### Added
+
+- Support PHP 8.2
+- Add request to delay closure params
+
+
+## 7.4.5 - 2022-06-20
+
+### Fixed
+
+* Fix change in port should be considered a change in origin
+* Fix `CURLOPT_HTTPAUTH` option not cleared on change of origin
+
+
+## 7.4.4 - 2022-06-09
+
+### Fixed
+
+* Fix failure to strip Authorization header on HTTP downgrade
+* Fix failure to strip the Cookie header on change in host or HTTP downgrade
+
+
+## 7.4.3 - 2022-05-25
+
+### Fixed
+
+* Fix cross-domain cookie leakage
+
+
+## 7.4.2 - 2022-03-20
+
+### Fixed
+
+- Remove curl auth on cross-domain redirects to align with the Authorization HTTP header
+- Reject non-HTTP schemes in StreamHandler
+- Set a default ssl.peer_name context in StreamHandler to allow `force_ip_resolve`
+
+
+## 7.4.1 - 2021-12-06
+
+### Changed
+
+- Replaced implicit URI to string coercion [#2946](https://github.com/guzzle/guzzle/pull/2946)
+- Allow `symfony/deprecation-contracts` version 3 [#2961](https://github.com/guzzle/guzzle/pull/2961)
+
+### Fixed
+
+- Only close curl handle if it's done [#2950](https://github.com/guzzle/guzzle/pull/2950)
+
+
+## 7.4.0 - 2021-10-18
+
+### Added
+
+- Support PHP 8.1 [#2929](https://github.com/guzzle/guzzle/pull/2929), [#2939](https://github.com/guzzle/guzzle/pull/2939)
+- Support `psr/log` version 2 and 3 [#2943](https://github.com/guzzle/guzzle/pull/2943)
+
+### Fixed
+
+- Make sure we always call `restore_error_handler()` [#2915](https://github.com/guzzle/guzzle/pull/2915)
+- Fix progress parameter type compatibility between the cURL and stream handlers [#2936](https://github.com/guzzle/guzzle/pull/2936)
+- Throw `InvalidArgumentException` when an incorrect `headers` array is provided [#2916](https://github.com/guzzle/guzzle/pull/2916), [#2942](https://github.com/guzzle/guzzle/pull/2942)
+
+### Changed
+
+- Be more strict with types [#2914](https://github.com/guzzle/guzzle/pull/2914), [#2917](https://github.com/guzzle/guzzle/pull/2917), [#2919](https://github.com/guzzle/guzzle/pull/2919), [#2945](https://github.com/guzzle/guzzle/pull/2945)
+
+
+## 7.3.0 - 2021-03-23
+
+### Added
+
+- Support for DER and P12 certificates [#2413](https://github.com/guzzle/guzzle/pull/2413)
+- Support the cURL (http://) scheme for StreamHandler proxies [#2850](https://github.com/guzzle/guzzle/pull/2850)
+- Support for `guzzlehttp/psr7:^2.0` [#2878](https://github.com/guzzle/guzzle/pull/2878)
+
+### Fixed
+
+- Handle exceptions on invalid header consistently between PHP versions and handlers [#2872](https://github.com/guzzle/guzzle/pull/2872)
+
+
+## 7.2.0 - 2020-10-10
+
+### Added
+
+- Support for PHP 8 [#2712](https://github.com/guzzle/guzzle/pull/2712), [#2715](https://github.com/guzzle/guzzle/pull/2715), [#2789](https://github.com/guzzle/guzzle/pull/2789)
+- Support passing a body summarizer to the http errors middleware [#2795](https://github.com/guzzle/guzzle/pull/2795)
+
+### Fixed
+
+- Handle exceptions during response creation [#2591](https://github.com/guzzle/guzzle/pull/2591)
+- Fix CURLOPT_ENCODING not to be overwritten [#2595](https://github.com/guzzle/guzzle/pull/2595)
+- Make sure the Request always has a body object [#2804](https://github.com/guzzle/guzzle/pull/2804)
+
+### Changed
+
+- The `TooManyRedirectsException` has a response [#2660](https://github.com/guzzle/guzzle/pull/2660)
+- Avoid "functions" from dependencies [#2712](https://github.com/guzzle/guzzle/pull/2712)
+
+### Deprecated
+
+- Using environment variable GUZZLE_CURL_SELECT_TIMEOUT [#2786](https://github.com/guzzle/guzzle/pull/2786)
+
+
+## 7.1.1 - 2020-09-30
+
+### Fixed
+
+- Incorrect EOF detection for response body streams on Windows.
+
+### Changed
+
+- We dont connect curl `sink` on HEAD requests.
+- Removed some PHP 5 workarounds
+
+
+## 7.1.0 - 2020-09-22
+
+### Added
+
+- `GuzzleHttp\MessageFormatterInterface`
+
+### Fixed
+
+- Fixed issue that caused cookies with no value not to be stored.
+- On redirects, we allow all safe methods like GET, HEAD and OPTIONS.
+- Fixed logging on empty responses.
+- Make sure MessageFormatter::format returns string
+
+### Deprecated
+
+- All functions in `GuzzleHttp` has been deprecated. Use static methods on `Utils` instead.
+- `ClientInterface::getConfig()`
+- `Client::getConfig()`
+- `Client::__call()`
+- `Utils::defaultCaBundle()`
+- `CurlFactory::LOW_CURL_VERSION_NUMBER`
+
+
+## 7.0.1 - 2020-06-27
+
+* Fix multiply defined functions fatal error [#2699](https://github.com/guzzle/guzzle/pull/2699)
+
+
+## 7.0.0 - 2020-06-27
+
+No changes since 7.0.0-rc1.
+
+
+## 7.0.0-rc1 - 2020-06-15
+
+### Changed
+
+* Use error level for logging errors in Middleware [#2629](https://github.com/guzzle/guzzle/pull/2629)
+* Disabled IDN support by default and require ext-intl to use it [#2675](https://github.com/guzzle/guzzle/pull/2675)
+
+
+## 7.0.0-beta2 - 2020-05-25
+
+### Added
+
+* Using `Utils` class instead of functions in the `GuzzleHttp` namespace. [#2546](https://github.com/guzzle/guzzle/pull/2546)
+* `ClientInterface::MAJOR_VERSION` [#2583](https://github.com/guzzle/guzzle/pull/2583)
+
+### Changed
+
+* Avoid the `getenv` function when unsafe [#2531](https://github.com/guzzle/guzzle/pull/2531)
+* Added real client methods [#2529](https://github.com/guzzle/guzzle/pull/2529)
+* Avoid functions due to global install conflicts [#2546](https://github.com/guzzle/guzzle/pull/2546)
+* Use Symfony intl-idn polyfill [#2550](https://github.com/guzzle/guzzle/pull/2550)
+* Adding methods for HTTP verbs like `Client::get()`, `Client::head()`, `Client::patch()` etc [#2529](https://github.com/guzzle/guzzle/pull/2529)
+* `ConnectException` extends `TransferException` [#2541](https://github.com/guzzle/guzzle/pull/2541)
+* Updated the default User Agent to "GuzzleHttp/7" [#2654](https://github.com/guzzle/guzzle/pull/2654)
+
+### Fixed
+
+* Various intl icu issues [#2626](https://github.com/guzzle/guzzle/pull/2626)
+
+### Removed
+
+* Pool option `pool_size` [#2528](https://github.com/guzzle/guzzle/pull/2528)
+
+
+## 7.0.0-beta1 - 2019-12-30
+
+The diff might look very big but 95% of Guzzle users will be able to upgrade without modification.
+Please see [the upgrade document](UPGRADING.md) that describes all BC breaking changes.
+
+### Added
+
+* Implement PSR-18 and dropped PHP 5 support [#2421](https://github.com/guzzle/guzzle/pull/2421) [#2474](https://github.com/guzzle/guzzle/pull/2474)
+* PHP 7 types [#2442](https://github.com/guzzle/guzzle/pull/2442) [#2449](https://github.com/guzzle/guzzle/pull/2449) [#2466](https://github.com/guzzle/guzzle/pull/2466) [#2497](https://github.com/guzzle/guzzle/pull/2497) [#2499](https://github.com/guzzle/guzzle/pull/2499)
+* IDN support for redirects [2424](https://github.com/guzzle/guzzle/pull/2424)
+
+### Changed
+
+* Dont allow passing null as third argument to `BadResponseException::__construct()` [#2427](https://github.com/guzzle/guzzle/pull/2427)
+* Use SAPI constant instead of method call [#2450](https://github.com/guzzle/guzzle/pull/2450)
+* Use native function invocation [#2444](https://github.com/guzzle/guzzle/pull/2444)
+* Better defaults for PHP installations with old ICU lib [2454](https://github.com/guzzle/guzzle/pull/2454)
+* Added visibility to all constants [#2462](https://github.com/guzzle/guzzle/pull/2462)
+* Dont allow passing `null` as URI to `Client::request()` and `Client::requestAsync()` [#2461](https://github.com/guzzle/guzzle/pull/2461)
+* Widen the exception argument to throwable [#2495](https://github.com/guzzle/guzzle/pull/2495)
+
+### Fixed
+
+* Logging when Promise rejected with a string [#2311](https://github.com/guzzle/guzzle/pull/2311)
+
+### Removed
+
+* Class `SeekException` [#2162](https://github.com/guzzle/guzzle/pull/2162)
+* `RequestException::getResponseBodySummary()` [#2425](https://github.com/guzzle/guzzle/pull/2425)
+* `CookieJar::getCookieValue()` [#2433](https://github.com/guzzle/guzzle/pull/2433)
+* `uri_template()` and `UriTemplate` [#2440](https://github.com/guzzle/guzzle/pull/2440)
+* Request options `save_to` and `exceptions` [#2464](https://github.com/guzzle/guzzle/pull/2464)
+
+
+## 6.5.2 - 2019-12-23
+
+* idn_to_ascii() fix for old PHP versions [#2489](https://github.com/guzzle/guzzle/pull/2489)
+
+
+## 6.5.1 - 2019-12-21
+
+* Better defaults for PHP installations with old ICU lib [#2454](https://github.com/guzzle/guzzle/pull/2454)
+* IDN support for redirects [#2424](https://github.com/guzzle/guzzle/pull/2424)
+
+
+## 6.5.0 - 2019-12-07
+
+* Improvement: Added support for reset internal queue in MockHandler. [#2143](https://github.com/guzzle/guzzle/pull/2143)
+* Improvement: Added support to pass arbitrary options to `curl_multi_init`. [#2287](https://github.com/guzzle/guzzle/pull/2287)
+* Fix: Gracefully handle passing `null` to the `header` option. [#2132](https://github.com/guzzle/guzzle/pull/2132)
+* Fix: `RetryMiddleware` did not do exponential delay between retires due unit mismatch. [#2132](https://github.com/guzzle/guzzle/pull/2132)
+* Fix: Prevent undefined offset when using array for ssl_key options. [#2348](https://github.com/guzzle/guzzle/pull/2348)
+* Deprecated `ClientInterface::VERSION`
+
+
+## 6.4.1 - 2019-10-23
+
+* No `guzzle.phar` was created in 6.4.0 due expired API token. This release will fix that
+* Added `parent::__construct()` to `FileCookieJar` and `SessionCookieJar`
+
+
+## 6.4.0 - 2019-10-23
+
+* Improvement: Improved error messages when using curl < 7.21.2 [#2108](https://github.com/guzzle/guzzle/pull/2108)
+* Fix: Test if response is readable before returning a summary in `RequestException::getResponseBodySummary()` [#2081](https://github.com/guzzle/guzzle/pull/2081)
+* Fix: Add support for GUZZLE_CURL_SELECT_TIMEOUT environment variable [#2161](https://github.com/guzzle/guzzle/pull/2161)
+* Improvement: Added `GuzzleHttp\Exception\InvalidArgumentException` [#2163](https://github.com/guzzle/guzzle/pull/2163)
+* Improvement: Added `GuzzleHttp\_current_time()` to use `hrtime()` if that function exists. [#2242](https://github.com/guzzle/guzzle/pull/2242)
+* Improvement: Added curl's `appconnect_time` in `TransferStats` [#2284](https://github.com/guzzle/guzzle/pull/2284)
+* Improvement: Make GuzzleException extend Throwable wherever it's available [#2273](https://github.com/guzzle/guzzle/pull/2273)
+* Fix: Prevent concurrent writes to file when saving `CookieJar` [#2335](https://github.com/guzzle/guzzle/pull/2335)
+* Improvement: Update `MockHandler` so we can test transfer time [#2362](https://github.com/guzzle/guzzle/pull/2362)
+
+
+## 6.3.3 - 2018-04-22
+
+* Fix: Default headers when decode_content is specified
+
+
+## 6.3.2 - 2018-03-26
+
+* Fix: Release process
+
+
+## 6.3.1 - 2018-03-26
+
+* Bug fix: Parsing 0 epoch expiry times in cookies [#2014](https://github.com/guzzle/guzzle/pull/2014)
+* Improvement: Better ConnectException detection [#2012](https://github.com/guzzle/guzzle/pull/2012)
+* Bug fix: Malformed domain that contains a "/" [#1999](https://github.com/guzzle/guzzle/pull/1999)
+* Bug fix: Undefined offset when a cookie has no first key-value pair [#1998](https://github.com/guzzle/guzzle/pull/1998)
+* Improvement: Support PHPUnit 6 [#1953](https://github.com/guzzle/guzzle/pull/1953)
+* Bug fix: Support empty headers [#1915](https://github.com/guzzle/guzzle/pull/1915)
+* Bug fix: Ignore case during header modifications [#1916](https://github.com/guzzle/guzzle/pull/1916)
+
++ Minor code cleanups, documentation fixes and clarifications.
+
+
+## 6.3.0 - 2017-06-22
+
+* Feature: force IP resolution (ipv4 or ipv6) [#1608](https://github.com/guzzle/guzzle/pull/1608), [#1659](https://github.com/guzzle/guzzle/pull/1659)
+* Improvement: Don't include summary in exception message when body is empty [#1621](https://github.com/guzzle/guzzle/pull/1621)
+* Improvement: Handle `on_headers` option in MockHandler [#1580](https://github.com/guzzle/guzzle/pull/1580)
+* Improvement: Added SUSE Linux CA path [#1609](https://github.com/guzzle/guzzle/issues/1609)
+* Improvement: Use class reference for getting the name of the class instead of using hardcoded strings [#1641](https://github.com/guzzle/guzzle/pull/1641)
+* Feature: Added `read_timeout` option [#1611](https://github.com/guzzle/guzzle/pull/1611)
+* Bug fix: PHP 7.x fixes [#1685](https://github.com/guzzle/guzzle/pull/1685), [#1686](https://github.com/guzzle/guzzle/pull/1686), [#1811](https://github.com/guzzle/guzzle/pull/1811)
+* Deprecation: BadResponseException instantiation without a response [#1642](https://github.com/guzzle/guzzle/pull/1642)
+* Feature: Added NTLM auth [#1569](https://github.com/guzzle/guzzle/pull/1569)
+* Feature: Track redirect HTTP status codes [#1711](https://github.com/guzzle/guzzle/pull/1711)
+* Improvement: Check handler type during construction [#1745](https://github.com/guzzle/guzzle/pull/1745)
+* Improvement: Always include the Content-Length if there's a body [#1721](https://github.com/guzzle/guzzle/pull/1721)
+* Feature: Added convenience method to access a cookie by name [#1318](https://github.com/guzzle/guzzle/pull/1318)
+* Bug fix: Fill `CURLOPT_CAPATH` and `CURLOPT_CAINFO` properly [#1684](https://github.com/guzzle/guzzle/pull/1684)
+* Improvement:  	Use `\GuzzleHttp\Promise\rejection_for` function instead of object init [#1827](https://github.com/guzzle/guzzle/pull/1827)
+
++ Minor code cleanups, documentation fixes and clarifications.
+
+
+## 6.2.3 - 2017-02-28
+
+* Fix deprecations with guzzle/psr7 version 1.4
+
+
+## 6.2.2 - 2016-10-08
+
+* Allow to pass nullable Response to delay callable
+* Only add scheme when host is present
+* Fix drain case where content-length is the literal string zero
+* Obfuscate in-URL credentials in exceptions
+
+
+## 6.2.1 - 2016-07-18
+
+* Address HTTP_PROXY security vulnerability, CVE-2016-5385:
+  https://httpoxy.org/
+* Fixing timeout bug with StreamHandler:
+  https://github.com/guzzle/guzzle/pull/1488
+* Only read up to `Content-Length` in PHP StreamHandler to avoid timeouts when
+  a server does not honor `Connection: close`.
+* Ignore URI fragment when sending requests.
+
+
+## 6.2.0 - 2016-03-21
+
+* Feature: added `GuzzleHttp\json_encode` and `GuzzleHttp\json_decode`.
+  https://github.com/guzzle/guzzle/pull/1389
+* Bug fix: Fix sleep calculation when waiting for delayed requests.
+  https://github.com/guzzle/guzzle/pull/1324
+* Feature: More flexible history containers.
+  https://github.com/guzzle/guzzle/pull/1373
+* Bug fix: defer sink stream opening in StreamHandler.
+  https://github.com/guzzle/guzzle/pull/1377
+* Bug fix: do not attempt to escape cookie values.
+  https://github.com/guzzle/guzzle/pull/1406
+* Feature: report original content encoding and length on decoded responses.
+  https://github.com/guzzle/guzzle/pull/1409
+* Bug fix: rewind seekable request bodies before dispatching to cURL.
+  https://github.com/guzzle/guzzle/pull/1422
+* Bug fix: provide an empty string to `http_build_query` for HHVM workaround.
+  https://github.com/guzzle/guzzle/pull/1367
+
+
+## 6.1.1 - 2015-11-22
+
+* Bug fix: Proxy::wrapSync() now correctly proxies to the appropriate handler
+  https://github.com/guzzle/guzzle/commit/911bcbc8b434adce64e223a6d1d14e9a8f63e4e4
+* Feature: HandlerStack is now more generic.
+  https://github.com/guzzle/guzzle/commit/f2102941331cda544745eedd97fc8fd46e1ee33e
+* Bug fix: setting verify to false in the StreamHandler now disables peer
+  verification. https://github.com/guzzle/guzzle/issues/1256
+* Feature: Middleware now uses an exception factory, including more error
+  context. https://github.com/guzzle/guzzle/pull/1282
+* Feature: better support for disabled functions.
+  https://github.com/guzzle/guzzle/pull/1287
+* Bug fix: fixed regression where MockHandler was not using `sink`.
+  https://github.com/guzzle/guzzle/pull/1292
+
+
+## 6.1.0 - 2015-09-08
+
+* Feature: Added the `on_stats` request option to provide access to transfer
+  statistics for requests. https://github.com/guzzle/guzzle/pull/1202
+* Feature: Added the ability to persist session cookies in CookieJars.
+  https://github.com/guzzle/guzzle/pull/1195
+* Feature: Some compatibility updates for Google APP Engine
+  https://github.com/guzzle/guzzle/pull/1216
+* Feature: Added support for NO_PROXY to prevent the use of a proxy based on
+  a simple set of rules. https://github.com/guzzle/guzzle/pull/1197
+* Feature: Cookies can now contain square brackets.
+  https://github.com/guzzle/guzzle/pull/1237
+* Bug fix: Now correctly parsing `=` inside of quotes in Cookies.
+  https://github.com/guzzle/guzzle/pull/1232
+* Bug fix: Cusotm cURL options now correctly override curl options of the
+  same name. https://github.com/guzzle/guzzle/pull/1221
+* Bug fix: Content-Type header is now added when using an explicitly provided
+  multipart body. https://github.com/guzzle/guzzle/pull/1218
+* Bug fix: Now ignoring Set-Cookie headers that have no name.
+* Bug fix: Reason phrase is no longer cast to an int in some cases in the
+  cURL handler. https://github.com/guzzle/guzzle/pull/1187
+* Bug fix: Remove the Authorization header when redirecting if the Host
+  header changes. https://github.com/guzzle/guzzle/pull/1207
+* Bug fix: Cookie path matching fixes
+  https://github.com/guzzle/guzzle/issues/1129
+* Bug fix: Fixing the cURL `body_as_string` setting
+  https://github.com/guzzle/guzzle/pull/1201
+* Bug fix: quotes are no longer stripped when parsing cookies.
+  https://github.com/guzzle/guzzle/issues/1172
+* Bug fix: `form_params` and `query` now always uses the `&` separator.
+  https://github.com/guzzle/guzzle/pull/1163
+* Bug fix: Adding a Content-Length to PHP stream wrapper requests if not set.
+  https://github.com/guzzle/guzzle/pull/1189
+
+
+## 6.0.2 - 2015-07-04
+
+* Fixed a memory leak in the curl handlers in which references to callbacks
+  were not being removed by `curl_reset`.
+* Cookies are now extracted properly before redirects.
+* Cookies now allow more character ranges.
+* Decoded Content-Encoding responses are now modified to correctly reflect
+  their state if the encoding was automatically removed by a handler. This
+  means that the `Content-Encoding` header may be removed an the
+  `Content-Length` modified to reflect the message size after removing the
+  encoding.
+* Added a more explicit error message when trying to use `form_params` and
+  `multipart` in the same request.
+* Several fixes for HHVM support.
+* Functions are now conditionally required using an additional level of
+  indirection to help with global Composer installations.
+
+
+## 6.0.1 - 2015-05-27
+
+* Fixed a bug with serializing the `query` request option where the `&`
+  separator was missing.
+* Added a better error message for when `body` is provided as an array. Please
+  use `form_params` or `multipart` instead.
+* Various doc fixes.
+
+
+## 6.0.0 - 2015-05-26
+
+* See the UPGRADING.md document for more information.
+* Added `multipart` and `form_params` request options.
+* Added `synchronous` request option.
+* Added the `on_headers` request option.
+* Fixed `expect` handling.
+* No longer adding default middlewares in the client ctor. These need to be
+  present on the provided handler in order to work.
+* Requests are no longer initiated when sending async requests with the
+  CurlMultiHandler. This prevents unexpected recursion from requests completing
+  while ticking the cURL loop.
+* Removed the semantics of setting `default` to `true`. This is no longer
+  required now that the cURL loop is not ticked for async requests.
+* Added request and response logging middleware.
+* No longer allowing self signed certificates when using the StreamHandler.
+* Ensuring that `sink` is valid if saving to a file.
+* Request exceptions now include a "handler context" which provides handler
+  specific contextual information.
+* Added `GuzzleHttp\RequestOptions` to allow request options to be applied
+  using constants.
+* `$maxHandles` has been removed from CurlMultiHandler.
+* `MultipartPostBody` is now part of the `guzzlehttp/psr7` package.
+
+
+## 5.3.0 - 2015-05-19
+
+* Mock now supports `save_to`
+* Marked `AbstractRequestEvent::getTransaction()` as public.
+* Fixed a bug in which multiple headers using different casing would overwrite
+  previous headers in the associative array.
+* Added `Utils::getDefaultHandler()`
+* Marked `GuzzleHttp\Client::getDefaultUserAgent` as deprecated.
+* URL scheme is now always lowercased.
+
+
+## 6.0.0-beta.1
+
+* Requires PHP >= 5.5
+* Updated to use PSR-7
+  * Requires immutable messages, which basically means an event based system
+    owned by a request instance is no longer possible.
+  * Utilizing the [Guzzle PSR-7 package](https://github.com/guzzle/psr7).
+  * Removed the dependency on `guzzlehttp/streams`. These stream abstractions
+    are available in the `guzzlehttp/psr7` package under the `GuzzleHttp\Psr7`
+    namespace.
+* Added middleware and handler system
+  * Replaced the Guzzle event and subscriber system with a middleware system.
+  * No longer depends on RingPHP, but rather places the HTTP handlers directly
+    in Guzzle, operating on PSR-7 messages.
+  * Retry logic is now encapsulated in `GuzzleHttp\Middleware::retry`, which
+    means the `guzzlehttp/retry-subscriber` is now obsolete.
+  * Mocking responses is now handled using `GuzzleHttp\Handler\MockHandler`.
+* Asynchronous responses
+  * No longer supports the `future` request option to send an async request.
+    Instead, use one of the `*Async` methods of a client (e.g., `requestAsync`,
+    `getAsync`, etc.).
+  * Utilizing `GuzzleHttp\Promise` instead of React's promise library to avoid
+    recursion required by chaining and forwarding react promises. See
+    https://github.com/guzzle/promises
+  * Added `requestAsync` and `sendAsync` to send request asynchronously.
+  * Added magic methods for `getAsync()`, `postAsync()`, etc. to send requests
+    asynchronously.
+* Request options
+  * POST and form updates
+    * Added the `form_fields` and `form_files` request options.
+    * Removed the `GuzzleHttp\Post` namespace.
+    * The `body` request option no longer accepts an array for POST requests.
+  * The `exceptions` request option has been deprecated in favor of the
+    `http_errors` request options.
+  * The `save_to` request option has been deprecated in favor of `sink` request
+    option.
+* Clients no longer accept an array of URI template string and variables for
+  URI variables. You will need to expand URI templates before passing them
+  into a client constructor or request method.
+* Client methods `get()`, `post()`, `put()`, `patch()`, `options()`, etc. are
+  now magic methods that will send synchronous requests.
+* Replaced `Utils.php` with plain functions in `functions.php`.
+* Removed `GuzzleHttp\Collection`.
+* Removed `GuzzleHttp\BatchResults`. Batched pool results are now returned as
+  an array.
+* Removed `GuzzleHttp\Query`. Query string handling is now handled using an
+  associative array passed into the `query` request option. The query string
+  is serialized using PHP's `http_build_query`. If you need more control, you
+  can pass the query string in as a string.
+* `GuzzleHttp\QueryParser` has been replaced with the
+  `GuzzleHttp\Psr7\parse_query`.
+
+
+## 5.2.0 - 2015-01-27
+
+* Added `AppliesHeadersInterface` to make applying headers to a request based
+  on the body more generic and not specific to `PostBodyInterface`.
+* Reduced the number of stack frames needed to send requests.
+* Nested futures are now resolved in the client rather than the RequestFsm
+* Finishing state transitions is now handled in the RequestFsm rather than the
+  RingBridge.
+* Added a guard in the Pool class to not use recursion for request retries.
+
+
+## 5.1.0 - 2014-12-19
+
+* Pool class no longer uses recursion when a request is intercepted.
+* The size of a Pool can now be dynamically adjusted using a callback.
+  See https://github.com/guzzle/guzzle/pull/943.
+* Setting a request option to `null` when creating a request with a client will
+  ensure that the option is not set. This allows you to overwrite default
+  request options on a per-request basis.
+  See https://github.com/guzzle/guzzle/pull/937.
+* Added the ability to limit which protocols are allowed for redirects by
+  specifying a `protocols` array in the `allow_redirects` request option.
+* Nested futures due to retries are now resolved when waiting for synchronous
+  responses. See https://github.com/guzzle/guzzle/pull/947.
+* `"0"` is now an allowed URI path. See
+  https://github.com/guzzle/guzzle/pull/935.
+* `Query` no longer typehints on the `$query` argument in the constructor,
+  allowing for strings and arrays.
+* Exceptions thrown in the `end` event are now correctly wrapped with Guzzle
+  specific exceptions if necessary.
+
+
+## 5.0.3 - 2014-11-03
+
+This change updates query strings so that they are treated as un-encoded values
+by default where the value represents an un-encoded value to send over the
+wire. A Query object then encodes the value before sending over the wire. This
+means that even value query string values (e.g., ":") are url encoded. This
+makes the Query class match PHP's http_build_query function. However, if you
+want to send requests over the wire using valid query string characters that do
+not need to be encoded, then you can provide a string to Url::setQuery() and
+pass true as the second argument to specify that the query string is a raw
+string that should not be parsed or encoded (unless a call to getQuery() is
+subsequently made, forcing the query-string to be converted into a Query
+object).
+
+
+## 5.0.2 - 2014-10-30
+
+* Added a trailing `\r\n` to multipart/form-data payloads. See
+  https://github.com/guzzle/guzzle/pull/871
+* Added a `GuzzleHttp\Pool::send()` convenience method to match the docs.
+* Status codes are now returned as integers. See
+  https://github.com/guzzle/guzzle/issues/881
+* No longer overwriting an existing `application/x-www-form-urlencoded` header
+  when sending POST requests, allowing for customized headers. See
+  https://github.com/guzzle/guzzle/issues/877
+* Improved path URL serialization.
+
+  * No longer double percent-encoding characters in the path or query string if
+    they are already encoded.
+  * Now properly encoding the supplied path to a URL object, instead of only
+    encoding ' ' and '?'.
+  * Note: This has been changed in 5.0.3 to now encode query string values by
+    default unless the `rawString` argument is provided when setting the query
+    string on a URL: Now allowing many more characters to be present in the
+    query string without being percent encoded. See https://tools.ietf.org/html/rfc3986#appendix-A
+
+
+## 5.0.1 - 2014-10-16
+
+Bugfix release.
+
+* Fixed an issue where connection errors still returned response object in
+  error and end events event though the response is unusable. This has been
+  corrected so that a response is not returned in the `getResponse` method of
+  these events if the response did not complete. https://github.com/guzzle/guzzle/issues/867
+* Fixed an issue where transfer statistics were not being populated in the
+  RingBridge. https://github.com/guzzle/guzzle/issues/866
+
+
+## 5.0.0 - 2014-10-12
+
+Adding support for non-blocking responses and some minor API cleanup.
+
+### New Features
+
+* Added support for non-blocking responses based on `guzzlehttp/guzzle-ring`.
+* Added a public API for creating a default HTTP adapter.
+* Updated the redirect plugin to be non-blocking so that redirects are sent
+  concurrently. Other plugins like this can now be updated to be non-blocking.
+* Added a "progress" event so that you can get upload and download progress
+  events.
+* Added `GuzzleHttp\Pool` which implements FutureInterface and transfers
+  requests concurrently using a capped pool size as efficiently as possible.
+* Added `hasListeners()` to EmitterInterface.
+* Removed `GuzzleHttp\ClientInterface::sendAll` and marked
+  `GuzzleHttp\Client::sendAll` as deprecated (it's still there, just not the
+  recommended way).
+
+### Breaking changes
+
+The breaking changes in this release are relatively minor. The biggest thing to
+look out for is that request and response objects no longer implement fluent
+interfaces.
+
+* Removed the fluent interfaces (i.e., `return $this`) from requests,
+  responses, `GuzzleHttp\Collection`, `GuzzleHttp\Url`,
+  `GuzzleHttp\Query`, `GuzzleHttp\Post\PostBody`, and
+  `GuzzleHttp\Cookie\SetCookie`. This blog post provides a good outline of
+  why I did this: https://ocramius.github.io/blog/fluent-interfaces-are-evil/.
+  This also makes the Guzzle message interfaces compatible with the current
+  PSR-7 message proposal.
+* Removed "functions.php", so that Guzzle is truly PSR-4 compliant. Except
+  for the HTTP request functions from function.php, these functions are now
+  implemented in `GuzzleHttp\Utils` using camelCase. `GuzzleHttp\json_decode`
+  moved to `GuzzleHttp\Utils::jsonDecode`. `GuzzleHttp\get_path` moved to
+  `GuzzleHttp\Utils::getPath`. `GuzzleHttp\set_path` moved to
+  `GuzzleHttp\Utils::setPath`. `GuzzleHttp\batch` should now be
+  `GuzzleHttp\Pool::batch`, which returns an `objectStorage`. Using functions.php
+  caused problems for many users: they aren't PSR-4 compliant, require an
+  explicit include, and needed an if-guard to ensure that the functions are not
+  declared multiple times.
+* Rewrote adapter layer.
+    * Removing all classes from `GuzzleHttp\Adapter`, these are now
+      implemented as callables that are stored in `GuzzleHttp\Ring\Client`.
+    * Removed the concept of "parallel adapters". Sending requests serially or
+      concurrently is now handled using a single adapter.
+    * Moved `GuzzleHttp\Adapter\Transaction` to `GuzzleHttp\Transaction`. The
+      Transaction object now exposes the request, response, and client as public
+      properties. The getters and setters have been removed.
+* Removed the "headers" event. This event was only useful for changing the
+  body a response once the headers of the response were known. You can implement
+  a similar behavior in a number of ways. One example might be to use a
+  FnStream that has access to the transaction being sent. For example, when the
+  first byte is written, you could check if the response headers match your
+  expectations, and if so, change the actual stream body that is being
+  written to.
+* Removed the `asArray` parameter from
+  `GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header
+  value as an array, then use the newly added `getHeaderAsArray()` method of
+  `MessageInterface`. This change makes the Guzzle interfaces compatible with
+  the PSR-7 interfaces.
+* `GuzzleHttp\Message\MessageFactory` no longer allows subclasses to add
+  custom request options using double-dispatch (this was an implementation
+  detail). Instead, you should now provide an associative array to the
+  constructor which is a mapping of the request option name mapping to a
+  function that applies the option value to a request.
+* Removed the concept of "throwImmediately" from exceptions and error events.
+  This control mechanism was used to stop a transfer of concurrent requests
+  from completing. This can now be handled by throwing the exception or by
+  cancelling a pool of requests or each outstanding future request individually.
+* Updated to "GuzzleHttp\Streams" 3.0.
+    * `GuzzleHttp\Stream\StreamInterface::getContents()` no longer accepts a
+      `maxLen` parameter. This update makes the Guzzle streams project
+      compatible with the current PSR-7 proposal.
+    * `GuzzleHttp\Stream\Stream::__construct`,
+      `GuzzleHttp\Stream\Stream::factory`, and
+      `GuzzleHttp\Stream\Utils::create` no longer accept a size in the second
+      argument. They now accept an associative array of options, including the
+      "size" key and "metadata" key which can be used to provide custom metadata.
+
+
+## 4.2.2 - 2014-09-08
+
+* Fixed a memory leak in the CurlAdapter when reusing cURL handles.
+* No longer using `request_fulluri` in stream adapter proxies.
+* Relative redirects are now based on the last response, not the first response.
+
+## 4.2.1 - 2014-08-19
+
+* Ensuring that the StreamAdapter does not always add a Content-Type header
+* Adding automated github releases with a phar and zip
+
+## 4.2.0 - 2014-08-17
+
+* Now merging in default options using a case-insensitive comparison.
+  Closes https://github.com/guzzle/guzzle/issues/767
+* Added the ability to automatically decode `Content-Encoding` response bodies
+  using the `decode_content` request option. This is set to `true` by default
+  to decode the response body if it comes over the wire with a
+  `Content-Encoding`. Set this value to `false` to disable decoding the
+  response content, and pass a string to provide a request `Accept-Encoding`
+  header and turn on automatic response decoding. This feature now allows you
+  to pass an `Accept-Encoding` header in the headers of a request but still
+  disable automatic response decoding.
+  Closes https://github.com/guzzle/guzzle/issues/764
+* Added the ability to throw an exception immediately when transferring
+  requests in parallel. Closes https://github.com/guzzle/guzzle/issues/760
+* Updating guzzlehttp/streams dependency to ~2.1
+* No longer utilizing the now deprecated namespaced methods from the stream
+  package.
+
+## 4.1.8 - 2014-08-14
+
+* Fixed an issue in the CurlFactory that caused setting the `stream=false`
+  request option to throw an exception.
+  See: https://github.com/guzzle/guzzle/issues/769
+* TransactionIterator now calls rewind on the inner iterator.
+  See: https://github.com/guzzle/guzzle/pull/765
+* You can now set the `Content-Type` header to `multipart/form-data`
+  when creating POST requests to force multipart bodies.
+  See https://github.com/guzzle/guzzle/issues/768
+
+## 4.1.7 - 2014-08-07
+
+* Fixed an error in the HistoryPlugin that caused the same request and response
+  to be logged multiple times when an HTTP protocol error occurs.
+* Ensuring that cURL does not add a default Content-Type when no Content-Type
+  has been supplied by the user. This prevents the adapter layer from modifying
+  the request that is sent over the wire after any listeners may have already
+  put the request in a desired state (e.g., signed the request).
+* Throwing an exception when you attempt to send requests that have the
+  "stream" set to true in parallel using the MultiAdapter.
+* Only calling curl_multi_select when there are active cURL handles. This was
+  previously changed and caused performance problems on some systems due to PHP
+  always selecting until the maximum select timeout.
+* Fixed a bug where multipart/form-data POST fields were not correctly
+  aggregated (e.g., values with "&").
+
+## 4.1.6 - 2014-08-03
+
+* Added helper methods to make it easier to represent messages as strings,
+  including getting the start line and getting headers as a string.
+
+## 4.1.5 - 2014-08-02
+
+* Automatically retrying cURL "Connection died, retrying a fresh connect"
+  errors when possible.
+* cURL implementation cleanup
+* Allowing multiple event subscriber listeners to be registered per event by
+  passing an array of arrays of listener configuration.
+
+## 4.1.4 - 2014-07-22
+
+* Fixed a bug that caused multi-part POST requests with more than one field to
+  serialize incorrectly.
+* Paths can now be set to "0"
+* `ResponseInterface::xml` now accepts a `libxml_options` option and added a
+  missing default argument that was required when parsing XML response bodies.
+* A `save_to` stream is now created lazily, which means that files are not
+  created on disk unless a request succeeds.
+
+## 4.1.3 - 2014-07-15
+
+* Various fixes to multipart/form-data POST uploads
+* Wrapping function.php in an if-statement to ensure Guzzle can be used
+  globally and in a Composer install
+* Fixed an issue with generating and merging in events to an event array
+* POST headers are only applied before sending a request to allow you to change
+  the query aggregator used before uploading
+* Added much more robust query string parsing
+* Fixed various parsing and normalization issues with URLs
+* Fixing an issue where multi-valued headers were not being utilized correctly
+  in the StreamAdapter
+
+## 4.1.2 - 2014-06-18
+
+* Added support for sending payloads with GET requests
+
+## 4.1.1 - 2014-06-08
+
+* Fixed an issue related to using custom message factory options in subclasses
+* Fixed an issue with nested form fields in a multi-part POST
+* Fixed an issue with using the `json` request option for POST requests
+* Added `ToArrayInterface` to `GuzzleHttp\Cookie\CookieJar`
+
+## 4.1.0 - 2014-05-27
+
+* Added a `json` request option to easily serialize JSON payloads.
+* Added a `GuzzleHttp\json_decode()` wrapper to safely parse JSON.
+* Added `setPort()` and `getPort()` to `GuzzleHttp\Message\RequestInterface`.
+* Added the ability to provide an emitter to a client in the client constructor.
+* Added the ability to persist a cookie session using $_SESSION.
+* Added a trait that can be used to add event listeners to an iterator.
+* Removed request method constants from RequestInterface.
+* Fixed warning when invalid request start-lines are received.
+* Updated MessageFactory to work with custom request option methods.
+* Updated cacert bundle to latest build.
+
+4.0.2 (2014-04-16)
+------------------
+
+* Proxy requests using the StreamAdapter now properly use request_fulluri (#632)
+* Added the ability to set scalars as POST fields (#628)
+
+## 4.0.1 - 2014-04-04
+
+* The HTTP status code of a response is now set as the exception code of
+  RequestException objects.
+* 303 redirects will now correctly switch from POST to GET requests.
+* The default parallel adapter of a client now correctly uses the MultiAdapter.
+* HasDataTrait now initializes the internal data array as an empty array so
+  that the toArray() method always returns an array.
+
+## 4.0.0 - 2014-03-29
+
+* For information on changes and upgrading, see:
+  https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40
+* Added `GuzzleHttp\batch()` as a convenience function for sending requests in
+  parallel without needing to write asynchronous code.
+* Restructured how events are added to `GuzzleHttp\ClientInterface::sendAll()`.
+  You can now pass a callable or an array of associative arrays where each
+  associative array contains the "fn", "priority", and "once" keys.
+
+## 4.0.0.rc-2 - 2014-03-25
+
+* Removed `getConfig()` and `setConfig()` from clients to avoid confusion
+  around whether things like base_url, message_factory, etc. should be able to
+  be retrieved or modified.
+* Added `getDefaultOption()` and `setDefaultOption()` to ClientInterface
+* functions.php functions were renamed using snake_case to match PHP idioms
+* Added support for `HTTP_PROXY`, `HTTPS_PROXY`, and
+  `GUZZLE_CURL_SELECT_TIMEOUT` environment variables
+* Added the ability to specify custom `sendAll()` event priorities
+* Added the ability to specify custom stream context options to the stream
+  adapter.
+* Added a functions.php function for `get_path()` and `set_path()`
+* CurlAdapter and MultiAdapter now use a callable to generate curl resources
+* MockAdapter now properly reads a body and emits a `headers` event
+* Updated Url class to check if a scheme and host are set before adding ":"
+  and "//". This allows empty Url (e.g., "") to be serialized as "".
+* Parsing invalid XML no longer emits warnings
+* Curl classes now properly throw AdapterExceptions
+* Various performance optimizations
+* Streams are created with the faster `Stream\create()` function
+* Marked deprecation_proxy() as internal
+* Test server is now a collection of static methods on a class
+
+## 4.0.0-rc.1 - 2014-03-15
+
+* See https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40
+
+## 3.8.1 - 2014-01-28
+
+* Bug: Always using GET requests when redirecting from a 303 response
+* Bug: CURLOPT_SSL_VERIFYHOST is now correctly set to false when setting `$certificateAuthority` to false in
+  `Guzzle\Http\ClientInterface::setSslVerification()`
+* Bug: RedirectPlugin now uses strict RFC 3986 compliance when combining a base URL with a relative URL
+* Bug: The body of a request can now be set to `"0"`
+* Sending PHP stream requests no longer forces `HTTP/1.0`
+* Adding more information to ExceptionCollection exceptions so that users have more context, including a stack trace of
+  each sub-exception
+* Updated the `$ref` attribute in service descriptions to merge over any existing parameters of a schema (rather than
+  clobbering everything).
+* Merging URLs will now use the query string object from the relative URL (thus allowing custom query aggregators)
+* Query strings are now parsed in a way that they do no convert empty keys with no value to have a dangling `=`.
+  For example `foo&bar=baz` is now correctly parsed and recognized as `foo&bar=baz` rather than `foo=&bar=baz`.
+* Now properly escaping the regular expression delimiter when matching Cookie domains.
+* Network access is now disabled when loading XML documents
+
+## 3.8.0 - 2013-12-05
+
+* Added the ability to define a POST name for a file
+* JSON response parsing now properly walks additionalProperties
+* cURL error code 18 is now retried automatically in the BackoffPlugin
+* Fixed a cURL error when URLs contain fragments
+* Fixed an issue in the BackoffPlugin retry event where it was trying to access all exceptions as if they were
+  CurlExceptions
+* CURLOPT_PROGRESS function fix for PHP 5.5 (69fcc1e)
+* Added the ability for Guzzle to work with older versions of cURL that do not support `CURLOPT_TIMEOUT_MS`
+* Fixed a bug that was encountered when parsing empty header parameters
+* UriTemplate now has a `setRegex()` method to match the docs
+* The `debug` request parameter now checks if it is truthy rather than if it exists
+* Setting the `debug` request parameter to true shows verbose cURL output instead of using the LogPlugin
+* Added the ability to combine URLs using strict RFC 3986 compliance
+* Command objects can now return the validation errors encountered by the command
+* Various fixes to cache revalidation (#437 and 29797e5)
+* Various fixes to the AsyncPlugin
+* Cleaned up build scripts
+
+## 3.7.4 - 2013-10-02
+
+* Bug fix: 0 is now an allowed value in a description parameter that has a default value (#430)
+* Bug fix: SchemaFormatter now returns an integer when formatting to a Unix timestamp
+  (see https://github.com/aws/aws-sdk-php/issues/147)
+* Bug fix: Cleaned up and fixed URL dot segment removal to properly resolve internal dots
+* Minimum PHP version is now properly specified as 5.3.3 (up from 5.3.2) (#420)
+* Updated the bundled cacert.pem (#419)
+* OauthPlugin now supports adding authentication to headers or query string (#425)
+
+## 3.7.3 - 2013-09-08
+
+* Added the ability to get the exception associated with a request/command when using `MultiTransferException` and
+  `CommandTransferException`.
+* Setting `additionalParameters` of a response to false is now honored when parsing responses with a service description
+* Schemas are only injected into response models when explicitly configured.
+* No longer guessing Content-Type based on the path of a request. Content-Type is now only guessed based on the path of
+  an EntityBody.
+* Bug fix: ChunkedIterator can now properly chunk a \Traversable as well as an \Iterator.
+* Bug fix: FilterIterator now relies on `\Iterator` instead of `\Traversable`.
+* Bug fix: Gracefully handling malformed responses in RequestMediator::writeResponseBody()
+* Bug fix: Replaced call to canCache with canCacheRequest in the CallbackCanCacheStrategy of the CachePlugin
+* Bug fix: Visiting XML attributes first before visiting XML children when serializing requests
+* Bug fix: Properly parsing headers that contain commas contained in quotes
+* Bug fix: mimetype guessing based on a filename is now case-insensitive
+
+## 3.7.2 - 2013-08-02
+
+* Bug fix: Properly URL encoding paths when using the PHP-only version of the UriTemplate expander
+  See https://github.com/guzzle/guzzle/issues/371
+* Bug fix: Cookie domains are now matched correctly according to RFC 6265
+  See https://github.com/guzzle/guzzle/issues/377
+* Bug fix: GET parameters are now used when calculating an OAuth signature
+* Bug fix: Fixed an issue with cache revalidation where the If-None-Match header was being double quoted
+* `Guzzle\Common\AbstractHasDispatcher::dispatch()` now returns the event that was dispatched
+* `Guzzle\Http\QueryString::factory()` now guesses the most appropriate query aggregator to used based on the input.
+  See https://github.com/guzzle/guzzle/issues/379
+* Added a way to add custom domain objects to service description parsing using the `operation.parse_class` event. See
+  https://github.com/guzzle/guzzle/pull/380
+* cURL multi cleanup and optimizations
+
+## 3.7.1 - 2013-07-05
+
+* Bug fix: Setting default options on a client now works
+* Bug fix: Setting options on HEAD requests now works. See #352
+* Bug fix: Moving stream factory before send event to before building the stream. See #353
+* Bug fix: Cookies no longer match on IP addresses per RFC 6265
+* Bug fix: Correctly parsing header parameters that are in `<>` and quotes
+* Added `cert` and `ssl_key` as request options
+* `Host` header can now diverge from the host part of a URL if the header is set manually
+* `Guzzle\Service\Command\LocationVisitor\Request\XmlVisitor` was rewritten to change from using SimpleXML to XMLWriter
+* OAuth parameters are only added via the plugin if they aren't already set
+* Exceptions are now thrown when a URL cannot be parsed
+* Returning `false` if `Guzzle\Http\EntityBody::getContentMd5()` fails
+* Not setting a `Content-MD5` on a command if calculating the Content-MD5 fails via the CommandContentMd5Plugin
+
+## 3.7.0 - 2013-06-10
+
+* See UPGRADING.md for more information on how to upgrade.
+* Requests now support the ability to specify an array of $options when creating a request to more easily modify a
+  request. You can pass a 'request.options' configuration setting to a client to apply default request options to
+  every request created by a client (e.g. default query string variables, headers, curl options, etc.).
+* Added a static facade class that allows you to use Guzzle with static methods and mount the class to `\Guzzle`.
+  See `Guzzle\Http\StaticClient::mount`.
+* Added `command.request_options` to `Guzzle\Service\Command\AbstractCommand` to pass request options to requests
+      created by a command (e.g. custom headers, query string variables, timeout settings, etc.).
+* Stream size in `Guzzle\Stream\PhpStreamRequestFactory` will now be set if Content-Length is returned in the
+  headers of a response
+* Added `Guzzle\Common\Collection::setPath($path, $value)` to set a value into an array using a nested key
+  (e.g. `$collection->setPath('foo/baz/bar', 'test'); echo $collection['foo']['bar']['bar'];`)
+* ServiceBuilders now support storing and retrieving arbitrary data
+* CachePlugin can now purge all resources for a given URI
+* CachePlugin can automatically purge matching cached items when a non-idempotent request is sent to a resource
+* CachePlugin now uses the Vary header to determine if a resource is a cache hit
+* `Guzzle\Http\Message\Response` now implements `\Serializable`
+* Added `Guzzle\Cache\CacheAdapterFactory::fromCache()` to more easily create cache adapters
+* `Guzzle\Service\ClientInterface::execute()` now accepts an array, single command, or Traversable
+* Fixed a bug in `Guzzle\Http\Message\Header\Link::addLink()`
+* Better handling of calculating the size of a stream in `Guzzle\Stream\Stream` using fstat() and caching the size
+* `Guzzle\Common\Exception\ExceptionCollection` now creates a more readable exception message
+* Fixing BC break: Added back the MonologLogAdapter implementation rather than extending from PsrLog so that older
+  Symfony users can still use the old version of Monolog.
+* Fixing BC break: Added the implementation back in for `Guzzle\Http\Message\AbstractMessage::getTokenizedHeader()`.
+  Now triggering an E_USER_DEPRECATED warning when used. Use `$message->getHeader()->parseParams()`.
+* Several performance improvements to `Guzzle\Common\Collection`
+* Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`:
+  createRequest, head, delete, put, patch, post, options, prepareRequest
+* Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()`
+* Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface`
+* Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to
+  `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a
+  resource, string, or EntityBody into the $options parameter to specify the download location of the response.
+* Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a
+  default `array()`
+* Added `Guzzle\Stream\StreamInterface::isRepeatable`
+* Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use
+  $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or
+  $client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))`.
+* Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use $client->getConfig()->getPath('request.options/headers')`.
+* Removed `Guzzle\Http\ClientInterface::expandTemplate()`
+* Removed `Guzzle\Http\ClientInterface::setRequestFactory()`
+* Removed `Guzzle\Http\ClientInterface::getCurlMulti()`
+* Removed `Guzzle\Http\Message\RequestInterface::canCache`
+* Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`
+* Removed `Guzzle\Http\Message\RequestInterface::isRedirect`
+* Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods.
+* You can now enable E_USER_DEPRECATED warnings to see if you are using a deprecated method by setting
+  `Guzzle\Common\Version::$emitWarnings` to true.
+* Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use
+      `$request->getResponseBody()->isRepeatable()` instead.
+* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use
+  `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
+* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use
+  `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
+* Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead.
+* Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead.
+* Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated
+* Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand.
+  These will work through Guzzle 4.0
+* Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use [request.options][params].
+* Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client.
+* Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use $client->getConfig()->getPath('request.options/headers')`.
+* Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`.
+* Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8.
+* Marked `Guzzle\Common\Collection::inject()` as deprecated.
+* Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest');`
+* CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a
+  CacheStorageInterface. These two objects and interface will be removed in a future version.
+* Always setting X-cache headers on cached responses
+* Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin
+* `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface
+  $request, Response $response);`
+* `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);`
+* `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);`
+* Added `CacheStorageInterface::purge($url)`
+* `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin
+  $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache,
+  CanCacheStrategyInterface $canCache = null)`
+* Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)`
+
+## 3.6.0 - 2013-05-29
+
+* ServiceDescription now implements ToArrayInterface
+* Added command.hidden_params to blacklist certain headers from being treated as additionalParameters
+* Guzzle can now correctly parse incomplete URLs
+* Mixed casing of headers are now forced to be a single consistent casing across all values for that header.
+* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution
+* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader().
+* Specific header implementations can be created for complex headers. When a message creates a header, it uses a
+  HeaderFactory which can map specific headers to specific header classes. There is now a Link header and
+  CacheControl header implementation.
+* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate
+* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti()
+* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in
+  Guzzle\Http\Curl\RequestMediator
+* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string.
+* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface
+* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders()
+* Removed Guzzle\Parser\ParserRegister::get(). Use getParser()
+* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser().
+* All response header helper functions return a string rather than mixing Header objects and strings inconsistently
+* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle
+  directly via interfaces
+* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist
+  but are a no-op until removed.
+* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a
+  `Guzzle\Service\Command\ArrayCommandInterface`.
+* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response
+  on a request while the request is still being transferred
+* The ability to case-insensitively search for header values
+* Guzzle\Http\Message\Header::hasExactHeader
+* Guzzle\Http\Message\Header::raw. Use getAll()
+* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object
+  instead.
+* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess
+* Added the ability to cast Model objects to a string to view debug information.
+
+## 3.5.0 - 2013-05-13
+
+* Bug: Fixed a regression so that request responses are parsed only once per oncomplete event rather than multiple times
+* Bug: Better cleanup of one-time events across the board (when an event is meant to fire once, it will now remove
+  itself from the EventDispatcher)
+* Bug: `Guzzle\Log\MessageFormatter` now properly writes "total_time" and "connect_time" values
+* Bug: Cloning an EntityEnclosingRequest now clones the EntityBody too
+* Bug: Fixed an undefined index error when parsing nested JSON responses with a sentAs parameter that reference a
+  non-existent key
+* Bug: All __call() method arguments are now required (helps with mocking frameworks)
+* Deprecating Response::getRequest() and now using a shallow clone of a request object to remove a circular reference
+  to help with refcount based garbage collection of resources created by sending a request
+* Deprecating ZF1 cache and log adapters. These will be removed in the next major version.
+* Deprecating `Response::getPreviousResponse()` (method signature still exists, but it's deprecated). Use the
+  HistoryPlugin for a history.
+* Added a `responseBody` alias for the `response_body` location
+* Refactored internals to no longer rely on Response::getRequest()
+* HistoryPlugin can now be cast to a string
+* HistoryPlugin now logs transactions rather than requests and responses to more accurately keep track of the requests
+  and responses that are sent over the wire
+* Added `getEffectiveUrl()` and `getRedirectCount()` to Response objects
+
+## 3.4.3 - 2013-04-30
+
+* Bug fix: Fixing bug introduced in 3.4.2 where redirect responses are duplicated on the final redirected response
+* Added a check to re-extract the temp cacert bundle from the phar before sending each request
+
+## 3.4.2 - 2013-04-29
+
+* Bug fix: Stream objects now work correctly with "a" and "a+" modes
+* Bug fix: Removing `Transfer-Encoding: chunked` header when a Content-Length is present
+* Bug fix: AsyncPlugin no longer forces HEAD requests
+* Bug fix: DateTime timezones are now properly handled when using the service description schema formatter
+* Bug fix: CachePlugin now properly handles stale-if-error directives when a request to the origin server fails
+* Setting a response on a request will write to the custom request body from the response body if one is specified
+* LogPlugin now writes to php://output when STDERR is undefined
+* Added the ability to set multiple POST files for the same key in a single call
+* application/x-www-form-urlencoded POSTs now use the utf-8 charset by default
+* Added the ability to queue CurlExceptions to the MockPlugin
+* Cleaned up how manual responses are queued on requests (removed "queued_response" and now using request.before_send)
+* Configuration loading now allows remote files
+
+## 3.4.1 - 2013-04-16
+
+* Large refactoring to how CurlMulti handles work. There is now a proxy that sits in front of a pool of CurlMulti
+  handles. This greatly simplifies the implementation, fixes a couple bugs, and provides a small performance boost.
+* Exceptions are now properly grouped when sending requests in parallel
+* Redirects are now properly aggregated when a multi transaction fails
+* Redirects now set the response on the original object even in the event of a failure
+* Bug fix: Model names are now properly set even when using $refs
+* Added support for PHP 5.5's CurlFile to prevent warnings with the deprecated @ syntax
+* Added support for oauth_callback in OAuth signatures
+* Added support for oauth_verifier in OAuth signatures
+* Added support to attempt to retrieve a command first literally, then ucfirst, the with inflection
+
+## 3.4.0 - 2013-04-11
+
+* Bug fix: URLs are now resolved correctly based on https://tools.ietf.org/html/rfc3986#section-5.2. #289
+* Bug fix: Absolute URLs with a path in a service description will now properly override the base URL. #289
+* Bug fix: Parsing a query string with a single PHP array value will now result in an array. #263
+* Bug fix: Better normalization of the User-Agent header to prevent duplicate headers. #264.
+* Bug fix: Added `number` type to service descriptions.
+* Bug fix: empty parameters are removed from an OAuth signature
+* Bug fix: Revalidating a cache entry prefers the Last-Modified over the Date header
+* Bug fix: Fixed "array to string" error when validating a union of types in a service description
+* Bug fix: Removed code that attempted to determine the size of a stream when data is written to the stream
+* Bug fix: Not including an `oauth_token` if the value is null in the OauthPlugin.
+* Bug fix: Now correctly aggregating successful requests and failed requests in CurlMulti when a redirect occurs.
+* The new default CURLOPT_TIMEOUT setting has been increased to 150 seconds so that Guzzle works on poor connections.
+* Added a feature to EntityEnclosingRequest::setBody() that will automatically set the Content-Type of the request if
+  the Content-Type can be determined based on the entity body or the path of the request.
+* Added the ability to overwrite configuration settings in a client when grabbing a throwaway client from a builder.
+* Added support for a PSR-3 LogAdapter.
+* Added a `command.after_prepare` event
+* Added `oauth_callback` parameter to the OauthPlugin
+* Added the ability to create a custom stream class when using a stream factory
+* Added a CachingEntityBody decorator
+* Added support for `additionalParameters` in service descriptions to define how custom parameters are serialized.
+* The bundled SSL certificate is now provided in the phar file and extracted when running Guzzle from a phar.
+* You can now send any EntityEnclosingRequest with POST fields or POST files and cURL will handle creating bodies
+* POST requests using a custom entity body are now treated exactly like PUT requests but with a custom cURL method. This
+  means that the redirect behavior of POST requests with custom bodies will not be the same as POST requests that use
+  POST fields or files (the latter is only used when emulating a form POST in the browser).
+* Lots of cleanup to CurlHandle::factory and RequestFactory::createRequest
+
+## 3.3.1 - 2013-03-10
+
+* Added the ability to create PHP streaming responses from HTTP requests
+* Bug fix: Running any filters when parsing response headers with service descriptions
+* Bug fix: OauthPlugin fixes to allow for multi-dimensional array signing, and sorting parameters before signing
+* Bug fix: Removed the adding of default empty arrays and false Booleans to responses in order to be consistent across
+  response location visitors.
+* Bug fix: Removed the possibility of creating configuration files with circular dependencies
+* RequestFactory::create() now uses the key of a POST file when setting the POST file name
+* Added xmlAllowEmpty to serialize an XML body even if no XML specific parameters are set
+
+## 3.3.0 - 2013-03-03
+
+* A large number of performance optimizations have been made
+* Bug fix: Added 'wb' as a valid write mode for streams
+* Bug fix: `Guzzle\Http\Message\Response::json()` now allows scalar values to be returned
+* Bug fix: Fixed bug in `Guzzle\Http\Message\Response` where wrapping quotes were stripped from `getEtag()`
+* BC: Removed `Guzzle\Http\Utils` class
+* BC: Setting a service description on a client will no longer modify the client's command factories.
+* BC: Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using
+  the 'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io'
+* BC: `Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getSteamType()` are no longer converted to
+  lowercase
+* Operation parameter objects are now lazy loaded internally
+* Added ErrorResponsePlugin that can throw errors for responses defined in service description operations' errorResponses
+* Added support for instantiating responseType=class responseClass classes. Classes must implement
+  `Guzzle\Service\Command\ResponseClassInterface`
+* Added support for additionalProperties for top-level parameters in responseType=model responseClasses. These
+  additional properties also support locations and can be used to parse JSON responses where the outermost part of the
+  JSON is an array
+* Added support for nested renaming of JSON models (rename sentAs to name)
+* CachePlugin
+    * Added support for stale-if-error so that the CachePlugin can now serve stale content from the cache on error
+    * Debug headers can now added to cached response in the CachePlugin
+
+## 3.2.0 - 2013-02-14
+
+* CurlMulti is no longer reused globally. A new multi object is created per-client. This helps to isolate clients.
+* URLs with no path no longer contain a "/" by default
+* Guzzle\Http\QueryString does no longer manages the leading "?". This is now handled in Guzzle\Http\Url.
+* BadResponseException no longer includes the full request and response message
+* Adding setData() to Guzzle\Service\Description\ServiceDescriptionInterface
+* Adding getResponseBody() to Guzzle\Http\Message\RequestInterface
+* Various updates to classes to use ServiceDescriptionInterface type hints rather than ServiceDescription
+* Header values can now be normalized into distinct values when multiple headers are combined with a comma separated list
+* xmlEncoding can now be customized for the XML declaration of a XML service description operation
+* Guzzle\Http\QueryString now uses Guzzle\Http\QueryAggregator\QueryAggregatorInterface objects to add custom value
+  aggregation and no longer uses callbacks
+* The URL encoding implementation of Guzzle\Http\QueryString can now be customized
+* Bug fix: Filters were not always invoked for array service description parameters
+* Bug fix: Redirects now use a target response body rather than a temporary response body
+* Bug fix: The default exponential backoff BackoffPlugin was not giving when the request threshold was exceeded
+* Bug fix: Guzzle now takes the first found value when grabbing Cache-Control directives
+
+## 3.1.2 - 2013-01-27
+
+* Refactored how operation responses are parsed. Visitors now include a before() method responsible for parsing the
+  response body. For example, the XmlVisitor now parses the XML response into an array in the before() method.
+* Fixed an issue where cURL would not automatically decompress responses when the Accept-Encoding header was sent
+* CURLOPT_SSL_VERIFYHOST is never set to 1 because it is deprecated (see 5e0ff2ef20f839e19d1eeb298f90ba3598784444)
+* Fixed a bug where redirect responses were not chained correctly using getPreviousResponse()
+* Setting default headers on a client after setting the user-agent will not erase the user-agent setting
+
+## 3.1.1 - 2013-01-20
+
+* Adding wildcard support to Guzzle\Common\Collection::getPath()
+* Adding alias support to ServiceBuilder configs
+* Adding Guzzle\Service\Resource\CompositeResourceIteratorFactory and cleaning up factory interface
+
+## 3.1.0 - 2013-01-12
+
+* BC: CurlException now extends from RequestException rather than BadResponseException
+* BC: Renamed Guzzle\Plugin\Cache\CanCacheStrategyInterface::canCache() to canCacheRequest() and added CanCacheResponse()
+* Added getData to ServiceDescriptionInterface
+* Added context array to RequestInterface::setState()
+* Bug: Removing hard dependency on the BackoffPlugin from Guzzle\Http
+* Bug: Adding required content-type when JSON request visitor adds JSON to a command
+* Bug: Fixing the serialization of a service description with custom data
+* Made it easier to deal with exceptions thrown when transferring commands or requests in parallel by providing
+  an array of successful and failed responses
+* Moved getPath from Guzzle\Service\Resource\Model to Guzzle\Common\Collection
+* Added Guzzle\Http\IoEmittingEntityBody
+* Moved command filtration from validators to location visitors
+* Added `extends` attributes to service description parameters
+* Added getModels to ServiceDescriptionInterface
+
+## 3.0.7 - 2012-12-19
+
+* Fixing phar detection when forcing a cacert to system if null or true
+* Allowing filename to be passed to `Guzzle\Http\Message\Request::setResponseBody()`
+* Cleaning up `Guzzle\Common\Collection::inject` method
+* Adding a response_body location to service descriptions
+
+## 3.0.6 - 2012-12-09
+
+* CurlMulti performance improvements
+* Adding setErrorResponses() to Operation
+* composer.json tweaks
+
+## 3.0.5 - 2012-11-18
+
+* Bug: Fixing an infinite recursion bug caused from revalidating with the CachePlugin
+* Bug: Response body can now be a string containing "0"
+* Bug: Using Guzzle inside of a phar uses system by default but now allows for a custom cacert
+* Bug: QueryString::fromString now properly parses query string parameters that contain equal signs
+* Added support for XML attributes in service description responses
+* DefaultRequestSerializer now supports array URI parameter values for URI template expansion
+* Added better mimetype guessing to requests and post files
+
+## 3.0.4 - 2012-11-11
+
+* Bug: Fixed a bug when adding multiple cookies to a request to use the correct glue value
+* Bug: Cookies can now be added that have a name, domain, or value set to "0"
+* Bug: Using the system cacert bundle when using the Phar
+* Added json and xml methods to Response to make it easier to parse JSON and XML response data into data structures
+* Enhanced cookie jar de-duplication
+* Added the ability to enable strict cookie jars that throw exceptions when invalid cookies are added
+* Added setStream to StreamInterface to actually make it possible to implement custom rewind behavior for entity bodies
+* Added the ability to create any sort of hash for a stream rather than just an MD5 hash
+
+## 3.0.3 - 2012-11-04
+
+* Implementing redirects in PHP rather than cURL
+* Added PECL URI template extension and using as default parser if available
+* Bug: Fixed Content-Length parsing of Response factory
+* Adding rewind() method to entity bodies and streams. Allows for custom rewinding of non-repeatable streams.
+* Adding ToArrayInterface throughout library
+* Fixing OauthPlugin to create unique nonce values per request
+
+## 3.0.2 - 2012-10-25
+
+* Magic methods are enabled by default on clients
+* Magic methods return the result of a command
+* Service clients no longer require a base_url option in the factory
+* Bug: Fixed an issue with URI templates where null template variables were being expanded
+
+## 3.0.1 - 2012-10-22
+
+* Models can now be used like regular collection objects by calling filter, map, etc.
+* Models no longer require a Parameter structure or initial data in the constructor
+* Added a custom AppendIterator to get around a PHP bug with the `\AppendIterator`
+
+## 3.0.0 - 2012-10-15
+
+* Rewrote service description format to be based on Swagger
+    * Now based on JSON schema
+    * Added nested input structures and nested response models
+    * Support for JSON and XML input and output models
+    * Renamed `commands` to `operations`
+    * Removed dot class notation
+    * Removed custom types
+* Broke the project into smaller top-level namespaces to be more component friendly
+* Removed support for XML configs and descriptions. Use arrays or JSON files.
+* Removed the Validation component and Inspector
+* Moved all cookie code to Guzzle\Plugin\Cookie
+* Magic methods on a Guzzle\Service\Client now return the command un-executed.
+* Calling getResult() or getResponse() on a command will lazily execute the command if needed.
+* Now shipping with cURL's CA certs and using it by default
+* Added previousResponse() method to response objects
+* No longer sending Accept and Accept-Encoding headers on every request
+* Only sending an Expect header by default when a payload is greater than 1MB
+* Added/moved client options:
+    * curl.blacklist to curl.option.blacklist
+    * Added ssl.certificate_authority
+* Added a Guzzle\Iterator component
+* Moved plugins from Guzzle\Http\Plugin to Guzzle\Plugin
+* Added a more robust backoff retry strategy (replaced the ExponentialBackoffPlugin)
+* Added a more robust caching plugin
+* Added setBody to response objects
+* Updating LogPlugin to use a more flexible MessageFormatter
+* Added a completely revamped build process
+* Cleaning up Collection class and removing default values from the get method
+* Fixed ZF2 cache adapters
+
+## 2.8.8 - 2012-10-15
+
+* Bug: Fixed a cookie issue that caused dot prefixed domains to not match where popular browsers did
+
+## 2.8.7 - 2012-09-30
+
+* Bug: Fixed config file aliases for JSON includes
+* Bug: Fixed cookie bug on a request object by using CookieParser to parse cookies on requests
+* Bug: Removing the path to a file when sending a Content-Disposition header on a POST upload
+* Bug: Hardening request and response parsing to account for missing parts
+* Bug: Fixed PEAR packaging
+* Bug: Fixed Request::getInfo
+* Bug: Fixed cases where CURLM_CALL_MULTI_PERFORM return codes were causing curl transactions to fail
+* Adding the ability for the namespace Iterator factory to look in multiple directories
+* Added more getters/setters/removers from service descriptions
+* Added the ability to remove POST fields from OAuth signatures
+* OAuth plugin now supports 2-legged OAuth
+
+## 2.8.6 - 2012-09-05
+
+* Added the ability to modify and build service descriptions
+* Added the use of visitors to apply parameters to locations in service descriptions using the dynamic command
+* Added a `json` parameter location
+* Now allowing dot notation for classes in the CacheAdapterFactory
+* Using the union of two arrays rather than an array_merge when extending service builder services and service params
+* Ensuring that a service is a string before doing strpos() checks on it when substituting services for references
+  in service builder config files.
+* Services defined in two different config files that include one another will by default replace the previously
+  defined service, but you can now create services that extend themselves and merge their settings over the previous
+* The JsonLoader now supports aliasing filenames with different filenames. This allows you to alias something like
+  '_default' with a default JSON configuration file.
+
+## 2.8.5 - 2012-08-29
+
+* Bug: Suppressed empty arrays from URI templates
+* Bug: Added the missing $options argument from ServiceDescription::factory to enable caching
+* Added support for HTTP responses that do not contain a reason phrase in the start-line
+* AbstractCommand commands are now invokable
+* Added a way to get the data used when signing an Oauth request before a request is sent
+
+## 2.8.4 - 2012-08-15
+
+* Bug: Custom delay time calculations are no longer ignored in the ExponentialBackoffPlugin
+* Added the ability to transfer entity bodies as a string rather than streamed. This gets around curl error 65. Set `body_as_string` in a request's curl options to enable.
+* Added a StreamInterface, EntityBodyInterface, and added ftell() to Guzzle\Common\Stream
+* Added an AbstractEntityBodyDecorator and a ReadLimitEntityBody decorator to transfer only a subset of a decorated stream
+* Stream and EntityBody objects will now return the file position to the previous position after a read required operation (e.g. getContentMd5())
+* Added additional response status codes
+* Removed SSL information from the default User-Agent header
+* DELETE requests can now send an entity body
+* Added an EventDispatcher to the ExponentialBackoffPlugin and added an ExponentialBackoffLogger to log backoff retries
+* Added the ability of the MockPlugin to consume mocked request bodies
+* LogPlugin now exposes request and response objects in the extras array
+
+## 2.8.3 - 2012-07-30
+
+* Bug: Fixed a case where empty POST requests were sent as GET requests
+* Bug: Fixed a bug in ExponentialBackoffPlugin that caused fatal errors when retrying an EntityEnclosingRequest that does not have a body
+* Bug: Setting the response body of a request to null after completing a request, not when setting the state of a request to new
+* Added multiple inheritance to service description commands
+* Added an ApiCommandInterface and added `getParamNames()` and `hasParam()`
+* Removed the default 2mb size cutoff from the Md5ValidatorPlugin so that it now defaults to validating everything
+* Changed CurlMulti::perform to pass a smaller timeout to CurlMulti::executeHandles
+
+## 2.8.2 - 2012-07-24
+
+* Bug: Query string values set to 0 are no longer dropped from the query string
+* Bug: A Collection object is no longer created each time a call is made to `Guzzle\Service\Command\AbstractCommand::getRequestHeaders()`
+* Bug: `+` is now treated as an encoded space when parsing query strings
+* QueryString and Collection performance improvements
+* Allowing dot notation for class paths in filters attribute of a service descriptions
+
+## 2.8.1 - 2012-07-16
+
+* Loosening Event Dispatcher dependency
+* POST redirects can now be customized using CURLOPT_POSTREDIR
+
+## 2.8.0 - 2012-07-15
+
+* BC: Guzzle\Http\Query
+    * Query strings with empty variables will always show an equal sign unless the variable is set to QueryString::BLANK (e.g. ?acl= vs ?acl)
+    * Changed isEncodingValues() and isEncodingFields() to isUrlEncoding()
+    * Changed setEncodeValues(bool) and setEncodeFields(bool) to useUrlEncoding(bool)
+    * Changed the aggregation functions of QueryString to be static methods
+    * Can now use fromString() with querystrings that have a leading ?
+* cURL configuration values can be specified in service descriptions using `curl.` prefixed parameters
+* Content-Length is set to 0 before emitting the request.before_send event when sending an empty request body
+* Cookies are no longer URL decoded by default
+* Bug: URI template variables set to null are no longer expanded
+
+## 2.7.2 - 2012-07-02
+
+* BC: Moving things to get ready for subtree splits. Moving Inflection into Common. Moving Guzzle\Http\Parser to Guzzle\Parser.
+* BC: Removing Guzzle\Common\Batch\Batch::count() and replacing it with isEmpty()
+* CachePlugin now allows for a custom request parameter function to check if a request can be cached
+* Bug fix: CachePlugin now only caches GET and HEAD requests by default
+* Bug fix: Using header glue when transferring headers over the wire
+* Allowing deeply nested arrays for composite variables in URI templates
+* Batch divisors can now return iterators or arrays
+
+## 2.7.1 - 2012-06-26
+
+* Minor patch to update version number in UA string
+* Updating build process
+
+## 2.7.0 - 2012-06-25
+
+* BC: Inflection classes moved to Guzzle\Inflection. No longer static methods. Can now inject custom inflectors into classes.
+* BC: Removed magic setX methods from commands
+* BC: Magic methods mapped to service description commands are now inflected in the command factory rather than the client __call() method
+* Verbose cURL options are no longer enabled by default. Set curl.debug to true on a client to enable.
+* Bug: Now allowing colons in a response start-line (e.g. HTTP/1.1 503 Service Unavailable: Back-end server is at capacity)
+* Guzzle\Service\Resource\ResourceIteratorApplyBatched now internally uses the Guzzle\Common\Batch namespace
+* Added Guzzle\Service\Plugin namespace and a PluginCollectionPlugin
+* Added the ability to set POST fields and files in a service description
+* Guzzle\Http\EntityBody::factory() now accepts objects with a __toString() method
+* Adding a command.before_prepare event to clients
+* Added BatchClosureTransfer and BatchClosureDivisor
+* BatchTransferException now includes references to the batch divisor and transfer strategies
+* Fixed some tests so that they pass more reliably
+* Added Guzzle\Common\Log\ArrayLogAdapter
+
+## 2.6.6 - 2012-06-10
+
+* BC: Removing Guzzle\Http\Plugin\BatchQueuePlugin
+* BC: Removing Guzzle\Service\Command\CommandSet
+* Adding generic batching system (replaces the batch queue plugin and command set)
+* Updating ZF cache and log adapters and now using ZF's composer repository
+* Bug: Setting the name of each ApiParam when creating through an ApiCommand
+* Adding result_type, result_doc, deprecated, and doc_url to service descriptions
+* Bug: Changed the default cookie header casing back to 'Cookie'
+
+## 2.6.5 - 2012-06-03
+
+* BC: Renaming Guzzle\Http\Message\RequestInterface::getResourceUri() to getResource()
+* BC: Removing unused AUTH_BASIC and AUTH_DIGEST constants from
+* BC: Guzzle\Http\Cookie is now used to manage Set-Cookie data, not Cookie data
+* BC: Renaming methods in the CookieJarInterface
+* Moving almost all cookie logic out of the CookiePlugin and into the Cookie or CookieJar implementations
+* Making the default glue for HTTP headers ';' instead of ','
+* Adding a removeValue to Guzzle\Http\Message\Header
+* Adding getCookies() to request interface.
+* Making it easier to add event subscribers to HasDispatcherInterface classes. Can now directly call addSubscriber()
+
+## 2.6.4 - 2012-05-30
+
+* BC: Cleaning up how POST files are stored in EntityEnclosingRequest objects. Adding PostFile class.
+* BC: Moving ApiCommand specific functionality from the Inspector and on to the ApiCommand
+* Bug: Fixing magic method command calls on clients
+* Bug: Email constraint only validates strings
+* Bug: Aggregate POST fields when POST files are present in curl handle
+* Bug: Fixing default User-Agent header
+* Bug: Only appending or prepending parameters in commands if they are specified
+* Bug: Not requiring response reason phrases or status codes to match a predefined list of codes
+* Allowing the use of dot notation for class namespaces when using instance_of constraint
+* Added any_match validation constraint
+* Added an AsyncPlugin
+* Passing request object to the calculateWait method of the ExponentialBackoffPlugin
+* Allowing the result of a command object to be changed
+* Parsing location and type sub values when instantiating a service description rather than over and over at runtime
+
+## 2.6.3 - 2012-05-23
+
+* [BC] Guzzle\Common\FromConfigInterface no longer requires any config options.
+* [BC] Refactoring how POST files are stored on an EntityEnclosingRequest. They are now separate from POST fields.
+* You can now use an array of data when creating PUT request bodies in the request factory.
+* Removing the requirement that HTTPS requests needed a Cache-Control: public directive to be cacheable.
+* [Http] Adding support for Content-Type in multipart POST uploads per upload
+* [Http] Added support for uploading multiple files using the same name (foo[0], foo[1])
+* Adding more POST data operations for easier manipulation of POST data.
+* You can now set empty POST fields.
+* The body of a request is only shown on EntityEnclosingRequest objects that do not use POST files.
+* Split the Guzzle\Service\Inspector::validateConfig method into two methods. One to initialize when a command is created, and one to validate.
+* CS updates
+
+## 2.6.2 - 2012-05-19
+
+* [Http] Better handling of nested scope requests in CurlMulti.  Requests are now always prepares in the send() method rather than the addRequest() method.
+
+## 2.6.1 - 2012-05-19
+
+* [BC] Removing 'path' support in service descriptions.  Use 'uri'.
+* [BC] Guzzle\Service\Inspector::parseDocBlock is now protected. Adding getApiParamsForClass() with cache.
+* [BC] Removing Guzzle\Common\NullObject.  Use https://github.com/mtdowling/NullObject if you need it.
+* [BC] Removing Guzzle\Common\XmlElement.
+* All commands, both dynamic and concrete, have ApiCommand objects.
+* Adding a fix for CurlMulti so that if all of the connections encounter some sort of curl error, then the loop exits.
+* Adding checks to EntityEnclosingRequest so that empty POST files and fields are ignored.
+* Making the method signature of Guzzle\Service\Builder\ServiceBuilder::factory more flexible.
+
+## 2.6.0 - 2012-05-15
+
+* [BC] Moving Guzzle\Service\Builder to Guzzle\Service\Builder\ServiceBuilder
+* [BC] Executing a Command returns the result of the command rather than the command
+* [BC] Moving all HTTP parsing logic to Guzzle\Http\Parsers. Allows for faster C implementations if needed.
+* [BC] Changing the Guzzle\Http\Message\Response::setProtocol() method to accept a protocol and version in separate args.
+* [BC] Moving ResourceIterator* to Guzzle\Service\Resource
+* [BC] Completely refactored ResourceIterators to iterate over a cloned command object
+* [BC] Moved Guzzle\Http\UriTemplate to Guzzle\Http\Parser\UriTemplate\UriTemplate
+* [BC] Guzzle\Guzzle is now deprecated
+* Moving Guzzle\Common\Guzzle::inject to Guzzle\Common\Collection::inject
+* Adding Guzzle\Version class to give version information about Guzzle
+* Adding Guzzle\Http\Utils class to provide getDefaultUserAgent() and getHttpDate()
+* Adding Guzzle\Curl\CurlVersion to manage caching curl_version() data
+* ServiceDescription and ServiceBuilder are now cacheable using similar configs
+* Changing the format of XML and JSON service builder configs.  Backwards compatible.
+* Cleaned up Cookie parsing
+* Trimming the default Guzzle User-Agent header
+* Adding a setOnComplete() method to Commands that is called when a command completes
+* Keeping track of requests that were mocked in the MockPlugin
+* Fixed a caching bug in the CacheAdapterFactory
+* Inspector objects can be injected into a Command object
+* Refactoring a lot of code and tests to be case insensitive when dealing with headers
+* Adding Guzzle\Http\Message\HeaderComparison for easy comparison of HTTP headers using a DSL
+* Adding the ability to set global option overrides to service builder configs
+* Adding the ability to include other service builder config files from within XML and JSON files
+* Moving the parseQuery method out of Url and on to QueryString::fromString() as a static factory method.
+
+## 2.5.0 - 2012-05-08
+
+* Major performance improvements
+* [BC] Simplifying Guzzle\Common\Collection.  Please check to see if you are using features that are now deprecated.
+* [BC] Using a custom validation system that allows a flyweight implementation for much faster validation. No longer using Symfony2 Validation component.
+* [BC] No longer supporting "{{ }}" for injecting into command or UriTemplates.  Use "{}"
+* Added the ability to passed parameters to all requests created by a client
+* Added callback functionality to the ExponentialBackoffPlugin
+* Using microtime in ExponentialBackoffPlugin to allow more granular backoff strategies.
+* Rewinding request stream bodies when retrying requests
+* Exception is thrown when JSON response body cannot be decoded
+* Added configurable magic method calls to clients and commands.  This is off by default.
+* Fixed a defect that added a hash to every parsed URL part
+* Fixed duplicate none generation for OauthPlugin.
+* Emitting an event each time a client is generated by a ServiceBuilder
+* Using an ApiParams object instead of a Collection for parameters of an ApiCommand
+* cache.* request parameters should be renamed to params.cache.*
+* Added the ability to set arbitrary curl options on requests (disable_wire, progress, etc.). See CurlHandle.
+* Added the ability to disable type validation of service descriptions
+* ServiceDescriptions and ServiceBuilders are now Serializable

+ 27 - 27
vendor/guzzlehttp/guzzle/LICENSE

@@ -1,27 +1,27 @@
-The MIT License (MIT)
-
-Copyright (c) 2011 Michael Dowling <mtdowling@gmail.com>
-Copyright (c) 2012 Jeremy Lindblom <jeremeamia@gmail.com>
-Copyright (c) 2014 Graham Campbell <hello@gjcampbell.co.uk>
-Copyright (c) 2015 Márk Sági-Kazár <mark.sagikazar@gmail.com>
-Copyright (c) 2015 Tobias Schultze <webmaster@tubo-world.de>
-Copyright (c) 2016 Tobias Nyholm <tobias.nyholm@gmail.com>
-Copyright (c) 2016 George Mponos <gmponos@gmail.com>
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
+The MIT License (MIT)
+
+Copyright (c) 2011 Michael Dowling <mtdowling@gmail.com>
+Copyright (c) 2012 Jeremy Lindblom <jeremeamia@gmail.com>
+Copyright (c) 2014 Graham Campbell <hello@gjcampbell.co.uk>
+Copyright (c) 2015 Márk Sági-Kazár <mark.sagikazar@gmail.com>
+Copyright (c) 2015 Tobias Schultze <webmaster@tubo-world.de>
+Copyright (c) 2016 Tobias Nyholm <tobias.nyholm@gmail.com>
+Copyright (c) 2016 George Mponos <gmponos@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 94 - 94
vendor/guzzlehttp/guzzle/README.md

@@ -1,94 +1,94 @@
-![Guzzle](.github/logo.png?raw=true)
-
-# Guzzle, PHP HTTP client
-
-[![Latest Version](https://img.shields.io/github/release/guzzle/guzzle.svg?style=flat-square)](https://github.com/guzzle/guzzle/releases)
-[![Build Status](https://img.shields.io/github/workflow/status/guzzle/guzzle/CI?label=ci%20build&style=flat-square)](https://github.com/guzzle/guzzle/actions?query=workflow%3ACI)
-[![Total Downloads](https://img.shields.io/packagist/dt/guzzlehttp/guzzle.svg?style=flat-square)](https://packagist.org/packages/guzzlehttp/guzzle)
-
-Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and
-trivial to integrate with web services.
-
-- Simple interface for building query strings, POST requests, streaming large
-  uploads, streaming large downloads, using HTTP cookies, uploading JSON data,
-  etc...
-- Can send both synchronous and asynchronous requests using the same interface.
-- Uses PSR-7 interfaces for requests, responses, and streams. This allows you
-  to utilize other PSR-7 compatible libraries with Guzzle.
-- Supports PSR-18 allowing interoperability between other PSR-18 HTTP Clients.
-- Abstracts away the underlying HTTP transport, allowing you to write
-  environment and transport agnostic code; i.e., no hard dependency on cURL,
-  PHP streams, sockets, or non-blocking event loops.
-- Middleware system allows you to augment and compose client behavior.
-
-```php
-$client = new \GuzzleHttp\Client();
-$response = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle');
-
-echo $response->getStatusCode(); // 200
-echo $response->getHeaderLine('content-type'); // 'application/json; charset=utf8'
-echo $response->getBody(); // '{"id": 1420053, "name": "guzzle", ...}'
-
-// Send an asynchronous request.
-$request = new \GuzzleHttp\Psr7\Request('GET', 'http://httpbin.org');
-$promise = $client->sendAsync($request)->then(function ($response) {
-    echo 'I completed! ' . $response->getBody();
-});
-
-$promise->wait();
-```
-
-## Help and docs
-
-We use GitHub issues only to discuss bugs and new features. For support please refer to:
-
-- [Documentation](https://docs.guzzlephp.org)
-- [Stack Overflow](https://stackoverflow.com/questions/tagged/guzzle)
-- [#guzzle](https://app.slack.com/client/T0D2S9JCT/CE6UAAKL4) channel on [PHP-HTTP Slack](https://slack.httplug.io/)
-- [Gitter](https://gitter.im/guzzle/guzzle)
-
-
-## Installing Guzzle
-
-The recommended way to install Guzzle is through
-[Composer](https://getcomposer.org/).
-
-```bash
-composer require guzzlehttp/guzzle
-```
-
-
-## Version Guidance
-
-| Version | Status              | Packagist           | Namespace    | Repo                | Docs                | PSR-7 | PHP Version  |
-|---------|---------------------|---------------------|--------------|---------------------|---------------------|-------|--------------|
-| 3.x     | EOL                 | `guzzle/guzzle`     | `Guzzle`     | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No    | >=5.3.3,<7.0 |
-| 4.x     | EOL                 | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A                 | No    | >=5.4,<7.0   |
-| 5.x     | EOL                 | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No    | >=5.4,<7.4   |
-| 6.x     | Security fixes only | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes   | >=5.5,<8.0   |
-| 7.x     | Latest              | `guzzlehttp/guzzle` | `GuzzleHttp` | [v7][guzzle-7-repo] | [v7][guzzle-7-docs] | Yes   | >=7.2.5,<8.3 |
-
-[guzzle-3-repo]: https://github.com/guzzle/guzzle3
-[guzzle-4-repo]: https://github.com/guzzle/guzzle/tree/4.x
-[guzzle-5-repo]: https://github.com/guzzle/guzzle/tree/5.3
-[guzzle-6-repo]: https://github.com/guzzle/guzzle/tree/6.5
-[guzzle-7-repo]: https://github.com/guzzle/guzzle
-[guzzle-3-docs]: https://guzzle3.readthedocs.io/
-[guzzle-5-docs]: https://docs.guzzlephp.org/en/5.3/
-[guzzle-6-docs]: https://docs.guzzlephp.org/en/6.5/
-[guzzle-7-docs]: https://docs.guzzlephp.org/en/latest/
-
-
-## Security
-
-If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/guzzle/security/policy) for more information.
-
-## License
-
-Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information.
-
-## For Enterprise
-
-Available as part of the Tidelift Subscription
-
-The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-guzzle?utm_source=packagist-guzzlehttp-guzzle&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
+![Guzzle](.github/logo.png?raw=true)
+
+# Guzzle, PHP HTTP client
+
+[![Latest Version](https://img.shields.io/github/release/guzzle/guzzle.svg?style=flat-square)](https://github.com/guzzle/guzzle/releases)
+[![Build Status](https://img.shields.io/github/workflow/status/guzzle/guzzle/CI?label=ci%20build&style=flat-square)](https://github.com/guzzle/guzzle/actions?query=workflow%3ACI)
+[![Total Downloads](https://img.shields.io/packagist/dt/guzzlehttp/guzzle.svg?style=flat-square)](https://packagist.org/packages/guzzlehttp/guzzle)
+
+Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and
+trivial to integrate with web services.
+
+- Simple interface for building query strings, POST requests, streaming large
+  uploads, streaming large downloads, using HTTP cookies, uploading JSON data,
+  etc...
+- Can send both synchronous and asynchronous requests using the same interface.
+- Uses PSR-7 interfaces for requests, responses, and streams. This allows you
+  to utilize other PSR-7 compatible libraries with Guzzle.
+- Supports PSR-18 allowing interoperability between other PSR-18 HTTP Clients.
+- Abstracts away the underlying HTTP transport, allowing you to write
+  environment and transport agnostic code; i.e., no hard dependency on cURL,
+  PHP streams, sockets, or non-blocking event loops.
+- Middleware system allows you to augment and compose client behavior.
+
+```php
+$client = new \GuzzleHttp\Client();
+$response = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle');
+
+echo $response->getStatusCode(); // 200
+echo $response->getHeaderLine('content-type'); // 'application/json; charset=utf8'
+echo $response->getBody(); // '{"id": 1420053, "name": "guzzle", ...}'
+
+// Send an asynchronous request.
+$request = new \GuzzleHttp\Psr7\Request('GET', 'http://httpbin.org');
+$promise = $client->sendAsync($request)->then(function ($response) {
+    echo 'I completed! ' . $response->getBody();
+});
+
+$promise->wait();
+```
+
+## Help and docs
+
+We use GitHub issues only to discuss bugs and new features. For support please refer to:
+
+- [Documentation](https://docs.guzzlephp.org)
+- [Stack Overflow](https://stackoverflow.com/questions/tagged/guzzle)
+- [#guzzle](https://app.slack.com/client/T0D2S9JCT/CE6UAAKL4) channel on [PHP-HTTP Slack](https://slack.httplug.io/)
+- [Gitter](https://gitter.im/guzzle/guzzle)
+
+
+## Installing Guzzle
+
+The recommended way to install Guzzle is through
+[Composer](https://getcomposer.org/).
+
+```bash
+composer require guzzlehttp/guzzle
+```
+
+
+## Version Guidance
+
+| Version | Status              | Packagist           | Namespace    | Repo                | Docs                | PSR-7 | PHP Version  |
+|---------|---------------------|---------------------|--------------|---------------------|---------------------|-------|--------------|
+| 3.x     | EOL                 | `guzzle/guzzle`     | `Guzzle`     | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No    | >=5.3.3,<7.0 |
+| 4.x     | EOL                 | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A                 | No    | >=5.4,<7.0   |
+| 5.x     | EOL                 | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No    | >=5.4,<7.4   |
+| 6.x     | Security fixes only | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes   | >=5.5,<8.0   |
+| 7.x     | Latest              | `guzzlehttp/guzzle` | `GuzzleHttp` | [v7][guzzle-7-repo] | [v7][guzzle-7-docs] | Yes   | >=7.2.5,<8.3 |
+
+[guzzle-3-repo]: https://github.com/guzzle/guzzle3
+[guzzle-4-repo]: https://github.com/guzzle/guzzle/tree/4.x
+[guzzle-5-repo]: https://github.com/guzzle/guzzle/tree/5.3
+[guzzle-6-repo]: https://github.com/guzzle/guzzle/tree/6.5
+[guzzle-7-repo]: https://github.com/guzzle/guzzle
+[guzzle-3-docs]: https://guzzle3.readthedocs.io/
+[guzzle-5-docs]: https://docs.guzzlephp.org/en/5.3/
+[guzzle-6-docs]: https://docs.guzzlephp.org/en/6.5/
+[guzzle-7-docs]: https://docs.guzzlephp.org/en/latest/
+
+
+## Security
+
+If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/guzzle/security/policy) for more information.
+
+## License
+
+Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information.
+
+## For Enterprise
+
+Available as part of the Tidelift Subscription
+
+The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-guzzle?utm_source=packagist-guzzlehttp-guzzle&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)

+ 1253 - 1253
vendor/guzzlehttp/guzzle/UPGRADING.md

@@ -1,1253 +1,1253 @@
-Guzzle Upgrade Guide
-====================
-
-6.0 to 7.0
-----------
-
-In order to take advantage of the new features of PHP, Guzzle dropped the support
-of PHP 5. The minimum supported PHP version is now PHP 7.2. Type hints and return
-types for functions and methods have been added wherever possible. 
-
-Please make sure:
-- You are calling a function or a method with the correct type.
-- If you extend a class of Guzzle; update all signatures on methods you override.
-
-#### Other backwards compatibility breaking changes
-
-- Class `GuzzleHttp\UriTemplate` is removed.
-- Class `GuzzleHttp\Exception\SeekException` is removed.
-- Classes `GuzzleHttp\Exception\BadResponseException`, `GuzzleHttp\Exception\ClientException`, 
-  `GuzzleHttp\Exception\ServerException` can no longer be initialized with an empty
-  Response as argument.
-- Class `GuzzleHttp\Exception\ConnectException` now extends `GuzzleHttp\Exception\TransferException`
-  instead of `GuzzleHttp\Exception\RequestException`.
-- Function `GuzzleHttp\Exception\ConnectException::getResponse()` is removed.
-- Function `GuzzleHttp\Exception\ConnectException::hasResponse()` is removed.
-- Constant `GuzzleHttp\ClientInterface::VERSION` is removed. Added `GuzzleHttp\ClientInterface::MAJOR_VERSION` instead.
-- Function `GuzzleHttp\Exception\RequestException::getResponseBodySummary` is removed.
-  Use `\GuzzleHttp\Psr7\get_message_body_summary` as an alternative.
-- Function `GuzzleHttp\Cookie\CookieJar::getCookieValue` is removed.
-- Request option `exception` is removed. Please use `http_errors`.
-- Request option `save_to` is removed. Please use `sink`.
-- Pool option `pool_size` is removed. Please use `concurrency`.
-- We now look for environment variables in the `$_SERVER` super global, due to thread safety issues with `getenv`. We continue to fallback to `getenv` in CLI environments, for maximum compatibility.
-- The `get`, `head`, `put`, `post`, `patch`, `delete`, `getAsync`, `headAsync`, `putAsync`, `postAsync`, `patchAsync`, and `deleteAsync` methods are now implemented as genuine methods on `GuzzleHttp\Client`, with strong typing. The original `__call` implementation remains unchanged for now, for maximum backwards compatibility, but won't be invoked under normal operation.
-- The `log` middleware will log the errors with level `error` instead of `notice` 
-- Support for international domain names (IDN) is now disabled by default, and enabling it requires installing ext-intl, linked against a modern version of the C library (ICU 4.6 or higher).
-
-#### Native functions calls
-
-All internal native functions calls of Guzzle are now prefixed with a slash. This
-change makes it impossible for method overloading by other libraries or applications.
-Example:
-
-```php
-// Before:
-curl_version();
-
-// After:
-\curl_version();
-```
-
-For the full diff you can check [here](https://github.com/guzzle/guzzle/compare/6.5.4..master).
-
-5.0 to 6.0
-----------
-
-Guzzle now uses [PSR-7](https://www.php-fig.org/psr/psr-7/) for HTTP messages.
-Due to the fact that these messages are immutable, this prompted a refactoring
-of Guzzle to use a middleware based system rather than an event system. Any
-HTTP message interaction (e.g., `GuzzleHttp\Message\Request`) need to be
-updated to work with the new immutable PSR-7 request and response objects. Any
-event listeners or subscribers need to be updated to become middleware
-functions that wrap handlers (or are injected into a
-`GuzzleHttp\HandlerStack`).
-
-- Removed `GuzzleHttp\BatchResults`
-- Removed `GuzzleHttp\Collection`
-- Removed `GuzzleHttp\HasDataTrait`
-- Removed `GuzzleHttp\ToArrayInterface`
-- The `guzzlehttp/streams` dependency has been removed. Stream functionality
-  is now present in the `GuzzleHttp\Psr7` namespace provided by the
-  `guzzlehttp/psr7` package.
-- Guzzle no longer uses ReactPHP promises and now uses the
-  `guzzlehttp/promises` library. We use a custom promise library for three
-  significant reasons:
-  1. React promises (at the time of writing this) are recursive. Promise
-     chaining and promise resolution will eventually blow the stack. Guzzle
-     promises are not recursive as they use a sort of trampolining technique.
-     Note: there has been movement in the React project to modify promises to
-     no longer utilize recursion.
-  2. Guzzle needs to have the ability to synchronously block on a promise to
-     wait for a result. Guzzle promises allows this functionality (and does
-     not require the use of recursion).
-  3. Because we need to be able to wait on a result, doing so using React
-     promises requires wrapping react promises with RingPHP futures. This
-     overhead is no longer needed, reducing stack sizes, reducing complexity,
-     and improving performance.
-- `GuzzleHttp\Mimetypes` has been moved to a function in
-  `GuzzleHttp\Psr7\mimetype_from_extension` and
-  `GuzzleHttp\Psr7\mimetype_from_filename`.
-- `GuzzleHttp\Query` and `GuzzleHttp\QueryParser` have been removed. Query
-  strings must now be passed into request objects as strings, or provided to
-  the `query` request option when creating requests with clients. The `query`
-  option uses PHP's `http_build_query` to convert an array to a string. If you
-  need a different serialization technique, you will need to pass the query
-  string in as a string. There are a couple helper functions that will make
-  working with query strings easier: `GuzzleHttp\Psr7\parse_query` and
-  `GuzzleHttp\Psr7\build_query`.
-- Guzzle no longer has a dependency on RingPHP. Due to the use of a middleware
-  system based on PSR-7, using RingPHP and it's middleware system as well adds
-  more complexity than the benefits it provides. All HTTP handlers that were
-  present in RingPHP have been modified to work directly with PSR-7 messages
-  and placed in the `GuzzleHttp\Handler` namespace. This significantly reduces
-  complexity in Guzzle, removes a dependency, and improves performance. RingPHP
-  will be maintained for Guzzle 5 support, but will no longer be a part of
-  Guzzle 6.
-- As Guzzle now uses a middleware based systems the event system and RingPHP
-  integration has been removed. Note: while the event system has been removed,
-  it is possible to add your own type of event system that is powered by the
-  middleware system.
-  - Removed the `Event` namespace.
-  - Removed the `Subscriber` namespace.
-  - Removed `Transaction` class
-  - Removed `RequestFsm`
-  - Removed `RingBridge`
-  - `GuzzleHttp\Subscriber\Cookie` is now provided by
-    `GuzzleHttp\Middleware::cookies`
-  - `GuzzleHttp\Subscriber\HttpError` is now provided by
-    `GuzzleHttp\Middleware::httpError`
-  - `GuzzleHttp\Subscriber\History` is now provided by
-    `GuzzleHttp\Middleware::history`
-  - `GuzzleHttp\Subscriber\Mock` is now provided by
-    `GuzzleHttp\Handler\MockHandler`
-  - `GuzzleHttp\Subscriber\Prepare` is now provided by
-    `GuzzleHttp\PrepareBodyMiddleware`
-  - `GuzzleHttp\Subscriber\Redirect` is now provided by
-    `GuzzleHttp\RedirectMiddleware`
-- Guzzle now uses `Psr\Http\Message\UriInterface` (implements in
-  `GuzzleHttp\Psr7\Uri`) for URI support. `GuzzleHttp\Url` is now gone.
-- Static functions in `GuzzleHttp\Utils` have been moved to namespaced
-  functions under the `GuzzleHttp` namespace. This requires either a Composer
-  based autoloader or you to include functions.php.
-- `GuzzleHttp\ClientInterface::getDefaultOption` has been renamed to
-  `GuzzleHttp\ClientInterface::getConfig`.
-- `GuzzleHttp\ClientInterface::setDefaultOption` has been removed.
-- The `json` and `xml` methods of response objects has been removed. With the
-  migration to strictly adhering to PSR-7 as the interface for Guzzle messages,
-  adding methods to message interfaces would actually require Guzzle messages
-  to extend from PSR-7 messages rather then work with them directly.
-
-## Migrating to middleware
-
-The change to PSR-7 unfortunately required significant refactoring to Guzzle
-due to the fact that PSR-7 messages are immutable. Guzzle 5 relied on an event
-system from plugins. The event system relied on mutability of HTTP messages and
-side effects in order to work. With immutable messages, you have to change your
-workflow to become more about either returning a value (e.g., functional
-middlewares) or setting a value on an object. Guzzle v6 has chosen the
-functional middleware approach.
-
-Instead of using the event system to listen for things like the `before` event,
-you now create a stack based middleware function that intercepts a request on
-the way in and the promise of the response on the way out. This is a much
-simpler and more predictable approach than the event system and works nicely
-with PSR-7 middleware. Due to the use of promises, the middleware system is
-also asynchronous.
-
-v5:
-
-```php
-use GuzzleHttp\Event\BeforeEvent;
-$client = new GuzzleHttp\Client();
-// Get the emitter and listen to the before event.
-$client->getEmitter()->on('before', function (BeforeEvent $e) {
-    // Guzzle v5 events relied on mutation
-    $e->getRequest()->setHeader('X-Foo', 'Bar');
-});
-```
-
-v6:
-
-In v6, you can modify the request before it is sent using the `mapRequest`
-middleware. The idiomatic way in v6 to modify the request/response lifecycle is
-to setup a handler middleware stack up front and inject the handler into a
-client.
-
-```php
-use GuzzleHttp\Middleware;
-// Create a handler stack that has all of the default middlewares attached
-$handler = GuzzleHttp\HandlerStack::create();
-// Push the handler onto the handler stack
-$handler->push(Middleware::mapRequest(function (RequestInterface $request) {
-    // Notice that we have to return a request object
-    return $request->withHeader('X-Foo', 'Bar');
-}));
-// Inject the handler into the client
-$client = new GuzzleHttp\Client(['handler' => $handler]);
-```
-
-## POST Requests
-
-This version added the [`form_params`](http://guzzle.readthedocs.org/en/latest/request-options.html#form_params)
-and `multipart` request options. `form_params` is an associative array of
-strings or array of strings and is used to serialize an
-`application/x-www-form-urlencoded` POST request. The
-[`multipart`](http://guzzle.readthedocs.org/en/latest/request-options.html#multipart)
-option is now used to send a multipart/form-data POST request.
-
-`GuzzleHttp\Post\PostFile` has been removed. Use the `multipart` option to add
-POST files to a multipart/form-data request.
-
-The `body` option no longer accepts an array to send POST requests. Please use
-`multipart` or `form_params` instead.
-
-The `base_url` option has been renamed to `base_uri`.
-
-4.x to 5.0
-----------
-
-## Rewritten Adapter Layer
-
-Guzzle now uses [RingPHP](http://ringphp.readthedocs.org/en/latest) to send
-HTTP requests. The `adapter` option in a `GuzzleHttp\Client` constructor
-is still supported, but it has now been renamed to `handler`. Instead of
-passing a `GuzzleHttp\Adapter\AdapterInterface`, you must now pass a PHP
-`callable` that follows the RingPHP specification.
-
-## Removed Fluent Interfaces
-
-[Fluent interfaces were removed](https://ocramius.github.io/blog/fluent-interfaces-are-evil/)
-from the following classes:
-
-- `GuzzleHttp\Collection`
-- `GuzzleHttp\Url`
-- `GuzzleHttp\Query`
-- `GuzzleHttp\Post\PostBody`
-- `GuzzleHttp\Cookie\SetCookie`
-
-## Removed functions.php
-
-Removed "functions.php", so that Guzzle is truly PSR-4 compliant. The following
-functions can be used as replacements.
-
-- `GuzzleHttp\json_decode` -> `GuzzleHttp\Utils::jsonDecode`
-- `GuzzleHttp\get_path` -> `GuzzleHttp\Utils::getPath`
-- `GuzzleHttp\Utils::setPath` -> `GuzzleHttp\set_path`
-- `GuzzleHttp\Pool::batch` -> `GuzzleHttp\batch`. This function is, however,
-  deprecated in favor of using `GuzzleHttp\Pool::batch()`.
-
-The "procedural" global client has been removed with no replacement (e.g.,
-`GuzzleHttp\get()`, `GuzzleHttp\post()`, etc.). Use a `GuzzleHttp\Client`
-object as a replacement.
-
-## `throwImmediately` has been removed
-
-The concept of "throwImmediately" has been removed from exceptions and error
-events. This control mechanism was used to stop a transfer of concurrent
-requests from completing. This can now be handled by throwing the exception or
-by cancelling a pool of requests or each outstanding future request
-individually.
-
-## headers event has been removed
-
-Removed the "headers" event. This event was only useful for changing the
-body a response once the headers of the response were known. You can implement
-a similar behavior in a number of ways. One example might be to use a
-FnStream that has access to the transaction being sent. For example, when the
-first byte is written, you could check if the response headers match your
-expectations, and if so, change the actual stream body that is being
-written to.
-
-## Updates to HTTP Messages
-
-Removed the `asArray` parameter from
-`GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header
-value as an array, then use the newly added `getHeaderAsArray()` method of
-`MessageInterface`. This change makes the Guzzle interfaces compatible with
-the PSR-7 interfaces.
-
-3.x to 4.0
-----------
-
-## Overarching changes:
-
-- Now requires PHP 5.4 or greater.
-- No longer requires cURL to send requests.
-- Guzzle no longer wraps every exception it throws. Only exceptions that are
-  recoverable are now wrapped by Guzzle.
-- Various namespaces have been removed or renamed.
-- No longer requiring the Symfony EventDispatcher. A custom event dispatcher
-  based on the Symfony EventDispatcher is
-  now utilized in `GuzzleHttp\Event\EmitterInterface` (resulting in significant
-  speed and functionality improvements).
-
-Changes per Guzzle 3.x namespace are described below.
-
-## Batch
-
-The `Guzzle\Batch` namespace has been removed. This is best left to
-third-parties to implement on top of Guzzle's core HTTP library.
-
-## Cache
-
-The `Guzzle\Cache` namespace has been removed. (Todo: No suitable replacement
-has been implemented yet, but hoping to utilize a PSR cache interface).
-
-## Common
-
-- Removed all of the wrapped exceptions. It's better to use the standard PHP
-  library for unrecoverable exceptions.
-- `FromConfigInterface` has been removed.
-- `Guzzle\Common\Version` has been removed. The VERSION constant can be found
-  at `GuzzleHttp\ClientInterface::VERSION`.
-
-### Collection
-
-- `getAll` has been removed. Use `toArray` to convert a collection to an array.
-- `inject` has been removed.
-- `keySearch` has been removed.
-- `getPath` no longer supports wildcard expressions. Use something better like
-  JMESPath for this.
-- `setPath` now supports appending to an existing array via the `[]` notation.
-
-### Events
-
-Guzzle no longer requires Symfony's EventDispatcher component. Guzzle now uses
-`GuzzleHttp\Event\Emitter`.
-
-- `Symfony\Component\EventDispatcher\EventDispatcherInterface` is replaced by
-  `GuzzleHttp\Event\EmitterInterface`.
-- `Symfony\Component\EventDispatcher\EventDispatcher` is replaced by
-  `GuzzleHttp\Event\Emitter`.
-- `Symfony\Component\EventDispatcher\Event` is replaced by
-  `GuzzleHttp\Event\Event`, and Guzzle now has an EventInterface in
-  `GuzzleHttp\Event\EventInterface`.
-- `AbstractHasDispatcher` has moved to a trait, `HasEmitterTrait`, and
-  `HasDispatcherInterface` has moved to `HasEmitterInterface`. Retrieving the
-  event emitter of a request, client, etc. now uses the `getEmitter` method
-  rather than the `getDispatcher` method.
-
-#### Emitter
-
-- Use the `once()` method to add a listener that automatically removes itself
-  the first time it is invoked.
-- Use the `listeners()` method to retrieve a list of event listeners rather than
-  the `getListeners()` method.
-- Use `emit()` instead of `dispatch()` to emit an event from an emitter.
-- Use `attach()` instead of `addSubscriber()` and `detach()` instead of
-  `removeSubscriber()`.
-
-```php
-$mock = new Mock();
-// 3.x
-$request->getEventDispatcher()->addSubscriber($mock);
-$request->getEventDispatcher()->removeSubscriber($mock);
-// 4.x
-$request->getEmitter()->attach($mock);
-$request->getEmitter()->detach($mock);
-```
-
-Use the `on()` method to add a listener rather than the `addListener()` method.
-
-```php
-// 3.x
-$request->getEventDispatcher()->addListener('foo', function (Event $event) { /* ... */ } );
-// 4.x
-$request->getEmitter()->on('foo', function (Event $event, $name) { /* ... */ } );
-```
-
-## Http
-
-### General changes
-
-- The cacert.pem certificate has been moved to `src/cacert.pem`.
-- Added the concept of adapters that are used to transfer requests over the
-  wire.
-- Simplified the event system.
-- Sending requests in parallel is still possible, but batching is no longer a
-  concept of the HTTP layer. Instead, you must use the `complete` and `error`
-  events to asynchronously manage parallel request transfers.
-- `Guzzle\Http\Url` has moved to `GuzzleHttp\Url`.
-- `Guzzle\Http\QueryString` has moved to `GuzzleHttp\Query`.
-- QueryAggregators have been rewritten so that they are simply callable
-  functions.
-- `GuzzleHttp\StaticClient` has been removed. Use the functions provided in
-  `functions.php` for an easy to use static client instance.
-- Exceptions in `GuzzleHttp\Exception` have been updated to all extend from
-  `GuzzleHttp\Exception\TransferException`.
-
-### Client
-
-Calling methods like `get()`, `post()`, `head()`, etc. no longer create and
-return a request, but rather creates a request, sends the request, and returns
-the response.
-
-```php
-// 3.0
-$request = $client->get('/');
-$response = $request->send();
-
-// 4.0
-$response = $client->get('/');
-
-// or, to mirror the previous behavior
-$request = $client->createRequest('GET', '/');
-$response = $client->send($request);
-```
-
-`GuzzleHttp\ClientInterface` has changed.
-
-- The `send` method no longer accepts more than one request. Use `sendAll` to
-  send multiple requests in parallel.
-- `setUserAgent()` has been removed. Use a default request option instead. You
-  could, for example, do something like:
-  `$client->setConfig('defaults/headers/User-Agent', 'Foo/Bar ' . $client::getDefaultUserAgent())`.
-- `setSslVerification()` has been removed. Use default request options instead,
-  like `$client->setConfig('defaults/verify', true)`.
-
-`GuzzleHttp\Client` has changed.
-
-- The constructor now accepts only an associative array. You can include a
-  `base_url` string or array to use a URI template as the base URL of a client.
-  You can also specify a `defaults` key that is an associative array of default
-  request options. You can pass an `adapter` to use a custom adapter,
-  `batch_adapter` to use a custom adapter for sending requests in parallel, or
-  a `message_factory` to change the factory used to create HTTP requests and
-  responses.
-- The client no longer emits a `client.create_request` event.
-- Creating requests with a client no longer automatically utilize a URI
-  template. You must pass an array into a creational method (e.g.,
-  `createRequest`, `get`, `put`, etc.) in order to expand a URI template.
-
-### Messages
-
-Messages no longer have references to their counterparts (i.e., a request no
-longer has a reference to it's response, and a response no loger has a
-reference to its request). This association is now managed through a
-`GuzzleHttp\Adapter\TransactionInterface` object. You can get references to
-these transaction objects using request events that are emitted over the
-lifecycle of a request.
-
-#### Requests with a body
-
-- `GuzzleHttp\Message\EntityEnclosingRequest` and
-  `GuzzleHttp\Message\EntityEnclosingRequestInterface` have been removed. The
-  separation between requests that contain a body and requests that do not
-  contain a body has been removed, and now `GuzzleHttp\Message\RequestInterface`
-  handles both use cases.
-- Any method that previously accepts a `GuzzleHttp\Response` object now accept a
-  `GuzzleHttp\Message\ResponseInterface`.
-- `GuzzleHttp\Message\RequestFactoryInterface` has been renamed to
-  `GuzzleHttp\Message\MessageFactoryInterface`. This interface is used to create
-  both requests and responses and is implemented in
-  `GuzzleHttp\Message\MessageFactory`.
-- POST field and file methods have been removed from the request object. You
-  must now use the methods made available to `GuzzleHttp\Post\PostBodyInterface`
-  to control the format of a POST body. Requests that are created using a
-  standard `GuzzleHttp\Message\MessageFactoryInterface` will automatically use
-  a `GuzzleHttp\Post\PostBody` body if the body was passed as an array or if
-  the method is POST and no body is provided.
-
-```php
-$request = $client->createRequest('POST', '/');
-$request->getBody()->setField('foo', 'bar');
-$request->getBody()->addFile(new PostFile('file_key', fopen('/path/to/content', 'r')));
-```
-
-#### Headers
-
-- `GuzzleHttp\Message\Header` has been removed. Header values are now simply
-  represented by an array of values or as a string. Header values are returned
-  as a string by default when retrieving a header value from a message. You can
-  pass an optional argument of `true` to retrieve a header value as an array
-  of strings instead of a single concatenated string.
-- `GuzzleHttp\PostFile` and `GuzzleHttp\PostFileInterface` have been moved to
-  `GuzzleHttp\Post`. This interface has been simplified and now allows the
-  addition of arbitrary headers.
-- Custom headers like `GuzzleHttp\Message\Header\Link` have been removed. Most
-  of the custom headers are now handled separately in specific
-  subscribers/plugins, and `GuzzleHttp\Message\HeaderValues::parseParams()` has
-  been updated to properly handle headers that contain parameters (like the
-  `Link` header).
-
-#### Responses
-
-- `GuzzleHttp\Message\Response::getInfo()` and
-  `GuzzleHttp\Message\Response::setInfo()` have been removed. Use the event
-  system to retrieve this type of information.
-- `GuzzleHttp\Message\Response::getRawHeaders()` has been removed.
-- `GuzzleHttp\Message\Response::getMessage()` has been removed.
-- `GuzzleHttp\Message\Response::calculateAge()` and other cache specific
-  methods have moved to the CacheSubscriber.
-- Header specific helper functions like `getContentMd5()` have been removed.
-  Just use `getHeader('Content-MD5')` instead.
-- `GuzzleHttp\Message\Response::setRequest()` and
-  `GuzzleHttp\Message\Response::getRequest()` have been removed. Use the event
-  system to work with request and response objects as a transaction.
-- `GuzzleHttp\Message\Response::getRedirectCount()` has been removed. Use the
-  Redirect subscriber instead.
-- `GuzzleHttp\Message\Response::isSuccessful()` and other related methods have
-  been removed. Use `getStatusCode()` instead.
-
-#### Streaming responses
-
-Streaming requests can now be created by a client directly, returning a
-`GuzzleHttp\Message\ResponseInterface` object that contains a body stream
-referencing an open PHP HTTP stream.
-
-```php
-// 3.0
-use Guzzle\Stream\PhpStreamRequestFactory;
-$request = $client->get('/');
-$factory = new PhpStreamRequestFactory();
-$stream = $factory->fromRequest($request);
-$data = $stream->read(1024);
-
-// 4.0
-$response = $client->get('/', ['stream' => true]);
-// Read some data off of the stream in the response body
-$data = $response->getBody()->read(1024);
-```
-
-#### Redirects
-
-The `configureRedirects()` method has been removed in favor of a
-`allow_redirects` request option.
-
-```php
-// Standard redirects with a default of a max of 5 redirects
-$request = $client->createRequest('GET', '/', ['allow_redirects' => true]);
-
-// Strict redirects with a custom number of redirects
-$request = $client->createRequest('GET', '/', [
-    'allow_redirects' => ['max' => 5, 'strict' => true]
-]);
-```
-
-#### EntityBody
-
-EntityBody interfaces and classes have been removed or moved to
-`GuzzleHttp\Stream`. All classes and interfaces that once required
-`GuzzleHttp\EntityBodyInterface` now require
-`GuzzleHttp\Stream\StreamInterface`. Creating a new body for a request no
-longer uses `GuzzleHttp\EntityBody::factory` but now uses
-`GuzzleHttp\Stream\Stream::factory` or even better:
-`GuzzleHttp\Stream\create()`.
-
-- `Guzzle\Http\EntityBodyInterface` is now `GuzzleHttp\Stream\StreamInterface`
-- `Guzzle\Http\EntityBody` is now `GuzzleHttp\Stream\Stream`
-- `Guzzle\Http\CachingEntityBody` is now `GuzzleHttp\Stream\CachingStream`
-- `Guzzle\Http\ReadLimitEntityBody` is now `GuzzleHttp\Stream\LimitStream`
-- `Guzzle\Http\IoEmittyinEntityBody` has been removed.
-
-#### Request lifecycle events
-
-Requests previously submitted a large number of requests. The number of events
-emitted over the lifecycle of a request has been significantly reduced to make
-it easier to understand how to extend the behavior of a request. All events
-emitted during the lifecycle of a request now emit a custom
-`GuzzleHttp\Event\EventInterface` object that contains context providing
-methods and a way in which to modify the transaction at that specific point in
-time (e.g., intercept the request and set a response on the transaction).
-
-- `request.before_send` has been renamed to `before` and now emits a
-  `GuzzleHttp\Event\BeforeEvent`
-- `request.complete` has been renamed to `complete` and now emits a
-  `GuzzleHttp\Event\CompleteEvent`.
-- `request.sent` has been removed. Use `complete`.
-- `request.success` has been removed. Use `complete`.
-- `error` is now an event that emits a `GuzzleHttp\Event\ErrorEvent`.
-- `request.exception` has been removed. Use `error`.
-- `request.receive.status_line` has been removed.
-- `curl.callback.progress` has been removed. Use a custom `StreamInterface` to
-  maintain a status update.
-- `curl.callback.write` has been removed. Use a custom `StreamInterface` to
-  intercept writes.
-- `curl.callback.read` has been removed. Use a custom `StreamInterface` to
-  intercept reads.
-
-`headers` is a new event that is emitted after the response headers of a
-request have been received before the body of the response is downloaded. This
-event emits a `GuzzleHttp\Event\HeadersEvent`.
-
-You can intercept a request and inject a response using the `intercept()` event
-of a `GuzzleHttp\Event\BeforeEvent`, `GuzzleHttp\Event\CompleteEvent`, and
-`GuzzleHttp\Event\ErrorEvent` event.
-
-See: http://docs.guzzlephp.org/en/latest/events.html
-
-## Inflection
-
-The `Guzzle\Inflection` namespace has been removed. This is not a core concern
-of Guzzle.
-
-## Iterator
-
-The `Guzzle\Iterator` namespace has been removed.
-
-- `Guzzle\Iterator\AppendIterator`, `Guzzle\Iterator\ChunkedIterator`, and
-  `Guzzle\Iterator\MethodProxyIterator` are nice, but not a core requirement of
-  Guzzle itself.
-- `Guzzle\Iterator\FilterIterator` is no longer needed because an equivalent
-  class is shipped with PHP 5.4.
-- `Guzzle\Iterator\MapIterator` is not really needed when using PHP 5.5 because
-  it's easier to just wrap an iterator in a generator that maps values.
-
-For a replacement of these iterators, see https://github.com/nikic/iter
-
-## Log
-
-The LogPlugin has moved to https://github.com/guzzle/log-subscriber. The
-`Guzzle\Log` namespace has been removed. Guzzle now relies on
-`Psr\Log\LoggerInterface` for all logging. The MessageFormatter class has been
-moved to `GuzzleHttp\Subscriber\Log\Formatter`.
-
-## Parser
-
-The `Guzzle\Parser` namespace has been removed. This was previously used to
-make it possible to plug in custom parsers for cookies, messages, URI
-templates, and URLs; however, this level of complexity is not needed in Guzzle
-so it has been removed.
-
-- Cookie: Cookie parsing logic has been moved to
-  `GuzzleHttp\Cookie\SetCookie::fromString`.
-- Message: Message parsing logic for both requests and responses has been moved
-  to `GuzzleHttp\Message\MessageFactory::fromMessage`. Message parsing is only
-  used in debugging or deserializing messages, so it doesn't make sense for
-  Guzzle as a library to add this level of complexity to parsing messages.
-- UriTemplate: URI template parsing has been moved to
-  `GuzzleHttp\UriTemplate`. The Guzzle library will automatically use the PECL
-  URI template library if it is installed.
-- Url: URL parsing is now performed in `GuzzleHttp\Url::fromString` (previously
-  it was `Guzzle\Http\Url::factory()`). If custom URL parsing is necessary,
-  then developers are free to subclass `GuzzleHttp\Url`.
-
-## Plugin
-
-The `Guzzle\Plugin` namespace has been renamed to `GuzzleHttp\Subscriber`.
-Several plugins are shipping with the core Guzzle library under this namespace.
-
-- `GuzzleHttp\Subscriber\Cookie`: Replaces the old CookiePlugin. Cookie jar
-  code has moved to `GuzzleHttp\Cookie`.
-- `GuzzleHttp\Subscriber\History`: Replaces the old HistoryPlugin.
-- `GuzzleHttp\Subscriber\HttpError`: Throws errors when a bad HTTP response is
-  received.
-- `GuzzleHttp\Subscriber\Mock`: Replaces the old MockPlugin.
-- `GuzzleHttp\Subscriber\Prepare`: Prepares the body of a request just before
-  sending. This subscriber is attached to all requests by default.
-- `GuzzleHttp\Subscriber\Redirect`: Replaces the RedirectPlugin.
-
-The following plugins have been removed (third-parties are free to re-implement
-these if needed):
-
-- `GuzzleHttp\Plugin\Async` has been removed.
-- `GuzzleHttp\Plugin\CurlAuth` has been removed.
-- `GuzzleHttp\Plugin\ErrorResponse\ErrorResponsePlugin` has been removed. This
-  functionality should instead be implemented with event listeners that occur
-  after normal response parsing occurs in the guzzle/command package.
-
-The following plugins are not part of the core Guzzle package, but are provided
-in separate repositories:
-
-- `Guzzle\Http\Plugin\BackoffPlugin` has been rewritten to be much simpler
-  to build custom retry policies using simple functions rather than various
-  chained classes. See: https://github.com/guzzle/retry-subscriber
-- `Guzzle\Http\Plugin\Cache\CachePlugin` has moved to
-  https://github.com/guzzle/cache-subscriber
-- `Guzzle\Http\Plugin\Log\LogPlugin` has moved to
-  https://github.com/guzzle/log-subscriber
-- `Guzzle\Http\Plugin\Md5\Md5Plugin` has moved to
-  https://github.com/guzzle/message-integrity-subscriber
-- `Guzzle\Http\Plugin\Mock\MockPlugin` has moved to
-  `GuzzleHttp\Subscriber\MockSubscriber`.
-- `Guzzle\Http\Plugin\Oauth\OauthPlugin` has moved to
-  https://github.com/guzzle/oauth-subscriber
-
-## Service
-
-The service description layer of Guzzle has moved into two separate packages:
-
-- http://github.com/guzzle/command Provides a high level abstraction over web
-  services by representing web service operations using commands.
-- http://github.com/guzzle/guzzle-services Provides an implementation of
-  guzzle/command that provides request serialization and response parsing using
-  Guzzle service descriptions.
-
-## Stream
-
-Stream have moved to a separate package available at
-https://github.com/guzzle/streams.
-
-`Guzzle\Stream\StreamInterface` has been given a large update to cleanly take
-on the responsibilities of `Guzzle\Http\EntityBody` and
-`Guzzle\Http\EntityBodyInterface` now that they have been removed. The number
-of methods implemented by the `StreamInterface` has been drastically reduced to
-allow developers to more easily extend and decorate stream behavior.
-
-## Removed methods from StreamInterface
-
-- `getStream` and `setStream` have been removed to better encapsulate streams.
-- `getMetadata` and `setMetadata` have been removed in favor of
-  `GuzzleHttp\Stream\MetadataStreamInterface`.
-- `getWrapper`, `getWrapperData`, `getStreamType`, and `getUri` have all been
-  removed. This data is accessible when
-  using streams that implement `GuzzleHttp\Stream\MetadataStreamInterface`.
-- `rewind` has been removed. Use `seek(0)` for a similar behavior.
-
-## Renamed methods
-
-- `detachStream` has been renamed to `detach`.
-- `feof` has been renamed to `eof`.
-- `ftell` has been renamed to `tell`.
-- `readLine` has moved from an instance method to a static class method of
-  `GuzzleHttp\Stream\Stream`.
-
-## Metadata streams
-
-`GuzzleHttp\Stream\MetadataStreamInterface` has been added to denote streams
-that contain additional metadata accessible via `getMetadata()`.
-`GuzzleHttp\Stream\StreamInterface::getMetadata` and
-`GuzzleHttp\Stream\StreamInterface::setMetadata` have been removed.
-
-## StreamRequestFactory
-
-The entire concept of the StreamRequestFactory has been removed. The way this
-was used in Guzzle 3 broke the actual interface of sending streaming requests
-(instead of getting back a Response, you got a StreamInterface). Streaming
-PHP requests are now implemented through the `GuzzleHttp\Adapter\StreamAdapter`.
-
-3.6 to 3.7
-----------
-
-### Deprecations
-
-- You can now enable E_USER_DEPRECATED warnings to see if you are using any deprecated methods.:
-
-```php
-\Guzzle\Common\Version::$emitWarnings = true;
-```
-
-The following APIs and options have been marked as deprecated:
-
-- Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use `$request->getResponseBody()->isRepeatable()` instead.
-- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
-- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
-- Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead.
-- Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead.
-- Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated
-- Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client.
-- Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8.
-- Marked `Guzzle\Common\Collection::inject()` as deprecated.
-- Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use
-  `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` or
-  `$client->setDefaultOption('auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));`
-
-3.7 introduces `request.options` as a parameter for a client configuration and as an optional argument to all creational
-request methods. When paired with a client's configuration settings, these options allow you to specify default settings
-for various aspects of a request. Because these options make other previous configuration options redundant, several
-configuration options and methods of a client and AbstractCommand have been deprecated.
-
-- Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use `$client->getDefaultOption('headers')`.
-- Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use `$client->setDefaultOption('headers/{header_name}', 'value')`.
-- Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use `$client->setDefaultOption('params/{param_name}', 'value')`
-- Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. These will work through Guzzle 4.0
-
-        $command = $client->getCommand('foo', array(
-            'command.headers' => array('Test' => '123'),
-            'command.response_body' => '/path/to/file'
-        ));
-
-        // Should be changed to:
-
-        $command = $client->getCommand('foo', array(
-            'command.request_options' => array(
-                'headers' => array('Test' => '123'),
-                'save_as' => '/path/to/file'
-            )
-        ));
-
-### Interface changes
-
-Additions and changes (you will need to update any implementations or subclasses you may have created):
-
-- Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`:
-  createRequest, head, delete, put, patch, post, options, prepareRequest
-- Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()`
-- Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface`
-- Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to
-  `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a
-  resource, string, or EntityBody into the $options parameter to specify the download location of the response.
-- Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a
-  default `array()`
-- Added `Guzzle\Stream\StreamInterface::isRepeatable`
-- Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods.
-
-The following methods were removed from interfaces. All of these methods are still available in the concrete classes
-that implement them, but you should update your code to use alternative methods:
-
-- Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use
-  `$client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or
-  `$client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))` or
-  `$client->setDefaultOption('headers/{header_name}', 'value')`. or
-  `$client->setDefaultOption('headers', array('header_name' => 'value'))`.
-- Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use `$client->getConfig()->getPath('request.options/headers')`.
-- Removed `Guzzle\Http\ClientInterface::expandTemplate()`. This is an implementation detail.
-- Removed `Guzzle\Http\ClientInterface::setRequestFactory()`. This is an implementation detail.
-- Removed `Guzzle\Http\ClientInterface::getCurlMulti()`. This is a very specific implementation detail.
-- Removed `Guzzle\Http\Message\RequestInterface::canCache`. Use the CachePlugin.
-- Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`. Use the HistoryPlugin.
-- Removed `Guzzle\Http\Message\RequestInterface::isRedirect`. Use the HistoryPlugin.
-
-### Cache plugin breaking changes
-
-- CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a
-  CacheStorageInterface. These two objects and interface will be removed in a future version.
-- Always setting X-cache headers on cached responses
-- Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin
-- `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface
-  $request, Response $response);`
-- `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);`
-- `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);`
-- Added `CacheStorageInterface::purge($url)`
-- `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin
-  $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache,
-  CanCacheStrategyInterface $canCache = null)`
-- Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)`
-
-3.5 to 3.6
-----------
-
-* Mixed casing of headers are now forced to be a single consistent casing across all values for that header.
-* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution
-* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader().
-  For example, setHeader() first removes the header using unset on a HeaderCollection and then calls addHeader().
-  Keeping the Host header and URL host in sync is now handled by overriding the addHeader method in Request.
-* Specific header implementations can be created for complex headers. When a message creates a header, it uses a
-  HeaderFactory which can map specific headers to specific header classes. There is now a Link header and
-  CacheControl header implementation.
-* Moved getLinks() from Response to just be used on a Link header object.
-
-If you previously relied on Guzzle\Http\Message\Header::raw(), then you will need to update your code to use the
-HeaderInterface (e.g. toArray(), getAll(), etc.).
-
-### Interface changes
-
-* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate
-* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti()
-* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in
-  Guzzle\Http\Curl\RequestMediator
-* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string.
-* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface
-* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders()
-
-### Removed deprecated functions
-
-* Removed Guzzle\Parser\ParserRegister::get(). Use getParser()
-* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser().
-
-### Deprecations
-
-* The ability to case-insensitively search for header values
-* Guzzle\Http\Message\Header::hasExactHeader
-* Guzzle\Http\Message\Header::raw. Use getAll()
-* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object
-  instead.
-
-### Other changes
-
-* All response header helper functions return a string rather than mixing Header objects and strings inconsistently
-* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle
-  directly via interfaces
-* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist
-  but are a no-op until removed.
-* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a
-  `Guzzle\Service\Command\ArrayCommandInterface`.
-* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response
-  on a request while the request is still being transferred
-* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess
-
-3.3 to 3.4
-----------
-
-Base URLs of a client now follow the rules of https://tools.ietf.org/html/rfc3986#section-5.2.2 when merging URLs.
-
-3.2 to 3.3
-----------
-
-### Response::getEtag() quote stripping removed
-
-`Guzzle\Http\Message\Response::getEtag()` no longer strips quotes around the ETag response header
-
-### Removed `Guzzle\Http\Utils`
-
-The `Guzzle\Http\Utils` class was removed. This class was only used for testing.
-
-### Stream wrapper and type
-
-`Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getStreamType()` are no longer converted to lowercase.
-
-### curl.emit_io became emit_io
-
-Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using the
-'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io'
-
-3.1 to 3.2
-----------
-
-### CurlMulti is no longer reused globally
-
-Before 3.2, the same CurlMulti object was reused globally for each client. This can cause issue where plugins added
-to a single client can pollute requests dispatched from other clients.
-
-If you still wish to reuse the same CurlMulti object with each client, then you can add a listener to the
-ServiceBuilder's `service_builder.create_client` event to inject a custom CurlMulti object into each client as it is
-created.
-
-```php
-$multi = new Guzzle\Http\Curl\CurlMulti();
-$builder = Guzzle\Service\Builder\ServiceBuilder::factory('/path/to/config.json');
-$builder->addListener('service_builder.create_client', function ($event) use ($multi) {
-    $event['client']->setCurlMulti($multi);
-}
-});
-```
-
-### No default path
-
-URLs no longer have a default path value of '/' if no path was specified.
-
-Before:
-
-```php
-$request = $client->get('http://www.foo.com');
-echo $request->getUrl();
-// >> http://www.foo.com/
-```
-
-After:
-
-```php
-$request = $client->get('http://www.foo.com');
-echo $request->getUrl();
-// >> http://www.foo.com
-```
-
-### Less verbose BadResponseException
-
-The exception message for `Guzzle\Http\Exception\BadResponseException` no longer contains the full HTTP request and
-response information. You can, however, get access to the request and response object by calling `getRequest()` or
-`getResponse()` on the exception object.
-
-### Query parameter aggregation
-
-Multi-valued query parameters are no longer aggregated using a callback function. `Guzzle\Http\Query` now has a
-setAggregator() method that accepts a `Guzzle\Http\QueryAggregator\QueryAggregatorInterface` object. This object is
-responsible for handling the aggregation of multi-valued query string variables into a flattened hash.
-
-2.8 to 3.x
-----------
-
-### Guzzle\Service\Inspector
-
-Change `\Guzzle\Service\Inspector::fromConfig` to `\Guzzle\Common\Collection::fromConfig`
-
-**Before**
-
-```php
-use Guzzle\Service\Inspector;
-
-class YourClient extends \Guzzle\Service\Client
-{
-    public static function factory($config = array())
-    {
-        $default = array();
-        $required = array('base_url', 'username', 'api_key');
-        $config = Inspector::fromConfig($config, $default, $required);
-
-        $client = new self(
-            $config->get('base_url'),
-            $config->get('username'),
-            $config->get('api_key')
-        );
-        $client->setConfig($config);
-
-        $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json'));
-
-        return $client;
-    }
-```
-
-**After**
-
-```php
-use Guzzle\Common\Collection;
-
-class YourClient extends \Guzzle\Service\Client
-{
-    public static function factory($config = array())
-    {
-        $default = array();
-        $required = array('base_url', 'username', 'api_key');
-        $config = Collection::fromConfig($config, $default, $required);
-
-        $client = new self(
-            $config->get('base_url'),
-            $config->get('username'),
-            $config->get('api_key')
-        );
-        $client->setConfig($config);
-
-        $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json'));
-
-        return $client;
-    }
-```
-
-### Convert XML Service Descriptions to JSON
-
-**Before**
-
-```xml
-<?xml version="1.0" encoding="UTF-8"?>
-<client>
-    <commands>
-        <!-- Groups -->
-        <command name="list_groups" method="GET" uri="groups.json">
-            <doc>Get a list of groups</doc>
-        </command>
-        <command name="search_groups" method="GET" uri='search.json?query="{{query}} type:group"'>
-            <doc>Uses a search query to get a list of groups</doc>
-            <param name="query" type="string" required="true" />
-        </command>
-        <command name="create_group" method="POST" uri="groups.json">
-            <doc>Create a group</doc>
-            <param name="data" type="array" location="body" filters="json_encode" doc="Group JSON"/>
-            <param name="Content-Type" location="header" static="application/json"/>
-        </command>
-        <command name="delete_group" method="DELETE" uri="groups/{{id}}.json">
-            <doc>Delete a group by ID</doc>
-            <param name="id" type="integer" required="true"/>
-        </command>
-        <command name="get_group" method="GET" uri="groups/{{id}}.json">
-            <param name="id" type="integer" required="true"/>
-        </command>
-        <command name="update_group" method="PUT" uri="groups/{{id}}.json">
-            <doc>Update a group</doc>
-            <param name="id" type="integer" required="true"/>
-            <param name="data" type="array" location="body" filters="json_encode" doc="Group JSON"/>
-            <param name="Content-Type" location="header" static="application/json"/>
-        </command>
-    </commands>
-</client>
-```
-
-**After**
-
-```json
-{
-    "name":       "Zendesk REST API v2",
-    "apiVersion": "2012-12-31",
-    "description":"Provides access to Zendesk views, groups, tickets, ticket fields, and users",
-    "operations": {
-        "list_groups":  {
-            "httpMethod":"GET",
-            "uri":       "groups.json",
-            "summary":   "Get a list of groups"
-        },
-        "search_groups":{
-            "httpMethod":"GET",
-            "uri":       "search.json?query=\"{query} type:group\"",
-            "summary":   "Uses a search query to get a list of groups",
-            "parameters":{
-                "query":{
-                    "location":   "uri",
-                    "description":"Zendesk Search Query",
-                    "type":       "string",
-                    "required":   true
-                }
-            }
-        },
-        "create_group": {
-            "httpMethod":"POST",
-            "uri":       "groups.json",
-            "summary":   "Create a group",
-            "parameters":{
-                "data":        {
-                    "type":       "array",
-                    "location":   "body",
-                    "description":"Group JSON",
-                    "filters":    "json_encode",
-                    "required":   true
-                },
-                "Content-Type":{
-                    "type":    "string",
-                    "location":"header",
-                    "static":  "application/json"
-                }
-            }
-        },
-        "delete_group": {
-            "httpMethod":"DELETE",
-            "uri":       "groups/{id}.json",
-            "summary":   "Delete a group",
-            "parameters":{
-                "id":{
-                    "location":   "uri",
-                    "description":"Group to delete by ID",
-                    "type":       "integer",
-                    "required":   true
-                }
-            }
-        },
-        "get_group":    {
-            "httpMethod":"GET",
-            "uri":       "groups/{id}.json",
-            "summary":   "Get a ticket",
-            "parameters":{
-                "id":{
-                    "location":   "uri",
-                    "description":"Group to get by ID",
-                    "type":       "integer",
-                    "required":   true
-                }
-            }
-        },
-        "update_group": {
-            "httpMethod":"PUT",
-            "uri":       "groups/{id}.json",
-            "summary":   "Update a group",
-            "parameters":{
-                "id":          {
-                    "location":   "uri",
-                    "description":"Group to update by ID",
-                    "type":       "integer",
-                    "required":   true
-                },
-                "data":        {
-                    "type":       "array",
-                    "location":   "body",
-                    "description":"Group JSON",
-                    "filters":    "json_encode",
-                    "required":   true
-                },
-                "Content-Type":{
-                    "type":    "string",
-                    "location":"header",
-                    "static":  "application/json"
-                }
-            }
-        }
-}
-```
-
-### Guzzle\Service\Description\ServiceDescription
-
-Commands are now called Operations
-
-**Before**
-
-```php
-use Guzzle\Service\Description\ServiceDescription;
-
-$sd = new ServiceDescription();
-$sd->getCommands();     // @returns ApiCommandInterface[]
-$sd->hasCommand($name);
-$sd->getCommand($name); // @returns ApiCommandInterface|null
-$sd->addCommand($command); // @param ApiCommandInterface $command
-```
-
-**After**
-
-```php
-use Guzzle\Service\Description\ServiceDescription;
-
-$sd = new ServiceDescription();
-$sd->getOperations();           // @returns OperationInterface[]
-$sd->hasOperation($name);
-$sd->getOperation($name);       // @returns OperationInterface|null
-$sd->addOperation($operation);  // @param OperationInterface $operation
-```
-
-### Guzzle\Common\Inflection\Inflector
-
-Namespace is now `Guzzle\Inflection\Inflector`
-
-### Guzzle\Http\Plugin
-
-Namespace is now `Guzzle\Plugin`. Many other changes occur within this namespace and are detailed in their own sections below.
-
-### Guzzle\Http\Plugin\LogPlugin and Guzzle\Common\Log
-
-Now `Guzzle\Plugin\Log\LogPlugin` and `Guzzle\Log` respectively.
-
-**Before**
-
-```php
-use Guzzle\Common\Log\ClosureLogAdapter;
-use Guzzle\Http\Plugin\LogPlugin;
-
-/** @var \Guzzle\Http\Client */
-$client;
-
-// $verbosity is an integer indicating desired message verbosity level
-$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $verbosity = LogPlugin::LOG_VERBOSE);
-```
-
-**After**
-
-```php
-use Guzzle\Log\ClosureLogAdapter;
-use Guzzle\Log\MessageFormatter;
-use Guzzle\Plugin\Log\LogPlugin;
-
-/** @var \Guzzle\Http\Client */
-$client;
-
-// $format is a string indicating desired message format -- @see MessageFormatter
-$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $format = MessageFormatter::DEBUG_FORMAT);
-```
-
-### Guzzle\Http\Plugin\CurlAuthPlugin
-
-Now `Guzzle\Plugin\CurlAuth\CurlAuthPlugin`.
-
-### Guzzle\Http\Plugin\ExponentialBackoffPlugin
-
-Now `Guzzle\Plugin\Backoff\BackoffPlugin`, and other changes.
-
-**Before**
-
-```php
-use Guzzle\Http\Plugin\ExponentialBackoffPlugin;
-
-$backoffPlugin = new ExponentialBackoffPlugin($maxRetries, array_merge(
-        ExponentialBackoffPlugin::getDefaultFailureCodes(), array(429)
-    ));
-
-$client->addSubscriber($backoffPlugin);
-```
-
-**After**
-
-```php
-use Guzzle\Plugin\Backoff\BackoffPlugin;
-use Guzzle\Plugin\Backoff\HttpBackoffStrategy;
-
-// Use convenient factory method instead -- see implementation for ideas of what
-// you can do with chaining backoff strategies
-$backoffPlugin = BackoffPlugin::getExponentialBackoff($maxRetries, array_merge(
-        HttpBackoffStrategy::getDefaultFailureCodes(), array(429)
-    ));
-$client->addSubscriber($backoffPlugin);
-```
-
-### Known Issues
-
-#### [BUG] Accept-Encoding header behavior changed unintentionally.
-
-(See #217) (Fixed in 09daeb8c666fb44499a0646d655a8ae36456575e)
-
-In version 2.8 setting the `Accept-Encoding` header would set the CURLOPT_ENCODING option, which permitted cURL to
-properly handle gzip/deflate compressed responses from the server. In versions affected by this bug this does not happen.
-See issue #217 for a workaround, or use a version containing the fix.
+Guzzle Upgrade Guide
+====================
+
+6.0 to 7.0
+----------
+
+In order to take advantage of the new features of PHP, Guzzle dropped the support
+of PHP 5. The minimum supported PHP version is now PHP 7.2. Type hints and return
+types for functions and methods have been added wherever possible. 
+
+Please make sure:
+- You are calling a function or a method with the correct type.
+- If you extend a class of Guzzle; update all signatures on methods you override.
+
+#### Other backwards compatibility breaking changes
+
+- Class `GuzzleHttp\UriTemplate` is removed.
+- Class `GuzzleHttp\Exception\SeekException` is removed.
+- Classes `GuzzleHttp\Exception\BadResponseException`, `GuzzleHttp\Exception\ClientException`, 
+  `GuzzleHttp\Exception\ServerException` can no longer be initialized with an empty
+  Response as argument.
+- Class `GuzzleHttp\Exception\ConnectException` now extends `GuzzleHttp\Exception\TransferException`
+  instead of `GuzzleHttp\Exception\RequestException`.
+- Function `GuzzleHttp\Exception\ConnectException::getResponse()` is removed.
+- Function `GuzzleHttp\Exception\ConnectException::hasResponse()` is removed.
+- Constant `GuzzleHttp\ClientInterface::VERSION` is removed. Added `GuzzleHttp\ClientInterface::MAJOR_VERSION` instead.
+- Function `GuzzleHttp\Exception\RequestException::getResponseBodySummary` is removed.
+  Use `\GuzzleHttp\Psr7\get_message_body_summary` as an alternative.
+- Function `GuzzleHttp\Cookie\CookieJar::getCookieValue` is removed.
+- Request option `exception` is removed. Please use `http_errors`.
+- Request option `save_to` is removed. Please use `sink`.
+- Pool option `pool_size` is removed. Please use `concurrency`.
+- We now look for environment variables in the `$_SERVER` super global, due to thread safety issues with `getenv`. We continue to fallback to `getenv` in CLI environments, for maximum compatibility.
+- The `get`, `head`, `put`, `post`, `patch`, `delete`, `getAsync`, `headAsync`, `putAsync`, `postAsync`, `patchAsync`, and `deleteAsync` methods are now implemented as genuine methods on `GuzzleHttp\Client`, with strong typing. The original `__call` implementation remains unchanged for now, for maximum backwards compatibility, but won't be invoked under normal operation.
+- The `log` middleware will log the errors with level `error` instead of `notice` 
+- Support for international domain names (IDN) is now disabled by default, and enabling it requires installing ext-intl, linked against a modern version of the C library (ICU 4.6 or higher).
+
+#### Native functions calls
+
+All internal native functions calls of Guzzle are now prefixed with a slash. This
+change makes it impossible for method overloading by other libraries or applications.
+Example:
+
+```php
+// Before:
+curl_version();
+
+// After:
+\curl_version();
+```
+
+For the full diff you can check [here](https://github.com/guzzle/guzzle/compare/6.5.4..master).
+
+5.0 to 6.0
+----------
+
+Guzzle now uses [PSR-7](https://www.php-fig.org/psr/psr-7/) for HTTP messages.
+Due to the fact that these messages are immutable, this prompted a refactoring
+of Guzzle to use a middleware based system rather than an event system. Any
+HTTP message interaction (e.g., `GuzzleHttp\Message\Request`) need to be
+updated to work with the new immutable PSR-7 request and response objects. Any
+event listeners or subscribers need to be updated to become middleware
+functions that wrap handlers (or are injected into a
+`GuzzleHttp\HandlerStack`).
+
+- Removed `GuzzleHttp\BatchResults`
+- Removed `GuzzleHttp\Collection`
+- Removed `GuzzleHttp\HasDataTrait`
+- Removed `GuzzleHttp\ToArrayInterface`
+- The `guzzlehttp/streams` dependency has been removed. Stream functionality
+  is now present in the `GuzzleHttp\Psr7` namespace provided by the
+  `guzzlehttp/psr7` package.
+- Guzzle no longer uses ReactPHP promises and now uses the
+  `guzzlehttp/promises` library. We use a custom promise library for three
+  significant reasons:
+  1. React promises (at the time of writing this) are recursive. Promise
+     chaining and promise resolution will eventually blow the stack. Guzzle
+     promises are not recursive as they use a sort of trampolining technique.
+     Note: there has been movement in the React project to modify promises to
+     no longer utilize recursion.
+  2. Guzzle needs to have the ability to synchronously block on a promise to
+     wait for a result. Guzzle promises allows this functionality (and does
+     not require the use of recursion).
+  3. Because we need to be able to wait on a result, doing so using React
+     promises requires wrapping react promises with RingPHP futures. This
+     overhead is no longer needed, reducing stack sizes, reducing complexity,
+     and improving performance.
+- `GuzzleHttp\Mimetypes` has been moved to a function in
+  `GuzzleHttp\Psr7\mimetype_from_extension` and
+  `GuzzleHttp\Psr7\mimetype_from_filename`.
+- `GuzzleHttp\Query` and `GuzzleHttp\QueryParser` have been removed. Query
+  strings must now be passed into request objects as strings, or provided to
+  the `query` request option when creating requests with clients. The `query`
+  option uses PHP's `http_build_query` to convert an array to a string. If you
+  need a different serialization technique, you will need to pass the query
+  string in as a string. There are a couple helper functions that will make
+  working with query strings easier: `GuzzleHttp\Psr7\parse_query` and
+  `GuzzleHttp\Psr7\build_query`.
+- Guzzle no longer has a dependency on RingPHP. Due to the use of a middleware
+  system based on PSR-7, using RingPHP and it's middleware system as well adds
+  more complexity than the benefits it provides. All HTTP handlers that were
+  present in RingPHP have been modified to work directly with PSR-7 messages
+  and placed in the `GuzzleHttp\Handler` namespace. This significantly reduces
+  complexity in Guzzle, removes a dependency, and improves performance. RingPHP
+  will be maintained for Guzzle 5 support, but will no longer be a part of
+  Guzzle 6.
+- As Guzzle now uses a middleware based systems the event system and RingPHP
+  integration has been removed. Note: while the event system has been removed,
+  it is possible to add your own type of event system that is powered by the
+  middleware system.
+  - Removed the `Event` namespace.
+  - Removed the `Subscriber` namespace.
+  - Removed `Transaction` class
+  - Removed `RequestFsm`
+  - Removed `RingBridge`
+  - `GuzzleHttp\Subscriber\Cookie` is now provided by
+    `GuzzleHttp\Middleware::cookies`
+  - `GuzzleHttp\Subscriber\HttpError` is now provided by
+    `GuzzleHttp\Middleware::httpError`
+  - `GuzzleHttp\Subscriber\History` is now provided by
+    `GuzzleHttp\Middleware::history`
+  - `GuzzleHttp\Subscriber\Mock` is now provided by
+    `GuzzleHttp\Handler\MockHandler`
+  - `GuzzleHttp\Subscriber\Prepare` is now provided by
+    `GuzzleHttp\PrepareBodyMiddleware`
+  - `GuzzleHttp\Subscriber\Redirect` is now provided by
+    `GuzzleHttp\RedirectMiddleware`
+- Guzzle now uses `Psr\Http\Message\UriInterface` (implements in
+  `GuzzleHttp\Psr7\Uri`) for URI support. `GuzzleHttp\Url` is now gone.
+- Static functions in `GuzzleHttp\Utils` have been moved to namespaced
+  functions under the `GuzzleHttp` namespace. This requires either a Composer
+  based autoloader or you to include functions.php.
+- `GuzzleHttp\ClientInterface::getDefaultOption` has been renamed to
+  `GuzzleHttp\ClientInterface::getConfig`.
+- `GuzzleHttp\ClientInterface::setDefaultOption` has been removed.
+- The `json` and `xml` methods of response objects has been removed. With the
+  migration to strictly adhering to PSR-7 as the interface for Guzzle messages,
+  adding methods to message interfaces would actually require Guzzle messages
+  to extend from PSR-7 messages rather then work with them directly.
+
+## Migrating to middleware
+
+The change to PSR-7 unfortunately required significant refactoring to Guzzle
+due to the fact that PSR-7 messages are immutable. Guzzle 5 relied on an event
+system from plugins. The event system relied on mutability of HTTP messages and
+side effects in order to work. With immutable messages, you have to change your
+workflow to become more about either returning a value (e.g., functional
+middlewares) or setting a value on an object. Guzzle v6 has chosen the
+functional middleware approach.
+
+Instead of using the event system to listen for things like the `before` event,
+you now create a stack based middleware function that intercepts a request on
+the way in and the promise of the response on the way out. This is a much
+simpler and more predictable approach than the event system and works nicely
+with PSR-7 middleware. Due to the use of promises, the middleware system is
+also asynchronous.
+
+v5:
+
+```php
+use GuzzleHttp\Event\BeforeEvent;
+$client = new GuzzleHttp\Client();
+// Get the emitter and listen to the before event.
+$client->getEmitter()->on('before', function (BeforeEvent $e) {
+    // Guzzle v5 events relied on mutation
+    $e->getRequest()->setHeader('X-Foo', 'Bar');
+});
+```
+
+v6:
+
+In v6, you can modify the request before it is sent using the `mapRequest`
+middleware. The idiomatic way in v6 to modify the request/response lifecycle is
+to setup a handler middleware stack up front and inject the handler into a
+client.
+
+```php
+use GuzzleHttp\Middleware;
+// Create a handler stack that has all of the default middlewares attached
+$handler = GuzzleHttp\HandlerStack::create();
+// Push the handler onto the handler stack
+$handler->push(Middleware::mapRequest(function (RequestInterface $request) {
+    // Notice that we have to return a request object
+    return $request->withHeader('X-Foo', 'Bar');
+}));
+// Inject the handler into the client
+$client = new GuzzleHttp\Client(['handler' => $handler]);
+```
+
+## POST Requests
+
+This version added the [`form_params`](http://guzzle.readthedocs.org/en/latest/request-options.html#form_params)
+and `multipart` request options. `form_params` is an associative array of
+strings or array of strings and is used to serialize an
+`application/x-www-form-urlencoded` POST request. The
+[`multipart`](http://guzzle.readthedocs.org/en/latest/request-options.html#multipart)
+option is now used to send a multipart/form-data POST request.
+
+`GuzzleHttp\Post\PostFile` has been removed. Use the `multipart` option to add
+POST files to a multipart/form-data request.
+
+The `body` option no longer accepts an array to send POST requests. Please use
+`multipart` or `form_params` instead.
+
+The `base_url` option has been renamed to `base_uri`.
+
+4.x to 5.0
+----------
+
+## Rewritten Adapter Layer
+
+Guzzle now uses [RingPHP](http://ringphp.readthedocs.org/en/latest) to send
+HTTP requests. The `adapter` option in a `GuzzleHttp\Client` constructor
+is still supported, but it has now been renamed to `handler`. Instead of
+passing a `GuzzleHttp\Adapter\AdapterInterface`, you must now pass a PHP
+`callable` that follows the RingPHP specification.
+
+## Removed Fluent Interfaces
+
+[Fluent interfaces were removed](https://ocramius.github.io/blog/fluent-interfaces-are-evil/)
+from the following classes:
+
+- `GuzzleHttp\Collection`
+- `GuzzleHttp\Url`
+- `GuzzleHttp\Query`
+- `GuzzleHttp\Post\PostBody`
+- `GuzzleHttp\Cookie\SetCookie`
+
+## Removed functions.php
+
+Removed "functions.php", so that Guzzle is truly PSR-4 compliant. The following
+functions can be used as replacements.
+
+- `GuzzleHttp\json_decode` -> `GuzzleHttp\Utils::jsonDecode`
+- `GuzzleHttp\get_path` -> `GuzzleHttp\Utils::getPath`
+- `GuzzleHttp\Utils::setPath` -> `GuzzleHttp\set_path`
+- `GuzzleHttp\Pool::batch` -> `GuzzleHttp\batch`. This function is, however,
+  deprecated in favor of using `GuzzleHttp\Pool::batch()`.
+
+The "procedural" global client has been removed with no replacement (e.g.,
+`GuzzleHttp\get()`, `GuzzleHttp\post()`, etc.). Use a `GuzzleHttp\Client`
+object as a replacement.
+
+## `throwImmediately` has been removed
+
+The concept of "throwImmediately" has been removed from exceptions and error
+events. This control mechanism was used to stop a transfer of concurrent
+requests from completing. This can now be handled by throwing the exception or
+by cancelling a pool of requests or each outstanding future request
+individually.
+
+## headers event has been removed
+
+Removed the "headers" event. This event was only useful for changing the
+body a response once the headers of the response were known. You can implement
+a similar behavior in a number of ways. One example might be to use a
+FnStream that has access to the transaction being sent. For example, when the
+first byte is written, you could check if the response headers match your
+expectations, and if so, change the actual stream body that is being
+written to.
+
+## Updates to HTTP Messages
+
+Removed the `asArray` parameter from
+`GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header
+value as an array, then use the newly added `getHeaderAsArray()` method of
+`MessageInterface`. This change makes the Guzzle interfaces compatible with
+the PSR-7 interfaces.
+
+3.x to 4.0
+----------
+
+## Overarching changes:
+
+- Now requires PHP 5.4 or greater.
+- No longer requires cURL to send requests.
+- Guzzle no longer wraps every exception it throws. Only exceptions that are
+  recoverable are now wrapped by Guzzle.
+- Various namespaces have been removed or renamed.
+- No longer requiring the Symfony EventDispatcher. A custom event dispatcher
+  based on the Symfony EventDispatcher is
+  now utilized in `GuzzleHttp\Event\EmitterInterface` (resulting in significant
+  speed and functionality improvements).
+
+Changes per Guzzle 3.x namespace are described below.
+
+## Batch
+
+The `Guzzle\Batch` namespace has been removed. This is best left to
+third-parties to implement on top of Guzzle's core HTTP library.
+
+## Cache
+
+The `Guzzle\Cache` namespace has been removed. (Todo: No suitable replacement
+has been implemented yet, but hoping to utilize a PSR cache interface).
+
+## Common
+
+- Removed all of the wrapped exceptions. It's better to use the standard PHP
+  library for unrecoverable exceptions.
+- `FromConfigInterface` has been removed.
+- `Guzzle\Common\Version` has been removed. The VERSION constant can be found
+  at `GuzzleHttp\ClientInterface::VERSION`.
+
+### Collection
+
+- `getAll` has been removed. Use `toArray` to convert a collection to an array.
+- `inject` has been removed.
+- `keySearch` has been removed.
+- `getPath` no longer supports wildcard expressions. Use something better like
+  JMESPath for this.
+- `setPath` now supports appending to an existing array via the `[]` notation.
+
+### Events
+
+Guzzle no longer requires Symfony's EventDispatcher component. Guzzle now uses
+`GuzzleHttp\Event\Emitter`.
+
+- `Symfony\Component\EventDispatcher\EventDispatcherInterface` is replaced by
+  `GuzzleHttp\Event\EmitterInterface`.
+- `Symfony\Component\EventDispatcher\EventDispatcher` is replaced by
+  `GuzzleHttp\Event\Emitter`.
+- `Symfony\Component\EventDispatcher\Event` is replaced by
+  `GuzzleHttp\Event\Event`, and Guzzle now has an EventInterface in
+  `GuzzleHttp\Event\EventInterface`.
+- `AbstractHasDispatcher` has moved to a trait, `HasEmitterTrait`, and
+  `HasDispatcherInterface` has moved to `HasEmitterInterface`. Retrieving the
+  event emitter of a request, client, etc. now uses the `getEmitter` method
+  rather than the `getDispatcher` method.
+
+#### Emitter
+
+- Use the `once()` method to add a listener that automatically removes itself
+  the first time it is invoked.
+- Use the `listeners()` method to retrieve a list of event listeners rather than
+  the `getListeners()` method.
+- Use `emit()` instead of `dispatch()` to emit an event from an emitter.
+- Use `attach()` instead of `addSubscriber()` and `detach()` instead of
+  `removeSubscriber()`.
+
+```php
+$mock = new Mock();
+// 3.x
+$request->getEventDispatcher()->addSubscriber($mock);
+$request->getEventDispatcher()->removeSubscriber($mock);
+// 4.x
+$request->getEmitter()->attach($mock);
+$request->getEmitter()->detach($mock);
+```
+
+Use the `on()` method to add a listener rather than the `addListener()` method.
+
+```php
+// 3.x
+$request->getEventDispatcher()->addListener('foo', function (Event $event) { /* ... */ } );
+// 4.x
+$request->getEmitter()->on('foo', function (Event $event, $name) { /* ... */ } );
+```
+
+## Http
+
+### General changes
+
+- The cacert.pem certificate has been moved to `src/cacert.pem`.
+- Added the concept of adapters that are used to transfer requests over the
+  wire.
+- Simplified the event system.
+- Sending requests in parallel is still possible, but batching is no longer a
+  concept of the HTTP layer. Instead, you must use the `complete` and `error`
+  events to asynchronously manage parallel request transfers.
+- `Guzzle\Http\Url` has moved to `GuzzleHttp\Url`.
+- `Guzzle\Http\QueryString` has moved to `GuzzleHttp\Query`.
+- QueryAggregators have been rewritten so that they are simply callable
+  functions.
+- `GuzzleHttp\StaticClient` has been removed. Use the functions provided in
+  `functions.php` for an easy to use static client instance.
+- Exceptions in `GuzzleHttp\Exception` have been updated to all extend from
+  `GuzzleHttp\Exception\TransferException`.
+
+### Client
+
+Calling methods like `get()`, `post()`, `head()`, etc. no longer create and
+return a request, but rather creates a request, sends the request, and returns
+the response.
+
+```php
+// 3.0
+$request = $client->get('/');
+$response = $request->send();
+
+// 4.0
+$response = $client->get('/');
+
+// or, to mirror the previous behavior
+$request = $client->createRequest('GET', '/');
+$response = $client->send($request);
+```
+
+`GuzzleHttp\ClientInterface` has changed.
+
+- The `send` method no longer accepts more than one request. Use `sendAll` to
+  send multiple requests in parallel.
+- `setUserAgent()` has been removed. Use a default request option instead. You
+  could, for example, do something like:
+  `$client->setConfig('defaults/headers/User-Agent', 'Foo/Bar ' . $client::getDefaultUserAgent())`.
+- `setSslVerification()` has been removed. Use default request options instead,
+  like `$client->setConfig('defaults/verify', true)`.
+
+`GuzzleHttp\Client` has changed.
+
+- The constructor now accepts only an associative array. You can include a
+  `base_url` string or array to use a URI template as the base URL of a client.
+  You can also specify a `defaults` key that is an associative array of default
+  request options. You can pass an `adapter` to use a custom adapter,
+  `batch_adapter` to use a custom adapter for sending requests in parallel, or
+  a `message_factory` to change the factory used to create HTTP requests and
+  responses.
+- The client no longer emits a `client.create_request` event.
+- Creating requests with a client no longer automatically utilize a URI
+  template. You must pass an array into a creational method (e.g.,
+  `createRequest`, `get`, `put`, etc.) in order to expand a URI template.
+
+### Messages
+
+Messages no longer have references to their counterparts (i.e., a request no
+longer has a reference to it's response, and a response no loger has a
+reference to its request). This association is now managed through a
+`GuzzleHttp\Adapter\TransactionInterface` object. You can get references to
+these transaction objects using request events that are emitted over the
+lifecycle of a request.
+
+#### Requests with a body
+
+- `GuzzleHttp\Message\EntityEnclosingRequest` and
+  `GuzzleHttp\Message\EntityEnclosingRequestInterface` have been removed. The
+  separation between requests that contain a body and requests that do not
+  contain a body has been removed, and now `GuzzleHttp\Message\RequestInterface`
+  handles both use cases.
+- Any method that previously accepts a `GuzzleHttp\Response` object now accept a
+  `GuzzleHttp\Message\ResponseInterface`.
+- `GuzzleHttp\Message\RequestFactoryInterface` has been renamed to
+  `GuzzleHttp\Message\MessageFactoryInterface`. This interface is used to create
+  both requests and responses and is implemented in
+  `GuzzleHttp\Message\MessageFactory`.
+- POST field and file methods have been removed from the request object. You
+  must now use the methods made available to `GuzzleHttp\Post\PostBodyInterface`
+  to control the format of a POST body. Requests that are created using a
+  standard `GuzzleHttp\Message\MessageFactoryInterface` will automatically use
+  a `GuzzleHttp\Post\PostBody` body if the body was passed as an array or if
+  the method is POST and no body is provided.
+
+```php
+$request = $client->createRequest('POST', '/');
+$request->getBody()->setField('foo', 'bar');
+$request->getBody()->addFile(new PostFile('file_key', fopen('/path/to/content', 'r')));
+```
+
+#### Headers
+
+- `GuzzleHttp\Message\Header` has been removed. Header values are now simply
+  represented by an array of values or as a string. Header values are returned
+  as a string by default when retrieving a header value from a message. You can
+  pass an optional argument of `true` to retrieve a header value as an array
+  of strings instead of a single concatenated string.
+- `GuzzleHttp\PostFile` and `GuzzleHttp\PostFileInterface` have been moved to
+  `GuzzleHttp\Post`. This interface has been simplified and now allows the
+  addition of arbitrary headers.
+- Custom headers like `GuzzleHttp\Message\Header\Link` have been removed. Most
+  of the custom headers are now handled separately in specific
+  subscribers/plugins, and `GuzzleHttp\Message\HeaderValues::parseParams()` has
+  been updated to properly handle headers that contain parameters (like the
+  `Link` header).
+
+#### Responses
+
+- `GuzzleHttp\Message\Response::getInfo()` and
+  `GuzzleHttp\Message\Response::setInfo()` have been removed. Use the event
+  system to retrieve this type of information.
+- `GuzzleHttp\Message\Response::getRawHeaders()` has been removed.
+- `GuzzleHttp\Message\Response::getMessage()` has been removed.
+- `GuzzleHttp\Message\Response::calculateAge()` and other cache specific
+  methods have moved to the CacheSubscriber.
+- Header specific helper functions like `getContentMd5()` have been removed.
+  Just use `getHeader('Content-MD5')` instead.
+- `GuzzleHttp\Message\Response::setRequest()` and
+  `GuzzleHttp\Message\Response::getRequest()` have been removed. Use the event
+  system to work with request and response objects as a transaction.
+- `GuzzleHttp\Message\Response::getRedirectCount()` has been removed. Use the
+  Redirect subscriber instead.
+- `GuzzleHttp\Message\Response::isSuccessful()` and other related methods have
+  been removed. Use `getStatusCode()` instead.
+
+#### Streaming responses
+
+Streaming requests can now be created by a client directly, returning a
+`GuzzleHttp\Message\ResponseInterface` object that contains a body stream
+referencing an open PHP HTTP stream.
+
+```php
+// 3.0
+use Guzzle\Stream\PhpStreamRequestFactory;
+$request = $client->get('/');
+$factory = new PhpStreamRequestFactory();
+$stream = $factory->fromRequest($request);
+$data = $stream->read(1024);
+
+// 4.0
+$response = $client->get('/', ['stream' => true]);
+// Read some data off of the stream in the response body
+$data = $response->getBody()->read(1024);
+```
+
+#### Redirects
+
+The `configureRedirects()` method has been removed in favor of a
+`allow_redirects` request option.
+
+```php
+// Standard redirects with a default of a max of 5 redirects
+$request = $client->createRequest('GET', '/', ['allow_redirects' => true]);
+
+// Strict redirects with a custom number of redirects
+$request = $client->createRequest('GET', '/', [
+    'allow_redirects' => ['max' => 5, 'strict' => true]
+]);
+```
+
+#### EntityBody
+
+EntityBody interfaces and classes have been removed or moved to
+`GuzzleHttp\Stream`. All classes and interfaces that once required
+`GuzzleHttp\EntityBodyInterface` now require
+`GuzzleHttp\Stream\StreamInterface`. Creating a new body for a request no
+longer uses `GuzzleHttp\EntityBody::factory` but now uses
+`GuzzleHttp\Stream\Stream::factory` or even better:
+`GuzzleHttp\Stream\create()`.
+
+- `Guzzle\Http\EntityBodyInterface` is now `GuzzleHttp\Stream\StreamInterface`
+- `Guzzle\Http\EntityBody` is now `GuzzleHttp\Stream\Stream`
+- `Guzzle\Http\CachingEntityBody` is now `GuzzleHttp\Stream\CachingStream`
+- `Guzzle\Http\ReadLimitEntityBody` is now `GuzzleHttp\Stream\LimitStream`
+- `Guzzle\Http\IoEmittyinEntityBody` has been removed.
+
+#### Request lifecycle events
+
+Requests previously submitted a large number of requests. The number of events
+emitted over the lifecycle of a request has been significantly reduced to make
+it easier to understand how to extend the behavior of a request. All events
+emitted during the lifecycle of a request now emit a custom
+`GuzzleHttp\Event\EventInterface` object that contains context providing
+methods and a way in which to modify the transaction at that specific point in
+time (e.g., intercept the request and set a response on the transaction).
+
+- `request.before_send` has been renamed to `before` and now emits a
+  `GuzzleHttp\Event\BeforeEvent`
+- `request.complete` has been renamed to `complete` and now emits a
+  `GuzzleHttp\Event\CompleteEvent`.
+- `request.sent` has been removed. Use `complete`.
+- `request.success` has been removed. Use `complete`.
+- `error` is now an event that emits a `GuzzleHttp\Event\ErrorEvent`.
+- `request.exception` has been removed. Use `error`.
+- `request.receive.status_line` has been removed.
+- `curl.callback.progress` has been removed. Use a custom `StreamInterface` to
+  maintain a status update.
+- `curl.callback.write` has been removed. Use a custom `StreamInterface` to
+  intercept writes.
+- `curl.callback.read` has been removed. Use a custom `StreamInterface` to
+  intercept reads.
+
+`headers` is a new event that is emitted after the response headers of a
+request have been received before the body of the response is downloaded. This
+event emits a `GuzzleHttp\Event\HeadersEvent`.
+
+You can intercept a request and inject a response using the `intercept()` event
+of a `GuzzleHttp\Event\BeforeEvent`, `GuzzleHttp\Event\CompleteEvent`, and
+`GuzzleHttp\Event\ErrorEvent` event.
+
+See: http://docs.guzzlephp.org/en/latest/events.html
+
+## Inflection
+
+The `Guzzle\Inflection` namespace has been removed. This is not a core concern
+of Guzzle.
+
+## Iterator
+
+The `Guzzle\Iterator` namespace has been removed.
+
+- `Guzzle\Iterator\AppendIterator`, `Guzzle\Iterator\ChunkedIterator`, and
+  `Guzzle\Iterator\MethodProxyIterator` are nice, but not a core requirement of
+  Guzzle itself.
+- `Guzzle\Iterator\FilterIterator` is no longer needed because an equivalent
+  class is shipped with PHP 5.4.
+- `Guzzle\Iterator\MapIterator` is not really needed when using PHP 5.5 because
+  it's easier to just wrap an iterator in a generator that maps values.
+
+For a replacement of these iterators, see https://github.com/nikic/iter
+
+## Log
+
+The LogPlugin has moved to https://github.com/guzzle/log-subscriber. The
+`Guzzle\Log` namespace has been removed. Guzzle now relies on
+`Psr\Log\LoggerInterface` for all logging. The MessageFormatter class has been
+moved to `GuzzleHttp\Subscriber\Log\Formatter`.
+
+## Parser
+
+The `Guzzle\Parser` namespace has been removed. This was previously used to
+make it possible to plug in custom parsers for cookies, messages, URI
+templates, and URLs; however, this level of complexity is not needed in Guzzle
+so it has been removed.
+
+- Cookie: Cookie parsing logic has been moved to
+  `GuzzleHttp\Cookie\SetCookie::fromString`.
+- Message: Message parsing logic for both requests and responses has been moved
+  to `GuzzleHttp\Message\MessageFactory::fromMessage`. Message parsing is only
+  used in debugging or deserializing messages, so it doesn't make sense for
+  Guzzle as a library to add this level of complexity to parsing messages.
+- UriTemplate: URI template parsing has been moved to
+  `GuzzleHttp\UriTemplate`. The Guzzle library will automatically use the PECL
+  URI template library if it is installed.
+- Url: URL parsing is now performed in `GuzzleHttp\Url::fromString` (previously
+  it was `Guzzle\Http\Url::factory()`). If custom URL parsing is necessary,
+  then developers are free to subclass `GuzzleHttp\Url`.
+
+## Plugin
+
+The `Guzzle\Plugin` namespace has been renamed to `GuzzleHttp\Subscriber`.
+Several plugins are shipping with the core Guzzle library under this namespace.
+
+- `GuzzleHttp\Subscriber\Cookie`: Replaces the old CookiePlugin. Cookie jar
+  code has moved to `GuzzleHttp\Cookie`.
+- `GuzzleHttp\Subscriber\History`: Replaces the old HistoryPlugin.
+- `GuzzleHttp\Subscriber\HttpError`: Throws errors when a bad HTTP response is
+  received.
+- `GuzzleHttp\Subscriber\Mock`: Replaces the old MockPlugin.
+- `GuzzleHttp\Subscriber\Prepare`: Prepares the body of a request just before
+  sending. This subscriber is attached to all requests by default.
+- `GuzzleHttp\Subscriber\Redirect`: Replaces the RedirectPlugin.
+
+The following plugins have been removed (third-parties are free to re-implement
+these if needed):
+
+- `GuzzleHttp\Plugin\Async` has been removed.
+- `GuzzleHttp\Plugin\CurlAuth` has been removed.
+- `GuzzleHttp\Plugin\ErrorResponse\ErrorResponsePlugin` has been removed. This
+  functionality should instead be implemented with event listeners that occur
+  after normal response parsing occurs in the guzzle/command package.
+
+The following plugins are not part of the core Guzzle package, but are provided
+in separate repositories:
+
+- `Guzzle\Http\Plugin\BackoffPlugin` has been rewritten to be much simpler
+  to build custom retry policies using simple functions rather than various
+  chained classes. See: https://github.com/guzzle/retry-subscriber
+- `Guzzle\Http\Plugin\Cache\CachePlugin` has moved to
+  https://github.com/guzzle/cache-subscriber
+- `Guzzle\Http\Plugin\Log\LogPlugin` has moved to
+  https://github.com/guzzle/log-subscriber
+- `Guzzle\Http\Plugin\Md5\Md5Plugin` has moved to
+  https://github.com/guzzle/message-integrity-subscriber
+- `Guzzle\Http\Plugin\Mock\MockPlugin` has moved to
+  `GuzzleHttp\Subscriber\MockSubscriber`.
+- `Guzzle\Http\Plugin\Oauth\OauthPlugin` has moved to
+  https://github.com/guzzle/oauth-subscriber
+
+## Service
+
+The service description layer of Guzzle has moved into two separate packages:
+
+- http://github.com/guzzle/command Provides a high level abstraction over web
+  services by representing web service operations using commands.
+- http://github.com/guzzle/guzzle-services Provides an implementation of
+  guzzle/command that provides request serialization and response parsing using
+  Guzzle service descriptions.
+
+## Stream
+
+Stream have moved to a separate package available at
+https://github.com/guzzle/streams.
+
+`Guzzle\Stream\StreamInterface` has been given a large update to cleanly take
+on the responsibilities of `Guzzle\Http\EntityBody` and
+`Guzzle\Http\EntityBodyInterface` now that they have been removed. The number
+of methods implemented by the `StreamInterface` has been drastically reduced to
+allow developers to more easily extend and decorate stream behavior.
+
+## Removed methods from StreamInterface
+
+- `getStream` and `setStream` have been removed to better encapsulate streams.
+- `getMetadata` and `setMetadata` have been removed in favor of
+  `GuzzleHttp\Stream\MetadataStreamInterface`.
+- `getWrapper`, `getWrapperData`, `getStreamType`, and `getUri` have all been
+  removed. This data is accessible when
+  using streams that implement `GuzzleHttp\Stream\MetadataStreamInterface`.
+- `rewind` has been removed. Use `seek(0)` for a similar behavior.
+
+## Renamed methods
+
+- `detachStream` has been renamed to `detach`.
+- `feof` has been renamed to `eof`.
+- `ftell` has been renamed to `tell`.
+- `readLine` has moved from an instance method to a static class method of
+  `GuzzleHttp\Stream\Stream`.
+
+## Metadata streams
+
+`GuzzleHttp\Stream\MetadataStreamInterface` has been added to denote streams
+that contain additional metadata accessible via `getMetadata()`.
+`GuzzleHttp\Stream\StreamInterface::getMetadata` and
+`GuzzleHttp\Stream\StreamInterface::setMetadata` have been removed.
+
+## StreamRequestFactory
+
+The entire concept of the StreamRequestFactory has been removed. The way this
+was used in Guzzle 3 broke the actual interface of sending streaming requests
+(instead of getting back a Response, you got a StreamInterface). Streaming
+PHP requests are now implemented through the `GuzzleHttp\Adapter\StreamAdapter`.
+
+3.6 to 3.7
+----------
+
+### Deprecations
+
+- You can now enable E_USER_DEPRECATED warnings to see if you are using any deprecated methods.:
+
+```php
+\Guzzle\Common\Version::$emitWarnings = true;
+```
+
+The following APIs and options have been marked as deprecated:
+
+- Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use `$request->getResponseBody()->isRepeatable()` instead.
+- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
+- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
+- Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead.
+- Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead.
+- Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated
+- Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client.
+- Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8.
+- Marked `Guzzle\Common\Collection::inject()` as deprecated.
+- Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use
+  `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` or
+  `$client->setDefaultOption('auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));`
+
+3.7 introduces `request.options` as a parameter for a client configuration and as an optional argument to all creational
+request methods. When paired with a client's configuration settings, these options allow you to specify default settings
+for various aspects of a request. Because these options make other previous configuration options redundant, several
+configuration options and methods of a client and AbstractCommand have been deprecated.
+
+- Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use `$client->getDefaultOption('headers')`.
+- Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use `$client->setDefaultOption('headers/{header_name}', 'value')`.
+- Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use `$client->setDefaultOption('params/{param_name}', 'value')`
+- Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. These will work through Guzzle 4.0
+
+        $command = $client->getCommand('foo', array(
+            'command.headers' => array('Test' => '123'),
+            'command.response_body' => '/path/to/file'
+        ));
+
+        // Should be changed to:
+
+        $command = $client->getCommand('foo', array(
+            'command.request_options' => array(
+                'headers' => array('Test' => '123'),
+                'save_as' => '/path/to/file'
+            )
+        ));
+
+### Interface changes
+
+Additions and changes (you will need to update any implementations or subclasses you may have created):
+
+- Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`:
+  createRequest, head, delete, put, patch, post, options, prepareRequest
+- Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()`
+- Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface`
+- Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to
+  `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a
+  resource, string, or EntityBody into the $options parameter to specify the download location of the response.
+- Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a
+  default `array()`
+- Added `Guzzle\Stream\StreamInterface::isRepeatable`
+- Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods.
+
+The following methods were removed from interfaces. All of these methods are still available in the concrete classes
+that implement them, but you should update your code to use alternative methods:
+
+- Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use
+  `$client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or
+  `$client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))` or
+  `$client->setDefaultOption('headers/{header_name}', 'value')`. or
+  `$client->setDefaultOption('headers', array('header_name' => 'value'))`.
+- Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use `$client->getConfig()->getPath('request.options/headers')`.
+- Removed `Guzzle\Http\ClientInterface::expandTemplate()`. This is an implementation detail.
+- Removed `Guzzle\Http\ClientInterface::setRequestFactory()`. This is an implementation detail.
+- Removed `Guzzle\Http\ClientInterface::getCurlMulti()`. This is a very specific implementation detail.
+- Removed `Guzzle\Http\Message\RequestInterface::canCache`. Use the CachePlugin.
+- Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`. Use the HistoryPlugin.
+- Removed `Guzzle\Http\Message\RequestInterface::isRedirect`. Use the HistoryPlugin.
+
+### Cache plugin breaking changes
+
+- CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a
+  CacheStorageInterface. These two objects and interface will be removed in a future version.
+- Always setting X-cache headers on cached responses
+- Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin
+- `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface
+  $request, Response $response);`
+- `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);`
+- `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);`
+- Added `CacheStorageInterface::purge($url)`
+- `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin
+  $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache,
+  CanCacheStrategyInterface $canCache = null)`
+- Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)`
+
+3.5 to 3.6
+----------
+
+* Mixed casing of headers are now forced to be a single consistent casing across all values for that header.
+* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution
+* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader().
+  For example, setHeader() first removes the header using unset on a HeaderCollection and then calls addHeader().
+  Keeping the Host header and URL host in sync is now handled by overriding the addHeader method in Request.
+* Specific header implementations can be created for complex headers. When a message creates a header, it uses a
+  HeaderFactory which can map specific headers to specific header classes. There is now a Link header and
+  CacheControl header implementation.
+* Moved getLinks() from Response to just be used on a Link header object.
+
+If you previously relied on Guzzle\Http\Message\Header::raw(), then you will need to update your code to use the
+HeaderInterface (e.g. toArray(), getAll(), etc.).
+
+### Interface changes
+
+* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate
+* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti()
+* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in
+  Guzzle\Http\Curl\RequestMediator
+* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string.
+* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface
+* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders()
+
+### Removed deprecated functions
+
+* Removed Guzzle\Parser\ParserRegister::get(). Use getParser()
+* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser().
+
+### Deprecations
+
+* The ability to case-insensitively search for header values
+* Guzzle\Http\Message\Header::hasExactHeader
+* Guzzle\Http\Message\Header::raw. Use getAll()
+* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object
+  instead.
+
+### Other changes
+
+* All response header helper functions return a string rather than mixing Header objects and strings inconsistently
+* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle
+  directly via interfaces
+* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist
+  but are a no-op until removed.
+* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a
+  `Guzzle\Service\Command\ArrayCommandInterface`.
+* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response
+  on a request while the request is still being transferred
+* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess
+
+3.3 to 3.4
+----------
+
+Base URLs of a client now follow the rules of https://tools.ietf.org/html/rfc3986#section-5.2.2 when merging URLs.
+
+3.2 to 3.3
+----------
+
+### Response::getEtag() quote stripping removed
+
+`Guzzle\Http\Message\Response::getEtag()` no longer strips quotes around the ETag response header
+
+### Removed `Guzzle\Http\Utils`
+
+The `Guzzle\Http\Utils` class was removed. This class was only used for testing.
+
+### Stream wrapper and type
+
+`Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getStreamType()` are no longer converted to lowercase.
+
+### curl.emit_io became emit_io
+
+Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using the
+'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io'
+
+3.1 to 3.2
+----------
+
+### CurlMulti is no longer reused globally
+
+Before 3.2, the same CurlMulti object was reused globally for each client. This can cause issue where plugins added
+to a single client can pollute requests dispatched from other clients.
+
+If you still wish to reuse the same CurlMulti object with each client, then you can add a listener to the
+ServiceBuilder's `service_builder.create_client` event to inject a custom CurlMulti object into each client as it is
+created.
+
+```php
+$multi = new Guzzle\Http\Curl\CurlMulti();
+$builder = Guzzle\Service\Builder\ServiceBuilder::factory('/path/to/config.json');
+$builder->addListener('service_builder.create_client', function ($event) use ($multi) {
+    $event['client']->setCurlMulti($multi);
+}
+});
+```
+
+### No default path
+
+URLs no longer have a default path value of '/' if no path was specified.
+
+Before:
+
+```php
+$request = $client->get('http://www.foo.com');
+echo $request->getUrl();
+// >> http://www.foo.com/
+```
+
+After:
+
+```php
+$request = $client->get('http://www.foo.com');
+echo $request->getUrl();
+// >> http://www.foo.com
+```
+
+### Less verbose BadResponseException
+
+The exception message for `Guzzle\Http\Exception\BadResponseException` no longer contains the full HTTP request and
+response information. You can, however, get access to the request and response object by calling `getRequest()` or
+`getResponse()` on the exception object.
+
+### Query parameter aggregation
+
+Multi-valued query parameters are no longer aggregated using a callback function. `Guzzle\Http\Query` now has a
+setAggregator() method that accepts a `Guzzle\Http\QueryAggregator\QueryAggregatorInterface` object. This object is
+responsible for handling the aggregation of multi-valued query string variables into a flattened hash.
+
+2.8 to 3.x
+----------
+
+### Guzzle\Service\Inspector
+
+Change `\Guzzle\Service\Inspector::fromConfig` to `\Guzzle\Common\Collection::fromConfig`
+
+**Before**
+
+```php
+use Guzzle\Service\Inspector;
+
+class YourClient extends \Guzzle\Service\Client
+{
+    public static function factory($config = array())
+    {
+        $default = array();
+        $required = array('base_url', 'username', 'api_key');
+        $config = Inspector::fromConfig($config, $default, $required);
+
+        $client = new self(
+            $config->get('base_url'),
+            $config->get('username'),
+            $config->get('api_key')
+        );
+        $client->setConfig($config);
+
+        $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json'));
+
+        return $client;
+    }
+```
+
+**After**
+
+```php
+use Guzzle\Common\Collection;
+
+class YourClient extends \Guzzle\Service\Client
+{
+    public static function factory($config = array())
+    {
+        $default = array();
+        $required = array('base_url', 'username', 'api_key');
+        $config = Collection::fromConfig($config, $default, $required);
+
+        $client = new self(
+            $config->get('base_url'),
+            $config->get('username'),
+            $config->get('api_key')
+        );
+        $client->setConfig($config);
+
+        $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json'));
+
+        return $client;
+    }
+```
+
+### Convert XML Service Descriptions to JSON
+
+**Before**
+
+```xml
+<?xml version="1.0" encoding="UTF-8"?>
+<client>
+    <commands>
+        <!-- Groups -->
+        <command name="list_groups" method="GET" uri="groups.json">
+            <doc>Get a list of groups</doc>
+        </command>
+        <command name="search_groups" method="GET" uri='search.json?query="{{query}} type:group"'>
+            <doc>Uses a search query to get a list of groups</doc>
+            <param name="query" type="string" required="true" />
+        </command>
+        <command name="create_group" method="POST" uri="groups.json">
+            <doc>Create a group</doc>
+            <param name="data" type="array" location="body" filters="json_encode" doc="Group JSON"/>
+            <param name="Content-Type" location="header" static="application/json"/>
+        </command>
+        <command name="delete_group" method="DELETE" uri="groups/{{id}}.json">
+            <doc>Delete a group by ID</doc>
+            <param name="id" type="integer" required="true"/>
+        </command>
+        <command name="get_group" method="GET" uri="groups/{{id}}.json">
+            <param name="id" type="integer" required="true"/>
+        </command>
+        <command name="update_group" method="PUT" uri="groups/{{id}}.json">
+            <doc>Update a group</doc>
+            <param name="id" type="integer" required="true"/>
+            <param name="data" type="array" location="body" filters="json_encode" doc="Group JSON"/>
+            <param name="Content-Type" location="header" static="application/json"/>
+        </command>
+    </commands>
+</client>
+```
+
+**After**
+
+```json
+{
+    "name":       "Zendesk REST API v2",
+    "apiVersion": "2012-12-31",
+    "description":"Provides access to Zendesk views, groups, tickets, ticket fields, and users",
+    "operations": {
+        "list_groups":  {
+            "httpMethod":"GET",
+            "uri":       "groups.json",
+            "summary":   "Get a list of groups"
+        },
+        "search_groups":{
+            "httpMethod":"GET",
+            "uri":       "search.json?query=\"{query} type:group\"",
+            "summary":   "Uses a search query to get a list of groups",
+            "parameters":{
+                "query":{
+                    "location":   "uri",
+                    "description":"Zendesk Search Query",
+                    "type":       "string",
+                    "required":   true
+                }
+            }
+        },
+        "create_group": {
+            "httpMethod":"POST",
+            "uri":       "groups.json",
+            "summary":   "Create a group",
+            "parameters":{
+                "data":        {
+                    "type":       "array",
+                    "location":   "body",
+                    "description":"Group JSON",
+                    "filters":    "json_encode",
+                    "required":   true
+                },
+                "Content-Type":{
+                    "type":    "string",
+                    "location":"header",
+                    "static":  "application/json"
+                }
+            }
+        },
+        "delete_group": {
+            "httpMethod":"DELETE",
+            "uri":       "groups/{id}.json",
+            "summary":   "Delete a group",
+            "parameters":{
+                "id":{
+                    "location":   "uri",
+                    "description":"Group to delete by ID",
+                    "type":       "integer",
+                    "required":   true
+                }
+            }
+        },
+        "get_group":    {
+            "httpMethod":"GET",
+            "uri":       "groups/{id}.json",
+            "summary":   "Get a ticket",
+            "parameters":{
+                "id":{
+                    "location":   "uri",
+                    "description":"Group to get by ID",
+                    "type":       "integer",
+                    "required":   true
+                }
+            }
+        },
+        "update_group": {
+            "httpMethod":"PUT",
+            "uri":       "groups/{id}.json",
+            "summary":   "Update a group",
+            "parameters":{
+                "id":          {
+                    "location":   "uri",
+                    "description":"Group to update by ID",
+                    "type":       "integer",
+                    "required":   true
+                },
+                "data":        {
+                    "type":       "array",
+                    "location":   "body",
+                    "description":"Group JSON",
+                    "filters":    "json_encode",
+                    "required":   true
+                },
+                "Content-Type":{
+                    "type":    "string",
+                    "location":"header",
+                    "static":  "application/json"
+                }
+            }
+        }
+}
+```
+
+### Guzzle\Service\Description\ServiceDescription
+
+Commands are now called Operations
+
+**Before**
+
+```php
+use Guzzle\Service\Description\ServiceDescription;
+
+$sd = new ServiceDescription();
+$sd->getCommands();     // @returns ApiCommandInterface[]
+$sd->hasCommand($name);
+$sd->getCommand($name); // @returns ApiCommandInterface|null
+$sd->addCommand($command); // @param ApiCommandInterface $command
+```
+
+**After**
+
+```php
+use Guzzle\Service\Description\ServiceDescription;
+
+$sd = new ServiceDescription();
+$sd->getOperations();           // @returns OperationInterface[]
+$sd->hasOperation($name);
+$sd->getOperation($name);       // @returns OperationInterface|null
+$sd->addOperation($operation);  // @param OperationInterface $operation
+```
+
+### Guzzle\Common\Inflection\Inflector
+
+Namespace is now `Guzzle\Inflection\Inflector`
+
+### Guzzle\Http\Plugin
+
+Namespace is now `Guzzle\Plugin`. Many other changes occur within this namespace and are detailed in their own sections below.
+
+### Guzzle\Http\Plugin\LogPlugin and Guzzle\Common\Log
+
+Now `Guzzle\Plugin\Log\LogPlugin` and `Guzzle\Log` respectively.
+
+**Before**
+
+```php
+use Guzzle\Common\Log\ClosureLogAdapter;
+use Guzzle\Http\Plugin\LogPlugin;
+
+/** @var \Guzzle\Http\Client */
+$client;
+
+// $verbosity is an integer indicating desired message verbosity level
+$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $verbosity = LogPlugin::LOG_VERBOSE);
+```
+
+**After**
+
+```php
+use Guzzle\Log\ClosureLogAdapter;
+use Guzzle\Log\MessageFormatter;
+use Guzzle\Plugin\Log\LogPlugin;
+
+/** @var \Guzzle\Http\Client */
+$client;
+
+// $format is a string indicating desired message format -- @see MessageFormatter
+$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $format = MessageFormatter::DEBUG_FORMAT);
+```
+
+### Guzzle\Http\Plugin\CurlAuthPlugin
+
+Now `Guzzle\Plugin\CurlAuth\CurlAuthPlugin`.
+
+### Guzzle\Http\Plugin\ExponentialBackoffPlugin
+
+Now `Guzzle\Plugin\Backoff\BackoffPlugin`, and other changes.
+
+**Before**
+
+```php
+use Guzzle\Http\Plugin\ExponentialBackoffPlugin;
+
+$backoffPlugin = new ExponentialBackoffPlugin($maxRetries, array_merge(
+        ExponentialBackoffPlugin::getDefaultFailureCodes(), array(429)
+    ));
+
+$client->addSubscriber($backoffPlugin);
+```
+
+**After**
+
+```php
+use Guzzle\Plugin\Backoff\BackoffPlugin;
+use Guzzle\Plugin\Backoff\HttpBackoffStrategy;
+
+// Use convenient factory method instead -- see implementation for ideas of what
+// you can do with chaining backoff strategies
+$backoffPlugin = BackoffPlugin::getExponentialBackoff($maxRetries, array_merge(
+        HttpBackoffStrategy::getDefaultFailureCodes(), array(429)
+    ));
+$client->addSubscriber($backoffPlugin);
+```
+
+### Known Issues
+
+#### [BUG] Accept-Encoding header behavior changed unintentionally.
+
+(See #217) (Fixed in 09daeb8c666fb44499a0646d655a8ae36456575e)
+
+In version 2.8 setting the `Accept-Encoding` header would set the CURLOPT_ENCODING option, which permitted cURL to
+properly handle gzip/deflate compressed responses from the server. In versions affected by this bug this does not happen.
+See issue #217 for a workaround, or use a version containing the fix.

+ 103 - 103
vendor/guzzlehttp/guzzle/composer.json

@@ -1,103 +1,103 @@
-{
-    "name": "guzzlehttp/guzzle",
-    "description": "Guzzle is a PHP HTTP client library",
-    "keywords": [
-        "framework",
-        "http",
-        "rest",
-        "web service",
-        "curl",
-        "client",
-        "HTTP client",
-        "PSR-7",
-        "PSR-18"
-    ],
-    "license": "MIT",
-    "authors": [
-        {
-            "name": "Graham Campbell",
-            "email": "hello@gjcampbell.co.uk",
-            "homepage": "https://github.com/GrahamCampbell"
-        },
-        {
-            "name": "Michael Dowling",
-            "email": "mtdowling@gmail.com",
-            "homepage": "https://github.com/mtdowling"
-        },
-        {
-            "name": "Jeremy Lindblom",
-            "email": "jeremeamia@gmail.com",
-            "homepage": "https://github.com/jeremeamia"
-        },
-        {
-            "name": "George Mponos",
-            "email": "gmponos@gmail.com",
-            "homepage": "https://github.com/gmponos"
-        },
-        {
-            "name": "Tobias Nyholm",
-            "email": "tobias.nyholm@gmail.com",
-            "homepage": "https://github.com/Nyholm"
-        },
-        {
-            "name": "Márk Sági-Kazár",
-            "email": "mark.sagikazar@gmail.com",
-            "homepage": "https://github.com/sagikazarmark"
-        },
-        {
-            "name": "Tobias Schultze",
-            "email": "webmaster@tubo-world.de",
-            "homepage": "https://github.com/Tobion"
-        }
-    ],
-    "require": {
-        "php": "^7.2.5 || ^8.0",
-        "ext-json": "*",
-        "guzzlehttp/promises": "^1.5.3 || ^2.0",
-        "guzzlehttp/psr7": "^1.9.1 || ^2.4.5",
-        "psr/http-client": "^1.0",
-        "symfony/deprecation-contracts": "^2.2 || ^3.0"
-    },
-    "provide": {
-        "psr/http-client-implementation": "1.0"
-    },
-    "require-dev": {
-        "ext-curl": "*",
-        "bamarni/composer-bin-plugin": "^1.8.1",
-        "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999",
-        "php-http/message-factory": "^1.1",
-        "phpunit/phpunit": "^8.5.29 || ^9.5.23",
-        "psr/log": "^1.1 || ^2.0 || ^3.0"
-    },
-    "suggest": {
-        "ext-curl": "Required for CURL handler support",
-        "ext-intl": "Required for Internationalized Domain Name (IDN) support",
-        "psr/log": "Required for using the Log middleware"
-    },
-    "config": {
-        "allow-plugins": {
-            "bamarni/composer-bin-plugin": true
-        },
-        "preferred-install": "dist",
-        "sort-packages": true
-    },
-    "extra": {
-        "bamarni-bin": {
-            "bin-links": true,
-            "forward-command": false
-        }
-    },
-    "autoload": {
-        "psr-4": {
-            "GuzzleHttp\\": "src/"
-        },
-        "files": [
-            "src/functions_include.php"
-        ]
-    },
-    "autoload-dev": {
-        "psr-4": {
-            "GuzzleHttp\\Tests\\": "tests/"
-        }
-    }
-}
+{
+    "name": "guzzlehttp/guzzle",
+    "description": "Guzzle is a PHP HTTP client library",
+    "keywords": [
+        "framework",
+        "http",
+        "rest",
+        "web service",
+        "curl",
+        "client",
+        "HTTP client",
+        "PSR-7",
+        "PSR-18"
+    ],
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Graham Campbell",
+            "email": "hello@gjcampbell.co.uk",
+            "homepage": "https://github.com/GrahamCampbell"
+        },
+        {
+            "name": "Michael Dowling",
+            "email": "mtdowling@gmail.com",
+            "homepage": "https://github.com/mtdowling"
+        },
+        {
+            "name": "Jeremy Lindblom",
+            "email": "jeremeamia@gmail.com",
+            "homepage": "https://github.com/jeremeamia"
+        },
+        {
+            "name": "George Mponos",
+            "email": "gmponos@gmail.com",
+            "homepage": "https://github.com/gmponos"
+        },
+        {
+            "name": "Tobias Nyholm",
+            "email": "tobias.nyholm@gmail.com",
+            "homepage": "https://github.com/Nyholm"
+        },
+        {
+            "name": "Márk Sági-Kazár",
+            "email": "mark.sagikazar@gmail.com",
+            "homepage": "https://github.com/sagikazarmark"
+        },
+        {
+            "name": "Tobias Schultze",
+            "email": "webmaster@tubo-world.de",
+            "homepage": "https://github.com/Tobion"
+        }
+    ],
+    "require": {
+        "php": "^7.2.5 || ^8.0",
+        "ext-json": "*",
+        "guzzlehttp/promises": "^1.5.3 || ^2.0",
+        "guzzlehttp/psr7": "^1.9.1 || ^2.4.5",
+        "psr/http-client": "^1.0",
+        "symfony/deprecation-contracts": "^2.2 || ^3.0"
+    },
+    "provide": {
+        "psr/http-client-implementation": "1.0"
+    },
+    "require-dev": {
+        "ext-curl": "*",
+        "bamarni/composer-bin-plugin": "^1.8.1",
+        "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999",
+        "php-http/message-factory": "^1.1",
+        "phpunit/phpunit": "^8.5.29 || ^9.5.23",
+        "psr/log": "^1.1 || ^2.0 || ^3.0"
+    },
+    "suggest": {
+        "ext-curl": "Required for CURL handler support",
+        "ext-intl": "Required for Internationalized Domain Name (IDN) support",
+        "psr/log": "Required for using the Log middleware"
+    },
+    "config": {
+        "allow-plugins": {
+            "bamarni/composer-bin-plugin": true
+        },
+        "preferred-install": "dist",
+        "sort-packages": true
+    },
+    "extra": {
+        "bamarni-bin": {
+            "bin-links": true,
+            "forward-command": false
+        }
+    },
+    "autoload": {
+        "psr-4": {
+            "GuzzleHttp\\": "src/"
+        },
+        "files": [
+            "src/functions_include.php"
+        ]
+    },
+    "autoload-dev": {
+        "psr-4": {
+            "GuzzleHttp\\Tests\\": "tests/"
+        }
+    }
+}

+ 28 - 28
vendor/guzzlehttp/guzzle/src/BodySummarizer.php

@@ -1,28 +1,28 @@
-<?php
-
-namespace GuzzleHttp;
-
-use Psr\Http\Message\MessageInterface;
-
-final class BodySummarizer implements BodySummarizerInterface
-{
-    /**
-     * @var int|null
-     */
-    private $truncateAt;
-
-    public function __construct(int $truncateAt = null)
-    {
-        $this->truncateAt = $truncateAt;
-    }
-
-    /**
-     * Returns a summarized message body.
-     */
-    public function summarize(MessageInterface $message): ?string
-    {
-        return $this->truncateAt === null
-            ? \GuzzleHttp\Psr7\Message::bodySummary($message)
-            : \GuzzleHttp\Psr7\Message::bodySummary($message, $this->truncateAt);
-    }
-}
+<?php
+
+namespace GuzzleHttp;
+
+use Psr\Http\Message\MessageInterface;
+
+final class BodySummarizer implements BodySummarizerInterface
+{
+    /**
+     * @var int|null
+     */
+    private $truncateAt;
+
+    public function __construct(int $truncateAt = null)
+    {
+        $this->truncateAt = $truncateAt;
+    }
+
+    /**
+     * Returns a summarized message body.
+     */
+    public function summarize(MessageInterface $message): ?string
+    {
+        return $this->truncateAt === null
+            ? \GuzzleHttp\Psr7\Message::bodySummary($message)
+            : \GuzzleHttp\Psr7\Message::bodySummary($message, $this->truncateAt);
+    }
+}

+ 13 - 13
vendor/guzzlehttp/guzzle/src/BodySummarizerInterface.php

@@ -1,13 +1,13 @@
-<?php
-
-namespace GuzzleHttp;
-
-use Psr\Http\Message\MessageInterface;
-
-interface BodySummarizerInterface
-{
-    /**
-     * Returns a summarized message body.
-     */
-    public function summarize(MessageInterface $message): ?string;
-}
+<?php
+
+namespace GuzzleHttp;
+
+use Psr\Http\Message\MessageInterface;
+
+interface BodySummarizerInterface
+{
+    /**
+     * Returns a summarized message body.
+     */
+    public function summarize(MessageInterface $message): ?string;
+}

+ 483 - 483
vendor/guzzlehttp/guzzle/src/Client.php

@@ -1,483 +1,483 @@
-<?php
-
-namespace GuzzleHttp;
-
-use GuzzleHttp\Cookie\CookieJar;
-use GuzzleHttp\Exception\GuzzleException;
-use GuzzleHttp\Exception\InvalidArgumentException;
-use GuzzleHttp\Promise as P;
-use GuzzleHttp\Promise\PromiseInterface;
-use Psr\Http\Message\RequestInterface;
-use Psr\Http\Message\ResponseInterface;
-use Psr\Http\Message\UriInterface;
-
-/**
- * @final
- */
-class Client implements ClientInterface, \Psr\Http\Client\ClientInterface
-{
-    use ClientTrait;
-
-    /**
-     * @var array Default request options
-     */
-    private $config;
-
-    /**
-     * Clients accept an array of constructor parameters.
-     *
-     * Here's an example of creating a client using a base_uri and an array of
-     * default request options to apply to each request:
-     *
-     *     $client = new Client([
-     *         'base_uri'        => 'http://www.foo.com/1.0/',
-     *         'timeout'         => 0,
-     *         'allow_redirects' => false,
-     *         'proxy'           => '192.168.16.1:10'
-     *     ]);
-     *
-     * Client configuration settings include the following options:
-     *
-     * - handler: (callable) Function that transfers HTTP requests over the
-     *   wire. The function is called with a Psr7\Http\Message\RequestInterface
-     *   and array of transfer options, and must return a
-     *   GuzzleHttp\Promise\PromiseInterface that is fulfilled with a
-     *   Psr7\Http\Message\ResponseInterface on success.
-     *   If no handler is provided, a default handler will be created
-     *   that enables all of the request options below by attaching all of the
-     *   default middleware to the handler.
-     * - base_uri: (string|UriInterface) Base URI of the client that is merged
-     *   into relative URIs. Can be a string or instance of UriInterface.
-     * - **: any request option
-     *
-     * @param array $config Client configuration settings.
-     *
-     * @see \GuzzleHttp\RequestOptions for a list of available request options.
-     */
-    public function __construct(array $config = [])
-    {
-        if (!isset($config['handler'])) {
-            $config['handler'] = HandlerStack::create();
-        } elseif (!\is_callable($config['handler'])) {
-            throw new InvalidArgumentException('handler must be a callable');
-        }
-
-        // Convert the base_uri to a UriInterface
-        if (isset($config['base_uri'])) {
-            $config['base_uri'] = Psr7\Utils::uriFor($config['base_uri']);
-        }
-
-        $this->configureDefaults($config);
-    }
-
-    /**
-     * @param string $method
-     * @param array  $args
-     *
-     * @return PromiseInterface|ResponseInterface
-     *
-     * @deprecated Client::__call will be removed in guzzlehttp/guzzle:8.0.
-     */
-    public function __call($method, $args)
-    {
-        if (\count($args) < 1) {
-            throw new InvalidArgumentException('Magic request methods require a URI and optional options array');
-        }
-
-        $uri = $args[0];
-        $opts = $args[1] ?? [];
-
-        return \substr($method, -5) === 'Async'
-            ? $this->requestAsync(\substr($method, 0, -5), $uri, $opts)
-            : $this->request($method, $uri, $opts);
-    }
-
-    /**
-     * Asynchronously send an HTTP request.
-     *
-     * @param array $options Request options to apply to the given
-     *                       request and to the transfer. See \GuzzleHttp\RequestOptions.
-     */
-    public function sendAsync(RequestInterface $request, array $options = []): PromiseInterface
-    {
-        // Merge the base URI into the request URI if needed.
-        $options = $this->prepareDefaults($options);
-
-        return $this->transfer(
-            $request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')),
-            $options
-        );
-    }
-
-    /**
-     * Send an HTTP request.
-     *
-     * @param array $options Request options to apply to the given
-     *                       request and to the transfer. See \GuzzleHttp\RequestOptions.
-     *
-     * @throws GuzzleException
-     */
-    public function send(RequestInterface $request, array $options = []): ResponseInterface
-    {
-        $options[RequestOptions::SYNCHRONOUS] = true;
-
-        return $this->sendAsync($request, $options)->wait();
-    }
-
-    /**
-     * The HttpClient PSR (PSR-18) specify this method.
-     *
-     * {@inheritDoc}
-     */
-    public function sendRequest(RequestInterface $request): ResponseInterface
-    {
-        $options[RequestOptions::SYNCHRONOUS] = true;
-        $options[RequestOptions::ALLOW_REDIRECTS] = false;
-        $options[RequestOptions::HTTP_ERRORS] = false;
-
-        return $this->sendAsync($request, $options)->wait();
-    }
-
-    /**
-     * Create and send an asynchronous HTTP request.
-     *
-     * Use an absolute path to override the base path of the client, or a
-     * relative path to append to the base path of the client. The URL can
-     * contain the query string as well. Use an array to provide a URL
-     * template and additional variables to use in the URL template expansion.
-     *
-     * @param string              $method  HTTP method
-     * @param string|UriInterface $uri     URI object or string.
-     * @param array               $options Request options to apply. See \GuzzleHttp\RequestOptions.
-     */
-    public function requestAsync(string $method, $uri = '', array $options = []): PromiseInterface
-    {
-        $options = $this->prepareDefaults($options);
-        // Remove request modifying parameter because it can be done up-front.
-        $headers = $options['headers'] ?? [];
-        $body = $options['body'] ?? null;
-        $version = $options['version'] ?? '1.1';
-        // Merge the URI into the base URI.
-        $uri = $this->buildUri(Psr7\Utils::uriFor($uri), $options);
-        if (\is_array($body)) {
-            throw $this->invalidBody();
-        }
-        $request = new Psr7\Request($method, $uri, $headers, $body, $version);
-        // Remove the option so that they are not doubly-applied.
-        unset($options['headers'], $options['body'], $options['version']);
-
-        return $this->transfer($request, $options);
-    }
-
-    /**
-     * Create and send an HTTP request.
-     *
-     * Use an absolute path to override the base path of the client, or a
-     * relative path to append to the base path of the client. The URL can
-     * contain the query string as well.
-     *
-     * @param string              $method  HTTP method.
-     * @param string|UriInterface $uri     URI object or string.
-     * @param array               $options Request options to apply. See \GuzzleHttp\RequestOptions.
-     *
-     * @throws GuzzleException
-     */
-    public function request(string $method, $uri = '', array $options = []): ResponseInterface
-    {
-        $options[RequestOptions::SYNCHRONOUS] = true;
-
-        return $this->requestAsync($method, $uri, $options)->wait();
-    }
-
-    /**
-     * Get a client configuration option.
-     *
-     * These options include default request options of the client, a "handler"
-     * (if utilized by the concrete client), and a "base_uri" if utilized by
-     * the concrete client.
-     *
-     * @param string|null $option The config option to retrieve.
-     *
-     * @return mixed
-     *
-     * @deprecated Client::getConfig will be removed in guzzlehttp/guzzle:8.0.
-     */
-    public function getConfig(?string $option = null)
-    {
-        return $option === null
-            ? $this->config
-            : ($this->config[$option] ?? null);
-    }
-
-    private function buildUri(UriInterface $uri, array $config): UriInterface
-    {
-        if (isset($config['base_uri'])) {
-            $uri = Psr7\UriResolver::resolve(Psr7\Utils::uriFor($config['base_uri']), $uri);
-        }
-
-        if (isset($config['idn_conversion']) && ($config['idn_conversion'] !== false)) {
-            $idnOptions = ($config['idn_conversion'] === true) ? \IDNA_DEFAULT : $config['idn_conversion'];
-            $uri = Utils::idnUriConvert($uri, $idnOptions);
-        }
-
-        return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri;
-    }
-
-    /**
-     * Configures the default options for a client.
-     */
-    private function configureDefaults(array $config): void
-    {
-        $defaults = [
-            'allow_redirects' => RedirectMiddleware::$defaultSettings,
-            'http_errors' => true,
-            'decode_content' => true,
-            'verify' => true,
-            'cookies' => false,
-            'idn_conversion' => false,
-        ];
-
-        // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set.
-
-        // We can only trust the HTTP_PROXY environment variable in a CLI
-        // process due to the fact that PHP has no reliable mechanism to
-        // get environment variables that start with "HTTP_".
-        if (\PHP_SAPI === 'cli' && ($proxy = Utils::getenv('HTTP_PROXY'))) {
-            $defaults['proxy']['http'] = $proxy;
-        }
-
-        if ($proxy = Utils::getenv('HTTPS_PROXY')) {
-            $defaults['proxy']['https'] = $proxy;
-        }
-
-        if ($noProxy = Utils::getenv('NO_PROXY')) {
-            $cleanedNoProxy = \str_replace(' ', '', $noProxy);
-            $defaults['proxy']['no'] = \explode(',', $cleanedNoProxy);
-        }
-
-        $this->config = $config + $defaults;
-
-        if (!empty($config['cookies']) && $config['cookies'] === true) {
-            $this->config['cookies'] = new CookieJar();
-        }
-
-        // Add the default user-agent header.
-        if (!isset($this->config['headers'])) {
-            $this->config['headers'] = ['User-Agent' => Utils::defaultUserAgent()];
-        } else {
-            // Add the User-Agent header if one was not already set.
-            foreach (\array_keys($this->config['headers']) as $name) {
-                if (\strtolower($name) === 'user-agent') {
-                    return;
-                }
-            }
-            $this->config['headers']['User-Agent'] = Utils::defaultUserAgent();
-        }
-    }
-
-    /**
-     * Merges default options into the array.
-     *
-     * @param array $options Options to modify by reference
-     */
-    private function prepareDefaults(array $options): array
-    {
-        $defaults = $this->config;
-
-        if (!empty($defaults['headers'])) {
-            // Default headers are only added if they are not present.
-            $defaults['_conditional'] = $defaults['headers'];
-            unset($defaults['headers']);
-        }
-
-        // Special handling for headers is required as they are added as
-        // conditional headers and as headers passed to a request ctor.
-        if (\array_key_exists('headers', $options)) {
-            // Allows default headers to be unset.
-            if ($options['headers'] === null) {
-                $defaults['_conditional'] = [];
-                unset($options['headers']);
-            } elseif (!\is_array($options['headers'])) {
-                throw new InvalidArgumentException('headers must be an array');
-            }
-        }
-
-        // Shallow merge defaults underneath options.
-        $result = $options + $defaults;
-
-        // Remove null values.
-        foreach ($result as $k => $v) {
-            if ($v === null) {
-                unset($result[$k]);
-            }
-        }
-
-        return $result;
-    }
-
-    /**
-     * Transfers the given request and applies request options.
-     *
-     * The URI of the request is not modified and the request options are used
-     * as-is without merging in default options.
-     *
-     * @param array $options See \GuzzleHttp\RequestOptions.
-     */
-    private function transfer(RequestInterface $request, array $options): PromiseInterface
-    {
-        $request = $this->applyOptions($request, $options);
-        /** @var HandlerStack $handler */
-        $handler = $options['handler'];
-
-        try {
-            return P\Create::promiseFor($handler($request, $options));
-        } catch (\Exception $e) {
-            return P\Create::rejectionFor($e);
-        }
-    }
-
-    /**
-     * Applies the array of request options to a request.
-     */
-    private function applyOptions(RequestInterface $request, array &$options): RequestInterface
-    {
-        $modify = [
-            'set_headers' => [],
-        ];
-
-        if (isset($options['headers'])) {
-            if (array_keys($options['headers']) === range(0, count($options['headers']) - 1)) {
-                throw new InvalidArgumentException('The headers array must have header name as keys.');
-            }
-            $modify['set_headers'] = $options['headers'];
-            unset($options['headers']);
-        }
-
-        if (isset($options['form_params'])) {
-            if (isset($options['multipart'])) {
-                throw new InvalidArgumentException('You cannot use '
-                    .'form_params and multipart at the same time. Use the '
-                    .'form_params option if you want to send application/'
-                    .'x-www-form-urlencoded requests, and the multipart '
-                    .'option to send multipart/form-data requests.');
-            }
-            $options['body'] = \http_build_query($options['form_params'], '', '&');
-            unset($options['form_params']);
-            // Ensure that we don't have the header in different case and set the new value.
-            $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']);
-            $options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded';
-        }
-
-        if (isset($options['multipart'])) {
-            $options['body'] = new Psr7\MultipartStream($options['multipart']);
-            unset($options['multipart']);
-        }
-
-        if (isset($options['json'])) {
-            $options['body'] = Utils::jsonEncode($options['json']);
-            unset($options['json']);
-            // Ensure that we don't have the header in different case and set the new value.
-            $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']);
-            $options['_conditional']['Content-Type'] = 'application/json';
-        }
-
-        if (!empty($options['decode_content'])
-            && $options['decode_content'] !== true
-        ) {
-            // Ensure that we don't have the header in different case and set the new value.
-            $options['_conditional'] = Psr7\Utils::caselessRemove(['Accept-Encoding'], $options['_conditional']);
-            $modify['set_headers']['Accept-Encoding'] = $options['decode_content'];
-        }
-
-        if (isset($options['body'])) {
-            if (\is_array($options['body'])) {
-                throw $this->invalidBody();
-            }
-            $modify['body'] = Psr7\Utils::streamFor($options['body']);
-            unset($options['body']);
-        }
-
-        if (!empty($options['auth']) && \is_array($options['auth'])) {
-            $value = $options['auth'];
-            $type = isset($value[2]) ? \strtolower($value[2]) : 'basic';
-            switch ($type) {
-                case 'basic':
-                    // Ensure that we don't have the header in different case and set the new value.
-                    $modify['set_headers'] = Psr7\Utils::caselessRemove(['Authorization'], $modify['set_headers']);
-                    $modify['set_headers']['Authorization'] = 'Basic '
-                        .\base64_encode("$value[0]:$value[1]");
-                    break;
-                case 'digest':
-                    // @todo: Do not rely on curl
-                    $options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_DIGEST;
-                    $options['curl'][\CURLOPT_USERPWD] = "$value[0]:$value[1]";
-                    break;
-                case 'ntlm':
-                    $options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_NTLM;
-                    $options['curl'][\CURLOPT_USERPWD] = "$value[0]:$value[1]";
-                    break;
-            }
-        }
-
-        if (isset($options['query'])) {
-            $value = $options['query'];
-            if (\is_array($value)) {
-                $value = \http_build_query($value, '', '&', \PHP_QUERY_RFC3986);
-            }
-            if (!\is_string($value)) {
-                throw new InvalidArgumentException('query must be a string or array');
-            }
-            $modify['query'] = $value;
-            unset($options['query']);
-        }
-
-        // Ensure that sink is not an invalid value.
-        if (isset($options['sink'])) {
-            // TODO: Add more sink validation?
-            if (\is_bool($options['sink'])) {
-                throw new InvalidArgumentException('sink must not be a boolean');
-            }
-        }
-
-        if (isset($options['version'])) {
-            $modify['version'] = $options['version'];
-        }
-
-        $request = Psr7\Utils::modifyRequest($request, $modify);
-        if ($request->getBody() instanceof Psr7\MultipartStream) {
-            // Use a multipart/form-data POST if a Content-Type is not set.
-            // Ensure that we don't have the header in different case and set the new value.
-            $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']);
-            $options['_conditional']['Content-Type'] = 'multipart/form-data; boundary='
-                .$request->getBody()->getBoundary();
-        }
-
-        // Merge in conditional headers if they are not present.
-        if (isset($options['_conditional'])) {
-            // Build up the changes so it's in a single clone of the message.
-            $modify = [];
-            foreach ($options['_conditional'] as $k => $v) {
-                if (!$request->hasHeader($k)) {
-                    $modify['set_headers'][$k] = $v;
-                }
-            }
-            $request = Psr7\Utils::modifyRequest($request, $modify);
-            // Don't pass this internal value along to middleware/handlers.
-            unset($options['_conditional']);
-        }
-
-        return $request;
-    }
-
-    /**
-     * Return an InvalidArgumentException with pre-set message.
-     */
-    private function invalidBody(): InvalidArgumentException
-    {
-        return new InvalidArgumentException('Passing in the "body" request '
-            .'option as an array to send a request is not supported. '
-            .'Please use the "form_params" request option to send a '
-            .'application/x-www-form-urlencoded request, or the "multipart" '
-            .'request option to send a multipart/form-data request.');
-    }
-}
+<?php
+
+namespace GuzzleHttp;
+
+use GuzzleHttp\Cookie\CookieJar;
+use GuzzleHttp\Exception\GuzzleException;
+use GuzzleHttp\Exception\InvalidArgumentException;
+use GuzzleHttp\Promise as P;
+use GuzzleHttp\Promise\PromiseInterface;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\UriInterface;
+
+/**
+ * @final
+ */
+class Client implements ClientInterface, \Psr\Http\Client\ClientInterface
+{
+    use ClientTrait;
+
+    /**
+     * @var array Default request options
+     */
+    private $config;
+
+    /**
+     * Clients accept an array of constructor parameters.
+     *
+     * Here's an example of creating a client using a base_uri and an array of
+     * default request options to apply to each request:
+     *
+     *     $client = new Client([
+     *         'base_uri'        => 'http://www.foo.com/1.0/',
+     *         'timeout'         => 0,
+     *         'allow_redirects' => false,
+     *         'proxy'           => '192.168.16.1:10'
+     *     ]);
+     *
+     * Client configuration settings include the following options:
+     *
+     * - handler: (callable) Function that transfers HTTP requests over the
+     *   wire. The function is called with a Psr7\Http\Message\RequestInterface
+     *   and array of transfer options, and must return a
+     *   GuzzleHttp\Promise\PromiseInterface that is fulfilled with a
+     *   Psr7\Http\Message\ResponseInterface on success.
+     *   If no handler is provided, a default handler will be created
+     *   that enables all of the request options below by attaching all of the
+     *   default middleware to the handler.
+     * - base_uri: (string|UriInterface) Base URI of the client that is merged
+     *   into relative URIs. Can be a string or instance of UriInterface.
+     * - **: any request option
+     *
+     * @param array $config Client configuration settings.
+     *
+     * @see \GuzzleHttp\RequestOptions for a list of available request options.
+     */
+    public function __construct(array $config = [])
+    {
+        if (!isset($config['handler'])) {
+            $config['handler'] = HandlerStack::create();
+        } elseif (!\is_callable($config['handler'])) {
+            throw new InvalidArgumentException('handler must be a callable');
+        }
+
+        // Convert the base_uri to a UriInterface
+        if (isset($config['base_uri'])) {
+            $config['base_uri'] = Psr7\Utils::uriFor($config['base_uri']);
+        }
+
+        $this->configureDefaults($config);
+    }
+
+    /**
+     * @param string $method
+     * @param array  $args
+     *
+     * @return PromiseInterface|ResponseInterface
+     *
+     * @deprecated Client::__call will be removed in guzzlehttp/guzzle:8.0.
+     */
+    public function __call($method, $args)
+    {
+        if (\count($args) < 1) {
+            throw new InvalidArgumentException('Magic request methods require a URI and optional options array');
+        }
+
+        $uri = $args[0];
+        $opts = $args[1] ?? [];
+
+        return \substr($method, -5) === 'Async'
+            ? $this->requestAsync(\substr($method, 0, -5), $uri, $opts)
+            : $this->request($method, $uri, $opts);
+    }
+
+    /**
+     * Asynchronously send an HTTP request.
+     *
+     * @param array $options Request options to apply to the given
+     *                       request and to the transfer. See \GuzzleHttp\RequestOptions.
+     */
+    public function sendAsync(RequestInterface $request, array $options = []): PromiseInterface
+    {
+        // Merge the base URI into the request URI if needed.
+        $options = $this->prepareDefaults($options);
+
+        return $this->transfer(
+            $request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')),
+            $options
+        );
+    }
+
+    /**
+     * Send an HTTP request.
+     *
+     * @param array $options Request options to apply to the given
+     *                       request and to the transfer. See \GuzzleHttp\RequestOptions.
+     *
+     * @throws GuzzleException
+     */
+    public function send(RequestInterface $request, array $options = []): ResponseInterface
+    {
+        $options[RequestOptions::SYNCHRONOUS] = true;
+
+        return $this->sendAsync($request, $options)->wait();
+    }
+
+    /**
+     * The HttpClient PSR (PSR-18) specify this method.
+     *
+     * {@inheritDoc}
+     */
+    public function sendRequest(RequestInterface $request): ResponseInterface
+    {
+        $options[RequestOptions::SYNCHRONOUS] = true;
+        $options[RequestOptions::ALLOW_REDIRECTS] = false;
+        $options[RequestOptions::HTTP_ERRORS] = false;
+
+        return $this->sendAsync($request, $options)->wait();
+    }
+
+    /**
+     * Create and send an asynchronous HTTP request.
+     *
+     * Use an absolute path to override the base path of the client, or a
+     * relative path to append to the base path of the client. The URL can
+     * contain the query string as well. Use an array to provide a URL
+     * template and additional variables to use in the URL template expansion.
+     *
+     * @param string              $method  HTTP method
+     * @param string|UriInterface $uri     URI object or string.
+     * @param array               $options Request options to apply. See \GuzzleHttp\RequestOptions.
+     */
+    public function requestAsync(string $method, $uri = '', array $options = []): PromiseInterface
+    {
+        $options = $this->prepareDefaults($options);
+        // Remove request modifying parameter because it can be done up-front.
+        $headers = $options['headers'] ?? [];
+        $body = $options['body'] ?? null;
+        $version = $options['version'] ?? '1.1';
+        // Merge the URI into the base URI.
+        $uri = $this->buildUri(Psr7\Utils::uriFor($uri), $options);
+        if (\is_array($body)) {
+            throw $this->invalidBody();
+        }
+        $request = new Psr7\Request($method, $uri, $headers, $body, $version);
+        // Remove the option so that they are not doubly-applied.
+        unset($options['headers'], $options['body'], $options['version']);
+
+        return $this->transfer($request, $options);
+    }
+
+    /**
+     * Create and send an HTTP request.
+     *
+     * Use an absolute path to override the base path of the client, or a
+     * relative path to append to the base path of the client. The URL can
+     * contain the query string as well.
+     *
+     * @param string              $method  HTTP method.
+     * @param string|UriInterface $uri     URI object or string.
+     * @param array               $options Request options to apply. See \GuzzleHttp\RequestOptions.
+     *
+     * @throws GuzzleException
+     */
+    public function request(string $method, $uri = '', array $options = []): ResponseInterface
+    {
+        $options[RequestOptions::SYNCHRONOUS] = true;
+
+        return $this->requestAsync($method, $uri, $options)->wait();
+    }
+
+    /**
+     * Get a client configuration option.
+     *
+     * These options include default request options of the client, a "handler"
+     * (if utilized by the concrete client), and a "base_uri" if utilized by
+     * the concrete client.
+     *
+     * @param string|null $option The config option to retrieve.
+     *
+     * @return mixed
+     *
+     * @deprecated Client::getConfig will be removed in guzzlehttp/guzzle:8.0.
+     */
+    public function getConfig(?string $option = null)
+    {
+        return $option === null
+            ? $this->config
+            : ($this->config[$option] ?? null);
+    }
+
+    private function buildUri(UriInterface $uri, array $config): UriInterface
+    {
+        if (isset($config['base_uri'])) {
+            $uri = Psr7\UriResolver::resolve(Psr7\Utils::uriFor($config['base_uri']), $uri);
+        }
+
+        if (isset($config['idn_conversion']) && ($config['idn_conversion'] !== false)) {
+            $idnOptions = ($config['idn_conversion'] === true) ? \IDNA_DEFAULT : $config['idn_conversion'];
+            $uri = Utils::idnUriConvert($uri, $idnOptions);
+        }
+
+        return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri;
+    }
+
+    /**
+     * Configures the default options for a client.
+     */
+    private function configureDefaults(array $config): void
+    {
+        $defaults = [
+            'allow_redirects' => RedirectMiddleware::$defaultSettings,
+            'http_errors' => true,
+            'decode_content' => true,
+            'verify' => true,
+            'cookies' => false,
+            'idn_conversion' => false,
+        ];
+
+        // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set.
+
+        // We can only trust the HTTP_PROXY environment variable in a CLI
+        // process due to the fact that PHP has no reliable mechanism to
+        // get environment variables that start with "HTTP_".
+        if (\PHP_SAPI === 'cli' && ($proxy = Utils::getenv('HTTP_PROXY'))) {
+            $defaults['proxy']['http'] = $proxy;
+        }
+
+        if ($proxy = Utils::getenv('HTTPS_PROXY')) {
+            $defaults['proxy']['https'] = $proxy;
+        }
+
+        if ($noProxy = Utils::getenv('NO_PROXY')) {
+            $cleanedNoProxy = \str_replace(' ', '', $noProxy);
+            $defaults['proxy']['no'] = \explode(',', $cleanedNoProxy);
+        }
+
+        $this->config = $config + $defaults;
+
+        if (!empty($config['cookies']) && $config['cookies'] === true) {
+            $this->config['cookies'] = new CookieJar();
+        }
+
+        // Add the default user-agent header.
+        if (!isset($this->config['headers'])) {
+            $this->config['headers'] = ['User-Agent' => Utils::defaultUserAgent()];
+        } else {
+            // Add the User-Agent header if one was not already set.
+            foreach (\array_keys($this->config['headers']) as $name) {
+                if (\strtolower($name) === 'user-agent') {
+                    return;
+                }
+            }
+            $this->config['headers']['User-Agent'] = Utils::defaultUserAgent();
+        }
+    }
+
+    /**
+     * Merges default options into the array.
+     *
+     * @param array $options Options to modify by reference
+     */
+    private function prepareDefaults(array $options): array
+    {
+        $defaults = $this->config;
+
+        if (!empty($defaults['headers'])) {
+            // Default headers are only added if they are not present.
+            $defaults['_conditional'] = $defaults['headers'];
+            unset($defaults['headers']);
+        }
+
+        // Special handling for headers is required as they are added as
+        // conditional headers and as headers passed to a request ctor.
+        if (\array_key_exists('headers', $options)) {
+            // Allows default headers to be unset.
+            if ($options['headers'] === null) {
+                $defaults['_conditional'] = [];
+                unset($options['headers']);
+            } elseif (!\is_array($options['headers'])) {
+                throw new InvalidArgumentException('headers must be an array');
+            }
+        }
+
+        // Shallow merge defaults underneath options.
+        $result = $options + $defaults;
+
+        // Remove null values.
+        foreach ($result as $k => $v) {
+            if ($v === null) {
+                unset($result[$k]);
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * Transfers the given request and applies request options.
+     *
+     * The URI of the request is not modified and the request options are used
+     * as-is without merging in default options.
+     *
+     * @param array $options See \GuzzleHttp\RequestOptions.
+     */
+    private function transfer(RequestInterface $request, array $options): PromiseInterface
+    {
+        $request = $this->applyOptions($request, $options);
+        /** @var HandlerStack $handler */
+        $handler = $options['handler'];
+
+        try {
+            return P\Create::promiseFor($handler($request, $options));
+        } catch (\Exception $e) {
+            return P\Create::rejectionFor($e);
+        }
+    }
+
+    /**
+     * Applies the array of request options to a request.
+     */
+    private function applyOptions(RequestInterface $request, array &$options): RequestInterface
+    {
+        $modify = [
+            'set_headers' => [],
+        ];
+
+        if (isset($options['headers'])) {
+            if (array_keys($options['headers']) === range(0, count($options['headers']) - 1)) {
+                throw new InvalidArgumentException('The headers array must have header name as keys.');
+            }
+            $modify['set_headers'] = $options['headers'];
+            unset($options['headers']);
+        }
+
+        if (isset($options['form_params'])) {
+            if (isset($options['multipart'])) {
+                throw new InvalidArgumentException('You cannot use '
+                    .'form_params and multipart at the same time. Use the '
+                    .'form_params option if you want to send application/'
+                    .'x-www-form-urlencoded requests, and the multipart '
+                    .'option to send multipart/form-data requests.');
+            }
+            $options['body'] = \http_build_query($options['form_params'], '', '&');
+            unset($options['form_params']);
+            // Ensure that we don't have the header in different case and set the new value.
+            $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']);
+            $options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded';
+        }
+
+        if (isset($options['multipart'])) {
+            $options['body'] = new Psr7\MultipartStream($options['multipart']);
+            unset($options['multipart']);
+        }
+
+        if (isset($options['json'])) {
+            $options['body'] = Utils::jsonEncode($options['json']);
+            unset($options['json']);
+            // Ensure that we don't have the header in different case and set the new value.
+            $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']);
+            $options['_conditional']['Content-Type'] = 'application/json';
+        }
+
+        if (!empty($options['decode_content'])
+            && $options['decode_content'] !== true
+        ) {
+            // Ensure that we don't have the header in different case and set the new value.
+            $options['_conditional'] = Psr7\Utils::caselessRemove(['Accept-Encoding'], $options['_conditional']);
+            $modify['set_headers']['Accept-Encoding'] = $options['decode_content'];
+        }
+
+        if (isset($options['body'])) {
+            if (\is_array($options['body'])) {
+                throw $this->invalidBody();
+            }
+            $modify['body'] = Psr7\Utils::streamFor($options['body']);
+            unset($options['body']);
+        }
+
+        if (!empty($options['auth']) && \is_array($options['auth'])) {
+            $value = $options['auth'];
+            $type = isset($value[2]) ? \strtolower($value[2]) : 'basic';
+            switch ($type) {
+                case 'basic':
+                    // Ensure that we don't have the header in different case and set the new value.
+                    $modify['set_headers'] = Psr7\Utils::caselessRemove(['Authorization'], $modify['set_headers']);
+                    $modify['set_headers']['Authorization'] = 'Basic '
+                        .\base64_encode("$value[0]:$value[1]");
+                    break;
+                case 'digest':
+                    // @todo: Do not rely on curl
+                    $options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_DIGEST;
+                    $options['curl'][\CURLOPT_USERPWD] = "$value[0]:$value[1]";
+                    break;
+                case 'ntlm':
+                    $options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_NTLM;
+                    $options['curl'][\CURLOPT_USERPWD] = "$value[0]:$value[1]";
+                    break;
+            }
+        }
+
+        if (isset($options['query'])) {
+            $value = $options['query'];
+            if (\is_array($value)) {
+                $value = \http_build_query($value, '', '&', \PHP_QUERY_RFC3986);
+            }
+            if (!\is_string($value)) {
+                throw new InvalidArgumentException('query must be a string or array');
+            }
+            $modify['query'] = $value;
+            unset($options['query']);
+        }
+
+        // Ensure that sink is not an invalid value.
+        if (isset($options['sink'])) {
+            // TODO: Add more sink validation?
+            if (\is_bool($options['sink'])) {
+                throw new InvalidArgumentException('sink must not be a boolean');
+            }
+        }
+
+        if (isset($options['version'])) {
+            $modify['version'] = $options['version'];
+        }
+
+        $request = Psr7\Utils::modifyRequest($request, $modify);
+        if ($request->getBody() instanceof Psr7\MultipartStream) {
+            // Use a multipart/form-data POST if a Content-Type is not set.
+            // Ensure that we don't have the header in different case and set the new value.
+            $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']);
+            $options['_conditional']['Content-Type'] = 'multipart/form-data; boundary='
+                .$request->getBody()->getBoundary();
+        }
+
+        // Merge in conditional headers if they are not present.
+        if (isset($options['_conditional'])) {
+            // Build up the changes so it's in a single clone of the message.
+            $modify = [];
+            foreach ($options['_conditional'] as $k => $v) {
+                if (!$request->hasHeader($k)) {
+                    $modify['set_headers'][$k] = $v;
+                }
+            }
+            $request = Psr7\Utils::modifyRequest($request, $modify);
+            // Don't pass this internal value along to middleware/handlers.
+            unset($options['_conditional']);
+        }
+
+        return $request;
+    }
+
+    /**
+     * Return an InvalidArgumentException with pre-set message.
+     */
+    private function invalidBody(): InvalidArgumentException
+    {
+        return new InvalidArgumentException('Passing in the "body" request '
+            .'option as an array to send a request is not supported. '
+            .'Please use the "form_params" request option to send a '
+            .'application/x-www-form-urlencoded request, or the "multipart" '
+            .'request option to send a multipart/form-data request.');
+    }
+}

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