Browse Source

安装环信php server sdk.composer require maniac/easemob-php

lizhen_gitee 10 months ago
parent
commit
ef62399049
100 changed files with 15986 additions and 635 deletions
  1. 2 1
      composer.json
  2. 3330 0
      composer.lock
  3. 0 18
      vendor/autoload.php
  4. 21 125
      vendor/composer/ClassLoader.php
  5. 11 33
      vendor/composer/InstalledVersions.php
  6. 1 1
      vendor/composer/autoload_classmap.php
  7. 8 7
      vendor/composer/autoload_files.php
  8. 1 1
      vendor/composer/autoload_namespaces.php
  9. 4 2
      vendor/composer/autoload_psr4.php
  10. 37 12
      vendor/composer/autoload_real.php
  11. 20 9
      vendor/composer/autoload_static.php
  12. 132 43
      vendor/composer/installed.json
  13. 71 62
      vendor/composer/installed.php
  14. 6 0
      vendor/ezyang/htmlpurifier/CHANGELOG.md
  15. 1 1
      vendor/ezyang/htmlpurifier/VERSION
  16. 2 3
      vendor/ezyang/htmlpurifier/composer.json
  17. 1 1
      vendor/ezyang/htmlpurifier/library/HTMLPurifier.includes.php
  18. 3 3
      vendor/ezyang/htmlpurifier/library/HTMLPurifier.php
  19. 17 15
      vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/FontFamily.php
  20. 1 1
      vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Host.php
  21. 1 5
      vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetBlank.php
  22. 37 4
      vendor/ezyang/htmlpurifier/library/HTMLPurifier/Bootstrap.php
  23. 91 108
      vendor/ezyang/htmlpurifier/library/HTMLPurifier/CSSDefinition.php
  24. 1 1
      vendor/ezyang/htmlpurifier/library/HTMLPurifier/Config.php
  25. 5 6
      vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer.php
  26. 1 1
      vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCacheFactory.php
  27. 151 155
      vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/ExtractStyleBlocks.php
  28. 0 1
      vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy.php
  29. 1 1
      vendor/ezyang/htmlpurifier/library/HTMLPurifier/LanguageFactory.php
  30. 1 1
      vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer.php
  31. 1 0
      vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DOMLex.php
  32. 0 5
      vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.php
  33. 3 3
      vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/tel.php
  34. 3 3
      vendor/ezyang/htmlpurifier/library/HTMLPurifier/UnitConverter.php
  35. 7 0
      vendor/maniac/easemob-php/.gitignore
  36. 135 0
      vendor/maniac/easemob-php/README.md
  37. 19 0
      vendor/maniac/easemob-php/autoload.php
  38. 23 0
      vendor/maniac/easemob-php/composer.json
  39. 2319 0
      vendor/maniac/easemob-php/composer.lock
  40. 63 0
      vendor/maniac/easemob-php/examples/attachment.php
  41. 58 0
      vendor/maniac/easemob-php/examples/auth.php
  42. 238 0
      vendor/maniac/easemob-php/examples/block.php
  43. 15 0
      vendor/maniac/easemob-php/examples/config.php
  44. 46 0
      vendor/maniac/easemob-php/examples/contact.php
  45. 251 0
      vendor/maniac/easemob-php/examples/group.php
  46. BIN
      vendor/maniac/easemob-php/examples/images/1.png
  47. 281 0
      vendor/maniac/easemob-php/examples/message.php
  48. 55 0
      vendor/maniac/easemob-php/examples/push.php
  49. 217 0
      vendor/maniac/easemob-php/examples/room.php
  50. 98 0
      vendor/maniac/easemob-php/examples/user.php
  51. 58 0
      vendor/maniac/easemob-php/examples/user_metadata.php
  52. 85 0
      vendor/maniac/easemob-php/examples/whiteList.php
  53. 12 0
      vendor/maniac/easemob-php/phpunit.xml
  54. 2 0
      vendor/maniac/easemob-php/runtime/.gitignore
  55. 102 0
      vendor/maniac/easemob-php/src/Agora/AccessToken2.php
  56. 53 0
      vendor/maniac/easemob-php/src/Agora/ChatTokenBuilder2.php
  57. 36 0
      vendor/maniac/easemob-php/src/Agora/Service.php
  58. 30 0
      vendor/maniac/easemob-php/src/Agora/ServiceChat.php
  59. 26 0
      vendor/maniac/easemob-php/src/Agora/ServiceFpa.php
  60. 35 0
      vendor/maniac/easemob-php/src/Agora/ServiceRtc.php
  61. 29 0
      vendor/maniac/easemob-php/src/Agora/ServiceRtm.php
  62. 33 0
      vendor/maniac/easemob-php/src/Agora/ServiceStreaming.php
  63. 77 0
      vendor/maniac/easemob-php/src/Agora/Util.php
  64. 176 0
      vendor/maniac/easemob-php/src/Attachment.php
  65. 429 0
      vendor/maniac/easemob-php/src/Auth.php
  66. 1172 0
      vendor/maniac/easemob-php/src/Block.php
  67. 64 0
      vendor/maniac/easemob-php/src/Cache/Cache.php
  68. 128 0
      vendor/maniac/easemob-php/src/Contact.php
  69. 1082 0
      vendor/maniac/easemob-php/src/Group.php
  70. 234 0
      vendor/maniac/easemob-php/src/Http/Http.php
  71. 44 0
      vendor/maniac/easemob-php/src/Http/Request.php
  72. 108 0
      vendor/maniac/easemob-php/src/Http/Response.php
  73. 630 0
      vendor/maniac/easemob-php/src/Message.php
  74. 179 0
      vendor/maniac/easemob-php/src/Push.php
  75. 855 0
      vendor/maniac/easemob-php/src/Room.php
  76. 422 0
      vendor/maniac/easemob-php/src/User.php
  77. 211 0
      vendor/maniac/easemob-php/src/UserMetadata.php
  78. 349 0
      vendor/maniac/easemob-php/src/WhiteList.php
  79. 39 0
      vendor/maniac/easemob-php/src/functions.php
  80. 19 0
      vendor/maniac/easemob-php/tests/AttachmentTest.php
  81. 45 0
      vendor/maniac/easemob-php/tests/Base.php
  82. 508 0
      vendor/maniac/easemob-php/tests/GroupTest.php
  83. 24 0
      vendor/maniac/easemob-php/tests/HistoryMessageTest.php
  84. 202 0
      vendor/maniac/easemob-php/tests/MessageTest.php
  85. 56 0
      vendor/maniac/easemob-php/tests/PushTest.php
  86. 216 0
      vendor/maniac/easemob-php/tests/RoomTest.php
  87. 32 0
      vendor/maniac/easemob-php/tests/UserMetadataTest.php
  88. 262 0
      vendor/maniac/easemob-php/tests/UserTest.php
  89. 15 0
      vendor/maniac/easemob-php/tests/Utils.php
  90. BIN
      vendor/maniac/easemob-php/tests/assets/1.png
  91. 1 0
      vendor/maniac/easemob-php/tests/assets/1.txt
  92. BIN
      vendor/maniac/easemob-php/tests/assets/11.png
  93. BIN
      vendor/maniac/easemob-php/tests/assets/11_thumb.png
  94. BIN
      vendor/maniac/easemob-php/tests/assets/mario.amr
  95. BIN
      vendor/maniac/easemob-php/tests/assets/movie.ogg
  96. 2 0
      vendor/maniac/easemob-php/tests/bootstrap.php
  97. 23 0
      vendor/maniac/easemob-php/tests/logs/junit.xml
  98. 48 0
      vendor/maniac/easemob-php/tests/logs/testdox.html
  99. 42 0
      vendor/maniac/easemob-php/tests/logs/testdox.xml
  100. 1 3
      vendor/overtrue/wechat/src/Kernel/Providers/LogServiceProvider.php

+ 2 - 1
composer.json

@@ -29,7 +29,8 @@
         "ext-curl": "*",
         "ext-pdo": "*",
         "ext-bcmath": "*",
-        "txthinking/mailer": "^2.0"
+        "txthinking/mailer": "^2.0",
+        "maniac/easemob-php": "^1.0"
     },
     "config": {
         "preferred-install": "dist",

+ 3330 - 0
composer.lock

@@ -0,0 +1,3330 @@
+{
+    "_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": "0dcf0fc13215ae3d0cf71485909917cf",
+    "packages": [
+        {
+            "name": "easywechat-composer/easywechat-composer",
+            "version": "1.4.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/mingyoung/easywechat-composer.git",
+                "reference": "3fc6a7ab6d3853c0f4e2922539b56cc37ef361cd"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/mingyoung/easywechat-composer/zipball/3fc6a7ab6d3853c0f4e2922539b56cc37ef361cd",
+                "reference": "3fc6a7ab6d3853c0f4e2922539b56cc37ef361cd",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "composer-plugin-api": "^1.0 || ^2.0",
+                "php": ">=7.0"
+            },
+            "require-dev": {
+                "composer/composer": "^1.0 || ^2.0",
+                "phpunit/phpunit": "^6.5 || ^7.0"
+            },
+            "type": "composer-plugin",
+            "extra": {
+                "class": "EasyWeChatComposer\\Plugin"
+            },
+            "autoload": {
+                "psr-4": {
+                    "EasyWeChatComposer\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "张铭阳",
+                    "email": "mingyoungcheung@gmail.com"
+                }
+            ],
+            "description": "The composer plugin for EasyWeChat",
+            "support": {
+                "issues": "https://github.com/mingyoung/easywechat-composer/issues",
+                "source": "https://github.com/mingyoung/easywechat-composer/tree/1.4.1"
+            },
+            "time": "2021-07-05T04:03:22+00:00"
+        },
+        {
+            "name": "ezyang/htmlpurifier",
+            "version": "v4.16.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ezyang/htmlpurifier.git",
+                "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/523407fb06eb9e5f3d59889b3978d5bfe94299c8",
+                "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0"
+            },
+            "require-dev": {
+                "cerdic/css-tidy": "^1.7 || ^2.0",
+                "simpletest/simpletest": "dev-master"
+            },
+            "suggest": {
+                "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.",
+                "ext-bcmath": "Used for unit conversion and imagecrash protection",
+                "ext-iconv": "Converts text to and from non-UTF-8 encodings",
+                "ext-tidy": "Used for pretty-printing HTML"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "library/HTMLPurifier.composer.php"
+                ],
+                "psr-0": {
+                    "HTMLPurifier": "library/"
+                },
+                "exclude-from-classmap": [
+                    "/library/HTMLPurifier/Language/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "LGPL-2.1-or-later"
+            ],
+            "authors": [
+                {
+                    "name": "Edward Z. Yang",
+                    "email": "admin@htmlpurifier.org",
+                    "homepage": "http://ezyang.com"
+                }
+            ],
+            "description": "Standards compliant HTML filter written in PHP",
+            "homepage": "http://htmlpurifier.org/",
+            "keywords": [
+                "html"
+            ],
+            "support": {
+                "issues": "https://github.com/ezyang/htmlpurifier/issues",
+                "source": "https://github.com/ezyang/htmlpurifier/tree/v4.16.0"
+            },
+            "time": "2022-09-18T07:06:19+00:00"
+        },
+        {
+            "name": "guzzlehttp/guzzle",
+            "version": "7.8.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/guzzle.git",
+                "reference": "41042bc7ab002487b876a0683fc8dce04ddce104"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104",
+                "reference": "41042bc7ab002487b876a0683fc8dce04ddce104",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "ext-json": "*",
+                "guzzlehttp/promises": "^1.5.3 || ^2.0.1",
+                "guzzlehttp/psr7": "^1.9.1 || ^2.5.1",
+                "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.2",
+                "ext-curl": "*",
+                "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999",
+                "php-http/message-factory": "^1.1",
+                "phpunit/phpunit": "^8.5.36 || ^9.6.15",
+                "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.8.1"
+            },
+            "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-12-03T20:35:24+00:00"
+        },
+        {
+            "name": "guzzlehttp/promises",
+            "version": "2.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/promises.git",
+                "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223",
+                "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "^7.2.5 || ^8.0"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.2",
+                "phpunit/phpunit": "^8.5.36 || ^9.6.15"
+            },
+            "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.2"
+            },
+            "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-12-03T20:19:20+00:00"
+        },
+        {
+            "name": "guzzlehttp/psr7",
+            "version": "2.6.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/psr7.git",
+                "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221",
+                "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "^7.2.5 || ^8.0",
+                "psr/http-factory": "^1.0",
+                "psr/http-message": "^1.1 || ^2.0",
+                "ralouphie/getallheaders": "^3.0"
+            },
+            "provide": {
+                "psr/http-factory-implementation": "1.0",
+                "psr/http-message-implementation": "1.0"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.2",
+                "http-interop/http-factory-tests": "^0.9",
+                "phpunit/phpunit": "^8.5.36 || ^9.6.15"
+            },
+            "suggest": {
+                "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
+            },
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": false
+                }
+            },
+            "autoload": {
+                "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"
+                },
+                {
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com",
+                    "homepage": "https://sagikazarmark.hu"
+                }
+            ],
+            "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/2.6.2"
+            },
+            "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-12-03T20:05:35+00:00"
+        },
+        {
+            "name": "karsonzhang/fastadmin-addons",
+            "version": "1.4.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/karsonzhang/fastadmin-addons.git",
+                "reference": "12b0b146bbdcb12c9f50c96baa3b7cc5f4c48ad6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/karsonzhang/fastadmin-addons/zipball/12b0b146bbdcb12c9f50c96baa3b7cc5f4c48ad6",
+                "reference": "12b0b146bbdcb12c9f50c96baa3b7cc5f4c48ad6",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "nelexa/zip": "^3.3 || ^4.0",
+                "php": ">=7.0.0"
+            },
+            "type": "library",
+            "extra": {
+                "think-config": {
+                    "addons": "src/config.php"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/common.php"
+                ],
+                "psr-4": {
+                    "think\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "Karson",
+                    "email": "karson@fastadmin.net"
+                },
+                {
+                    "name": "xiaobo.sun",
+                    "email": "xiaobo.sun@qq.com"
+                }
+            ],
+            "description": "addons package for fastadmin",
+            "homepage": "https://github.com/karsonzhang/fastadmin-addons",
+            "support": {
+                "issues": "https://github.com/karsonzhang/fastadmin-addons/issues",
+                "source": "https://github.com/karsonzhang/fastadmin-addons/tree/v1.4.0"
+            },
+            "time": "2024-03-28T04:15:16+00:00"
+        },
+        {
+            "name": "maennchen/zipstream-php",
+            "version": "2.2.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/maennchen/ZipStream-PHP.git",
+                "reference": "30ad6f93cf3efe4192bc7a4c9cad11ff8f4f237f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/30ad6f93cf3efe4192bc7a4c9cad11ff8f4f237f",
+                "reference": "30ad6f93cf3efe4192bc7a4c9cad11ff8f4f237f",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "myclabs/php-enum": "^1.5",
+                "php": "^7.4 || ^8.0",
+                "psr/http-message": "^1.0",
+                "symfony/polyfill-mbstring": "^1.0"
+            },
+            "require-dev": {
+                "ext-zip": "*",
+                "friendsofphp/php-cs-fixer": "^3.9",
+                "guzzlehttp/guzzle": "^6.5.3 || ^7.2.0",
+                "mikey179/vfsstream": "^1.6",
+                "php-coveralls/php-coveralls": "^2.4",
+                "phpunit/phpunit": "^8.5.8 || ^9.4.2",
+                "vimeo/psalm": "^4.1"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "ZipStream\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Paul Duncan",
+                    "email": "pabs@pablotron.org"
+                },
+                {
+                    "name": "Jonatan Männchen",
+                    "email": "jonatan@maennchen.ch"
+                },
+                {
+                    "name": "Jesse Donat",
+                    "email": "donatj@gmail.com"
+                },
+                {
+                    "name": "András Kolesár",
+                    "email": "kolesar@kolesar.hu"
+                }
+            ],
+            "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.",
+            "keywords": [
+                "stream",
+                "zip"
+            ],
+            "support": {
+                "issues": "https://github.com/maennchen/ZipStream-PHP/issues",
+                "source": "https://github.com/maennchen/ZipStream-PHP/tree/2.2.6"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/maennchen",
+                    "type": "github"
+                },
+                {
+                    "url": "https://opencollective.com/zipstream",
+                    "type": "open_collective"
+                }
+            ],
+            "time": "2022-11-25T18:57:19+00:00"
+        },
+        {
+            "name": "maniac/easemob-php",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/easemob/im-php-server-sdk.git",
+                "reference": "36b550328c9911957becde2fd62b9379ba45865c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/easemob/im-php-server-sdk/zipball/36b550328c9911957becde2fd62b9379ba45865c",
+                "reference": "36b550328c9911957becde2fd62b9379ba45865c",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/functions.php"
+                ],
+                "psr-4": {
+                    "tests\\": "tests/",
+                    "Easemob\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "maniac",
+                    "email": "maniac.liu@easemob.com"
+                }
+            ],
+            "description": "PHP Server SDK for IM.",
+            "support": {
+                "issues": "https://github.com/easemob/im-php-server-sdk/issues",
+                "source": "https://github.com/easemob/im-php-server-sdk/tree/1.0.0"
+            },
+            "time": "2022-06-13T07:32:04+00:00"
+        },
+        {
+            "name": "markbaker/complex",
+            "version": "3.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/MarkBaker/PHPComplex.git",
+                "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9",
+                "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "^7.2 || ^8.0"
+            },
+            "require-dev": {
+                "dealerdirect/phpcodesniffer-composer-installer": "dev-master",
+                "phpcompatibility/php-compatibility": "^9.3",
+                "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
+                "squizlabs/php_codesniffer": "^3.7"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Complex\\": "classes/src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Mark Baker",
+                    "email": "mark@lange.demon.co.uk"
+                }
+            ],
+            "description": "PHP Class for working with complex numbers",
+            "homepage": "https://github.com/MarkBaker/PHPComplex",
+            "keywords": [
+                "complex",
+                "mathematics"
+            ],
+            "support": {
+                "issues": "https://github.com/MarkBaker/PHPComplex/issues",
+                "source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2"
+            },
+            "time": "2022-12-06T16:21:08+00:00"
+        },
+        {
+            "name": "markbaker/matrix",
+            "version": "3.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/MarkBaker/PHPMatrix.git",
+                "reference": "728434227fe21be27ff6d86621a1b13107a2562c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c",
+                "reference": "728434227fe21be27ff6d86621a1b13107a2562c",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "^7.1 || ^8.0"
+            },
+            "require-dev": {
+                "dealerdirect/phpcodesniffer-composer-installer": "dev-master",
+                "phpcompatibility/php-compatibility": "^9.3",
+                "phpdocumentor/phpdocumentor": "2.*",
+                "phploc/phploc": "^4.0",
+                "phpmd/phpmd": "2.*",
+                "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
+                "sebastian/phpcpd": "^4.0",
+                "squizlabs/php_codesniffer": "^3.7"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Matrix\\": "classes/src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Mark Baker",
+                    "email": "mark@demon-angel.eu"
+                }
+            ],
+            "description": "PHP Class for working with matrices",
+            "homepage": "https://github.com/MarkBaker/PHPMatrix",
+            "keywords": [
+                "mathematics",
+                "matrix",
+                "vector"
+            ],
+            "support": {
+                "issues": "https://github.com/MarkBaker/PHPMatrix/issues",
+                "source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1"
+            },
+            "time": "2022-12-02T22:17:43+00:00"
+        },
+        {
+            "name": "monolog/monolog",
+            "version": "2.9.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Seldaek/monolog.git",
+                "reference": "437cb3628f4cf6042cc10ae97fc2b8472e48ca1f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/437cb3628f4cf6042cc10ae97fc2b8472e48ca1f",
+                "reference": "437cb3628f4cf6042cc10ae97fc2b8472e48ca1f",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.2",
+                "psr/log": "^1.0.1 || ^2.0 || ^3.0"
+            },
+            "provide": {
+                "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0"
+            },
+            "require-dev": {
+                "aws/aws-sdk-php": "^2.4.9 || ^3.0",
+                "doctrine/couchdb": "~1.0@dev",
+                "elasticsearch/elasticsearch": "^7 || ^8",
+                "ext-json": "*",
+                "graylog2/gelf-php": "^1.4.2 || ^2@dev",
+                "guzzlehttp/guzzle": "^7.4",
+                "guzzlehttp/psr7": "^2.2",
+                "mongodb/mongodb": "^1.8",
+                "php-amqplib/php-amqplib": "~2.4 || ^3",
+                "phpspec/prophecy": "^1.15",
+                "phpstan/phpstan": "^0.12.91",
+                "phpunit/phpunit": "^8.5.14",
+                "predis/predis": "^1.1 || ^2.0",
+                "rollbar/rollbar": "^1.3 || ^2 || ^3",
+                "ruflin/elastica": "^7",
+                "swiftmailer/swiftmailer": "^5.3|^6.0",
+                "symfony/mailer": "^5.4 || ^6",
+                "symfony/mime": "^5.4 || ^6"
+            },
+            "suggest": {
+                "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
+                "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
+                "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
+                "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
+                "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler",
+                "ext-mbstring": "Allow to work properly with unicode symbols",
+                "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
+                "ext-openssl": "Required to send log messages using SSL",
+                "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)",
+                "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
+                "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
+                "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
+                "rollbar/rollbar": "Allow sending log messages to Rollbar",
+                "ruflin/elastica": "Allow sending log messages to an Elastic Search server"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "2.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Monolog\\": "src/Monolog"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jordi Boggiano",
+                    "email": "j.boggiano@seld.be",
+                    "homepage": "https://seld.be"
+                }
+            ],
+            "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
+            "homepage": "https://github.com/Seldaek/monolog",
+            "keywords": [
+                "log",
+                "logging",
+                "psr-3"
+            ],
+            "support": {
+                "issues": "https://github.com/Seldaek/monolog/issues",
+                "source": "https://github.com/Seldaek/monolog/tree/2.9.2"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/Seldaek",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/monolog/monolog",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2023-10-27T15:25:26+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": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "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": "nelexa/zip",
+            "version": "4.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Ne-Lexa/php-zip.git",
+                "reference": "88a1b6549be813278ff2dd3b6b2ac188827634a7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Ne-Lexa/php-zip/zipball/88a1b6549be813278ff2dd3b6b2ac188827634a7",
+                "reference": "88a1b6549be813278ff2dd3b6b2ac188827634a7",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "ext-zlib": "*",
+                "php": "^7.4 || ^8.0",
+                "psr/http-message": "*",
+                "symfony/finder": "*"
+            },
+            "require-dev": {
+                "ext-bz2": "*",
+                "ext-dom": "*",
+                "ext-fileinfo": "*",
+                "ext-iconv": "*",
+                "ext-openssl": "*",
+                "ext-xml": "*",
+                "friendsofphp/php-cs-fixer": "^3.4.0",
+                "guzzlehttp/psr7": "^1.6",
+                "phpunit/phpunit": "^9",
+                "symfony/http-foundation": "*",
+                "symfony/var-dumper": "*",
+                "vimeo/psalm": "^4.6"
+            },
+            "suggest": {
+                "ext-bz2": "Needed to support BZIP2 compression",
+                "ext-fileinfo": "Needed to get mime-type file",
+                "ext-iconv": "Needed to support convert zip entry name to requested character encoding",
+                "ext-openssl": "Needed to support encrypt zip entries or use ext-mcrypt"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "PhpZip\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ne-Lexa",
+                    "email": "alexey@nelexa.ru",
+                    "role": "Developer"
+                }
+            ],
+            "description": "PhpZip is a php-library for extended work with ZIP-archives. Open, create, update, delete, extract and get info tool. Supports appending to existing ZIP files, WinZip AES encryption, Traditional PKWARE Encryption, BZIP2 compression, external file attributes and ZIP64 extensions. Alternative ZipArchive. It does not require php-zip extension.",
+            "homepage": "https://github.com/Ne-Lexa/php-zip",
+            "keywords": [
+                "archive",
+                "extract",
+                "unzip",
+                "winzip",
+                "zip",
+                "ziparchive"
+            ],
+            "support": {
+                "issues": "https://github.com/Ne-Lexa/php-zip/issues",
+                "source": "https://github.com/Ne-Lexa/php-zip/tree/4.0.2"
+            },
+            "time": "2022-06-17T11:17:46+00:00"
+        },
+        {
+            "name": "overtrue/pinyin",
+            "version": "3.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/overtrue/pinyin.git",
+                "reference": "3b781d267197b74752daa32814d3a2cf5d140779"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/overtrue/pinyin/zipball/3b781d267197b74752daa32814d3a2cf5d140779",
+                "reference": "3b781d267197b74752daa32814d3a2cf5d140779",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.8"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Overtrue\\Pinyin\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Carlos",
+                    "homepage": "http://github.com/overtrue"
+                }
+            ],
+            "description": "Chinese to pinyin translator.",
+            "homepage": "https://github.com/overtrue/pinyin",
+            "keywords": [
+                "Chinese",
+                "Pinyin",
+                "cn2pinyin"
+            ],
+            "support": {
+                "issues": "https://github.com/overtrue/pinyin/issues",
+                "source": "https://github.com/overtrue/pinyin/tree/master"
+            },
+            "time": "2017-07-10T07:20:01+00:00"
+        },
+        {
+            "name": "overtrue/socialite",
+            "version": "2.0.24",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/overtrue/socialite.git",
+                "reference": "ee7e7b000ec7d64f2b8aba1f6a2eec5cdf3f8bec"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/overtrue/socialite/zipball/ee7e7b000ec7d64f2b8aba1f6a2eec5cdf3f8bec",
+                "reference": "ee7e7b000ec7d64f2b8aba1f6a2eec5cdf3f8bec",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "ext-json": "*",
+                "guzzlehttp/guzzle": "^5.0|^6.0|^7.0",
+                "php": ">=5.6",
+                "symfony/http-foundation": "^2.7|^3.0|^4.0|^5.0"
+            },
+            "require-dev": {
+                "mockery/mockery": "~1.2",
+                "phpunit/phpunit": "^6.0|^7.0|^8.0|^9.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Overtrue\\Socialite\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "overtrue",
+                    "email": "anzhengchao@gmail.com"
+                }
+            ],
+            "description": "A collection of OAuth 2 packages that extracts from laravel/socialite.",
+            "keywords": [
+                "login",
+                "oauth",
+                "qq",
+                "social",
+                "wechat",
+                "weibo"
+            ],
+            "support": {
+                "issues": "https://github.com/overtrue/socialite/issues",
+                "source": "https://github.com/overtrue/socialite/tree/2.0.24"
+            },
+            "funding": [
+                {
+                    "url": "https://www.patreon.com/overtrue",
+                    "type": "patreon"
+                }
+            ],
+            "time": "2021-05-13T16:04:48+00:00"
+        },
+        {
+            "name": "overtrue/wechat",
+            "version": "4.6.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/w7corp/easywechat.git",
+                "reference": "52af4cbe777cd4aea307beafa0a4518c347467b1"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/w7corp/easywechat/zipball/52af4cbe777cd4aea307beafa0a4518c347467b1",
+                "reference": "52af4cbe777cd4aea307beafa0a4518c347467b1",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "easywechat-composer/easywechat-composer": "^1.1",
+                "ext-fileinfo": "*",
+                "ext-openssl": "*",
+                "ext-simplexml": "*",
+                "guzzlehttp/guzzle": "^6.2 || ^7.0",
+                "monolog/monolog": "^1.22 || ^2.0",
+                "overtrue/socialite": "~2.0",
+                "php": ">=7.2",
+                "pimple/pimple": "^3.0",
+                "psr/simple-cache": "^1.0",
+                "symfony/cache": "^3.3 || ^4.3 || ^5.0",
+                "symfony/event-dispatcher": "^4.3 || ^5.0",
+                "symfony/http-foundation": "^2.7 || ^3.0 || ^4.0 || ^5.0",
+                "symfony/psr-http-message-bridge": "^0.3 || ^1.0 || ^2.0"
+            },
+            "require-dev": {
+                "friendsofphp/php-cs-fixer": "^2.15",
+                "mikey179/vfsstream": "^1.6",
+                "mockery/mockery": "^1.2.3",
+                "phpstan/phpstan": "^0.12.0",
+                "phpunit/phpunit": "^7.5"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/Kernel/Support/Helpers.php",
+                    "src/Kernel/Helpers.php"
+                ],
+                "psr-4": {
+                    "EasyWeChat\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "overtrue",
+                    "email": "anzhengchao@gmail.com"
+                }
+            ],
+            "description": "微信SDK",
+            "keywords": [
+                "easywechat",
+                "sdk",
+                "wechat",
+                "weixin",
+                "weixin-sdk"
+            ],
+            "support": {
+                "issues": "https://github.com/w7corp/easywechat/issues",
+                "source": "https://github.com/w7corp/easywechat/tree/4.6.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/overtrue",
+                    "type": "github"
+                }
+            ],
+            "abandoned": "w7corp/easywechat",
+            "time": "2022-08-24T07:30:42+00:00"
+        },
+        {
+            "name": "phpoffice/phpspreadsheet",
+            "version": "1.19.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
+                "reference": "a9ab55bfae02eecffb3df669a2e19ba0e2f04bbf"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/a9ab55bfae02eecffb3df669a2e19ba0e2f04bbf",
+                "reference": "a9ab55bfae02eecffb3df669a2e19ba0e2f04bbf",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "ext-ctype": "*",
+                "ext-dom": "*",
+                "ext-fileinfo": "*",
+                "ext-gd": "*",
+                "ext-iconv": "*",
+                "ext-libxml": "*",
+                "ext-mbstring": "*",
+                "ext-simplexml": "*",
+                "ext-xml": "*",
+                "ext-xmlreader": "*",
+                "ext-xmlwriter": "*",
+                "ext-zip": "*",
+                "ext-zlib": "*",
+                "ezyang/htmlpurifier": "^4.13",
+                "maennchen/zipstream-php": "^2.1",
+                "markbaker/complex": "^3.0",
+                "markbaker/matrix": "^3.0",
+                "php": "^7.2 || ^8.0",
+                "psr/http-client": "^1.0",
+                "psr/http-factory": "^1.0",
+                "psr/simple-cache": "^1.0"
+            },
+            "require-dev": {
+                "dealerdirect/phpcodesniffer-composer-installer": "dev-master",
+                "dompdf/dompdf": "^1.0",
+                "friendsofphp/php-cs-fixer": "^2.18",
+                "jpgraph/jpgraph": "^4.0",
+                "mpdf/mpdf": "^8.0",
+                "phpcompatibility/php-compatibility": "^9.3",
+                "phpstan/phpstan": "^0.12.82",
+                "phpstan/phpstan-phpunit": "^0.12.18",
+                "phpunit/phpunit": "^8.5",
+                "squizlabs/php_codesniffer": "^3.5",
+                "tecnickcom/tcpdf": "^6.3"
+            },
+            "suggest": {
+                "dompdf/dompdf": "Option for rendering PDF with PDF Writer (doesn't yet support PHP8)",
+                "jpgraph/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers",
+                "mpdf/mpdf": "Option for rendering PDF with PDF Writer",
+                "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer (doesn't yet support PHP8)"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Maarten Balliauw",
+                    "homepage": "https://blog.maartenballiauw.be"
+                },
+                {
+                    "name": "Mark Baker",
+                    "homepage": "https://markbakeruk.net"
+                },
+                {
+                    "name": "Franck Lefevre",
+                    "homepage": "https://rootslabs.net"
+                },
+                {
+                    "name": "Erik Tilt"
+                },
+                {
+                    "name": "Adrien Crivelli"
+                }
+            ],
+            "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
+            "homepage": "https://github.com/PHPOffice/PhpSpreadsheet",
+            "keywords": [
+                "OpenXML",
+                "excel",
+                "gnumeric",
+                "ods",
+                "php",
+                "spreadsheet",
+                "xls",
+                "xlsx"
+            ],
+            "support": {
+                "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
+                "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.19.0"
+            },
+            "time": "2021-10-31T15:09:20+00:00"
+        },
+        {
+            "name": "pimple/pimple",
+            "version": "v3.5.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/silexphp/Pimple.git",
+                "reference": "a94b3a4db7fb774b3d78dad2315ddc07629e1bed"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/silexphp/Pimple/zipball/a94b3a4db7fb774b3d78dad2315ddc07629e1bed",
+                "reference": "a94b3a4db7fb774b3d78dad2315ddc07629e1bed",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.2.5",
+                "psr/container": "^1.1 || ^2.0"
+            },
+            "require-dev": {
+                "symfony/phpunit-bridge": "^5.4@dev"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.4.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Pimple": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                }
+            ],
+            "description": "Pimple, a simple Dependency Injection Container",
+            "homepage": "https://pimple.symfony.com",
+            "keywords": [
+                "container",
+                "dependency injection"
+            ],
+            "support": {
+                "source": "https://github.com/silexphp/Pimple/tree/v3.5.0"
+            },
+            "time": "2021-10-28T11:13:42+00:00"
+        },
+        {
+            "name": "psr/cache",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/cache.git",
+                "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8",
+                "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Cache\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for caching libraries",
+            "keywords": [
+                "cache",
+                "psr",
+                "psr-6"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/cache/tree/master"
+            },
+            "time": "2016-08-06T20:24:11+00:00"
+        },
+        {
+            "name": "psr/container",
+            "version": "2.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/container.git",
+                "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+                "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.4.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "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/2.0.2"
+            },
+            "time": "2021-11-05T16:47:00+00:00"
+        },
+        {
+            "name": "psr/event-dispatcher",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/event-dispatcher.git",
+                "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0",
+                "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.2.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\EventDispatcher\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Standard interfaces for event handling.",
+            "keywords": [
+                "events",
+                "psr",
+                "psr-14"
+            ],
+            "support": {
+                "issues": "https://github.com/php-fig/event-dispatcher/issues",
+                "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0"
+            },
+            "time": "2019-01-08T18:20:26+00:00"
+        },
+        {
+            "name": "psr/http-client",
+            "version": "1.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-client.git",
+                "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
+                "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "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"
+            },
+            "time": "2023-09-23T14:17:50+00:00"
+        },
+        {
+            "name": "psr/http-factory",
+            "version": "1.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-factory.git",
+                "reference": "e616d01114759c4c489f93b099585439f795fe35"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35",
+                "reference": "e616d01114759c4c489f93b099585439f795fe35",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.0.0",
+                "psr/http-message": "^1.0 || ^2.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Message\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "https://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interfaces for PSR-7 HTTP message factories",
+            "keywords": [
+                "factory",
+                "http",
+                "message",
+                "psr",
+                "psr-17",
+                "psr-7",
+                "request",
+                "response"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/http-factory/tree/1.0.2"
+            },
+            "time": "2023-04-10T20:10:41+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": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "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": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "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": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "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": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "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/cache",
+            "version": "v5.4.36",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/cache.git",
+                "reference": "a30f316214d908cf5874f700f3f3fb29ceee91ba"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/cache/zipball/a30f316214d908cf5874f700f3f3fb29ceee91ba",
+                "reference": "a30f316214d908cf5874f700f3f3fb29ceee91ba",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.2.5",
+                "psr/cache": "^1.0|^2.0",
+                "psr/log": "^1.1|^2|^3",
+                "symfony/cache-contracts": "^1.1.7|^2",
+                "symfony/deprecation-contracts": "^2.1|^3",
+                "symfony/polyfill-php73": "^1.9",
+                "symfony/polyfill-php80": "^1.16",
+                "symfony/service-contracts": "^1.1|^2|^3",
+                "symfony/var-exporter": "^4.4|^5.0|^6.0"
+            },
+            "conflict": {
+                "doctrine/dbal": "<2.13.1",
+                "symfony/dependency-injection": "<4.4",
+                "symfony/http-kernel": "<4.4",
+                "symfony/var-dumper": "<4.4"
+            },
+            "provide": {
+                "psr/cache-implementation": "1.0|2.0",
+                "psr/simple-cache-implementation": "1.0|2.0",
+                "symfony/cache-implementation": "1.0|2.0"
+            },
+            "require-dev": {
+                "cache/integration-tests": "dev-master",
+                "doctrine/cache": "^1.6|^2.0",
+                "doctrine/dbal": "^2.13.1|^3|^4",
+                "predis/predis": "^1.1",
+                "psr/simple-cache": "^1.0|^2.0",
+                "symfony/config": "^4.4|^5.0|^6.0",
+                "symfony/dependency-injection": "^4.4|^5.0|^6.0",
+                "symfony/filesystem": "^4.4|^5.0|^6.0",
+                "symfony/http-kernel": "^4.4|^5.0|^6.0",
+                "symfony/messenger": "^4.4|^5.0|^6.0",
+                "symfony/var-dumper": "^4.4|^5.0|^6.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Cache\\": ""
+                },
+                "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 extended PSR-6, PSR-16 (and tags) implementations",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "caching",
+                "psr6"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/cache/tree/v5.4.36"
+            },
+            "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": "2024-02-19T13:08:14+00:00"
+        },
+        {
+            "name": "symfony/cache-contracts",
+            "version": "v2.5.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/cache-contracts.git",
+                "reference": "64be4a7acb83b6f2bf6de9a02cee6dad41277ebc"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/64be4a7acb83b6f2bf6de9a02cee6dad41277ebc",
+                "reference": "64be4a7acb83b6f2bf6de9a02cee6dad41277ebc",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.2.5",
+                "psr/cache": "^1.0|^2.0|^3.0"
+            },
+            "suggest": {
+                "symfony/cache-implementation": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "2.5-dev"
+                },
+                "thanks": {
+                    "name": "symfony/contracts",
+                    "url": "https://github.com/symfony/contracts"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Contracts\\Cache\\": ""
+                }
+            },
+            "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": "Generic abstractions related to caching",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "abstractions",
+                "contracts",
+                "decoupling",
+                "interfaces",
+                "interoperability",
+                "standards"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/cache-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": "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": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "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": "symfony/event-dispatcher",
+            "version": "v5.4.35",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/event-dispatcher.git",
+                "reference": "7a69a85c7ea5bdd1e875806a99c51a87d3a74b38"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/7a69a85c7ea5bdd1e875806a99c51a87d3a74b38",
+                "reference": "7a69a85c7ea5bdd1e875806a99c51a87d3a74b38",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.2.5",
+                "symfony/deprecation-contracts": "^2.1|^3",
+                "symfony/event-dispatcher-contracts": "^2|^3",
+                "symfony/polyfill-php80": "^1.16"
+            },
+            "conflict": {
+                "symfony/dependency-injection": "<4.4"
+            },
+            "provide": {
+                "psr/event-dispatcher-implementation": "1.0",
+                "symfony/event-dispatcher-implementation": "2.0"
+            },
+            "require-dev": {
+                "psr/log": "^1|^2|^3",
+                "symfony/config": "^4.4|^5.0|^6.0",
+                "symfony/dependency-injection": "^4.4|^5.0|^6.0",
+                "symfony/error-handler": "^4.4|^5.0|^6.0",
+                "symfony/expression-language": "^4.4|^5.0|^6.0",
+                "symfony/http-foundation": "^4.4|^5.0|^6.0",
+                "symfony/service-contracts": "^1.1|^2|^3",
+                "symfony/stopwatch": "^4.4|^5.0|^6.0"
+            },
+            "suggest": {
+                "symfony/dependency-injection": "",
+                "symfony/http-kernel": ""
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\EventDispatcher\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.35"
+            },
+            "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": "2024-01-23T13:51:25+00:00"
+        },
+        {
+            "name": "symfony/event-dispatcher-contracts",
+            "version": "v2.5.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/event-dispatcher-contracts.git",
+                "reference": "f98b54df6ad059855739db6fcbc2d36995283fe1"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/f98b54df6ad059855739db6fcbc2d36995283fe1",
+                "reference": "f98b54df6ad059855739db6fcbc2d36995283fe1",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.2.5",
+                "psr/event-dispatcher": "^1"
+            },
+            "suggest": {
+                "symfony/event-dispatcher-implementation": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "2.5-dev"
+                },
+                "thanks": {
+                    "name": "symfony/contracts",
+                    "url": "https://github.com/symfony/contracts"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Contracts\\EventDispatcher\\": ""
+                }
+            },
+            "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": "Generic abstractions related to dispatching event",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "abstractions",
+                "contracts",
+                "decoupling",
+                "interfaces",
+                "interoperability",
+                "standards"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/event-dispatcher-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": "symfony/finder",
+            "version": "v5.4.27",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/finder.git",
+                "reference": "ff4bce3c33451e7ec778070e45bd23f74214cd5d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/finder/zipball/ff4bce3c33451e7ec778070e45bd23f74214cd5d",
+                "reference": "ff4bce3c33451e7ec778070e45bd23f74214cd5d",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.2.5",
+                "symfony/deprecation-contracts": "^2.1|^3",
+                "symfony/polyfill-php80": "^1.16"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Finder\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Finds files and directories via an intuitive fluent interface",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/finder/tree/v5.4.27"
+            },
+            "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": "2023-07-31T08:02:31+00:00"
+        },
+        {
+            "name": "symfony/http-foundation",
+            "version": "v5.4.35",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/http-foundation.git",
+                "reference": "f2ab692a22aef1cd54beb893aa0068bdfb093928"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f2ab692a22aef1cd54beb893aa0068bdfb093928",
+                "reference": "f2ab692a22aef1cd54beb893aa0068bdfb093928",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.2.5",
+                "symfony/deprecation-contracts": "^2.1|^3",
+                "symfony/polyfill-mbstring": "~1.1",
+                "symfony/polyfill-php80": "^1.16"
+            },
+            "require-dev": {
+                "predis/predis": "~1.0",
+                "symfony/cache": "^4.4|^5.0|^6.0",
+                "symfony/dependency-injection": "^5.4|^6.0",
+                "symfony/expression-language": "^4.4|^5.0|^6.0",
+                "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4",
+                "symfony/mime": "^4.4|^5.0|^6.0",
+                "symfony/rate-limiter": "^5.2|^6.0"
+            },
+            "suggest": {
+                "symfony/mime": "To use the file extension guesser"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\HttpFoundation\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Defines an object-oriented layer for the HTTP specification",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/http-foundation/tree/v5.4.35"
+            },
+            "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": "2024-01-23T13:51:25+00:00"
+        },
+        {
+            "name": "symfony/polyfill-mbstring",
+            "version": "v1.28.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-mbstring.git",
+                "reference": "42292d99c55abe617799667f454222c54c60e229"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229",
+                "reference": "42292d99c55abe617799667f454222c54c60e229",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "provide": {
+                "ext-mbstring": "*"
+            },
+            "suggest": {
+                "ext-mbstring": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.28-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.28.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": "2023-07-28T09:04:16+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php73",
+            "version": "v1.28.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php73.git",
+                "reference": "fe2f306d1d9d346a7fee353d0d5012e401e984b5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fe2f306d1d9d346a7fee353d0d5012e401e984b5",
+                "reference": "fe2f306d1d9d346a7fee353d0d5012e401e984b5",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.28-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php73\\": ""
+                },
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "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.3+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-php73/tree/v1.28.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": "2023-01-26T09:26:14+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php80",
+            "version": "v1.29.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php80.git",
+                "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
+                "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "type": "library",
+            "extra": {
+                "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.29.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": "2024-01-29T20:11:03+00:00"
+        },
+        {
+            "name": "symfony/psr-http-message-bridge",
+            "version": "v2.3.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/psr-http-message-bridge.git",
+                "reference": "581ca6067eb62640de5ff08ee1ba6850a0ee472e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/581ca6067eb62640de5ff08ee1ba6850a0ee472e",
+                "reference": "581ca6067eb62640de5ff08ee1ba6850a0ee472e",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.2.5",
+                "psr/http-message": "^1.0 || ^2.0",
+                "symfony/deprecation-contracts": "^2.5 || ^3.0",
+                "symfony/http-foundation": "^5.4 || ^6.0"
+            },
+            "require-dev": {
+                "nyholm/psr7": "^1.1",
+                "psr/log": "^1.1 || ^2 || ^3",
+                "symfony/browser-kit": "^5.4 || ^6.0",
+                "symfony/config": "^5.4 || ^6.0",
+                "symfony/event-dispatcher": "^5.4 || ^6.0",
+                "symfony/framework-bundle": "^5.4 || ^6.0",
+                "symfony/http-kernel": "^5.4 || ^6.0",
+                "symfony/phpunit-bridge": "^6.2"
+            },
+            "suggest": {
+                "nyholm/psr7": "For a super lightweight PSR-7/17 implementation"
+            },
+            "type": "symfony-bridge",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "2.3-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Bridge\\PsrHttpMessage\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "http://symfony.com/contributors"
+                }
+            ],
+            "description": "PSR HTTP message bridge",
+            "homepage": "http://symfony.com",
+            "keywords": [
+                "http",
+                "http-message",
+                "psr-17",
+                "psr-7"
+            ],
+            "support": {
+                "issues": "https://github.com/symfony/psr-http-message-bridge/issues",
+                "source": "https://github.com/symfony/psr-http-message-bridge/tree/v2.3.1"
+            },
+            "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": "2023-07-26T11:53:26+00:00"
+        },
+        {
+            "name": "symfony/service-contracts",
+            "version": "v1.1.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/service-contracts.git",
+                "reference": "191afdcb5804db960d26d8566b7e9a2843cab3a0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/service-contracts/zipball/191afdcb5804db960d26d8566b7e9a2843cab3a0",
+                "reference": "191afdcb5804db960d26d8566b7e9a2843cab3a0",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "^7.1.3"
+            },
+            "suggest": {
+                "psr/container": "",
+                "symfony/service-implementation": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Contracts\\Service\\": ""
+                }
+            },
+            "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": "Generic abstractions related to writing services",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "abstractions",
+                "contracts",
+                "decoupling",
+                "interfaces",
+                "interoperability",
+                "standards"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/service-contracts/tree/v1.1.2"
+            },
+            "time": "2019-05-28T07:50:59+00:00"
+        },
+        {
+            "name": "symfony/var-exporter",
+            "version": "v5.4.35",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/var-exporter.git",
+                "reference": "abb0a151b62d6b07e816487e20040464af96cae7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/var-exporter/zipball/abb0a151b62d6b07e816487e20040464af96cae7",
+                "reference": "abb0a151b62d6b07e816487e20040464af96cae7",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.2.5",
+                "symfony/polyfill-php80": "^1.16"
+            },
+            "require-dev": {
+                "symfony/var-dumper": "^4.4.9|^5.0.9|^6.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\VarExporter\\": ""
+                },
+                "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": "Allows exporting any serializable PHP data structure to plain PHP code",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "clone",
+                "construct",
+                "export",
+                "hydrate",
+                "instantiate",
+                "serialize"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/var-exporter/tree/v5.4.35"
+            },
+            "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": "2024-01-23T13:51:25+00:00"
+        },
+        {
+            "name": "topthink/framework",
+            "version": "dev-master",
+            "source": {
+                "type": "git",
+                "url": "https://gitee.com/fastadminnet/framework.git",
+                "reference": "c79fdd579e3b87c96692ff1924dac324fa60d7bd"
+            },
+            "require": {
+                "php": ">=7.1.0",
+                "topthink/think-installer": "~1.0"
+            },
+            "require-dev": {
+                "johnkary/phpunit-speedtrap": "^1.0",
+                "mikey179/vfsstream": "~1.6",
+                "phpdocumentor/reflection-docblock": "^2.0",
+                "phploc/phploc": "2.*",
+                "phpunit/phpunit": "4.8.*",
+                "sebastian/phpcpd": "2.*"
+            },
+            "default-branch": true,
+            "type": "think-framework",
+            "autoload": {
+                "psr-4": {
+                    "think\\": "library/think"
+                }
+            },
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "liu21st",
+                    "email": "liu21st@gmail.com"
+                }
+            ],
+            "description": "the new thinkphp framework",
+            "homepage": "http://thinkphp.cn/",
+            "keywords": [
+                "ORM",
+                "framework",
+                "thinkphp"
+            ],
+            "time": "2024-04-11T02:40:58+00:00"
+        },
+        {
+            "name": "topthink/think-captcha",
+            "version": "v1.0.9",
+            "source": {
+                "type": "git",
+                "url": "https://gitee.com/fastadminnet/think-captcha.git",
+                "reference": "9be9dd7e61c7fa3c478c4b92910d7230b94d0d23"
+            },
+            "require": {
+                "topthink/framework": "~5.0.0 || dev-master",
+                "topthink/think-installer": ">=1.0.10"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "think\\captcha\\": "src/"
+                },
+                "files": [
+                    "src/helper.php"
+                ]
+            },
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "description": "captcha package for thinkphp5",
+            "time": "2023-07-16T09:41:14+00:00"
+        },
+        {
+            "name": "topthink/think-helper",
+            "version": "v1.0.7",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-helper.git",
+                "reference": "5f92178606c8ce131d36b37a57c58eb71e55f019"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-helper/zipball/5f92178606c8ce131d36b37a57c58eb71e55f019",
+                "reference": "5f92178606c8ce131d36b37a57c58eb71e55f019",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/helper.php"
+                ],
+                "psr-4": {
+                    "think\\helper\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "description": "The ThinkPHP5 Helper Package",
+            "support": {
+                "issues": "https://github.com/top-think/think-helper/issues",
+                "source": "https://github.com/top-think/think-helper/tree/master"
+            },
+            "time": "2018-10-05T00:43:21+00:00"
+        },
+        {
+            "name": "topthink/think-installer",
+            "version": "v1.0.14",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-installer.git",
+                "reference": "eae1740ac264a55c06134b6685dfb9f837d004d1"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-installer/zipball/eae1740ac264a55c06134b6685dfb9f837d004d1",
+                "reference": "eae1740ac264a55c06134b6685dfb9f837d004d1",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "composer-plugin-api": "^1.0||^2.0"
+            },
+            "require-dev": {
+                "composer/composer": "^1.0||^2.0"
+            },
+            "type": "composer-plugin",
+            "extra": {
+                "class": "think\\composer\\Plugin"
+            },
+            "autoload": {
+                "psr-4": {
+                    "think\\composer\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "support": {
+                "issues": "https://github.com/top-think/think-installer/issues",
+                "source": "https://github.com/top-think/think-installer/tree/v1.0.14"
+            },
+            "time": "2021-03-25T08:34:02+00:00"
+        },
+        {
+            "name": "topthink/think-queue",
+            "version": "v1.1.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-queue.git",
+                "reference": "250650eb0e8ea5af4cfdc7ae46f3f4e0a24ac245"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-queue/zipball/250650eb0e8ea5af4cfdc7ae46f3f4e0a24ac245",
+                "reference": "250650eb0e8ea5af4cfdc7ae46f3f4e0a24ac245",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "topthink/think-helper": ">=1.0.4",
+                "topthink/think-installer": ">=1.0.10"
+            },
+            "require-dev": {
+                "topthink/framework": "~5.0.0"
+            },
+            "type": "think-extend",
+            "extra": {
+                "think-config": {
+                    "queue": "src/config.php"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/common.php"
+                ],
+                "psr-4": {
+                    "think\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "description": "The ThinkPHP5 Queue Package",
+            "support": {
+                "issues": "https://github.com/top-think/think-queue/issues",
+                "source": "https://github.com/top-think/think-queue/tree/master"
+            },
+            "time": "2018-10-15T10:16:55+00:00"
+        },
+        {
+            "name": "txthinking/mailer",
+            "version": "v2.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/txthinking/Mailer.git",
+                "reference": "09013cf9dad3aac195f66ae5309e8c3343c018e9"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/txthinking/Mailer/zipball/09013cf9dad3aac195f66ae5309e8c3343c018e9",
+                "reference": "09013cf9dad3aac195f66ae5309e8c3343c018e9",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.2",
+                "psr/log": "~1.0"
+            },
+            "require-dev": {
+                "monolog/monolog": "~1.13",
+                "phpunit/phpunit": "~4.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Tx\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Cloud",
+                    "email": "cloud@txthinking.com",
+                    "homepage": "http://www.txthinking.com",
+                    "role": "Thinker"
+                },
+                {
+                    "name": "Matt Sowers",
+                    "email": "msowers@erblearn.org"
+                }
+            ],
+            "description": "A very lightweight PHP SMTP mail sender",
+            "homepage": "http://github.com/txthinking/Mailer",
+            "keywords": [
+                "mail",
+                "smtp"
+            ],
+            "support": {
+                "issues": "https://github.com/txthinking/Mailer/issues",
+                "source": "https://github.com/txthinking/Mailer/tree/master"
+            },
+            "time": "2018-10-09T10:47:23+00:00"
+        }
+    ],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": {
+        "topthink/framework": 20
+    },
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": {
+        "php": ">=7.2.0",
+        "ext-json": "*",
+        "ext-curl": "*",
+        "ext-pdo": "*",
+        "ext-bcmath": "*"
+    },
+    "platform-dev": [],
+    "plugin-api-version": "2.1.0"
+}

+ 0 - 18
vendor/autoload.php

@@ -2,24 +2,6 @@
 
 // autoload.php @generated by Composer
 
-if (PHP_VERSION_ID < 50600) {
-    if (!headers_sent()) {
-        header('HTTP/1.1 500 Internal Server Error');
-    }
-    $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
-    if (!ini_get('display_errors')) {
-        if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
-            fwrite(STDERR, $err);
-        } elseif (!headers_sent()) {
-            echo $err;
-        }
-    }
-    trigger_error(
-        $err,
-        E_USER_ERROR
-    );
-}
-
 require_once __DIR__ . '/composer/autoload_real.php';
 
 return ComposerAutoloaderInitf3106b6ef3260b6914241eab0bed11c1::getLoader();

+ 21 - 125
vendor/composer/ClassLoader.php

@@ -42,79 +42,30 @@ namespace Composer\Autoload;
  */
 class ClassLoader
 {
-    /** @var \Closure(string):void */
-    private static $includeFile;
-
-    /** @var ?string */
     private $vendorDir;
 
     // PSR-4
-    /**
-     * @var array[]
-     * @psalm-var array<string, array<string, int>>
-     */
     private $prefixLengthsPsr4 = array();
-    /**
-     * @var array[]
-     * @psalm-var array<string, array<int, string>>
-     */
     private $prefixDirsPsr4 = array();
-    /**
-     * @var array[]
-     * @psalm-var array<string, string>
-     */
     private $fallbackDirsPsr4 = array();
 
     // PSR-0
-    /**
-     * @var array[]
-     * @psalm-var array<string, array<string, string[]>>
-     */
     private $prefixesPsr0 = array();
-    /**
-     * @var array[]
-     * @psalm-var array<string, string>
-     */
     private $fallbackDirsPsr0 = array();
 
-    /** @var bool */
     private $useIncludePath = false;
-
-    /**
-     * @var string[]
-     * @psalm-var array<string, string>
-     */
     private $classMap = array();
-
-    /** @var bool */
     private $classMapAuthoritative = false;
-
-    /**
-     * @var bool[]
-     * @psalm-var array<string, bool>
-     */
     private $missingClasses = array();
-
-    /** @var ?string */
     private $apcuPrefix;
 
-    /**
-     * @var self[]
-     */
     private static $registeredLoaders = array();
 
-    /**
-     * @param ?string $vendorDir
-     */
     public function __construct($vendorDir = null)
     {
         $this->vendorDir = $vendorDir;
-        self::initializeIncludeClosure();
     }
 
-    /**
-     * @return string[]
-     */
     public function getPrefixes()
     {
         if (!empty($this->prefixesPsr0)) {
@@ -124,47 +75,28 @@ class ClassLoader
         return array();
     }
 
-    /**
-     * @return array[]
-     * @psalm-return array<string, array<int, string>>
-     */
     public function getPrefixesPsr4()
     {
         return $this->prefixDirsPsr4;
     }
 
-    /**
-     * @return array[]
-     * @psalm-return array<string, string>
-     */
     public function getFallbackDirs()
     {
         return $this->fallbackDirsPsr0;
     }
 
-    /**
-     * @return array[]
-     * @psalm-return array<string, string>
-     */
     public function getFallbackDirsPsr4()
     {
         return $this->fallbackDirsPsr4;
     }
 
-    /**
-     * @return string[] Array of classname => path
-     * @psalm-return array<string, string>
-     */
     public function getClassMap()
     {
         return $this->classMap;
     }
 
     /**
-     * @param string[] $classMap Class to filename map
-     * @psalm-param array<string, string> $classMap
-     *
-     * @return void
+     * @param array $classMap Class to filename map
      */
     public function addClassMap(array $classMap)
     {
@@ -179,11 +111,9 @@ class ClassLoader
      * Registers a set of PSR-0 directories for a given prefix, either
      * appending or prepending to the ones previously set for this prefix.
      *
-     * @param string          $prefix  The prefix
-     * @param string[]|string $paths   The PSR-0 root directories
-     * @param bool            $prepend Whether to prepend the directories
-     *
-     * @return void
+     * @param string       $prefix  The prefix
+     * @param array|string $paths   The PSR-0 root directories
+     * @param bool         $prepend Whether to prepend the directories
      */
     public function add($prefix, $paths, $prepend = false)
     {
@@ -226,13 +156,11 @@ class ClassLoader
      * Registers a set of PSR-4 directories for a given namespace, either
      * appending or prepending to the ones previously set for this namespace.
      *
-     * @param string          $prefix  The prefix/namespace, with trailing '\\'
-     * @param string[]|string $paths   The PSR-4 base directories
-     * @param bool            $prepend Whether to prepend the directories
+     * @param string       $prefix  The prefix/namespace, with trailing '\\'
+     * @param array|string $paths   The PSR-4 base directories
+     * @param bool         $prepend Whether to prepend the directories
      *
      * @throws \InvalidArgumentException
-     *
-     * @return void
      */
     public function addPsr4($prefix, $paths, $prepend = false)
     {
@@ -276,10 +204,8 @@ class ClassLoader
      * Registers a set of PSR-0 directories for a given prefix,
      * replacing any others previously set for this prefix.
      *
-     * @param string          $prefix The prefix
-     * @param string[]|string $paths  The PSR-0 base directories
-     *
-     * @return void
+     * @param string       $prefix The prefix
+     * @param array|string $paths  The PSR-0 base directories
      */
     public function set($prefix, $paths)
     {
@@ -294,12 +220,10 @@ class ClassLoader
      * Registers a set of PSR-4 directories for a given namespace,
      * replacing any others previously set for this namespace.
      *
-     * @param string          $prefix The prefix/namespace, with trailing '\\'
-     * @param string[]|string $paths  The PSR-4 base directories
+     * @param string       $prefix The prefix/namespace, with trailing '\\'
+     * @param array|string $paths  The PSR-4 base directories
      *
      * @throws \InvalidArgumentException
-     *
-     * @return void
      */
     public function setPsr4($prefix, $paths)
     {
@@ -319,8 +243,6 @@ class ClassLoader
      * Turns on searching the include path for class files.
      *
      * @param bool $useIncludePath
-     *
-     * @return void
      */
     public function setUseIncludePath($useIncludePath)
     {
@@ -343,8 +265,6 @@ class ClassLoader
      * that have not been registered with the class map.
      *
      * @param bool $classMapAuthoritative
-     *
-     * @return void
      */
     public function setClassMapAuthoritative($classMapAuthoritative)
     {
@@ -365,8 +285,6 @@ class ClassLoader
      * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
      *
      * @param string|null $apcuPrefix
-     *
-     * @return void
      */
     public function setApcuPrefix($apcuPrefix)
     {
@@ -387,8 +305,6 @@ class ClassLoader
      * Registers this instance as an autoloader.
      *
      * @param bool $prepend Whether to prepend the autoloader or not
-     *
-     * @return void
      */
     public function register($prepend = false)
     {
@@ -408,8 +324,6 @@ class ClassLoader
 
     /**
      * Unregisters this instance as an autoloader.
-     *
-     * @return void
      */
     public function unregister()
     {
@@ -429,8 +343,7 @@ class ClassLoader
     public function loadClass($class)
     {
         if ($file = $this->findFile($class)) {
-            $includeFile = self::$includeFile;
-            $includeFile($file);
+            includeFile($file);
 
             return true;
         }
@@ -490,11 +403,6 @@ class ClassLoader
         return self::$registeredLoaders;
     }
 
-    /**
-     * @param  string       $class
-     * @param  string       $ext
-     * @return string|false
-     */
     private function findFileWithExtension($class, $ext)
     {
         // PSR-4 lookup
@@ -560,26 +468,14 @@ class ClassLoader
 
         return false;
     }
+}
 
-    /**
-     * @return void
-     */
-    private static function initializeIncludeClosure()
-    {
-        if (self::$includeFile !== null) {
-            return;
-        }
-
-        /**
-         * Scope isolated include.
-         *
-         * Prevents access to $this/self from included files.
-         *
-         * @param  string $file
-         * @return void
-         */
-        self::$includeFile = \Closure::bind(static function($file) {
-            include $file;
-        }, null, null);
-    }
+/**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ */
+function includeFile($file)
+{
+    include $file;
 }

+ 11 - 33
vendor/composer/InstalledVersions.php

@@ -20,27 +20,12 @@ use Composer\Semver\VersionParser;
  *
  * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
  *
- * To require its presence, you can require `composer-runtime-api ^2.0`
- *
- * @final
+ * To require it's presence, you can require `composer-runtime-api ^2.0`
  */
 class InstalledVersions
 {
-    /**
-     * @var mixed[]|null
-     * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
-     */
     private static $installed;
-
-    /**
-     * @var bool|null
-     */
     private static $canGetVendors;
-
-    /**
-     * @var array[]
-     * @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
-     */
     private static $installedByVendor = array();
 
     /**
@@ -98,7 +83,7 @@ class InstalledVersions
     {
         foreach (self::getInstalled() as $installed) {
             if (isset($installed['versions'][$packageName])) {
-                return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
+                return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
             }
         }
 
@@ -119,7 +104,7 @@ class InstalledVersions
      */
     public static function satisfies(VersionParser $parser, $packageName, $constraint)
     {
-        $constraint = $parser->parseConstraints((string) $constraint);
+        $constraint = $parser->parseConstraints($constraint);
         $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
 
         return $provided->matches($constraint);
@@ -243,7 +228,7 @@ class InstalledVersions
 
     /**
      * @return array
-     * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
+     * @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}
      */
     public static function getRootPackage()
     {
@@ -257,7 +242,7 @@ class InstalledVersions
      *
      * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
      * @return array[]
-     * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
+     * @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>}
      */
     public static function getRawData()
     {
@@ -280,7 +265,7 @@ class InstalledVersions
      * Returns the raw data of all installed.php which are currently loaded for custom implementations
      *
      * @return array[]
-     * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
+     * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>}>
      */
     public static function getAllRawData()
     {
@@ -303,7 +288,7 @@ class InstalledVersions
      * @param  array[] $data A vendor/composer/installed.php data set
      * @return void
      *
-     * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
+     * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>} $data
      */
     public static function reload($data)
     {
@@ -313,7 +298,7 @@ class InstalledVersions
 
     /**
      * @return array[]
-     * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
+     * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>}>
      */
     private static function getInstalled()
     {
@@ -328,9 +313,7 @@ class InstalledVersions
                 if (isset(self::$installedByVendor[$vendorDir])) {
                     $installed[] = self::$installedByVendor[$vendorDir];
                 } elseif (is_file($vendorDir.'/composer/installed.php')) {
-                    /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
-                    $required = require $vendorDir.'/composer/installed.php';
-                    $installed[] = self::$installedByVendor[$vendorDir] = $required;
+                    $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
                     if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
                         self::$installed = $installed[count($installed) - 1];
                     }
@@ -342,17 +325,12 @@ class InstalledVersions
             // only require the installed.php file if this file is loaded from its dumped location,
             // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
             if (substr(__DIR__, -8, 1) !== 'C') {
-                /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
-                $required = require __DIR__ . '/installed.php';
-                self::$installed = $required;
+                self::$installed = require __DIR__ . '/installed.php';
             } else {
                 self::$installed = array();
             }
         }
-
-        if (self::$installed !== array()) {
-            $installed[] = self::$installed;
-        }
+        $installed[] = self::$installed;
 
         return $installed;
     }

+ 1 - 1
vendor/composer/autoload_classmap.php

@@ -2,7 +2,7 @@
 
 // autoload_classmap.php @generated by Composer
 
-$vendorDir = dirname(__DIR__);
+$vendorDir = dirname(dirname(__FILE__));
 $baseDir = dirname($vendorDir);
 
 return array(

+ 8 - 7
vendor/composer/autoload_files.php

@@ -2,21 +2,22 @@
 
 // autoload_files.php @generated by Composer
 
-$vendorDir = dirname(__DIR__);
+$vendorDir = dirname(dirname(__FILE__));
 $baseDir = dirname($vendorDir);
 
 return array(
-    '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
     'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
-    '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
+    '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
     '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
-    '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
+    '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
     '0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php',
-    '2cffec82183ee1cea088009cef9a6fc3' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',
+    '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
     '9b552a3cc426e3287cc811caefa3cf53' => $vendorDir . '/topthink/think-helper/src/helper.php',
+    '2cffec82183ee1cea088009cef9a6fc3' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',
     '488987c28e9b5e95a1ce6b6bcb94606c' => $vendorDir . '/karsonzhang/fastadmin-addons/src/common.php',
-    'f0e7e63bbb278a92db02393536748c5f' => $vendorDir . '/overtrue/wechat/src/Kernel/Support/Helpers.php',
-    '6747f579ad6817f318cc3a7e7a0abb93' => $vendorDir . '/overtrue/wechat/src/Kernel/Helpers.php',
     '1cfd2761b63b0a29ed23657ea394cb2d' => $vendorDir . '/topthink/think-captcha/src/helper.php',
     'cc56288302d9df745d97c934d6a6e5f0' => $vendorDir . '/topthink/think-queue/src/common.php',
+    '0f5bcb416d34db1c9ad4167f1809a53c' => $vendorDir . '/maniac/easemob-php/src/functions.php',
+    'f0e7e63bbb278a92db02393536748c5f' => $vendorDir . '/overtrue/wechat/src/Kernel/Support/Helpers.php',
+    '6747f579ad6817f318cc3a7e7a0abb93' => $vendorDir . '/overtrue/wechat/src/Kernel/Helpers.php',
 );

+ 1 - 1
vendor/composer/autoload_namespaces.php

@@ -2,7 +2,7 @@
 
 // autoload_namespaces.php @generated by Composer
 
-$vendorDir = dirname(__DIR__);
+$vendorDir = dirname(dirname(__FILE__));
 $baseDir = dirname($vendorDir);
 
 return array(

+ 4 - 2
vendor/composer/autoload_psr4.php

@@ -2,14 +2,15 @@
 
 // autoload_psr4.php @generated by Composer
 
-$vendorDir = dirname(__DIR__);
+$vendorDir = dirname(dirname(__FILE__));
 $baseDir = dirname($vendorDir);
 
 return array(
     'think\\helper\\' => array($vendorDir . '/topthink/think-helper/src'),
     'think\\composer\\' => array($vendorDir . '/topthink/think-installer/src'),
     'think\\captcha\\' => array($vendorDir . '/topthink/think-captcha/src'),
-    'think\\' => array($baseDir . '/thinkphp/library/think', $vendorDir . '/topthink/think-queue/src', $vendorDir . '/karsonzhang/fastadmin-addons/src'),
+    'think\\' => array($vendorDir . '/karsonzhang/fastadmin-addons/src', $baseDir . '/thinkphp/library/think', $vendorDir . '/topthink/think-queue/src'),
+    'tests\\' => array($vendorDir . '/maniac/easemob-php/tests'),
     'ZipStream\\' => array($vendorDir . '/maennchen/zipstream-php/src'),
     'Tx\\' => array($vendorDir . '/txthinking/mailer/src'),
     'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
@@ -43,5 +44,6 @@ return array(
     'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
     'EasyWeChat\\' => array($vendorDir . '/overtrue/wechat/src'),
     'EasyWeChatComposer\\' => array($vendorDir . '/easywechat-composer/easywechat-composer/src'),
+    'Easemob\\' => array($vendorDir . '/maniac/easemob-php/src'),
     'Complex\\' => array($vendorDir . '/markbaker/complex/classes/src'),
 );

+ 37 - 12
vendor/composer/autoload_real.php

@@ -25,26 +25,51 @@ class ComposerAutoloaderInitf3106b6ef3260b6914241eab0bed11c1
         require __DIR__ . '/platform_check.php';
 
         spl_autoload_register(array('ComposerAutoloaderInitf3106b6ef3260b6914241eab0bed11c1', 'loadClassLoader'), true, true);
-        self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
+        self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
         spl_autoload_unregister(array('ComposerAutoloaderInitf3106b6ef3260b6914241eab0bed11c1', 'loadClassLoader'));
 
-        require __DIR__ . '/autoload_static.php';
-        call_user_func(\Composer\Autoload\ComposerStaticInitf3106b6ef3260b6914241eab0bed11c1::getInitializer($loader));
+        $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
+        if ($useStaticLoader) {
+            require __DIR__ . '/autoload_static.php';
 
-        $loader->register(true);
+            call_user_func(\Composer\Autoload\ComposerStaticInitf3106b6ef3260b6914241eab0bed11c1::getInitializer($loader));
+        } else {
+            $map = require __DIR__ . '/autoload_namespaces.php';
+            foreach ($map as $namespace => $path) {
+                $loader->set($namespace, $path);
+            }
 
-        $filesToLoad = \Composer\Autoload\ComposerStaticInitf3106b6ef3260b6914241eab0bed11c1::$files;
-        $requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
-            if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
-                $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
+            $map = require __DIR__ . '/autoload_psr4.php';
+            foreach ($map as $namespace => $path) {
+                $loader->setPsr4($namespace, $path);
+            }
 
-                require $file;
+            $classMap = require __DIR__ . '/autoload_classmap.php';
+            if ($classMap) {
+                $loader->addClassMap($classMap);
             }
-        }, null, null);
-        foreach ($filesToLoad as $fileIdentifier => $file) {
-            $requireFile($fileIdentifier, $file);
+        }
+
+        $loader->register(true);
+
+        if ($useStaticLoader) {
+            $includeFiles = Composer\Autoload\ComposerStaticInitf3106b6ef3260b6914241eab0bed11c1::$files;
+        } else {
+            $includeFiles = require __DIR__ . '/autoload_files.php';
+        }
+        foreach ($includeFiles as $fileIdentifier => $file) {
+            composerRequiref3106b6ef3260b6914241eab0bed11c1($fileIdentifier, $file);
         }
 
         return $loader;
     }
 }
+
+function composerRequiref3106b6ef3260b6914241eab0bed11c1($fileIdentifier, $file)
+{
+    if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
+        require $file;
+
+        $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
+    }
+}

+ 20 - 9
vendor/composer/autoload_static.php

@@ -7,19 +7,20 @@ namespace Composer\Autoload;
 class ComposerStaticInitf3106b6ef3260b6914241eab0bed11c1
 {
     public static $files = array (
-        '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
         'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
-        '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
+        '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
         '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
-        '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
+        '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
         '0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php',
-        '2cffec82183ee1cea088009cef9a6fc3' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',
+        '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
         '9b552a3cc426e3287cc811caefa3cf53' => __DIR__ . '/..' . '/topthink/think-helper/src/helper.php',
+        '2cffec82183ee1cea088009cef9a6fc3' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',
         '488987c28e9b5e95a1ce6b6bcb94606c' => __DIR__ . '/..' . '/karsonzhang/fastadmin-addons/src/common.php',
-        'f0e7e63bbb278a92db02393536748c5f' => __DIR__ . '/..' . '/overtrue/wechat/src/Kernel/Support/Helpers.php',
-        '6747f579ad6817f318cc3a7e7a0abb93' => __DIR__ . '/..' . '/overtrue/wechat/src/Kernel/Helpers.php',
         '1cfd2761b63b0a29ed23657ea394cb2d' => __DIR__ . '/..' . '/topthink/think-captcha/src/helper.php',
         'cc56288302d9df745d97c934d6a6e5f0' => __DIR__ . '/..' . '/topthink/think-queue/src/common.php',
+        '0f5bcb416d34db1c9ad4167f1809a53c' => __DIR__ . '/..' . '/maniac/easemob-php/src/functions.php',
+        'f0e7e63bbb278a92db02393536748c5f' => __DIR__ . '/..' . '/overtrue/wechat/src/Kernel/Support/Helpers.php',
+        '6747f579ad6817f318cc3a7e7a0abb93' => __DIR__ . '/..' . '/overtrue/wechat/src/Kernel/Helpers.php',
     );
 
     public static $prefixLengthsPsr4 = array (
@@ -29,6 +30,7 @@ class ComposerStaticInitf3106b6ef3260b6914241eab0bed11c1
             'think\\composer\\' => 15,
             'think\\captcha\\' => 14,
             'think\\' => 6,
+            'tests\\' => 6,
         ),
         'Z' => 
         array (
@@ -86,6 +88,7 @@ class ComposerStaticInitf3106b6ef3260b6914241eab0bed11c1
         array (
             'EasyWeChat\\' => 11,
             'EasyWeChatComposer\\' => 19,
+            'Easemob\\' => 8,
         ),
         'C' => 
         array (
@@ -108,9 +111,13 @@ class ComposerStaticInitf3106b6ef3260b6914241eab0bed11c1
         ),
         'think\\' => 
         array (
-            0 => __DIR__ . '/../..' . '/thinkphp/library/think',
-            1 => __DIR__ . '/..' . '/topthink/think-queue/src',
-            2 => __DIR__ . '/..' . '/karsonzhang/fastadmin-addons/src',
+            0 => __DIR__ . '/..' . '/karsonzhang/fastadmin-addons/src',
+            1 => __DIR__ . '/../..' . '/thinkphp/library/think',
+            2 => __DIR__ . '/..' . '/topthink/think-queue/src',
+        ),
+        'tests\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/maniac/easemob-php/tests',
         ),
         'ZipStream\\' => 
         array (
@@ -245,6 +252,10 @@ class ComposerStaticInitf3106b6ef3260b6914241eab0bed11c1
         array (
             0 => __DIR__ . '/..' . '/easywechat-composer/easywechat-composer/src',
         ),
+        'Easemob\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/maniac/easemob-php/src',
+        ),
         'Complex\\' => 
         array (
             0 => __DIR__ . '/..' . '/markbaker/complex/classes/src',

+ 132 - 43
vendor/composer/installed.json

@@ -53,21 +53,27 @@
         },
         {
             "name": "ezyang/htmlpurifier",
-            "version": "v4.17.0",
-            "version_normalized": "4.17.0.0",
+            "version": "v4.16.0",
+            "version_normalized": "4.16.0.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/ezyang/htmlpurifier.git",
-                "reference": "bbc513d79acf6691fa9cf10f192c90dd2957f18c"
+                "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/bbc513d79acf6691fa9cf10f192c90dd2957f18c",
-                "reference": "bbc513d79acf6691fa9cf10f192c90dd2957f18c",
-                "shasum": ""
+                "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/523407fb06eb9e5f3d59889b3978d5bfe94299c8",
+                "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
             },
             "require": {
-                "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0"
+                "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0"
             },
             "require-dev": {
                 "cerdic/css-tidy": "^1.7 || ^2.0",
@@ -79,7 +85,7 @@
                 "ext-iconv": "Converts text to and from non-UTF-8 encodings",
                 "ext-tidy": "Used for pretty-printing HTML"
             },
-            "time": "2023-11-17T15:01:25+00:00",
+            "time": "2022-09-18T07:06:19+00:00",
             "type": "library",
             "installation-source": "dist",
             "autoload": {
@@ -111,7 +117,7 @@
             ],
             "support": {
                 "issues": "https://github.com/ezyang/htmlpurifier/issues",
-                "source": "https://github.com/ezyang/htmlpurifier/tree/v4.17.0"
+                "source": "https://github.com/ezyang/htmlpurifier/tree/v4.16.0"
             },
             "install-path": "../ezyang/htmlpurifier"
         },
@@ -588,6 +594,59 @@
             "install-path": "../maennchen/zipstream-php"
         },
         {
+            "name": "maniac/easemob-php",
+            "version": "1.0.0",
+            "version_normalized": "1.0.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/easemob/im-php-server-sdk.git",
+                "reference": "36b550328c9911957becde2fd62b9379ba45865c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/easemob/im-php-server-sdk/zipball/36b550328c9911957becde2fd62b9379ba45865c",
+                "reference": "36b550328c9911957becde2fd62b9379ba45865c",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "time": "2022-06-13T07:32:04+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "files": [
+                    "src/functions.php"
+                ],
+                "psr-4": {
+                    "tests\\": "tests/",
+                    "Easemob\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "maniac",
+                    "email": "maniac.liu@easemob.com"
+                }
+            ],
+            "description": "PHP Server SDK for IM.",
+            "support": {
+                "issues": "https://github.com/easemob/im-php-server-sdk/issues",
+                "source": "https://github.com/easemob/im-php-server-sdk/tree/1.0.0"
+            },
+            "install-path": "../maniac/easemob-php"
+        },
+        {
             "name": "markbaker/complex",
             "version": "3.0.2",
             "version_normalized": "3.0.2.0",
@@ -1065,18 +1124,24 @@
         },
         {
             "name": "overtrue/wechat",
-            "version": "4.9.0",
-            "version_normalized": "4.9.0.0",
+            "version": "4.6.0",
+            "version_normalized": "4.6.0.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/w7corp/easywechat.git",
-                "reference": "92791f5d957269c633b9aa175f842f6006f945b1"
+                "reference": "52af4cbe777cd4aea307beafa0a4518c347467b1"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/w7corp/easywechat/zipball/92791f5d957269c633b9aa175f842f6006f945b1",
-                "reference": "92791f5d957269c633b9aa175f842f6006f945b1",
-                "shasum": ""
+                "url": "https://api.github.com/repos/w7corp/easywechat/zipball/52af4cbe777cd4aea307beafa0a4518c347467b1",
+                "reference": "52af4cbe777cd4aea307beafa0a4518c347467b1",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
             },
             "require": {
                 "easywechat-composer/easywechat-composer": "^1.1",
@@ -1101,7 +1166,7 @@
                 "phpstan/phpstan": "^0.12.0",
                 "phpunit/phpunit": "^7.5"
             },
-            "time": "2023-04-28T03:30:34+00:00",
+            "time": "2022-08-24T07:30:42+00:00",
             "type": "library",
             "installation-source": "dist",
             "autoload": {
@@ -1133,7 +1198,7 @@
             ],
             "support": {
                 "issues": "https://github.com/w7corp/easywechat/issues",
-                "source": "https://github.com/w7corp/easywechat/tree/4.9.0"
+                "source": "https://github.com/w7corp/easywechat/tree/4.6.0"
             },
             "funding": [
                 {
@@ -2215,25 +2280,31 @@
         },
         {
             "name": "symfony/finder",
-            "version": "v5.4.35",
-            "version_normalized": "5.4.35.0",
+            "version": "v5.4.27",
+            "version_normalized": "5.4.27.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/finder.git",
-                "reference": "abe6d6f77d9465fed3cd2d029b29d03b56b56435"
+                "reference": "ff4bce3c33451e7ec778070e45bd23f74214cd5d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/finder/zipball/abe6d6f77d9465fed3cd2d029b29d03b56b56435",
-                "reference": "abe6d6f77d9465fed3cd2d029b29d03b56b56435",
-                "shasum": ""
+                "url": "https://api.github.com/repos/symfony/finder/zipball/ff4bce3c33451e7ec778070e45bd23f74214cd5d",
+                "reference": "ff4bce3c33451e7ec778070e45bd23f74214cd5d",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
             },
             "require": {
                 "php": ">=7.2.5",
                 "symfony/deprecation-contracts": "^2.1|^3",
                 "symfony/polyfill-php80": "^1.16"
             },
-            "time": "2024-01-23T13:51:25+00:00",
+            "time": "2023-07-31T08:02:31+00:00",
             "type": "library",
             "installation-source": "dist",
             "autoload": {
@@ -2261,7 +2332,7 @@
             "description": "Finds files and directories via an intuitive fluent interface",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/finder/tree/v5.4.35"
+                "source": "https://github.com/symfony/finder/tree/v5.4.27"
             },
             "funding": [
                 {
@@ -2360,18 +2431,24 @@
         },
         {
             "name": "symfony/polyfill-mbstring",
-            "version": "v1.29.0",
-            "version_normalized": "1.29.0.0",
+            "version": "v1.28.0",
+            "version_normalized": "1.28.0.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-mbstring.git",
-                "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec"
+                "reference": "42292d99c55abe617799667f454222c54c60e229"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
-                "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
-                "shasum": ""
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229",
+                "reference": "42292d99c55abe617799667f454222c54c60e229",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
             },
             "require": {
                 "php": ">=7.1"
@@ -2382,9 +2459,12 @@
             "suggest": {
                 "ext-mbstring": "For best performance"
             },
-            "time": "2024-01-29T20:11:03+00:00",
+            "time": "2023-07-28T09:04:16+00:00",
             "type": "library",
             "extra": {
+                "branch-alias": {
+                    "dev-main": "1.28-dev"
+                },
                 "thanks": {
                     "name": "symfony/polyfill",
                     "url": "https://github.com/symfony/polyfill"
@@ -2423,7 +2503,7 @@
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0"
+                "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0"
             },
             "funding": [
                 {
@@ -2443,25 +2523,34 @@
         },
         {
             "name": "symfony/polyfill-php73",
-            "version": "v1.29.0",
-            "version_normalized": "1.29.0.0",
+            "version": "v1.28.0",
+            "version_normalized": "1.28.0.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-php73.git",
-                "reference": "21bd091060673a1177ae842c0ef8fe30893114d2"
+                "reference": "fe2f306d1d9d346a7fee353d0d5012e401e984b5"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/21bd091060673a1177ae842c0ef8fe30893114d2",
-                "reference": "21bd091060673a1177ae842c0ef8fe30893114d2",
-                "shasum": ""
+                "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fe2f306d1d9d346a7fee353d0d5012e401e984b5",
+                "reference": "fe2f306d1d9d346a7fee353d0d5012e401e984b5",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
             },
             "require": {
                 "php": ">=7.1"
             },
-            "time": "2024-01-29T20:11:03+00:00",
+            "time": "2023-01-26T09:26:14+00:00",
             "type": "library",
             "extra": {
+                "branch-alias": {
+                    "dev-main": "1.28-dev"
+                },
                 "thanks": {
                     "name": "symfony/polyfill",
                     "url": "https://github.com/symfony/polyfill"
@@ -2502,7 +2591,7 @@
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-php73/tree/v1.29.0"
+                "source": "https://github.com/symfony/polyfill-php73/tree/v1.28.0"
             },
             "funding": [
                 {
@@ -2842,7 +2931,7 @@
             "source": {
                 "type": "git",
                 "url": "https://gitee.com/fastadminnet/framework.git",
-                "reference": "3883e15817ab39f2d342ca007bc8f5b6d8b6f508"
+                "reference": "c79fdd579e3b87c96692ff1924dac324fa60d7bd"
             },
             "require": {
                 "php": ">=7.1.0",
@@ -2856,7 +2945,7 @@
                 "phpunit/phpunit": "4.8.*",
                 "sebastian/phpcpd": "2.*"
             },
-            "time": "2024-03-26T09:19:29+00:00",
+            "time": "2024-04-11T02:40:58+00:00",
             "default-branch": true,
             "type": "think-framework",
             "installation-source": "source",

+ 71 - 62
vendor/composer/installed.php

@@ -1,184 +1,193 @@
 <?php return array(
     'root' => array(
-        'name' => 'karsonzhang/fastadmin',
-        'pretty_version' => '1.x-dev',
-        'version' => '1.9999999.9999999.9999999-dev',
-        'reference' => 'e8a804afada3b8f71627a1c4f2734f2656099415',
+        'pretty_version' => 'dev-master',
+        'version' => 'dev-master',
         'type' => 'project',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
+        'reference' => 'fb06ef7a16e89c00a8ecec2b9b9b3129459a963b',
+        'name' => 'karsonzhang/fastadmin',
         'dev' => true,
     ),
     'versions' => array(
         'easywechat-composer/easywechat-composer' => array(
             'pretty_version' => '1.4.1',
             'version' => '1.4.1.0',
-            'reference' => '3fc6a7ab6d3853c0f4e2922539b56cc37ef361cd',
             'type' => 'composer-plugin',
             'install_path' => __DIR__ . '/../easywechat-composer/easywechat-composer',
             'aliases' => array(),
+            'reference' => '3fc6a7ab6d3853c0f4e2922539b56cc37ef361cd',
             'dev_requirement' => false,
         ),
         'ezyang/htmlpurifier' => array(
-            'pretty_version' => 'v4.17.0',
-            'version' => '4.17.0.0',
-            'reference' => 'bbc513d79acf6691fa9cf10f192c90dd2957f18c',
+            'pretty_version' => 'v4.16.0',
+            'version' => '4.16.0.0',
             'type' => 'library',
             'install_path' => __DIR__ . '/../ezyang/htmlpurifier',
             'aliases' => array(),
+            'reference' => '523407fb06eb9e5f3d59889b3978d5bfe94299c8',
             'dev_requirement' => false,
         ),
         'guzzlehttp/guzzle' => array(
             'pretty_version' => '7.8.1',
             'version' => '7.8.1.0',
-            'reference' => '41042bc7ab002487b876a0683fc8dce04ddce104',
             'type' => 'library',
             'install_path' => __DIR__ . '/../guzzlehttp/guzzle',
             'aliases' => array(),
+            'reference' => '41042bc7ab002487b876a0683fc8dce04ddce104',
             'dev_requirement' => false,
         ),
         'guzzlehttp/promises' => array(
             'pretty_version' => '2.0.2',
             'version' => '2.0.2.0',
-            'reference' => 'bbff78d96034045e58e13dedd6ad91b5d1253223',
             'type' => 'library',
             'install_path' => __DIR__ . '/../guzzlehttp/promises',
             'aliases' => array(),
+            'reference' => 'bbff78d96034045e58e13dedd6ad91b5d1253223',
             'dev_requirement' => false,
         ),
         'guzzlehttp/psr7' => array(
             'pretty_version' => '2.6.2',
             'version' => '2.6.2.0',
-            'reference' => '45b30f99ac27b5ca93cb4831afe16285f57b8221',
             'type' => 'library',
             'install_path' => __DIR__ . '/../guzzlehttp/psr7',
             'aliases' => array(),
+            'reference' => '45b30f99ac27b5ca93cb4831afe16285f57b8221',
             'dev_requirement' => false,
         ),
         'karsonzhang/fastadmin' => array(
-            'pretty_version' => '1.x-dev',
-            'version' => '1.9999999.9999999.9999999-dev',
-            'reference' => 'e8a804afada3b8f71627a1c4f2734f2656099415',
+            'pretty_version' => 'dev-master',
+            'version' => 'dev-master',
             'type' => 'project',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),
+            'reference' => 'fb06ef7a16e89c00a8ecec2b9b9b3129459a963b',
             'dev_requirement' => false,
         ),
         'karsonzhang/fastadmin-addons' => array(
             'pretty_version' => '1.4.0',
             'version' => '1.4.0.0',
-            'reference' => '12b0b146bbdcb12c9f50c96baa3b7cc5f4c48ad6',
             'type' => 'library',
             'install_path' => __DIR__ . '/../karsonzhang/fastadmin-addons',
             'aliases' => array(),
+            'reference' => '12b0b146bbdcb12c9f50c96baa3b7cc5f4c48ad6',
             'dev_requirement' => false,
         ),
         'maennchen/zipstream-php' => array(
             'pretty_version' => '2.2.6',
             'version' => '2.2.6.0',
-            'reference' => '30ad6f93cf3efe4192bc7a4c9cad11ff8f4f237f',
             'type' => 'library',
             'install_path' => __DIR__ . '/../maennchen/zipstream-php',
             'aliases' => array(),
+            'reference' => '30ad6f93cf3efe4192bc7a4c9cad11ff8f4f237f',
+            'dev_requirement' => false,
+        ),
+        'maniac/easemob-php' => array(
+            'pretty_version' => '1.0.0',
+            'version' => '1.0.0.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../maniac/easemob-php',
+            'aliases' => array(),
+            'reference' => '36b550328c9911957becde2fd62b9379ba45865c',
             'dev_requirement' => false,
         ),
         'markbaker/complex' => array(
             'pretty_version' => '3.0.2',
             'version' => '3.0.2.0',
-            'reference' => '95c56caa1cf5c766ad6d65b6344b807c1e8405b9',
             'type' => 'library',
             'install_path' => __DIR__ . '/../markbaker/complex',
             'aliases' => array(),
+            'reference' => '95c56caa1cf5c766ad6d65b6344b807c1e8405b9',
             'dev_requirement' => false,
         ),
         'markbaker/matrix' => array(
             'pretty_version' => '3.0.1',
             'version' => '3.0.1.0',
-            'reference' => '728434227fe21be27ff6d86621a1b13107a2562c',
             'type' => 'library',
             'install_path' => __DIR__ . '/../markbaker/matrix',
             'aliases' => array(),
+            'reference' => '728434227fe21be27ff6d86621a1b13107a2562c',
             'dev_requirement' => false,
         ),
         'monolog/monolog' => array(
             'pretty_version' => '2.9.2',
             'version' => '2.9.2.0',
-            'reference' => '437cb3628f4cf6042cc10ae97fc2b8472e48ca1f',
             'type' => 'library',
             'install_path' => __DIR__ . '/../monolog/monolog',
             'aliases' => array(),
+            'reference' => '437cb3628f4cf6042cc10ae97fc2b8472e48ca1f',
             'dev_requirement' => false,
         ),
         'myclabs/php-enum' => array(
             'pretty_version' => '1.8.4',
             'version' => '1.8.4.0',
-            'reference' => 'a867478eae49c9f59ece437ae7f9506bfaa27483',
             'type' => 'library',
             'install_path' => __DIR__ . '/../myclabs/php-enum',
             'aliases' => array(),
+            'reference' => 'a867478eae49c9f59ece437ae7f9506bfaa27483',
             'dev_requirement' => false,
         ),
         'nelexa/zip' => array(
             'pretty_version' => '4.0.2',
             'version' => '4.0.2.0',
-            'reference' => '88a1b6549be813278ff2dd3b6b2ac188827634a7',
             'type' => 'library',
             'install_path' => __DIR__ . '/../nelexa/zip',
             'aliases' => array(),
+            'reference' => '88a1b6549be813278ff2dd3b6b2ac188827634a7',
             'dev_requirement' => false,
         ),
         'overtrue/pinyin' => array(
             'pretty_version' => '3.0.6',
             'version' => '3.0.6.0',
-            'reference' => '3b781d267197b74752daa32814d3a2cf5d140779',
             'type' => 'library',
             'install_path' => __DIR__ . '/../overtrue/pinyin',
             'aliases' => array(),
+            'reference' => '3b781d267197b74752daa32814d3a2cf5d140779',
             'dev_requirement' => false,
         ),
         'overtrue/socialite' => array(
             'pretty_version' => '2.0.24',
             'version' => '2.0.24.0',
-            'reference' => 'ee7e7b000ec7d64f2b8aba1f6a2eec5cdf3f8bec',
             'type' => 'library',
             'install_path' => __DIR__ . '/../overtrue/socialite',
             'aliases' => array(),
+            'reference' => 'ee7e7b000ec7d64f2b8aba1f6a2eec5cdf3f8bec',
             'dev_requirement' => false,
         ),
         'overtrue/wechat' => array(
-            'pretty_version' => '4.9.0',
-            'version' => '4.9.0.0',
-            'reference' => '92791f5d957269c633b9aa175f842f6006f945b1',
+            'pretty_version' => '4.6.0',
+            'version' => '4.6.0.0',
             'type' => 'library',
             'install_path' => __DIR__ . '/../overtrue/wechat',
             'aliases' => array(),
+            'reference' => '52af4cbe777cd4aea307beafa0a4518c347467b1',
             'dev_requirement' => false,
         ),
         'phpoffice/phpspreadsheet' => array(
             'pretty_version' => '1.19.0',
             'version' => '1.19.0.0',
-            'reference' => 'a9ab55bfae02eecffb3df669a2e19ba0e2f04bbf',
             'type' => 'library',
             'install_path' => __DIR__ . '/../phpoffice/phpspreadsheet',
             'aliases' => array(),
+            'reference' => 'a9ab55bfae02eecffb3df669a2e19ba0e2f04bbf',
             'dev_requirement' => false,
         ),
         'pimple/pimple' => array(
             'pretty_version' => 'v3.5.0',
             'version' => '3.5.0.0',
-            'reference' => 'a94b3a4db7fb774b3d78dad2315ddc07629e1bed',
             'type' => 'library',
             'install_path' => __DIR__ . '/../pimple/pimple',
             'aliases' => array(),
+            'reference' => 'a94b3a4db7fb774b3d78dad2315ddc07629e1bed',
             'dev_requirement' => false,
         ),
         'psr/cache' => array(
             'pretty_version' => '1.0.1',
             'version' => '1.0.1.0',
-            'reference' => 'd11b50ad223250cf17b86e38383413f5a6764bf8',
             'type' => 'library',
             'install_path' => __DIR__ . '/../psr/cache',
             'aliases' => array(),
+            'reference' => 'd11b50ad223250cf17b86e38383413f5a6764bf8',
             'dev_requirement' => false,
         ),
         'psr/cache-implementation' => array(
@@ -190,19 +199,19 @@
         'psr/container' => array(
             'pretty_version' => '2.0.2',
             'version' => '2.0.2.0',
-            'reference' => 'c71ecc56dfe541dbd90c5360474fbc405f8d5963',
             'type' => 'library',
             'install_path' => __DIR__ . '/../psr/container',
             'aliases' => array(),
+            'reference' => 'c71ecc56dfe541dbd90c5360474fbc405f8d5963',
             'dev_requirement' => false,
         ),
         'psr/event-dispatcher' => array(
             'pretty_version' => '1.0.0',
             'version' => '1.0.0.0',
-            'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0',
             'type' => 'library',
             'install_path' => __DIR__ . '/../psr/event-dispatcher',
             'aliases' => array(),
+            'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0',
             'dev_requirement' => false,
         ),
         'psr/event-dispatcher-implementation' => array(
@@ -214,10 +223,10 @@
         'psr/http-client' => array(
             'pretty_version' => '1.0.3',
             'version' => '1.0.3.0',
-            'reference' => 'bb5906edc1c324c9a05aa0873d40117941e5fa90',
             'type' => 'library',
             'install_path' => __DIR__ . '/../psr/http-client',
             'aliases' => array(),
+            'reference' => 'bb5906edc1c324c9a05aa0873d40117941e5fa90',
             'dev_requirement' => false,
         ),
         'psr/http-client-implementation' => array(
@@ -229,10 +238,10 @@
         'psr/http-factory' => array(
             'pretty_version' => '1.0.2',
             'version' => '1.0.2.0',
-            'reference' => 'e616d01114759c4c489f93b099585439f795fe35',
             'type' => 'library',
             'install_path' => __DIR__ . '/../psr/http-factory',
             'aliases' => array(),
+            'reference' => 'e616d01114759c4c489f93b099585439f795fe35',
             'dev_requirement' => false,
         ),
         'psr/http-factory-implementation' => array(
@@ -244,10 +253,10 @@
         'psr/http-message' => array(
             'pretty_version' => '1.1',
             'version' => '1.1.0.0',
-            'reference' => 'cb6ce4845ce34a8ad9e68117c10ee90a29919eba',
             'type' => 'library',
             'install_path' => __DIR__ . '/../psr/http-message',
             'aliases' => array(),
+            'reference' => 'cb6ce4845ce34a8ad9e68117c10ee90a29919eba',
             'dev_requirement' => false,
         ),
         'psr/http-message-implementation' => array(
@@ -259,10 +268,10 @@
         'psr/log' => array(
             'pretty_version' => '1.1.4',
             'version' => '1.1.4.0',
-            'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11',
             'type' => 'library',
             'install_path' => __DIR__ . '/../psr/log',
             'aliases' => array(),
+            'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11',
             'dev_requirement' => false,
         ),
         'psr/log-implementation' => array(
@@ -274,10 +283,10 @@
         'psr/simple-cache' => array(
             'pretty_version' => '1.0.1',
             'version' => '1.0.1.0',
-            'reference' => '408d5eafb83c57f6365a3ca330ff23aa4a5fa39b',
             'type' => 'library',
             'install_path' => __DIR__ . '/../psr/simple-cache',
             'aliases' => array(),
+            'reference' => '408d5eafb83c57f6365a3ca330ff23aa4a5fa39b',
             'dev_requirement' => false,
         ),
         'psr/simple-cache-implementation' => array(
@@ -289,28 +298,28 @@
         'ralouphie/getallheaders' => array(
             'pretty_version' => '3.0.3',
             'version' => '3.0.3.0',
-            'reference' => '120b605dfeb996808c31b6477290a714d356e822',
             'type' => 'library',
             'install_path' => __DIR__ . '/../ralouphie/getallheaders',
             'aliases' => array(),
+            'reference' => '120b605dfeb996808c31b6477290a714d356e822',
             'dev_requirement' => false,
         ),
         'symfony/cache' => array(
             'pretty_version' => 'v5.4.36',
             'version' => '5.4.36.0',
-            'reference' => 'a30f316214d908cf5874f700f3f3fb29ceee91ba',
             'type' => 'library',
             'install_path' => __DIR__ . '/../symfony/cache',
             'aliases' => array(),
+            'reference' => 'a30f316214d908cf5874f700f3f3fb29ceee91ba',
             'dev_requirement' => false,
         ),
         'symfony/cache-contracts' => array(
             'pretty_version' => 'v2.5.2',
             'version' => '2.5.2.0',
-            'reference' => '64be4a7acb83b6f2bf6de9a02cee6dad41277ebc',
             'type' => 'library',
             'install_path' => __DIR__ . '/../symfony/cache-contracts',
             'aliases' => array(),
+            'reference' => '64be4a7acb83b6f2bf6de9a02cee6dad41277ebc',
             'dev_requirement' => false,
         ),
         'symfony/cache-implementation' => array(
@@ -322,28 +331,28 @@
         'symfony/deprecation-contracts' => array(
             'pretty_version' => 'v2.5.2',
             'version' => '2.5.2.0',
-            'reference' => 'e8b495ea28c1d97b5e0c121748d6f9b53d075c66',
             'type' => 'library',
             'install_path' => __DIR__ . '/../symfony/deprecation-contracts',
             'aliases' => array(),
+            'reference' => 'e8b495ea28c1d97b5e0c121748d6f9b53d075c66',
             'dev_requirement' => false,
         ),
         'symfony/event-dispatcher' => array(
             'pretty_version' => 'v5.4.35',
             'version' => '5.4.35.0',
-            'reference' => '7a69a85c7ea5bdd1e875806a99c51a87d3a74b38',
             'type' => 'library',
             'install_path' => __DIR__ . '/../symfony/event-dispatcher',
             'aliases' => array(),
+            'reference' => '7a69a85c7ea5bdd1e875806a99c51a87d3a74b38',
             'dev_requirement' => false,
         ),
         'symfony/event-dispatcher-contracts' => array(
             'pretty_version' => 'v2.5.2',
             'version' => '2.5.2.0',
-            'reference' => 'f98b54df6ad059855739db6fcbc2d36995283fe1',
             'type' => 'library',
             'install_path' => __DIR__ . '/../symfony/event-dispatcher-contracts',
             'aliases' => array(),
+            'reference' => 'f98b54df6ad059855739db6fcbc2d36995283fe1',
             'dev_requirement' => false,
         ),
         'symfony/event-dispatcher-implementation' => array(
@@ -353,131 +362,131 @@
             ),
         ),
         'symfony/finder' => array(
-            'pretty_version' => 'v5.4.35',
-            'version' => '5.4.35.0',
-            'reference' => 'abe6d6f77d9465fed3cd2d029b29d03b56b56435',
+            'pretty_version' => 'v5.4.27',
+            'version' => '5.4.27.0',
             'type' => 'library',
             'install_path' => __DIR__ . '/../symfony/finder',
             'aliases' => array(),
+            'reference' => 'ff4bce3c33451e7ec778070e45bd23f74214cd5d',
             'dev_requirement' => false,
         ),
         'symfony/http-foundation' => array(
             'pretty_version' => 'v5.4.35',
             'version' => '5.4.35.0',
-            'reference' => 'f2ab692a22aef1cd54beb893aa0068bdfb093928',
             'type' => 'library',
             'install_path' => __DIR__ . '/../symfony/http-foundation',
             'aliases' => array(),
+            'reference' => 'f2ab692a22aef1cd54beb893aa0068bdfb093928',
             'dev_requirement' => false,
         ),
         'symfony/polyfill-mbstring' => array(
-            'pretty_version' => 'v1.29.0',
-            'version' => '1.29.0.0',
-            'reference' => '9773676c8a1bb1f8d4340a62efe641cf76eda7ec',
+            'pretty_version' => 'v1.28.0',
+            'version' => '1.28.0.0',
             'type' => 'library',
             'install_path' => __DIR__ . '/../symfony/polyfill-mbstring',
             'aliases' => array(),
+            'reference' => '42292d99c55abe617799667f454222c54c60e229',
             'dev_requirement' => false,
         ),
         'symfony/polyfill-php73' => array(
-            'pretty_version' => 'v1.29.0',
-            'version' => '1.29.0.0',
-            'reference' => '21bd091060673a1177ae842c0ef8fe30893114d2',
+            'pretty_version' => 'v1.28.0',
+            'version' => '1.28.0.0',
             'type' => 'library',
             'install_path' => __DIR__ . '/../symfony/polyfill-php73',
             'aliases' => array(),
+            'reference' => 'fe2f306d1d9d346a7fee353d0d5012e401e984b5',
             'dev_requirement' => false,
         ),
         'symfony/polyfill-php80' => array(
             'pretty_version' => 'v1.29.0',
             'version' => '1.29.0.0',
-            'reference' => '87b68208d5c1188808dd7839ee1e6c8ec3b02f1b',
             'type' => 'library',
             'install_path' => __DIR__ . '/../symfony/polyfill-php80',
             'aliases' => array(),
+            'reference' => '87b68208d5c1188808dd7839ee1e6c8ec3b02f1b',
             'dev_requirement' => false,
         ),
         'symfony/psr-http-message-bridge' => array(
             'pretty_version' => 'v2.3.1',
             'version' => '2.3.1.0',
-            'reference' => '581ca6067eb62640de5ff08ee1ba6850a0ee472e',
             'type' => 'symfony-bridge',
             'install_path' => __DIR__ . '/../symfony/psr-http-message-bridge',
             'aliases' => array(),
+            'reference' => '581ca6067eb62640de5ff08ee1ba6850a0ee472e',
             'dev_requirement' => false,
         ),
         'symfony/service-contracts' => array(
             'pretty_version' => 'v1.1.2',
             'version' => '1.1.2.0',
-            'reference' => '191afdcb5804db960d26d8566b7e9a2843cab3a0',
             'type' => 'library',
             'install_path' => __DIR__ . '/../symfony/service-contracts',
             'aliases' => array(),
+            'reference' => '191afdcb5804db960d26d8566b7e9a2843cab3a0',
             'dev_requirement' => false,
         ),
         'symfony/var-exporter' => array(
             'pretty_version' => 'v5.4.35',
             'version' => '5.4.35.0',
-            'reference' => 'abb0a151b62d6b07e816487e20040464af96cae7',
             'type' => 'library',
             'install_path' => __DIR__ . '/../symfony/var-exporter',
             'aliases' => array(),
+            'reference' => 'abb0a151b62d6b07e816487e20040464af96cae7',
             'dev_requirement' => false,
         ),
         'topthink/framework' => array(
             'pretty_version' => 'dev-master',
             'version' => 'dev-master',
-            'reference' => '3883e15817ab39f2d342ca007bc8f5b6d8b6f508',
             'type' => 'think-framework',
             'install_path' => __DIR__ . '/../../thinkphp',
             'aliases' => array(
                 0 => '9999999-dev',
             ),
+            'reference' => 'c79fdd579e3b87c96692ff1924dac324fa60d7bd',
             'dev_requirement' => false,
         ),
         'topthink/think-captcha' => array(
             'pretty_version' => 'v1.0.9',
             'version' => '1.0.9.0',
-            'reference' => '9be9dd7e61c7fa3c478c4b92910d7230b94d0d23',
             'type' => 'library',
             'install_path' => __DIR__ . '/../topthink/think-captcha',
             'aliases' => array(),
+            'reference' => '9be9dd7e61c7fa3c478c4b92910d7230b94d0d23',
             'dev_requirement' => false,
         ),
         'topthink/think-helper' => array(
             'pretty_version' => 'v1.0.7',
             'version' => '1.0.7.0',
-            'reference' => '5f92178606c8ce131d36b37a57c58eb71e55f019',
             'type' => 'library',
             'install_path' => __DIR__ . '/../topthink/think-helper',
             'aliases' => array(),
+            'reference' => '5f92178606c8ce131d36b37a57c58eb71e55f019',
             'dev_requirement' => false,
         ),
         'topthink/think-installer' => array(
             'pretty_version' => 'v1.0.14',
             'version' => '1.0.14.0',
-            'reference' => 'eae1740ac264a55c06134b6685dfb9f837d004d1',
             'type' => 'composer-plugin',
             'install_path' => __DIR__ . '/../topthink/think-installer',
             'aliases' => array(),
+            'reference' => 'eae1740ac264a55c06134b6685dfb9f837d004d1',
             'dev_requirement' => false,
         ),
         'topthink/think-queue' => array(
             'pretty_version' => 'v1.1.6',
             'version' => '1.1.6.0',
-            'reference' => '250650eb0e8ea5af4cfdc7ae46f3f4e0a24ac245',
             'type' => 'think-extend',
             'install_path' => __DIR__ . '/../topthink/think-queue',
             'aliases' => array(),
+            'reference' => '250650eb0e8ea5af4cfdc7ae46f3f4e0a24ac245',
             'dev_requirement' => false,
         ),
         'txthinking/mailer' => array(
             'pretty_version' => 'v2.0.1',
             'version' => '2.0.1.0',
-            'reference' => '09013cf9dad3aac195f66ae5309e8c3343c018e9',
             'type' => 'library',
             'install_path' => __DIR__ . '/../txthinking/mailer',
             'aliases' => array(),
+            'reference' => '09013cf9dad3aac195f66ae5309e8c3343c018e9',
             'dev_requirement' => false,
         ),
     ),

+ 6 - 0
vendor/ezyang/htmlpurifier/CHANGELOG.md

@@ -0,0 +1,6 @@
+# [4.16.0](https://github.com/ezyang/htmlpurifier/compare/v4.15.0...v4.16.0) (2022-09-18)
+
+
+### Features
+
+* add semantic release ([#307](https://github.com/ezyang/htmlpurifier/issues/307)) ([db31243](https://github.com/ezyang/htmlpurifier/commit/db312435cb9d8d73395f75f9642a43ba6de5e903)), closes [#322](https://github.com/ezyang/htmlpurifier/issues/322) [#323](https://github.com/ezyang/htmlpurifier/issues/323) [#326](https://github.com/ezyang/htmlpurifier/issues/326) [#327](https://github.com/ezyang/htmlpurifier/issues/327) [#328](https://github.com/ezyang/htmlpurifier/issues/328) [#329](https://github.com/ezyang/htmlpurifier/issues/329) [#330](https://github.com/ezyang/htmlpurifier/issues/330) [#331](https://github.com/ezyang/htmlpurifier/issues/331) [#332](https://github.com/ezyang/htmlpurifier/issues/332) [#333](https://github.com/ezyang/htmlpurifier/issues/333) [#337](https://github.com/ezyang/htmlpurifier/issues/337) [#335](https://github.com/ezyang/htmlpurifier/issues/335) [ezyang/htmlpurifier#334](https://github.com/ezyang/htmlpurifier/issues/334) [#336](https://github.com/ezyang/htmlpurifier/issues/336) [#338](https://github.com/ezyang/htmlpurifier/issues/338)

+ 1 - 1
vendor/ezyang/htmlpurifier/VERSION

@@ -1 +1 @@
-4.17.0
+4.15.0

+ 2 - 3
vendor/ezyang/htmlpurifier/composer.json

@@ -13,7 +13,7 @@
         }
     ],
     "require": {
-        "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0"
+        "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0"
     },
     "require-dev": {
         "cerdic/css-tidy": "^1.7 || ^2.0",
@@ -38,8 +38,7 @@
     "repositories": [
         {
             "type": "vcs",
-            "url": "https://github.com/ezyang/simpletest.git",
-            "no-api": true
+            "url": "https://github.com/ezyang/simpletest.git"
         }
     ]
 }

+ 1 - 1
vendor/ezyang/htmlpurifier/library/HTMLPurifier.includes.php

@@ -7,7 +7,7 @@
  * primary concern and you are using an opcode cache. PLEASE DO NOT EDIT THIS
  * FILE, changes will be overwritten the next time the script is run.
  *
- * @version 4.17.0
+ * @version 4.15.0
  *
  * @warning
  *      You must *not* include any other HTML Purifier files before this file,

+ 3 - 3
vendor/ezyang/htmlpurifier/library/HTMLPurifier.php

@@ -19,7 +19,7 @@
  */
 
 /*
-    HTML Purifier 4.17.0 - Standards Compliant HTML Filtering
+    HTML Purifier 4.15.0 - Standards Compliant HTML Filtering
     Copyright (C) 2006-2008 Edward Z. Yang
 
     This library is free software; you can redistribute it and/or
@@ -58,12 +58,12 @@ class HTMLPurifier
      * Version of HTML Purifier.
      * @type string
      */
-    public $version = '4.17.0';
+    public $version = '4.15.0';
 
     /**
      * Constant with version of HTML Purifier.
      */
-    const VERSION = '4.17.0';
+    const VERSION = '4.15.0';
 
     /**
      * Global configuration object.

+ 17 - 15
vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/FontFamily.php

@@ -10,21 +10,23 @@ class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef
 
     public function __construct()
     {
-        // Lowercase letters
-        $l = range('a', 'z');
-        // Uppercase letters
-        $u = range('A', 'Z');
-        // Digits
-        $d = range('0', '9');
-        // Special bytes used by UTF-8
-        $b = array_map('chr', range(0x80, 0xFF));
-        // All valid characters for the mask
-        $c = array_merge($l, $u, $d, $b);
-        // Concatenate all valid characters into a string 
-        // Use '_- ' as an initial value
-        $this->mask = array_reduce($c, function ($carry, $value) {
-            return $carry . $value;
-        }, '_- ');
+        $this->mask = '_- ';
+        for ($c = 'a'; $c <= 'z'; $c++) {
+            $this->mask .= $c;
+        }
+        for ($c = 'A'; $c <= 'Z'; $c++) {
+            $this->mask .= $c;
+        }
+        for ($c = '0'; $c <= '9'; $c++) {
+            $this->mask .= $c;
+        } // cast-y, but should be fine
+        // special bytes used by UTF-8
+        for ($i = 0x80; $i <= 0xFF; $i++) {
+            // We don't bother excluding invalid bytes in this range,
+            // because the our restriction of well-formed UTF-8 will
+            // prevent these from ever occurring.
+            $this->mask .= chr($i);
+        }
 
         /*
             PHP's internal strcspn implementation is

+ 1 - 1
vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Host.php

@@ -106,7 +106,7 @@ class HTMLPurifier_AttrDef_URI_Host extends HTMLPurifier_AttrDef
         // If we have Net_IDNA2 support, we can support IRIs by
         // punycoding them. (This is the most portable thing to do,
         // since otherwise we have to assume browsers support
-        } elseif ($config->get('Core.EnableIDNA') && class_exists('Net_IDNA2')) {
+        } elseif ($config->get('Core.EnableIDNA')) {
             $idna = new Net_IDNA2(array('encoding' => 'utf8', 'overlong' => false, 'strict' => true));
             // we need to encode each period separately
             $parts = explode('.', $string);

+ 1 - 5
vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetBlank.php

@@ -33,11 +33,7 @@ class HTMLPurifier_AttrTransform_TargetBlank extends HTMLPurifier_AttrTransform
 
         // XXX Kind of inefficient
         $url = $this->parser->parse($attr['href']);
-        
-        // Ignore invalid schemes (e.g. `javascript:`)
-        if (!($scheme = $url->getSchemeObj($config, $context))) {
-            return $attr;
-        }
+        $scheme = $url->getSchemeObj($config, $context);
 
         if ($scheme->browsable && !$url->isBenign($config, $context)) {
             $attr['target'] = '_blank';

+ 37 - 4
vendor/ezyang/htmlpurifier/library/HTMLPurifier/Bootstrap.php

@@ -79,11 +79,44 @@ class HTMLPurifier_Bootstrap
     public static function registerAutoload()
     {
         $autoload = array('HTMLPurifier_Bootstrap', 'autoload');
-        if (spl_autoload_functions() === false) {
+        if (($funcs = spl_autoload_functions()) === false) {
             spl_autoload_register($autoload);
-        } else {
-            // prepend flag exists, no need for shenanigans
-            spl_autoload_register($autoload, true, true);
+        } elseif (function_exists('spl_autoload_unregister')) {
+            if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
+                // prepend flag exists, no need for shenanigans
+                spl_autoload_register($autoload, true, true);
+            } else {
+                $buggy  = version_compare(PHP_VERSION, '5.2.11', '<');
+                $compat = version_compare(PHP_VERSION, '5.1.2', '<=') &&
+                          version_compare(PHP_VERSION, '5.1.0', '>=');
+                foreach ($funcs as $func) {
+                    if ($buggy && is_array($func)) {
+                        // :TRICKY: There are some compatibility issues and some
+                        // places where we need to error out
+                        $reflector = new ReflectionMethod($func[0], $func[1]);
+                        if (!$reflector->isStatic()) {
+                            throw new Exception(
+                                'HTML Purifier autoloader registrar is not compatible
+                                with non-static object methods due to PHP Bug #44144;
+                                Please do not use HTMLPurifier.autoload.php (or any
+                                file that includes this file); instead, place the code:
+                                spl_autoload_register(array(\'HTMLPurifier_Bootstrap\', \'autoload\'))
+                                after your own autoloaders.'
+                            );
+                        }
+                        // Suprisingly, spl_autoload_register supports the
+                        // Class::staticMethod callback format, although call_user_func doesn't
+                        if ($compat) {
+                            $func = implode('::', $func);
+                        }
+                    }
+                    spl_autoload_unregister($func);
+                }
+                spl_autoload_register($autoload);
+                foreach ($funcs as $func) {
+                    spl_autoload_register($func);
+                }
+            }
         }
     }
 }

+ 91 - 108
vendor/ezyang/htmlpurifier/library/HTMLPurifier/CSSDefinition.php

@@ -13,7 +13,7 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
      * Assoc array of attribute name to definition object.
      * @type HTMLPurifier_AttrDef[]
      */
-    public $info = [];
+    public $info = array();
 
     /**
      * Constructs the info array.  The meat of this class.
@@ -22,7 +22,7 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
     protected function doSetup($config)
     {
         $this->info['text-align'] = new HTMLPurifier_AttrDef_Enum(
-            ['left', 'right', 'center', 'justify'],
+            array('left', 'right', 'center', 'justify'),
             false
         );
 
@@ -31,7 +31,7 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
             $this->info['border-right-style'] =
             $this->info['border-left-style'] =
             $this->info['border-top-style'] = new HTMLPurifier_AttrDef_Enum(
-                [
+                array(
                     'none',
                     'hidden',
                     'dotted',
@@ -42,42 +42,42 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
                     'ridge',
                     'inset',
                     'outset'
-                ],
+                ),
                 false
             );
 
         $this->info['border-style'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_style);
 
         $this->info['clear'] = new HTMLPurifier_AttrDef_Enum(
-            ['none', 'left', 'right', 'both'],
+            array('none', 'left', 'right', 'both'),
             false
         );
         $this->info['float'] = new HTMLPurifier_AttrDef_Enum(
-            ['none', 'left', 'right'],
+            array('none', 'left', 'right'),
             false
         );
         $this->info['font-style'] = new HTMLPurifier_AttrDef_Enum(
-            ['normal', 'italic', 'oblique'],
+            array('normal', 'italic', 'oblique'),
             false
         );
         $this->info['font-variant'] = new HTMLPurifier_AttrDef_Enum(
-            ['normal', 'small-caps'],
+            array('normal', 'small-caps'),
             false
         );
 
         $uri_or_none = new HTMLPurifier_AttrDef_CSS_Composite(
-            [
-                new HTMLPurifier_AttrDef_Enum(['none']),
+            array(
+                new HTMLPurifier_AttrDef_Enum(array('none')),
                 new HTMLPurifier_AttrDef_CSS_URI()
-            ]
+            )
         );
 
         $this->info['list-style-position'] = new HTMLPurifier_AttrDef_Enum(
-            ['inside', 'outside'],
+            array('inside', 'outside'),
             false
         );
         $this->info['list-style-type'] = new HTMLPurifier_AttrDef_Enum(
-            [
+            array(
                 'disc',
                 'circle',
                 'square',
@@ -87,7 +87,7 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
                 'lower-alpha',
                 'upper-alpha',
                 'none'
-            ],
+            ),
             false
         );
         $this->info['list-style-image'] = $uri_or_none;
@@ -95,34 +95,34 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
         $this->info['list-style'] = new HTMLPurifier_AttrDef_CSS_ListStyle($config);
 
         $this->info['text-transform'] = new HTMLPurifier_AttrDef_Enum(
-            ['capitalize', 'uppercase', 'lowercase', 'none'],
+            array('capitalize', 'uppercase', 'lowercase', 'none'),
             false
         );
         $this->info['color'] = new HTMLPurifier_AttrDef_CSS_Color();
 
         $this->info['background-image'] = $uri_or_none;
         $this->info['background-repeat'] = new HTMLPurifier_AttrDef_Enum(
-            ['repeat', 'repeat-x', 'repeat-y', 'no-repeat']
+            array('repeat', 'repeat-x', 'repeat-y', 'no-repeat')
         );
         $this->info['background-attachment'] = new HTMLPurifier_AttrDef_Enum(
-            ['scroll', 'fixed']
+            array('scroll', 'fixed')
         );
         $this->info['background-position'] = new HTMLPurifier_AttrDef_CSS_BackgroundPosition();
 
         $this->info['background-size'] = new HTMLPurifier_AttrDef_CSS_Composite(
-            [
+            array(
                 new HTMLPurifier_AttrDef_Enum(
-                    [
+                    array(
                         'auto',
                         'cover',
                         'contain',
                         'initial',
                         'inherit',
-                    ]
+                    )
                 ),
                 new HTMLPurifier_AttrDef_CSS_Percentage(),
                 new HTMLPurifier_AttrDef_CSS_Length()
-            ]
+            )
         );
 
         $border_color =
@@ -131,10 +131,10 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
             $this->info['border-left-color'] =
             $this->info['border-right-color'] =
             $this->info['background-color'] = new HTMLPurifier_AttrDef_CSS_Composite(
-                [
-                    new HTMLPurifier_AttrDef_Enum(['transparent']),
+                array(
+                    new HTMLPurifier_AttrDef_Enum(array('transparent')),
                     new HTMLPurifier_AttrDef_CSS_Color()
-                ]
+                )
             );
 
         $this->info['background'] = new HTMLPurifier_AttrDef_CSS_Background($config);
@@ -146,32 +146,32 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
             $this->info['border-bottom-width'] =
             $this->info['border-left-width'] =
             $this->info['border-right-width'] = new HTMLPurifier_AttrDef_CSS_Composite(
-                [
-                    new HTMLPurifier_AttrDef_Enum(['thin', 'medium', 'thick']),
+                array(
+                    new HTMLPurifier_AttrDef_Enum(array('thin', 'medium', 'thick')),
                     new HTMLPurifier_AttrDef_CSS_Length('0') //disallow negative
-                ]
+                )
             );
 
         $this->info['border-width'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_width);
 
         $this->info['letter-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(
-            [
-                new HTMLPurifier_AttrDef_Enum(['normal']),
+            array(
+                new HTMLPurifier_AttrDef_Enum(array('normal')),
                 new HTMLPurifier_AttrDef_CSS_Length()
-            ]
+            )
         );
 
         $this->info['word-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(
-            [
-                new HTMLPurifier_AttrDef_Enum(['normal']),
+            array(
+                new HTMLPurifier_AttrDef_Enum(array('normal')),
                 new HTMLPurifier_AttrDef_CSS_Length()
-            ]
+            )
         );
 
         $this->info['font-size'] = new HTMLPurifier_AttrDef_CSS_Composite(
-            [
+            array(
                 new HTMLPurifier_AttrDef_Enum(
-                    [
+                    array(
                         'xx-small',
                         'x-small',
                         'small',
@@ -181,20 +181,20 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
                         'xx-large',
                         'larger',
                         'smaller'
-                    ]
+                    )
                 ),
                 new HTMLPurifier_AttrDef_CSS_Percentage(),
                 new HTMLPurifier_AttrDef_CSS_Length()
-            ]
+            )
         );
 
         $this->info['line-height'] = new HTMLPurifier_AttrDef_CSS_Composite(
-            [
-                new HTMLPurifier_AttrDef_Enum(['normal']),
+            array(
+                new HTMLPurifier_AttrDef_Enum(array('normal')),
                 new HTMLPurifier_AttrDef_CSS_Number(true), // no negatives
                 new HTMLPurifier_AttrDef_CSS_Length('0'),
                 new HTMLPurifier_AttrDef_CSS_Percentage(true)
-            ]
+            )
         );
 
         $margin =
@@ -202,11 +202,11 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
             $this->info['margin-bottom'] =
             $this->info['margin-left'] =
             $this->info['margin-right'] = new HTMLPurifier_AttrDef_CSS_Composite(
-                [
+                array(
                     new HTMLPurifier_AttrDef_CSS_Length(),
                     new HTMLPurifier_AttrDef_CSS_Percentage(),
-                    new HTMLPurifier_AttrDef_Enum(['auto'])
-                ]
+                    new HTMLPurifier_AttrDef_Enum(array('auto'))
+                )
             );
 
         $this->info['margin'] = new HTMLPurifier_AttrDef_CSS_Multiple($margin);
@@ -217,41 +217,41 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
             $this->info['padding-bottom'] =
             $this->info['padding-left'] =
             $this->info['padding-right'] = new HTMLPurifier_AttrDef_CSS_Composite(
-                [
+                array(
                     new HTMLPurifier_AttrDef_CSS_Length('0'),
                     new HTMLPurifier_AttrDef_CSS_Percentage(true)
-                ]
+                )
             );
 
         $this->info['padding'] = new HTMLPurifier_AttrDef_CSS_Multiple($padding);
 
         $this->info['text-indent'] = new HTMLPurifier_AttrDef_CSS_Composite(
-            [
+            array(
                 new HTMLPurifier_AttrDef_CSS_Length(),
                 new HTMLPurifier_AttrDef_CSS_Percentage()
-            ]
+            )
         );
 
         $trusted_wh = new HTMLPurifier_AttrDef_CSS_Composite(
-            [
+            array(
                 new HTMLPurifier_AttrDef_CSS_Length('0'),
                 new HTMLPurifier_AttrDef_CSS_Percentage(true),
-                new HTMLPurifier_AttrDef_Enum(['auto', 'initial', 'inherit'])
-            ]
+                new HTMLPurifier_AttrDef_Enum(array('auto', 'initial', 'inherit'))
+            )
         );
         $trusted_min_wh = new HTMLPurifier_AttrDef_CSS_Composite(
-            [
+            array(
                 new HTMLPurifier_AttrDef_CSS_Length('0'),
                 new HTMLPurifier_AttrDef_CSS_Percentage(true),
-                new HTMLPurifier_AttrDef_Enum(['initial', 'inherit'])
-            ]
+                new HTMLPurifier_AttrDef_Enum(array('initial', 'inherit'))
+            )
         );
         $trusted_max_wh = new HTMLPurifier_AttrDef_CSS_Composite(
-            [
+            array(
                 new HTMLPurifier_AttrDef_CSS_Length('0'),
                 new HTMLPurifier_AttrDef_CSS_Percentage(true),
-                new HTMLPurifier_AttrDef_Enum(['none', 'initial', 'inherit'])
-            ]
+                new HTMLPurifier_AttrDef_Enum(array('none', 'initial', 'inherit'))
+            )
         );
         $max = $config->get('CSS.MaxImgLength');
 
@@ -263,10 +263,10 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
                     'img',
                     // For img tags:
                     new HTMLPurifier_AttrDef_CSS_Composite(
-                        [
+                        array(
                             new HTMLPurifier_AttrDef_CSS_Length('0', $max),
-                            new HTMLPurifier_AttrDef_Enum(['auto'])
-                        ]
+                            new HTMLPurifier_AttrDef_Enum(array('auto'))
+                        )
                     ),
                     // For everyone else:
                     $trusted_wh
@@ -279,10 +279,10 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
                     'img',
                     // For img tags:
                     new HTMLPurifier_AttrDef_CSS_Composite(
-                        [
+                        array(
                             new HTMLPurifier_AttrDef_CSS_Length('0', $max),
-                            new HTMLPurifier_AttrDef_Enum(['initial', 'inherit'])
-                        ]
+                            new HTMLPurifier_AttrDef_Enum(array('initial', 'inherit'))
+                        )
                     ),
                     // For everyone else:
                     $trusted_min_wh
@@ -295,39 +295,22 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
                     'img',
                     // For img tags:
                     new HTMLPurifier_AttrDef_CSS_Composite(
-                        [
+                        array(
                             new HTMLPurifier_AttrDef_CSS_Length('0', $max),
-                            new HTMLPurifier_AttrDef_Enum(['none', 'initial', 'inherit'])
-                        ]
+                            new HTMLPurifier_AttrDef_Enum(array('none', 'initial', 'inherit'))
+                        )
                     ),
                     // For everyone else:
                     $trusted_max_wh
                 );
 
-        // text-decoration and related shorthands
         $this->info['text-decoration'] = new HTMLPurifier_AttrDef_CSS_TextDecoration();
 
-        $this->info['text-decoration-line'] = new HTMLPurifier_AttrDef_Enum(
-            ['none', 'underline', 'overline', 'line-through', 'initial', 'inherit']
-        );
-
-        $this->info['text-decoration-style'] = new HTMLPurifier_AttrDef_Enum(
-            ['solid', 'double', 'dotted', 'dashed', 'wavy', 'initial', 'inherit']
-        );
-
-        $this->info['text-decoration-color'] = new HTMLPurifier_AttrDef_CSS_Color();
-
-        $this->info['text-decoration-thickness'] = new HTMLPurifier_AttrDef_CSS_Composite([
-            new HTMLPurifier_AttrDef_CSS_Length(),
-            new HTMLPurifier_AttrDef_CSS_Percentage(),
-            new HTMLPurifier_AttrDef_Enum(['auto', 'from-font', 'initial', 'inherit'])
-        ]);
-
         $this->info['font-family'] = new HTMLPurifier_AttrDef_CSS_FontFamily();
 
         // this could use specialized code
         $this->info['font-weight'] = new HTMLPurifier_AttrDef_Enum(
-            [
+            array(
                 'normal',
                 'bold',
                 'bolder',
@@ -341,7 +324,7 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
                 '700',
                 '800',
                 '900'
-            ],
+            ),
             false
         );
 
@@ -357,21 +340,21 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
         $this->info['border-right'] = new HTMLPurifier_AttrDef_CSS_Border($config);
 
         $this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum(
-            ['collapse', 'separate']
+            array('collapse', 'separate')
         );
 
         $this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum(
-            ['top', 'bottom']
+            array('top', 'bottom')
         );
 
         $this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum(
-            ['auto', 'fixed']
+            array('auto', 'fixed')
         );
 
         $this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite(
-            [
+            array(
                 new HTMLPurifier_AttrDef_Enum(
-                    [
+                    array(
                         'baseline',
                         'sub',
                         'super',
@@ -380,11 +363,11 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
                         'middle',
                         'bottom',
                         'text-bottom'
-                    ]
+                    )
                 ),
                 new HTMLPurifier_AttrDef_CSS_Length(),
                 new HTMLPurifier_AttrDef_CSS_Percentage()
-            ]
+            )
         );
 
         $this->info['border-spacing'] = new HTMLPurifier_AttrDef_CSS_Multiple(new HTMLPurifier_AttrDef_CSS_Length(), 2);
@@ -392,7 +375,7 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
         // These CSS properties don't work on many browsers, but we live
         // in THE FUTURE!
         $this->info['white-space'] = new HTMLPurifier_AttrDef_Enum(
-            ['nowrap', 'normal', 'pre', 'pre-wrap', 'pre-line']
+            array('nowrap', 'normal', 'pre', 'pre-wrap', 'pre-line')
         );
 
         if ($config->get('CSS.Proprietary')) {
@@ -439,21 +422,21 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
         // more CSS3
         $this->info['page-break-after'] =
         $this->info['page-break-before'] = new HTMLPurifier_AttrDef_Enum(
-            [
+            array(
                 'auto',
                 'always',
                 'avoid',
                 'left',
                 'right'
-            ]
+            )
         );
-        $this->info['page-break-inside'] = new HTMLPurifier_AttrDef_Enum(['auto', 'avoid']);
+        $this->info['page-break-inside'] = new HTMLPurifier_AttrDef_Enum(array('auto', 'avoid'));
 
         $border_radius = new HTMLPurifier_AttrDef_CSS_Composite(
-            [
+            array(
                 new HTMLPurifier_AttrDef_CSS_Percentage(true), // disallow negative
                 new HTMLPurifier_AttrDef_CSS_Length('0') // disallow negative
-            ]);
+            ));
 
         $this->info['border-top-left-radius'] =
         $this->info['border-top-right-radius'] =
@@ -470,7 +453,7 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
     protected function doSetupTricky($config)
     {
         $this->info['display'] = new HTMLPurifier_AttrDef_Enum(
-            [
+            array(
                 'inline',
                 'block',
                 'list-item',
@@ -489,12 +472,12 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
                 'table-cell',
                 'table-caption',
                 'none'
-            ]
+            )
         );
         $this->info['visibility'] = new HTMLPurifier_AttrDef_Enum(
-            ['visible', 'hidden', 'collapse']
+            array('visible', 'hidden', 'collapse')
         );
-        $this->info['overflow'] = new HTMLPurifier_AttrDef_Enum(['visible', 'hidden', 'auto', 'scroll']);
+        $this->info['overflow'] = new HTMLPurifier_AttrDef_Enum(array('visible', 'hidden', 'auto', 'scroll'));
         $this->info['opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
     }
 
@@ -504,23 +487,23 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
     protected function doSetupTrusted($config)
     {
         $this->info['position'] = new HTMLPurifier_AttrDef_Enum(
-            ['static', 'relative', 'absolute', 'fixed']
+            array('static', 'relative', 'absolute', 'fixed')
         );
         $this->info['top'] =
         $this->info['left'] =
         $this->info['right'] =
         $this->info['bottom'] = new HTMLPurifier_AttrDef_CSS_Composite(
-            [
+            array(
                 new HTMLPurifier_AttrDef_CSS_Length(),
                 new HTMLPurifier_AttrDef_CSS_Percentage(),
-                new HTMLPurifier_AttrDef_Enum(['auto']),
-            ]
+                new HTMLPurifier_AttrDef_Enum(array('auto')),
+            )
         );
         $this->info['z-index'] = new HTMLPurifier_AttrDef_CSS_Composite(
-            [
+            array(
                 new HTMLPurifier_AttrDef_Integer(),
-                new HTMLPurifier_AttrDef_Enum(['auto']),
-            ]
+                new HTMLPurifier_AttrDef_Enum(array('auto')),
+            )
         );
     }
 

+ 1 - 1
vendor/ezyang/htmlpurifier/library/HTMLPurifier/Config.php

@@ -21,7 +21,7 @@ class HTMLPurifier_Config
      * HTML Purifier's version
      * @type string
      */
-    public $version = '4.17.0';
+    public $version = '4.15.0';
 
     /**
      * Whether or not to automatically finalize

+ 5 - 6
vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer.php

@@ -287,14 +287,13 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
             } elseif (filegroup($dir) === posix_getgid()) {
                 $chmod = $chmod | 0070;
             } else {
-              // PHP's probably running as nobody, it is
-              // not obvious how to fix this (777 is probably
-              // bad if you are multi-user), let the user figure it out
-                $chmod = null;
+                // PHP's probably running as nobody, so we'll
+                // need to give global permissions
+                $chmod = $chmod | 0777;
             }
             trigger_error(
-                'Directory ' . $dir . ' not writable. ' .
-                ($chmod === null ? '' : 'Please chmod to ' . decoct($chmod)),
+                'Directory ' . $dir . ' not writable, ' .
+                'please chmod to ' . decoct($chmod),
                 E_USER_WARNING
             );
         } else {

+ 1 - 1
vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCacheFactory.php

@@ -71,7 +71,7 @@ class HTMLPurifier_DefinitionCacheFactory
             return $this->caches[$method][$type];
         }
         if (isset($this->implementations[$method]) &&
-            class_exists($class = $this->implementations[$method])) {
+            class_exists($class = $this->implementations[$method], false)) {
             $cache = new $class($type);
         } else {
             if ($method != 'Serializer') {

+ 151 - 155
vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/ExtractStyleBlocks.php

@@ -146,179 +146,175 @@ class HTMLPurifier_Filter_ExtractStyleBlocks extends HTMLPurifier_Filter
         foreach ($this->_tidy->css as $k => $decls) {
             // $decls are all CSS declarations inside an @ selector
             $new_decls = array();
-            if (is_array($decls)) {
-                foreach ($decls as $selector => $style) {
-                    $selector = trim($selector);
-                    if ($selector === '') {
-                        continue;
-                    } // should not happen
-                    // Parse the selector
-                    // Here is the relevant part of the CSS grammar:
-                    //
-                    // ruleset
-                    //   : selector [ ',' S* selector ]* '{' ...
-                    // selector
-                    //   : simple_selector [ combinator selector | S+ [ combinator? selector ]? ]?
-                    // combinator
-                    //   : '+' S*
-                    //   : '>' S*
-                    // simple_selector
-                    //   : element_name [ HASH | class | attrib | pseudo ]*
-                    //   | [ HASH | class | attrib | pseudo ]+
-                    // element_name
-                    //   : IDENT | '*'
-                    //   ;
-                    // class
-                    //   : '.' IDENT
-                    //   ;
-                    // attrib
-                    //   : '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S*
-                    //     [ IDENT | STRING ] S* ]? ']'
-                    //   ;
-                    // pseudo
-                    //   : ':' [ IDENT | FUNCTION S* [IDENT S*]? ')' ]
-                    //   ;
-                    //
-                    // For reference, here are the relevant tokens:
-                    //
-                    // HASH         #{name}
-                    // IDENT        {ident}
-                    // INCLUDES     ==
-                    // DASHMATCH    |=
-                    // STRING       {string}
-                    // FUNCTION     {ident}\(
-                    //
-                    // And the lexical scanner tokens
-                    //
-                    // name         {nmchar}+
-                    // nmchar       [_a-z0-9-]|{nonascii}|{escape}
-                    // nonascii     [\240-\377]
-                    // escape       {unicode}|\\[^\r\n\f0-9a-f]
-                    // unicode      \\{h}}{1,6}(\r\n|[ \t\r\n\f])?
-                    // ident        -?{nmstart}{nmchar*}
-                    // nmstart      [_a-z]|{nonascii}|{escape}
-                    // string       {string1}|{string2}
-                    // string1      \"([^\n\r\f\\"]|\\{nl}|{escape})*\"
-                    // string2      \'([^\n\r\f\\"]|\\{nl}|{escape})*\'
-                    //
-                    // We'll implement a subset (in order to reduce attack
-                    // surface); in particular:
-                    //
-                    //      - No Unicode support
-                    //      - No escapes support
-                    //      - No string support (by proxy no attrib support)
-                    //      - element_name is matched against allowed
-                    //        elements (some people might find this
-                    //        annoying...)
-                    //      - Pseudo-elements one of :first-child, :link,
-                    //        :visited, :active, :hover, :focus
+            foreach ($decls as $selector => $style) {
+                $selector = trim($selector);
+                if ($selector === '') {
+                    continue;
+                } // should not happen
+                // Parse the selector
+                // Here is the relevant part of the CSS grammar:
+                //
+                // ruleset
+                //   : selector [ ',' S* selector ]* '{' ...
+                // selector
+                //   : simple_selector [ combinator selector | S+ [ combinator? selector ]? ]?
+                // combinator
+                //   : '+' S*
+                //   : '>' S*
+                // simple_selector
+                //   : element_name [ HASH | class | attrib | pseudo ]*
+                //   | [ HASH | class | attrib | pseudo ]+
+                // element_name
+                //   : IDENT | '*'
+                //   ;
+                // class
+                //   : '.' IDENT
+                //   ;
+                // attrib
+                //   : '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S*
+                //     [ IDENT | STRING ] S* ]? ']'
+                //   ;
+                // pseudo
+                //   : ':' [ IDENT | FUNCTION S* [IDENT S*]? ')' ]
+                //   ;
+                //
+                // For reference, here are the relevant tokens:
+                //
+                // HASH         #{name}
+                // IDENT        {ident}
+                // INCLUDES     ==
+                // DASHMATCH    |=
+                // STRING       {string}
+                // FUNCTION     {ident}\(
+                //
+                // And the lexical scanner tokens
+                //
+                // name         {nmchar}+
+                // nmchar       [_a-z0-9-]|{nonascii}|{escape}
+                // nonascii     [\240-\377]
+                // escape       {unicode}|\\[^\r\n\f0-9a-f]
+                // unicode      \\{h}}{1,6}(\r\n|[ \t\r\n\f])?
+                // ident        -?{nmstart}{nmchar*}
+                // nmstart      [_a-z]|{nonascii}|{escape}
+                // string       {string1}|{string2}
+                // string1      \"([^\n\r\f\\"]|\\{nl}|{escape})*\"
+                // string2      \'([^\n\r\f\\"]|\\{nl}|{escape})*\'
+                //
+                // We'll implement a subset (in order to reduce attack
+                // surface); in particular:
+                //
+                //      - No Unicode support
+                //      - No escapes support
+                //      - No string support (by proxy no attrib support)
+                //      - element_name is matched against allowed
+                //        elements (some people might find this
+                //        annoying...)
+                //      - Pseudo-elements one of :first-child, :link,
+                //        :visited, :active, :hover, :focus
 
-                    // handle ruleset
-                    $selectors = array_map('trim', explode(',', $selector));
-                    $new_selectors = array();
-                    foreach ($selectors as $sel) {
-                        // split on +, > and spaces
-                        $basic_selectors = preg_split('/\s*([+> ])\s*/', $sel, -1, PREG_SPLIT_DELIM_CAPTURE);
-                        // even indices are chunks, odd indices are
-                        // delimiters
-                        $nsel = null;
-                        $delim = null; // guaranteed to be non-null after
-                        // two loop iterations
-                        for ($i = 0, $c = count($basic_selectors); $i < $c; $i++) {
-                            $x = $basic_selectors[$i];
-                            if ($i % 2) {
-                                // delimiter
-                                if ($x === ' ') {
-                                    $delim = ' ';
-                                } else {
-                                    $delim = ' ' . $x . ' ';
-                                }
+                // handle ruleset
+                $selectors = array_map('trim', explode(',', $selector));
+                $new_selectors = array();
+                foreach ($selectors as $sel) {
+                    // split on +, > and spaces
+                    $basic_selectors = preg_split('/\s*([+> ])\s*/', $sel, -1, PREG_SPLIT_DELIM_CAPTURE);
+                    // even indices are chunks, odd indices are
+                    // delimiters
+                    $nsel = null;
+                    $delim = null; // guaranteed to be non-null after
+                    // two loop iterations
+                    for ($i = 0, $c = count($basic_selectors); $i < $c; $i++) {
+                        $x = $basic_selectors[$i];
+                        if ($i % 2) {
+                            // delimiter
+                            if ($x === ' ') {
+                                $delim = ' ';
                             } else {
-                                // simple selector
-                                $components = preg_split('/([#.:])/', $x, -1, PREG_SPLIT_DELIM_CAPTURE);
-                                $sdelim = null;
-                                $nx = null;
-                                for ($j = 0, $cc = count($components); $j < $cc; $j++) {
-                                    $y = $components[$j];
-                                    if ($j === 0) {
-                                        if ($y === '*' || isset($html_definition->info[$y = strtolower($y)])) {
-                                            $nx = $y;
-                                        } else {
-                                            // $nx stays null; this matters
-                                            // if we don't manage to find
-                                            // any valid selector content,
-                                            // in which case we ignore the
-                                            // outer $delim
-                                        }
-                                    } elseif ($j % 2) {
-                                        // set delimiter
-                                        $sdelim = $y;
+                                $delim = ' ' . $x . ' ';
+                            }
+                        } else {
+                            // simple selector
+                            $components = preg_split('/([#.:])/', $x, -1, PREG_SPLIT_DELIM_CAPTURE);
+                            $sdelim = null;
+                            $nx = null;
+                            for ($j = 0, $cc = count($components); $j < $cc; $j++) {
+                                $y = $components[$j];
+                                if ($j === 0) {
+                                    if ($y === '*' || isset($html_definition->info[$y = strtolower($y)])) {
+                                        $nx = $y;
                                     } else {
-                                        $attrdef = null;
-                                        if ($sdelim === '#') {
-                                            $attrdef = $this->_id_attrdef;
-                                        } elseif ($sdelim === '.') {
-                                            $attrdef = $this->_class_attrdef;
-                                        } elseif ($sdelim === ':') {
-                                            $attrdef = $this->_enum_attrdef;
-                                        } else {
-                                            throw new HTMLPurifier_Exception('broken invariant sdelim and preg_split');
+                                        // $nx stays null; this matters
+                                        // if we don't manage to find
+                                        // any valid selector content,
+                                        // in which case we ignore the
+                                        // outer $delim
+                                    }
+                                } elseif ($j % 2) {
+                                    // set delimiter
+                                    $sdelim = $y;
+                                } else {
+                                    $attrdef = null;
+                                    if ($sdelim === '#') {
+                                        $attrdef = $this->_id_attrdef;
+                                    } elseif ($sdelim === '.') {
+                                        $attrdef = $this->_class_attrdef;
+                                    } elseif ($sdelim === ':') {
+                                        $attrdef = $this->_enum_attrdef;
+                                    } else {
+                                        throw new HTMLPurifier_Exception('broken invariant sdelim and preg_split');
+                                    }
+                                    $r = $attrdef->validate($y, $config, $context);
+                                    if ($r !== false) {
+                                        if ($r !== true) {
+                                            $y = $r;
                                         }
-                                        $r = $attrdef->validate($y, $config, $context);
-                                        if ($r !== false) {
-                                            if ($r !== true) {
-                                                $y = $r;
-                                            }
-                                            if ($nx === null) {
-                                                $nx = '';
-                                            }
-                                            $nx .= $sdelim . $y;
+                                        if ($nx === null) {
+                                            $nx = '';
                                         }
+                                        $nx .= $sdelim . $y;
                                     }
                                 }
-                                if ($nx !== null) {
-                                    if ($nsel === null) {
-                                        $nsel = $nx;
-                                    } else {
-                                        $nsel .= $delim . $nx;
-                                    }
+                            }
+                            if ($nx !== null) {
+                                if ($nsel === null) {
+                                    $nsel = $nx;
                                 } else {
-                                    // delimiters to the left of invalid
-                                    // basic selector ignored
+                                    $nsel .= $delim . $nx;
                                 }
+                            } else {
+                                // delimiters to the left of invalid
+                                // basic selector ignored
                             }
                         }
-                        if ($nsel !== null) {
-                            if (!empty($scopes)) {
-                                foreach ($scopes as $s) {
-                                    $new_selectors[] = "$s $nsel";
-                                }
-                            } else {
-                                $new_selectors[] = $nsel;
+                    }
+                    if ($nsel !== null) {
+                        if (!empty($scopes)) {
+                            foreach ($scopes as $s) {
+                                $new_selectors[] = "$s $nsel";
                             }
+                        } else {
+                            $new_selectors[] = $nsel;
                         }
                     }
-                    if (empty($new_selectors)) {
+                }
+                if (empty($new_selectors)) {
+                    continue;
+                }
+                $selector = implode(', ', $new_selectors);
+                foreach ($style as $name => $value) {
+                    if (!isset($css_definition->info[$name])) {
+                        unset($style[$name]);
                         continue;
                     }
-                    $selector = implode(', ', $new_selectors);
-                    foreach ($style as $name => $value) {
-                        if (!isset($css_definition->info[$name])) {
-                            unset($style[$name]);
-                            continue;
-                        }
-                        $def = $css_definition->info[$name];
-                        $ret = $def->validate($value, $config, $context);
-                        if ($ret === false) {
-                            unset($style[$name]);
-                        } else {
-                            $style[$name] = $ret;
-                        }
+                    $def = $css_definition->info[$name];
+                    $ret = $def->validate($value, $config, $context);
+                    if ($ret === false) {
+                        unset($style[$name]);
+                    } else {
+                        $style[$name] = $ret;
                     }
-                    $new_decls[$selector] = $style;
                 }
-            } else {
-                continue;
+                $new_decls[$selector] = $style;
             }
             $new_css[$k] = $new_decls;
         }

+ 0 - 1
vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy.php

@@ -221,7 +221,6 @@ class HTMLPurifier_HTMLModule_Tidy extends HTMLPurifier_HTMLModule
      */
     public function makeFixes()
     {
-        return array();
     }
 }
 

+ 1 - 1
vendor/ezyang/htmlpurifier/library/HTMLPurifier/LanguageFactory.php

@@ -109,7 +109,7 @@ class HTMLPurifier_LanguageFactory
         } else {
             $class = 'HTMLPurifier_Language_' . $pcode;
             $file  = $this->dir . '/Language/classes/' . $code . '.php';
-            if (file_exists($file) || class_exists($class)) {
+            if (file_exists($file) || class_exists($class, false)) {
                 $lang = new $class($config, $context);
             } else {
                 // Go fallback

+ 1 - 1
vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer.php

@@ -101,7 +101,7 @@ class HTMLPurifier_Lexer
                         break;
                     }
 
-                    if (class_exists('DOMDocument') &&
+                    if (class_exists('DOMDocument', false) &&
                         method_exists('DOMDocument', 'loadHTML') &&
                         !extension_loaded('domxml')
                     ) {

+ 1 - 0
vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DOMLex.php

@@ -104,6 +104,7 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
      * To iterate is human, to recurse divine - L. Peter Deutsch
      * @param DOMNode $node DOMNode to be tokenized.
      * @param HTMLPurifier_Token[] $tokens   Array-list of already tokenized tokens.
+     * @return HTMLPurifier_Token of node appended to previously passed tokens.
      */
     protected function tokenizeDOM($node, &$tokens, $config)
     {

+ 0 - 5
vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.php

@@ -33,11 +33,6 @@ class HTMLPurifier_Printer_ConfigForm extends HTMLPurifier_Printer
     protected $compress = false;
 
     /**
-     * @var HTMLPurifier_Config
-     */
-    protected $genConfig;
-
-    /**
      * @param string $name Form element name for directives to be stuffed into
      * @param string $doc_url String documentation URL, will have fragment tagged on
      * @param bool $compress Integer max length before compressing a directive name, set to false to turn off

+ 3 - 3
vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/tel.php

@@ -33,11 +33,11 @@ class HTMLPurifier_URIScheme_tel extends HTMLPurifier_URIScheme
         $uri->host     = null;
         $uri->port     = null;
 
-        // Delete all non-numeric characters, commas, and non-x characters
+        // Delete all non-numeric characters, non-x characters
         // from phone number, EXCEPT for a leading plus sign.
-        $uri->path = preg_replace('/(?!^\+)[^\dx,]/', '',
+        $uri->path = preg_replace('/(?!^\+)[^\dx]/', '',
                      // Normalize e(x)tension to lower-case
-                     str_replace('X', 'x', rawurldecode($uri->path)));
+                     str_replace('X', 'x', $uri->path));
 
         return true;
     }

+ 3 - 3
vendor/ezyang/htmlpurifier/library/HTMLPurifier/UnitConverter.php

@@ -261,7 +261,7 @@ class HTMLPurifier_UnitConverter
      */
     private function round($n, $sigfigs)
     {
-        $new_log = (int)floor(log(abs((float)$n), 10)); // Number of digits left of decimal - 1
+        $new_log = (int)floor(log(abs($n), 10)); // Number of digits left of decimal - 1
         $rp = $sigfigs - $new_log - 1; // Number of decimal places needed
         $neg = $n < 0 ? '-' : ''; // Negative sign
         if ($this->bcmath) {
@@ -276,7 +276,7 @@ class HTMLPurifier_UnitConverter
             }
             return $n;
         } else {
-            return $this->scale(round((float)$n, $sigfigs - $new_log - 1), $rp + 1);
+            return $this->scale(round($n, $sigfigs - $new_log - 1), $rp + 1);
         }
     }
 
@@ -300,7 +300,7 @@ class HTMLPurifier_UnitConverter
             // Now we return it, truncating the zero that was rounded off.
             return substr($precise, 0, -1) . str_repeat('0', -$scale + 1);
         }
-        return number_format((float)$r, $scale, '.', '');
+        return sprintf('%.' . $scale . 'f', (float)$r);
     }
 }
 

+ 7 - 0
vendor/maniac/easemob-php/.gitignore

@@ -0,0 +1,7 @@
+.DS_Store
+.phpdoc
+.phpunit.result.cache
+/vendor/
+/docs/
+/doxygen/
+deploy.sh

+ 135 - 0
vendor/maniac/easemob-php/README.md

@@ -0,0 +1,135 @@
+## PHP SDK
+
+### 介绍
+PHP SDK 是对环信 IM [服务端 API](https://docs-im.easemob.com/im/server/ready/intro) 的封装,这样做是为了节省服务器端开发者对接环信 API 的时间,只需要配置自己的 appkey 相关信息即可使用。
+
+### 功能
+PHP SDK 提供了用户、消息、群组、聊天室等资源的操作管理能力。
+
+### 依赖
+- PHP 5.3+
+
+### 安装
+直接使用 [composer](https://www.phpcomposer.com/) 进行安装
+
+```
+composer require maniac/easemob-php
+```
+
+### 目录结构
+- examples 示例文件目录
+- runtime 临时文件、缓存文件目录
+- src 核心文件目录
+- tests 测试文件目录
+
+### 准备
+在使用 PHP SDK 之前,需要准备环信 appkey、Client ID、ClientSecret。
+
+如果你有环信管理后台账号并创建过应用,请先登录环信管理后台,点击 [这里](https://console.easemob.com/user/login),然后到“应用列表” → 点击“查看”即可获取到 appkey、Client ID、ClientSecret。
+
+如果你没有环信管理后台账号,请先注册账号,点击 [这里](https://console.easemob.com/user/register),注册成功后请登录,然后点击“添加应用”,添加成功后点击“查看”即可获取到 appkey、Client ID、ClientSecret。
+
+### 使用
+如果使用 Laravel、YII、ThinkPHP 之类的框架,composer 安装的库会自动加载,如果没有使用框架,需要手动引入 `vendor/autoload.php` 文件。
+
+使用所有的类之前,都要先初始化授权对象,然后再初始化其他类时,传入授权对象
+```php
+require 'vendor/autoload.php';
+
+use Easemob\Auth;
+use Easemob\User;
+
+$auth = new Auth("appKey", "Client ID", "ClientSecret");
+$user = new User($auth);
+```
+
+根据业务资源,API 分为:
+
+- Attachment 用于上传下载附件
+- Block 用于限制访问(将用户加入黑名单、群组/聊天室禁言等)
+- Contact 用于管理联系人(添加好友等)
+- Group 用于管理群组
+- Message 用于发送消息
+- User 用于管理用户
+- UserMetadata 用于管理用户属性
+- Push 用于管理用户推送(设置推送免打扰等)
+- Room 用于管理聊天室
+- WhiteList 用于管理白名单
+
+每个业务资源对应一个方法,例如,用户相关的 API,都可以在 User 类中找到。
+
+举个例子,我们要注册一个用户,就可以这样写:
+
+```php
+require 'vendor/autoload.php';
+
+use Easemob\Auth;
+use Easemob\User;
+
+$auth = new Auth("appKey", "Client ID", "ClientSecret");
+$user = new User($auth);
+
+// 注册单个用户
+$data = array(
+    'username' => 'user1',
+    'password' => 'user1',
+    'nickname' => 'user1',
+);
+$user->create($data);
+
+ 
+// 批量注册用户
+$data = array(
+    array(
+        'username' => 'user2',
+        'password' => 'user2',
+        'nickname' => 'user2',
+    ),
+    array(
+        'username' => 'user3',
+        'password' => 'user3',
+        'nickname' => 'user3',
+    ),
+);
+$user->create($data);
+```
+
+### 参考
+- PHP SDK 的 api 文档在 [这里](https://easemob.github.io/im-php-server-sdk/annotated.html)
+- PHP SDK 开源地址在 [这里](https://github.com/easemob/im-php-server-sdk)
+
+### 常见问题
+
+1.关于 PHP 低版本中文乱码问题
+
+在纯 PHP 页面中使用
+
+```php
+header("Content-Type:text/html;charset=utf-8");
+```
+
+在 HTML 和 PHP 混编的页面中使用
+
+```html
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+```
+
+2.关于 SDK 返回的错误码和错误描述
+
+PHP SDK 会直接返回 REST API 的错误码及错误描述,具体请参考 [服务器端 REST API 常见错误码](https://docs-im.easemob.com/im/other/errorcode/restapi)
+
+3.使用代理的情况
+
+在初始化授权对象 Auth 之后,可以设置代理:
+
+```php
+require 'vendor/autoload.php';
+
+use Easemob\Auth;
+use Easemob\Http\Http
+
+$easemob = $config['easemob'];
+$auth = new Auth("appKey", "Client ID", "ClientSecret");
+// 设置代理
+Http::setProxy("ip地址", 8080);
+```

+ 19 - 0
vendor/maniac/easemob-php/autoload.php

@@ -0,0 +1,19 @@
+<?php
+/**
+ * @ignore 自动加载
+ */
+if ( file_exists(dirname(__FILE__).'/vendor/autoload.php') ) {
+    require_once dirname(__FILE__).'/vendor/autoload.php';
+}
+
+function class_loader($className)
+{
+    $file = __DIR__ . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . str_replace("Easemob" . DIRECTORY_SEPARATOR, "", str_replace("\\", DIRECTORY_SEPARATOR, $className)) . '.php';
+    if (file_exists($file)) {
+        require_once $file;
+    }
+}
+
+spl_autoload_register('class_loader');
+
+require __DIR__ . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'functions.php';

+ 23 - 0
vendor/maniac/easemob-php/composer.json

@@ -0,0 +1,23 @@
+{
+    "name": "maniac/easemob-php",
+    "description": "PHP Server SDK for IM.",
+    "license": "MIT",
+    "authors": [
+      {
+          "name": "maniac",
+          "email": "maniac.liu@easemob.com"
+      }
+    ],
+    "require": {
+      "php": ">=5.3.3"
+    },
+    "autoload": {
+      "files": [
+        "src/functions.php"
+      ],
+      "psr-4": {
+          "Easemob\\": "src/", 
+          "tests\\": "tests/"
+      }
+    }
+}

+ 2319 - 0
vendor/maniac/easemob-php/composer.lock

@@ -0,0 +1,2319 @@
+{
+    "_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": "e6ebd96819b95bb9556c1d5676de347d",
+    "packages": [],
+    "packages-dev": [
+        {
+            "name": "doctrine/instantiator",
+            "version": "1.4.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/doctrine/instantiator.git",
+                "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b",
+                "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "^7.1 || ^8.0"
+            },
+            "require-dev": {
+                "doctrine/coding-standard": "^8.0",
+                "ext-pdo": "*",
+                "ext-phar": "*",
+                "phpbench/phpbench": "^0.13 || 1.0.0-alpha2",
+                "phpstan/phpstan": "^0.12",
+                "phpstan/phpstan-phpunit": "^0.12",
+                "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Marco Pivetta",
+                    "email": "ocramius@gmail.com",
+                    "homepage": "https://ocramius.github.io/"
+                }
+            ],
+            "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+            "homepage": "https://www.doctrine-project.org/projects/instantiator.html",
+            "keywords": [
+                "constructor",
+                "instantiate"
+            ],
+            "support": {
+                "issues": "https://github.com/doctrine/instantiator/issues",
+                "source": "https://github.com/doctrine/instantiator/tree/1.4.0"
+            },
+            "funding": [
+                {
+                    "url": "https://www.doctrine-project.org/sponsorship.html",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://www.patreon.com/phpdoctrine",
+                    "type": "patreon"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-11-10T18:47:58+00:00"
+        },
+        {
+            "name": "myclabs/deep-copy",
+            "version": "1.10.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/myclabs/DeepCopy.git",
+                "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220",
+                "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "^7.1 || ^8.0"
+            },
+            "replace": {
+                "myclabs/deep-copy": "self.version"
+            },
+            "require-dev": {
+                "doctrine/collections": "^1.0",
+                "doctrine/common": "^2.6",
+                "phpunit/phpunit": "^7.1"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/DeepCopy/deep_copy.php"
+                ],
+                "psr-4": {
+                    "DeepCopy\\": "src/DeepCopy/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "Create deep copies (clones) of your objects",
+            "keywords": [
+                "clone",
+                "copy",
+                "duplicate",
+                "object",
+                "object graph"
+            ],
+            "support": {
+                "issues": "https://github.com/myclabs/DeepCopy/issues",
+                "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2"
+            },
+            "funding": [
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-11-13T09:40:50+00:00"
+        },
+        {
+            "name": "nikic/php-parser",
+            "version": "v4.13.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/nikic/PHP-Parser.git",
+                "reference": "210577fe3cf7badcc5814d99455df46564f3c077"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/210577fe3cf7badcc5814d99455df46564f3c077",
+                "reference": "210577fe3cf7badcc5814d99455df46564f3c077",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "ext-tokenizer": "*",
+                "php": ">=7.0"
+            },
+            "require-dev": {
+                "ircmaxell/php-yacc": "^0.0.7",
+                "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0"
+            },
+            "bin": [
+                "bin/php-parse"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.9-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "PhpParser\\": "lib/PhpParser"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Nikita Popov"
+                }
+            ],
+            "description": "A PHP parser written in PHP",
+            "keywords": [
+                "parser",
+                "php"
+            ],
+            "support": {
+                "issues": "https://github.com/nikic/PHP-Parser/issues",
+                "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.2"
+            },
+            "time": "2021-11-30T19:35:32+00:00"
+        },
+        {
+            "name": "phar-io/manifest",
+            "version": "2.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phar-io/manifest.git",
+                "reference": "97803eca37d319dfa7826cc2437fc020857acb53"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53",
+                "reference": "97803eca37d319dfa7826cc2437fc020857acb53",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "ext-dom": "*",
+                "ext-phar": "*",
+                "ext-xmlwriter": "*",
+                "phar-io/version": "^3.0.1",
+                "php": "^7.2 || ^8.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Arne Blankerts",
+                    "email": "arne@blankerts.de",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Sebastian Heuer",
+                    "email": "sebastian@phpeople.de",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
+            "support": {
+                "issues": "https://github.com/phar-io/manifest/issues",
+                "source": "https://github.com/phar-io/manifest/tree/2.0.3"
+            },
+            "time": "2021-07-20T11:28:43+00:00"
+        },
+        {
+            "name": "phar-io/version",
+            "version": "3.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phar-io/version.git",
+                "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+                "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "^7.2 || ^8.0"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Arne Blankerts",
+                    "email": "arne@blankerts.de",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Sebastian Heuer",
+                    "email": "sebastian@phpeople.de",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Library for handling version information and constraints",
+            "support": {
+                "issues": "https://github.com/phar-io/version/issues",
+                "source": "https://github.com/phar-io/version/tree/3.2.1"
+            },
+            "time": "2022-02-21T01:04:05+00:00"
+        },
+        {
+            "name": "phpdocumentor/reflection-common",
+            "version": "2.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
+                "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b",
+                "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "^7.2 || ^8.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-2.x": "2.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "phpDocumentor\\Reflection\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jaap van Otterdijk",
+                    "email": "opensource@ijaap.nl"
+                }
+            ],
+            "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
+            "homepage": "http://www.phpdoc.org",
+            "keywords": [
+                "FQSEN",
+                "phpDocumentor",
+                "phpdoc",
+                "reflection",
+                "static analysis"
+            ],
+            "support": {
+                "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues",
+                "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x"
+            },
+            "time": "2020-06-27T09:03:43+00:00"
+        },
+        {
+            "name": "phpdocumentor/reflection-docblock",
+            "version": "5.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+                "reference": "622548b623e81ca6d78b721c5e029f4ce664f170"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170",
+                "reference": "622548b623e81ca6d78b721c5e029f4ce664f170",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "ext-filter": "*",
+                "php": "^7.2 || ^8.0",
+                "phpdocumentor/reflection-common": "^2.2",
+                "phpdocumentor/type-resolver": "^1.3",
+                "webmozart/assert": "^1.9.1"
+            },
+            "require-dev": {
+                "mockery/mockery": "~1.3.2",
+                "psalm/phar": "^4.8"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "5.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "phpDocumentor\\Reflection\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Mike van Riel",
+                    "email": "me@mikevanriel.com"
+                },
+                {
+                    "name": "Jaap van Otterdijk",
+                    "email": "account@ijaap.nl"
+                }
+            ],
+            "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
+            "support": {
+                "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
+                "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0"
+            },
+            "time": "2021-10-19T17:43:47+00:00"
+        },
+        {
+            "name": "phpdocumentor/type-resolver",
+            "version": "1.6.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpDocumentor/TypeResolver.git",
+                "reference": "93ebd0014cab80c4ea9f5e297ea48672f1b87706"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/93ebd0014cab80c4ea9f5e297ea48672f1b87706",
+                "reference": "93ebd0014cab80c4ea9f5e297ea48672f1b87706",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "^7.2 || ^8.0",
+                "phpdocumentor/reflection-common": "^2.0"
+            },
+            "require-dev": {
+                "ext-tokenizer": "*",
+                "psalm/phar": "^4.8"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-1.x": "1.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "phpDocumentor\\Reflection\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Mike van Riel",
+                    "email": "me@mikevanriel.com"
+                }
+            ],
+            "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
+            "support": {
+                "issues": "https://github.com/phpDocumentor/TypeResolver/issues",
+                "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.0"
+            },
+            "time": "2022-01-04T19:58:01+00:00"
+        },
+        {
+            "name": "phpspec/prophecy",
+            "version": "v1.15.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpspec/prophecy.git",
+                "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13",
+                "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "doctrine/instantiator": "^1.2",
+                "php": "^7.2 || ~8.0, <8.2",
+                "phpdocumentor/reflection-docblock": "^5.2",
+                "sebastian/comparator": "^3.0 || ^4.0",
+                "sebastian/recursion-context": "^3.0 || ^4.0"
+            },
+            "require-dev": {
+                "phpspec/phpspec": "^6.0 || ^7.0",
+                "phpunit/phpunit": "^8.0 || ^9.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Prophecy\\": "src/Prophecy"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Konstantin Kudryashov",
+                    "email": "ever.zet@gmail.com",
+                    "homepage": "http://everzet.com"
+                },
+                {
+                    "name": "Marcello Duarte",
+                    "email": "marcello.duarte@gmail.com"
+                }
+            ],
+            "description": "Highly opinionated mocking framework for PHP 5.3+",
+            "homepage": "https://github.com/phpspec/prophecy",
+            "keywords": [
+                "Double",
+                "Dummy",
+                "fake",
+                "mock",
+                "spy",
+                "stub"
+            ],
+            "support": {
+                "issues": "https://github.com/phpspec/prophecy/issues",
+                "source": "https://github.com/phpspec/prophecy/tree/v1.15.0"
+            },
+            "time": "2021-12-08T12:19:24+00:00"
+        },
+        {
+            "name": "phpunit/php-code-coverage",
+            "version": "9.2.14",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+                "reference": "9f4d60b6afe5546421462b76cd4e633ebc364ab4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/9f4d60b6afe5546421462b76cd4e633ebc364ab4",
+                "reference": "9f4d60b6afe5546421462b76cd4e633ebc364ab4",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "ext-dom": "*",
+                "ext-libxml": "*",
+                "ext-xmlwriter": "*",
+                "nikic/php-parser": "^4.13.0",
+                "php": ">=7.3",
+                "phpunit/php-file-iterator": "^3.0.3",
+                "phpunit/php-text-template": "^2.0.2",
+                "sebastian/code-unit-reverse-lookup": "^2.0.2",
+                "sebastian/complexity": "^2.0",
+                "sebastian/environment": "^5.1.2",
+                "sebastian/lines-of-code": "^1.0.3",
+                "sebastian/version": "^3.0.1",
+                "theseer/tokenizer": "^1.2.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "suggest": {
+                "ext-pcov": "*",
+                "ext-xdebug": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "9.2-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+            "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+            "keywords": [
+                "coverage",
+                "testing",
+                "xunit"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
+                "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.14"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2022-02-28T12:38:02+00:00"
+        },
+        {
+            "name": "phpunit/php-file-iterator",
+            "version": "3.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+                "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf",
+                "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+            "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+            "keywords": [
+                "filesystem",
+                "iterator"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
+                "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2021-12-02T12:48:52+00:00"
+        },
+        {
+            "name": "phpunit/php-invoker",
+            "version": "3.1.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-invoker.git",
+                "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67",
+                "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "ext-pcntl": "*",
+                "phpunit/phpunit": "^9.3"
+            },
+            "suggest": {
+                "ext-pcntl": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.1-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Invoke callables with a timeout",
+            "homepage": "https://github.com/sebastianbergmann/php-invoker/",
+            "keywords": [
+                "process"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/php-invoker/issues",
+                "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-09-28T05:58:55+00:00"
+        },
+        {
+            "name": "phpunit/php-text-template",
+            "version": "2.0.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-text-template.git",
+                "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28",
+                "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Simple template engine.",
+            "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+            "keywords": [
+                "template"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
+                "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-10-26T05:33:50+00:00"
+        },
+        {
+            "name": "phpunit/php-timer",
+            "version": "5.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-timer.git",
+                "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2",
+                "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "5.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Utility class for timing",
+            "homepage": "https://github.com/sebastianbergmann/php-timer/",
+            "keywords": [
+                "timer"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/php-timer/issues",
+                "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-10-26T13:16:10+00:00"
+        },
+        {
+            "name": "phpunit/phpunit",
+            "version": "9.5.16",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/phpunit.git",
+                "reference": "5ff8c545a50226c569310a35f4fa89d79f1ddfdc"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5ff8c545a50226c569310a35f4fa89d79f1ddfdc",
+                "reference": "5ff8c545a50226c569310a35f4fa89d79f1ddfdc",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "doctrine/instantiator": "^1.3.1",
+                "ext-dom": "*",
+                "ext-json": "*",
+                "ext-libxml": "*",
+                "ext-mbstring": "*",
+                "ext-xml": "*",
+                "ext-xmlwriter": "*",
+                "myclabs/deep-copy": "^1.10.1",
+                "phar-io/manifest": "^2.0.3",
+                "phar-io/version": "^3.0.2",
+                "php": ">=7.3",
+                "phpspec/prophecy": "^1.12.1",
+                "phpunit/php-code-coverage": "^9.2.13",
+                "phpunit/php-file-iterator": "^3.0.5",
+                "phpunit/php-invoker": "^3.1.1",
+                "phpunit/php-text-template": "^2.0.3",
+                "phpunit/php-timer": "^5.0.2",
+                "sebastian/cli-parser": "^1.0.1",
+                "sebastian/code-unit": "^1.0.6",
+                "sebastian/comparator": "^4.0.5",
+                "sebastian/diff": "^4.0.3",
+                "sebastian/environment": "^5.1.3",
+                "sebastian/exporter": "^4.0.3",
+                "sebastian/global-state": "^5.0.1",
+                "sebastian/object-enumerator": "^4.0.3",
+                "sebastian/resource-operations": "^3.0.3",
+                "sebastian/type": "^2.3.4",
+                "sebastian/version": "^3.0.2"
+            },
+            "require-dev": {
+                "ext-pdo": "*",
+                "phpspec/prophecy-phpunit": "^2.0.1"
+            },
+            "suggest": {
+                "ext-soap": "*",
+                "ext-xdebug": "*"
+            },
+            "bin": [
+                "phpunit"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "9.5-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/Framework/Assert/Functions.php"
+                ],
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "The PHP Unit Testing framework.",
+            "homepage": "https://phpunit.de/",
+            "keywords": [
+                "phpunit",
+                "testing",
+                "xunit"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/phpunit/issues",
+                "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.16"
+            },
+            "funding": [
+                {
+                    "url": "https://phpunit.de/sponsors.html",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2022-02-23T17:10:58+00:00"
+        },
+        {
+            "name": "sebastian/cli-parser",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/cli-parser.git",
+                "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2",
+                "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library for parsing CLI options",
+            "homepage": "https://github.com/sebastianbergmann/cli-parser",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/cli-parser/issues",
+                "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-09-28T06:08:49+00:00"
+        },
+        {
+            "name": "sebastian/code-unit",
+            "version": "1.0.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/code-unit.git",
+                "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120",
+                "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Collection of value objects that represent the PHP code units",
+            "homepage": "https://github.com/sebastianbergmann/code-unit",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/code-unit/issues",
+                "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-10-26T13:08:54+00:00"
+        },
+        {
+            "name": "sebastian/code-unit-reverse-lookup",
+            "version": "2.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+                "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5",
+                "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Looks up which function or method a line of code belongs to",
+            "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
+                "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-09-28T05:30:19+00:00"
+        },
+        {
+            "name": "sebastian/comparator",
+            "version": "4.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/comparator.git",
+                "reference": "55f4261989e546dc112258c7a75935a81a7ce382"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382",
+                "reference": "55f4261989e546dc112258c7a75935a81a7ce382",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.3",
+                "sebastian/diff": "^4.0",
+                "sebastian/exporter": "^4.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Volker Dusch",
+                    "email": "github@wallbash.com"
+                },
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@2bepublished.at"
+                }
+            ],
+            "description": "Provides the functionality to compare PHP values for equality",
+            "homepage": "https://github.com/sebastianbergmann/comparator",
+            "keywords": [
+                "comparator",
+                "compare",
+                "equality"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/comparator/issues",
+                "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-10-26T15:49:45+00:00"
+        },
+        {
+            "name": "sebastian/complexity",
+            "version": "2.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/complexity.git",
+                "reference": "739b35e53379900cc9ac327b2147867b8b6efd88"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88",
+                "reference": "739b35e53379900cc9ac327b2147867b8b6efd88",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "nikic/php-parser": "^4.7",
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library for calculating the complexity of PHP code units",
+            "homepage": "https://github.com/sebastianbergmann/complexity",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/complexity/issues",
+                "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-10-26T15:52:27+00:00"
+        },
+        {
+            "name": "sebastian/diff",
+            "version": "4.0.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/diff.git",
+                "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d",
+                "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3",
+                "symfony/process": "^4.2 || ^5"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Kore Nordmann",
+                    "email": "mail@kore-nordmann.de"
+                }
+            ],
+            "description": "Diff implementation",
+            "homepage": "https://github.com/sebastianbergmann/diff",
+            "keywords": [
+                "diff",
+                "udiff",
+                "unidiff",
+                "unified diff"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/diff/issues",
+                "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-10-26T13:10:38+00:00"
+        },
+        {
+            "name": "sebastian/environment",
+            "version": "5.1.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/environment.git",
+                "reference": "388b6ced16caa751030f6a69e588299fa09200ac"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac",
+                "reference": "388b6ced16caa751030f6a69e588299fa09200ac",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "suggest": {
+                "ext-posix": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "5.1-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Provides functionality to handle HHVM/PHP environments",
+            "homepage": "http://www.github.com/sebastianbergmann/environment",
+            "keywords": [
+                "Xdebug",
+                "environment",
+                "hhvm"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/environment/issues",
+                "source": "https://github.com/sebastianbergmann/environment/tree/5.1.3"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-09-28T05:52:38+00:00"
+        },
+        {
+            "name": "sebastian/exporter",
+            "version": "4.0.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/exporter.git",
+                "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9",
+                "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.3",
+                "sebastian/recursion-context": "^4.0"
+            },
+            "require-dev": {
+                "ext-mbstring": "*",
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Volker Dusch",
+                    "email": "github@wallbash.com"
+                },
+                {
+                    "name": "Adam Harvey",
+                    "email": "aharvey@php.net"
+                },
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@gmail.com"
+                }
+            ],
+            "description": "Provides the functionality to export PHP variables for visualization",
+            "homepage": "https://www.github.com/sebastianbergmann/exporter",
+            "keywords": [
+                "export",
+                "exporter"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/exporter/issues",
+                "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.4"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2021-11-11T14:18:36+00:00"
+        },
+        {
+            "name": "sebastian/global-state",
+            "version": "5.0.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/global-state.git",
+                "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2",
+                "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.3",
+                "sebastian/object-reflector": "^2.0",
+                "sebastian/recursion-context": "^4.0"
+            },
+            "require-dev": {
+                "ext-dom": "*",
+                "phpunit/phpunit": "^9.3"
+            },
+            "suggest": {
+                "ext-uopz": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "5.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Snapshotting of global state",
+            "homepage": "http://www.github.com/sebastianbergmann/global-state",
+            "keywords": [
+                "global state"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/global-state/issues",
+                "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2022-02-14T08:28:10+00:00"
+        },
+        {
+            "name": "sebastian/lines-of-code",
+            "version": "1.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/lines-of-code.git",
+                "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc",
+                "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "nikic/php-parser": "^4.6",
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library for counting the lines of code in PHP source code",
+            "homepage": "https://github.com/sebastianbergmann/lines-of-code",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
+                "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-11-28T06:42:11+00:00"
+        },
+        {
+            "name": "sebastian/object-enumerator",
+            "version": "4.0.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+                "reference": "5c9eeac41b290a3712d88851518825ad78f45c71"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71",
+                "reference": "5c9eeac41b290a3712d88851518825ad78f45c71",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.3",
+                "sebastian/object-reflector": "^2.0",
+                "sebastian/recursion-context": "^4.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Traverses array structures and object graphs to enumerate all referenced objects",
+            "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
+                "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-10-26T13:12:34+00:00"
+        },
+        {
+            "name": "sebastian/object-reflector",
+            "version": "2.0.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/object-reflector.git",
+                "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7",
+                "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Allows reflection of object attributes, including inherited and non-public ones",
+            "homepage": "https://github.com/sebastianbergmann/object-reflector/",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/object-reflector/issues",
+                "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-10-26T13:14:26+00:00"
+        },
+        {
+            "name": "sebastian/recursion-context",
+            "version": "4.0.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/recursion-context.git",
+                "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172",
+                "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Adam Harvey",
+                    "email": "aharvey@php.net"
+                }
+            ],
+            "description": "Provides functionality to recursively process PHP variables",
+            "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
+                "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-10-26T13:17:30+00:00"
+        },
+        {
+            "name": "sebastian/resource-operations",
+            "version": "3.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/resource-operations.git",
+                "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8",
+                "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Provides a list of PHP built-in functions that operate on resources",
+            "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/resource-operations/issues",
+                "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-09-28T06:45:17+00:00"
+        },
+        {
+            "name": "sebastian/type",
+            "version": "2.3.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/type.git",
+                "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8cd8a1c753c90bc1a0f5372170e3e489136f914",
+                "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.3-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Collection of value objects that represent the types of the PHP type system",
+            "homepage": "https://github.com/sebastianbergmann/type",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/type/issues",
+                "source": "https://github.com/sebastianbergmann/type/tree/2.3.4"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2021-06-15T12:49:02+00:00"
+        },
+        {
+            "name": "sebastian/version",
+            "version": "3.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/version.git",
+                "reference": "c6c1022351a901512170118436c764e473f6de8c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c",
+                "reference": "c6c1022351a901512170118436c764e473f6de8c",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+            "homepage": "https://github.com/sebastianbergmann/version",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/version/issues",
+                "source": "https://github.com/sebastianbergmann/version/tree/3.0.2"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-09-28T06:39:44+00:00"
+        },
+        {
+            "name": "symfony/polyfill-ctype",
+            "version": "v1.24.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-ctype.git",
+                "reference": "30885182c981ab175d4d034db0f6f469898070ab"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab",
+                "reference": "30885182c981ab175d4d034db0f6f469898070ab",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "provide": {
+                "ext-ctype": "*"
+            },
+            "suggest": {
+                "ext-ctype": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.23-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Ctype\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Gert de Pagter",
+                    "email": "BackEndTea@gmail.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for ctype functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "ctype",
+                "polyfill",
+                "portable"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-ctype/tree/v1.24.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": "2021-10-20T20:35:02+00:00"
+        },
+        {
+            "name": "theseer/tokenizer",
+            "version": "1.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/theseer/tokenizer.git",
+                "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e",
+                "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "ext-dom": "*",
+                "ext-tokenizer": "*",
+                "ext-xmlwriter": "*",
+                "php": "^7.2 || ^8.0"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Arne Blankerts",
+                    "email": "arne@blankerts.de",
+                    "role": "Developer"
+                }
+            ],
+            "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
+            "support": {
+                "issues": "https://github.com/theseer/tokenizer/issues",
+                "source": "https://github.com/theseer/tokenizer/tree/1.2.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/theseer",
+                    "type": "github"
+                }
+            ],
+            "time": "2021-07-28T10:34:58+00:00"
+        },
+        {
+            "name": "webmozart/assert",
+            "version": "1.10.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/webmozarts/assert.git",
+                "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25",
+                "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "^7.2 || ^8.0",
+                "symfony/polyfill-ctype": "^1.8"
+            },
+            "conflict": {
+                "phpstan/phpstan": "<0.12.20",
+                "vimeo/psalm": "<4.6.1 || 4.6.2"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^8.5.13"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.10-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Webmozart\\Assert\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@gmail.com"
+                }
+            ],
+            "description": "Assertions to validate method input/output with nice error messages.",
+            "keywords": [
+                "assert",
+                "check",
+                "validate"
+            ],
+            "support": {
+                "issues": "https://github.com/webmozarts/assert/issues",
+                "source": "https://github.com/webmozarts/assert/tree/1.10.0"
+            },
+            "time": "2021-03-09T10:59:23+00:00"
+        }
+    ],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": [],
+    "platform-dev": [],
+    "plugin-api-version": "2.1.0"
+}

+ 63 - 0
vendor/maniac/easemob-php/examples/attachment.php

@@ -0,0 +1,63 @@
+<?php
+/*
+ * 上传下载附件示例
+ */
+require_once __DIR__ . '/../autoload.php';
+$config = require_once 'config.php';
+
+use Easemob\Auth;
+use Easemob\Attachment;
+
+// 初始化授权对象,环信 token 初始化
+$easemob = $config['easemob'];
+$auth = new Auth($easemob['app_key'], $easemob['client_id'], $easemob['client_secret']);
+
+// 设置 REST 域名,沙箱环境使用,不是沙箱环境会自动获取
+if (isset($easemob['api_uri']) && $easemob['api_uri']) {
+    $auth->setApiUri($easemob['api_uri']);
+}
+
+// 实例化对象
+$attachment = new Attachment($auth);
+
+echo '<pre>';
+
+
+/* 
+// 上传文件
+$data = $attachment->uploadFile('images/1.png');
+var_dump($data);
+// array(3) {
+//     ["uuid"]=>
+//     string(36) "a4364f90-b0d8-11ec-8cfe-b57d5aca4e63"
+//     ["type"]=>
+//     string(8) "chatfile"
+//     ["share-secret"]=>
+//     string(48) "pDZPmrDYEeyGTyPdhVc_kj_MzWvSzIfi3bg_fgkvpVjvQAo5"
+// }
+$data = $attachment->uploadFile('/usr/share/nginx/html/sdk.com/examples/images/1.png', true);
+var_dump($data);
+// array(3) {
+//     ["uuid"]=>
+//     string(36) "a4615730-b0d8-11ec-92b5-1fe1ed287b6a"
+//     ["type"]=>
+//     string(8) "chatfile"
+//     ["share-secret"]=>
+//     string(48) "pGF-QLDYEeyPS6lef7QYBPQMYwHMCVRt34DkF3KYLMETh4s0"
+// }
+ */
+
+
+/* 
+// 下载文件
+var_dump($attachment->downloadFile('images/11.png', 'a4364f90-b0d8-11ec-8cfe-b57d5aca4e63'));
+// 下载文件
+var_dump($attachment->downloadFile('images/111/22.png', 'a4615730-b0d8-11ec-92b5-1fe1ed287b6a', 'pGF-QLDYEeyPS6lef7QYBPQMYwHMCVRt34DkF3KYLMETh4s0'));
+ */
+
+
+/* 
+// 下载缩略图
+var_dump($attachment->downloadThumb('images/11_thumb.png', 'a4364f90-b0d8-11ec-8cfe-b57d5aca4e63'));
+var_dump($attachment->downloadThumb('images/111/11_thumb.png', 'a4615730-b0d8-11ec-92b5-1fe1ed287b6a', 'pGF-QLDYEeyPS6lef7QYBPQMYwHMCVRt34DkF3KYLMETh4s0'));
+ */

+ 58 - 0
vendor/maniac/easemob-php/examples/auth.php

@@ -0,0 +1,58 @@
+<?php
+/*
+ * 用户示例
+ */
+require_once __DIR__ . '/../autoload.php';
+$config = require_once 'config.php';
+
+use Easemob\Auth;
+use Easemob\User;
+use Easemob\Agora\ServiceRtc;
+
+
+// 初始化授权对象,环信 token 初始化
+$easemob = $config['easemob'];
+$auth = new Auth($easemob['app_key'], $easemob['client_id'], $easemob['client_secret']);
+
+// 设置 REST 域名,沙箱环境使用,不是沙箱环境会自动获取
+if (isset($easemob['api_uri']) && $easemob['api_uri']) {
+    $auth->setApiUri($easemob['api_uri']);
+}
+
+
+/* 
+// 初始化授权对象,声网 token 初始化
+$agora = $config['agora'];
+$auth = new Auth($agora['app_key'], $agora['app_id'], $agora['app_certificate'], true, $agora['expire_time']);
+$auth->setApiUri('http://a41.easemob.com');
+ */
+
+
+echo '<pre>';
+
+
+/* 
+// 生成 Easemob userToken
+var_dump($auth->getUserToken('user4', 'user4'));
+ */
+
+
+/* 
+// 生成仅含 AgoraChat 权限的 Agora userToken
+var_dump($auth->getUserToken('bb46ab60-5c07-11ec-8cc6-b5d98ea414b4', null));
+ */
+
+
+/* 
+// 生成包含 AgoraChat 权限和 AgoraRTC (JOIN_CHANNEL) 权限的 Agora userToken
+
+// grant rtc privileges
+$serviceRtc = new ServiceRtc('7d72365eb983485397e3e3f9d460bdda', '2882341273');
+$serviceRtc->addPrivilege($serviceRtc::PRIVILEGE_JOIN_CHANNEL, 600);
+
+$configuration = array(
+    $serviceRtc,
+);
+
+var_dump($auth->getUserToken('bb46ab60-5c07-11ec-8cc6-b5d98ea414b4', null, 600, $configuration));
+ */

+ 238 - 0
vendor/maniac/easemob-php/examples/block.php

@@ -0,0 +1,238 @@
+<?php
+/*
+ * 用于限制访问(将用户加入黑名单、群组/聊天室禁言等)
+ */
+require_once __DIR__ . '/../autoload.php';
+$config = require_once 'config.php';
+
+use Easemob\Auth;
+use Easemob\User;
+use Easemob\Block;
+
+// 初始化授权对象,环信 token 初始化
+$easemob = $config['easemob'];
+$auth = new Auth($easemob['app_key'], $easemob['client_id'], $easemob['client_secret']);
+
+// 设置 REST 域名,沙箱环境使用,不是沙箱环境会自动获取
+if (isset($easemob['api_uri']) && $easemob['api_uri']) {
+    $auth->setApiUri($easemob['api_uri']);
+}
+
+// 实例化对象
+$block = new Block($auth);
+$user = new User($auth);
+
+echo '<pre>';
+
+
+/* 
+// 获取用户黑名单
+var_dump($block->getUsersBlockedFromSendMsgToUser('user3'));
+ */
+
+
+/* 
+// 添加用户黑名单
+var_dump($block->getUsersBlockedFromSendMsgToUser('user1'));
+var_dump($block->blockUserSendMsgToUser('user1', array('user2', 'user3')));
+var_dump($block->getUsersBlockedFromSendMsgToUser('user1'));
+ */
+
+
+/* 
+var_dump($block->getUsersBlockedFromSendMsgToUser('user1'));
+// 移除用户黑名单
+var_dump($block->unblockUserSendMsgToUser('user1', 'user3'));
+var_dump($block->getUsersBlockedFromSendMsgToUser('user1'));
+ */
+
+
+/* 
+var_dump($user->get('user1'));
+// 用户账号禁用
+var_dump($block->blockUserLogin('user1'));
+var_dump($user->get('user1'));
+// 用户账号解禁
+var_dump($block->unblockUserLogin('user1'));
+var_dump($user->get('user1'));
+ */
+
+
+/* 
+// 设置用户全局禁言
+var_dump($block->blockUserSendMsg('user3'));
+ */
+
+
+/* 
+// 取消用户全局禁言
+var_dump($block->unblockUserSendMsg('user3'));
+ */
+
+
+/* 
+// 查询单个帐号全局禁言
+var_dump($block->getUserBlocked('user3'));
+ */
+
+
+/* 
+// 查询APPKEY的用户禁言
+var_dump($block->getAppBlocked());
+ */
+
+
+// Group
+
+ /* 
+// 查询群组黑名单
+var_dump($block->getUsersBlockedJoinGroup('177627064238081'));
+ */
+
+
+/* 
+var_dump($block->getUsersBlockedJoinGroup('177627064238081'));
+// 添加单个用户至群组黑名单
+var_dump($block->blockUserJoinGroup('177627064238081', 'user4'));
+var_dump($block->getUsersBlockedJoinGroup('177627064238081'));
+ */
+
+
+/* 
+var_dump($block->getUsersBlockedJoinGroup('177627064238081'));
+// 批量添加用户至群组黑名单
+var_dump($block->blockUsersJoinGroup('177627064238081', array('user1', 'user3')));
+var_dump($block->getUsersBlockedJoinGroup('177627064238081'));
+ */
+
+
+/* 
+var_dump($block->getUsersBlockedJoinGroup('177627064238081'));
+// 从群组黑名单移除单个用户
+var_dump($block->unblockUserJoinGroup('177627064238081', 'user4'));
+var_dump($block->getUsersBlockedJoinGroup('177627064238081'));
+ */
+
+
+/* 
+// 批量从群组黑名单移除用户
+var_dump($block->getUsersBlockedJoinGroup('177627064238081'));
+var_dump($block->unblockUsersJoinGroup('177627064238081', array('user1', 'user3')));
+var_dump($block->getUsersBlockedJoinGroup('177627064238081'));
+ */
+
+
+/* 
+var_dump($block->getUsersBlockedSendMsgToGroup('177627064238081'));
+// 添加群禁言
+var_dump($block->blockUserSendMsgToGroup('177627064238081', array('user4')));
+var_dump($block->getUsersBlockedSendMsgToGroup('177627064238081'));
+var_dump($block->blockUserSendMsgToGroup('177627064238081', array('user5', 'user6')));
+var_dump($block->getUsersBlockedSendMsgToGroup('177627064238081'));
+ */
+
+
+/* 
+var_dump($block->getUsersBlockedSendMsgToGroup('177627064238081'));
+// 移除群禁言
+var_dump($block->unblockUserSendMsgToGroup('177627064238081', array('user4')));
+var_dump($block->getUsersBlockedSendMsgToGroup('177627064238081'));
+var_dump($block->unblockUserSendMsgToGroup('177627064238081', array('user5', 'user6')));
+var_dump($block->getUsersBlockedSendMsgToGroup('177627064238081'));
+ */
+
+ 
+/* 
+// 获取群禁言列表
+var_dump($block->getUsersBlockedSendMsgToGroup('177627064238081'));
+ */
+
+
+/* 
+// 禁言群组全体成员
+var_dump($block->blockAllUserSendMsgToGroup('177627064238081'));
+ */
+
+
+/* 
+// 解除群组全员禁言
+var_dump($block->unblockAllUserSendMsgToGroup('177627064238081'));
+ */
+
+
+// Room
+
+/*  
+// 查询聊天室黑名单
+var_dump($block->getUsersBlockedJoinRoom('177630845403137'));
+ */
+
+
+/* 
+var_dump($block->getUsersBlockedJoinRoom('177630845403137'));
+// 添加单个用户至聊天室黑名单
+var_dump($block->blockUserJoinRoom('177630845403137', 'user11'));
+var_dump($block->getUsersBlockedJoinRoom('177630845403137'));
+ */
+
+
+/* 
+var_dump($block->getUsersBlockedJoinRoom('177630845403137'));
+// 批量添加用户至聊天室黑名单
+var_dump($block->blockUsersJoinRoom('177630845403137', array('user12', 'user13')));
+var_dump($block->getUsersBlockedJoinRoom('177630845403137'));
+ */
+
+
+/* 
+var_dump($block->getUsersBlockedJoinRoom('177630845403137'));
+// 从聊天室黑名单移除单个用户
+var_dump($block->unblockUserJoinRoom('177630845403137', 'user11'));
+var_dump($block->getUsersBlockedJoinRoom('177630845403137'));
+ */
+
+
+/* 
+// 批量从聊天室黑名单移除用户
+var_dump($block->getUsersBlockedJoinRoom('177630845403137'));
+var_dump($block->unblockUsersJoinRoom('177630845403137', array('user12', 'user13')));
+var_dump($block->getUsersBlockedJoinRoom('177630845403137'));
+ */
+
+
+/* 
+var_dump($block->getUsersBlockedSendMsgToRoom('177630783537155'));
+// 添加聊天室禁言
+var_dump($block->blockUserSendMsgToRoom('177630783537155', array('user4')));
+var_dump($block->getUsersBlockedSendMsgToRoom('177630783537155'));
+var_dump($block->blockUserSendMsgToRoom('177630783537155', array('user2', 'user3')));
+var_dump($block->getUsersBlockedSendMsgToRoom('177630783537155'));
+ */
+
+
+/* 
+var_dump($block->getUsersBlockedSendMsgToRoom('177630783537155'));
+// 移除聊天室禁言
+var_dump($block->unblockUserSendMsgToRoom('177630783537155', array('user4')));
+var_dump($block->getUsersBlockedSendMsgToRoom('177630783537155'));
+var_dump($block->unblockUserSendMsgToRoom('177630783537155', array('user2', 'user3')));
+var_dump($block->getUsersBlockedSendMsgToRoom('177630783537155'));
+ */
+
+ 
+/* 
+// 获取聊天室禁言列表
+var_dump($block->getUsersBlockedSendMsgToRoom('177630783537155'));
+ */
+
+
+/* 
+// 禁言聊天室全体成员
+var_dump($block->blockAllUserSendMsgToRoom('177630783537155'));
+ */
+
+
+/* 
+// 解除聊天室全员禁言
+var_dump($block->unblockAllUserSendMsgToRoom('177630783537155'));
+ */

+ 15 - 0
vendor/maniac/easemob-php/examples/config.php

@@ -0,0 +1,15 @@
+<?php
+return array(
+    'easemob' => array(
+        'app_key' => 'appKey',
+        'client_id' => 'Client ID',
+        'client_secret' => 'ClientSecret',
+        'api_uri' => '',    // REST 域名
+    ),
+    'agora' => array(
+        'app_key' => 'appKey',
+        'app_id' => 'App ID',
+        'app_certificate' => 'App Certificate',
+        'expire_time' => 900,
+    ),
+);

+ 46 - 0
vendor/maniac/easemob-php/examples/contact.php

@@ -0,0 +1,46 @@
+<?php
+/*
+ * 联系人示例
+ */
+require_once __DIR__ . '/../autoload.php';
+$config = require_once 'config.php';
+
+use Easemob\Auth;
+use Easemob\Contact;
+
+// 初始化授权对象,环信 token 初始化
+$easemob = $config['easemob'];
+$auth = new Auth($easemob['app_key'], $easemob['client_id'], $easemob['client_secret']);
+
+// 设置 REST 域名,沙箱环境使用,不是沙箱环境会自动获取
+if (isset($easemob['api_uri']) && $easemob['api_uri']) {
+    $auth->setApiUri($easemob['api_uri']);
+}
+
+// 实例化对象
+$contact = new Contact($auth);
+
+echo '<pre>';
+
+
+/* 
+var_dump($contact->get('user3'));
+// 添加联系人
+var_dump($contact->add('user3', 'user4'));
+var_dump($contact->add('user3', 'user5'));
+var_dump($contact->get('user3'));
+ */
+
+
+/* 
+// 获取联系人列表
+var_dump($contact->get('user1'));
+ */
+
+
+/* 
+var_dump($contact->get('user3'));
+// 移除联系人
+var_dump($contact->remove('user3', 'user4'));
+var_dump($contact->get('user3'));
+ */

+ 251 - 0
vendor/maniac/easemob-php/examples/group.php

@@ -0,0 +1,251 @@
+<?php
+/*
+ * 群组管理示例
+ */
+require_once __DIR__ . '/../autoload.php';
+$config = require_once 'config.php';
+
+use Easemob\Auth;
+use Easemob\Group;
+
+// 初始化授权对象,环信 token 初始化
+$easemob = $config['easemob'];
+$auth = new Auth($easemob['app_key'], $easemob['client_id'], $easemob['client_secret']);
+
+// 设置 REST 域名,沙箱环境使用,不是沙箱环境会自动获取
+if (isset($easemob['api_uri']) && $easemob['api_uri']) {
+    $auth->setApiUri($easemob['api_uri']);
+}
+
+// 实例化对象
+$group = new Group($auth);
+
+echo '<pre>';
+
+
+/* 
+// 创建公开群
+var_dump($group->createPublicGroup('user1', 'public_group', 'public_group_desc', array('user2', 'user3')));
+// 177627064238081
+// 创建私有群
+var_dump($group->createPrivateGroup('user1', 'private_group', 'private_group_desc', array('user2', 'user3')));
+// 177627064238082
+ */
+
+
+/* 
+for ($i = 0; $i < 20; $i++) {
+    var_dump($group->createPublicGroup('user1', 'public_group_' . $i, 'public_group_desc_' . $i, array('user2', 'user3')));
+}
+// string(15) "177627101986819"
+// string(15) "177627103035393"
+// string(15) "177627103035394"
+// string(15) "177627103035396"
+// string(15) "177627103035397"
+// string(15) "177627104083969"
+// string(15) "177627104083970"
+// string(15) "177627104083971"
+// string(15) "177627104083973"
+// string(15) "177627104083974"
+// string(15) "177627105132545"
+// string(15) "177627105132546"
+// string(15) "177627105132547"
+// string(15) "177627105132548"
+// string(15) "177627105132549"
+// string(15) "177627106181121"
+// string(15) "177627106181122"
+// string(15) "177627106181123"
+// string(15) "177627106181124"
+// string(15) "177627106181125"
+// string(15) "177627106181126"
+// string(15) "177627107229697"
+ */
+
+
+/* 
+// 分页获取 App 中所有的群组
+var_dump($group->listGroups(2));
+var_dump($group->listGroups(2, 'ZGNiMjRmNGY1YjczYjlhYTNkYjk1MDY2YmEyNzFmODQ6aW06Z3JvdXA6MTExNTIxMDkxNTE5MzI3NyNkZW1vOjI'));
+ */
+
+
+/*  
+// 获取 App 中所有的群组
+var_dump($group->listAllGroups());
+ */
+
+
+/* 
+// 分页获取单个用户加入的所有群组,第 1 页
+var_dump($group->listGroupsUserJoined('user1', 1, 1));
+// 分页获取单个用户加入的所有群组,第 2 页
+var_dump($group->listGroupsUserJoined('user1', 1, 2));
+ */
+
+
+/* 
+// 获取单个用户加入的所有群组
+var_dump($group->listAllGroupsUserJoined('user1'));
+ */
+
+
+/* 
+// 获取群组详情
+var_dump($group->getGroup('177627064238081'));
+var_dump($group->getGroup('177627064238081,177627064238082'));
+ */
+
+
+/* 
+var_dump($group->getGroup('177627064238081'));
+// 修改群组信息
+$data = array(
+    'group_id' => '177627064238081',
+    'groupname' => 'test group',
+    'description' => 'test description',
+    'maxusers' => 400,
+    'membersonly' => true,
+    'allowinvites' => true,
+    'custom' => 'test custom',
+);
+var_dump($group->updateGroup($data));
+var_dump($group->getGroup('177627064238081'));
+ */
+
+
+/* 
+// 删除群组
+var_dump($group->getGroup('177627107229697'));
+var_dump($group->destroyGroup('177627107229697'));
+var_dump($group->getGroup('177627107229697'));
+ */
+
+
+/* 
+// 获取群组公告
+var_dump($group->getGroupAnnouncement('177627064238081'));
+ */
+
+
+/* 
+var_dump($group->getGroupAnnouncement('177627064238081'));
+// 修改群组公告
+var_dump($group->updateGroupAnnouncement('177627064238081', 'test 公告内容xxx'));
+var_dump($group->getGroupAnnouncement('177627064238081'));
+ */
+
+
+/* 
+// 获取群组共享文件
+var_dump($group->getGroupShareFiles('177627064238081'));
+ */
+
+
+/* 
+// 上传群组共享文件
+var_dump($group->uploadGroupShareFile('177627064238081', './images/1.png'));
+array(6) {
+    ["file_url"]=>
+    string(120) "https://a1.easemob.com/1115210915193277/demo/chatgroups/177627064238081/share_files/59f53fc0-b18e-11ec-9abc-6766e56acf4e"
+    ["group_id"]=>
+    string(15) "177627064238081"
+    ["file_name"]=>
+    string(14) "./images/1.png"
+    ["created"]=>
+    int(1648798549436)
+    ["file_id"]=>
+    string(36) "59f53fc0-b18e-11ec-9abc-6766e56acf4e"
+    ["file_size"]=>
+    int(19161)
+}
+ */
+
+
+/* 
+// 下载群组共享文件
+var_dump($group->downloadGroupShareFile('llxx.png', '177627064238081', '59f53fc0-b18e-11ec-9abc-6766e56acf4e'));
+ */
+
+
+/* 
+var_dump($group->getGroupShareFiles('177627064238081'));
+// 删除群组共享文件
+var_dump($group->deleteGroupShareFile('177627064238081', '59f53fc0-b18e-11ec-9abc-6766e56acf4e'));
+var_dump($group->getGroupShareFiles('177627064238081'));
+ */
+
+
+/* 
+// 分页获取群组成员
+var_dump($group->listGroupMembers('177627064238081', 1));
+var_dump($group->listGroupMembers('177627064238081', 1, 2));
+ */
+
+
+/*  
+// 获取群组全部成员
+var_dump($group->listAllGroupMembers('177627101986819'));
+ */
+
+
+/* 
+// 添加单个群组成员
+var_dump($group->listAllGroupMembers('177627064238081'));
+var_dump($group->addGroupMember('177627064238081', 'user4'));
+var_dump($group->listAllGroupMembers('177627064238081'));
+ */
+
+
+/* 
+// 批量添加群组成员
+var_dump($group->listAllGroupMembers('177627101986819'));
+var_dump($group->addGroupMembers('177627101986819', array('user4', 'user5', 'user6', 'user7')));
+var_dump($group->listAllGroupMembers('177627101986819'));
+ */
+
+
+/* 
+var_dump($group->listAllGroupMembers('177627064238081'));
+// 移除单个群组成员
+var_dump($group->removeGroupMember('177627064238081', 'user7'));
+var_dump($group->listAllGroupMembers('177627064238081'));
+ */
+
+
+/* 
+var_dump($group->listAllGroupMembers('177627064238081'));
+// 批量移除群组成员
+var_dump($group->removeGroupMembers('177627064238081', array('user5', 'user6')));
+var_dump($group->listAllGroupMembers('177627064238081'));
+ */
+
+
+/* 
+// 获取群管理员列表
+var_dump($group->listGroupAdmins('177627064238081'));
+ */
+
+
+/* 
+var_dump($group->listGroupAdmins('177627064238081'));
+// 添加群管理员
+var_dump($group->addGroupAdmin('177627064238081', 'user4'));
+var_dump($group->addGroupAdmin('177627064238081', 'user3'));
+var_dump($group->listGroupAdmins('177627064238081'));
+ */
+
+
+/* 
+var_dump($group->listGroupAdmins('177627064238081'));
+// 移除群管理员
+var_dump($group->removeGroupAdmin('177627064238081', 'user4'));
+var_dump($group->listGroupAdmins('177627064238081'));
+ */
+
+
+/* 
+var_dump($group->getGroup('177627064238081'));
+// 转让群组
+var_dump($group->updateGroupOwner('177627064238081', 'user2'));
+var_dump($group->getGroup('177627064238081'));
+ */

BIN
vendor/maniac/easemob-php/examples/images/1.png


+ 281 - 0
vendor/maniac/easemob-php/examples/message.php

@@ -0,0 +1,281 @@
+<?php
+/*
+ * 发送消息示例
+ */
+require_once __DIR__ . '/../autoload.php';
+$config = require_once 'config.php';
+
+use Easemob\Auth;
+use Easemob\Message;
+
+// 初始化授权对象,环信 token 初始化
+$easemob = $config['easemob'];
+$auth = new Auth($easemob['app_key'], $easemob['client_id'], $easemob['client_secret']);
+
+// 设置 REST 域名,沙箱环境使用,不是沙箱环境会自动获取
+if (isset($easemob['api_uri']) && $easemob['api_uri']) {
+    $auth->setApiUri($easemob['api_uri']);
+}
+
+// 实例化对象
+$message = new Message($auth);
+
+echo '<pre>';
+
+
+/* 
+// 获取用户离线消息数
+var_dump($message->countMissedMessages('user1'));
+ */
+
+
+/* 
+var_dump($message->countMissedMessages('user4'));
+var_dump($message->countMissedMessages('user5'));
+// 发送文本消息
+$msg = array(
+    'msg' => 'testmessage',
+    // 扩展,可以没有这个字段,但是如果有,值不能是 “ext:null” 这种形式,否则出错。
+    'ext' => array(
+        'ext1' => 'val1',
+        'ext2' => 'val2',
+    ),
+);
+var_dump($message->text('users', array('user4', 'user5'), $msg, 'user3'));
+// array(2) {
+//     ["user5"]=>
+//     string(18) "992810797698646052"
+//     ["user4"]=>
+//     string(18) "992810797698650148"
+// }
+var_dump($message->countMissedMessages('user4'));
+var_dump($message->countMissedMessages('user5'));
+ */
+
+
+/* 
+// 发送文本消息(仅发送给在线用户,消息同步给发送发)
+$msg = array(
+    'msg' => 'testmessage',
+    // 扩展,可以没有这个字段,但是如果有,值不能是 “ext:null” 这种形式,否则出错。
+    'ext' => array(
+        'ext1' => 'val1',
+        'ext2' => 'val2',
+    ),
+);
+var_dump($message->text('users', array('user4', 'user5'), $msg, 'user3', true, true));
+ */
+
+
+/* 
+var_dump($message->countMissedMessages('user4'));
+var_dump($message->countMissedMessages('user5'));
+// 发送图片消息
+$msg = array(
+    'filename' => '1.png',   // 图片名称
+    'uuid' => 'a4364f90-b0d8-11ec-8cfe-b57d5aca4e63',   // 成功上传文件返回的UUID
+    'secret' => 'pDZPmrDYEeyGTyPdhVc_kj_MzWvSzIfi3bg_fgkvpVjvQAo5', // 成功上传文件后返回的secret
+    'size' => array(    // 图片尺寸;height:高度,width:宽
+        'width' => 36,
+        'height' => 36,
+    ),
+);
+var_dump($message->image('users', array('user4', 'user5'), $msg, 'user3'));
+// array(2) {
+//     ["user5"]=>
+//     string(18) "992037625382568852"
+//     ["user4"]=>
+//     string(18) "992037625382572948"
+// }
+var_dump($message->countMissedMessages('user4'));
+var_dump($message->countMissedMessages('user5'));
+ */
+
+
+/* 
+var_dump($message->countMissedMessages('user4'));
+var_dump($message->countMissedMessages('user5'));
+// 发送语音消息
+$msg = array(
+    'filename' => '1.aud.silk',   // 语音名称
+    'uuid' => 'a4364f90-b0d8-11ec-8cfe-b57d5aca4e63',   // 成功上传文件返回的UUID
+    'secret' => 'pDZPmrDYEeyGTyPdhVc_kj_MzWvSzIfi3bg_fgkvpVjvQAo5', // 成功上传文件后返回的secret
+    'length' => 10, // 语音时间(单位:秒)
+);
+var_dump($message->audio('users', array('user4', 'user5'), $msg, 'user3'));
+// array(2) {
+//     ["user5"]=>
+//     string(18) "992037819046167688"
+//     ["user4"]=>
+//     string(18) "992037819046171784"
+// }
+var_dump($message->countMissedMessages('user4'));
+var_dump($message->countMissedMessages('user5'));
+ */
+
+
+/* 
+var_dump($message->countMissedMessages('user4'));
+var_dump($message->countMissedMessages('user5'));
+// 发送视频消息
+$msg = array(
+    'filename' => 'movie.mp4',  // 视频文件名称
+    'uuid' => 'a4364f90-b0d8-11ec-8cfe-b57d5aca4e63',   // 成功上传文件返回的UUID
+    'secret' => 'pDZPmrDYEeyGTyPdhVc_kj_MzWvSzIfi3bg_fgkvpVjvQAo5', // 成功上传文件后返回的secret
+    'thumb_uuid' => 'a4364f90-b0d8-11ec-8cfe-b57d5aca4e63',  // 成功上传视频缩略图返回的 UUID
+    'thumb_secret' => 'pDZPmrDYEeyGTyPdhVc_kj_MzWvSzIfi3bg_fgkvpVjvQAo5',   // 成功上传视频缩略图后返回的secret
+    'length' => 13, // 视频播放长度
+    'file_length' => 318465,    // 视频文件大小(单位:字节)
+);
+var_dump($message->video('users', array('user4', 'user5'), $msg, 'user3'));
+// array(2) {
+//     ["user5"]=>
+//     string(18) "992038184839807128"
+//     ["user4"]=>
+//     string(18) "992038184839811224"
+// }
+var_dump($message->countMissedMessages('user4'));
+var_dump($message->countMissedMessages('user5'));
+ */
+
+
+/* 
+var_dump($message->countMissedMessages('user4'));
+var_dump($message->countMissedMessages('user5'));
+// 发送文件消息
+$msg = array(
+    'filename' => '1.txt',  // 文件名称
+    'uuid' => 'a4364f90-b0d8-11ec-8cfe-b57d5aca4e63',   // 成功上传文件返回的UUID
+    'secret' => 'pDZPmrDYEeyGTyPdhVc_kj_MzWvSzIfi3bg_fgkvpVjvQAo5', // 成功上传文件后返回的secret
+);
+var_dump($message->file('users', array('user4', 'user5'), $msg, 'user3'));
+// array(2) {
+//     ["user5"]=>
+//     string(18) "992038408064860188"
+//     ["user4"]=>
+//     string(18) "992038408064864284"
+// }
+var_dump($message->countMissedMessages('user4'));
+var_dump($message->countMissedMessages('user5'));
+ */
+
+
+/* 
+var_dump($message->countMissedMessages('user4'));
+var_dump($message->countMissedMessages('user5'));
+// 发送位置消息
+$msg = array(
+    'lat' => '39.966',  // 纬度
+    'lng' => '116.322',   // 经度
+    'addr' => '中国北京市海淀区中关村', // 地址
+);
+var_dump($message->location('users', array('user4', 'user5'), $msg, 'user3'));
+// array(2) {
+//     ["user5"]=>
+//     string(18) "992038552529272984"
+//     ["user4"]=>
+//     string(18) "992038552529277080"
+// }
+var_dump($message->countMissedMessages('user4'));
+var_dump($message->countMissedMessages('user5'));
+ */
+
+
+/* 
+var_dump($message->countMissedMessages('user4'));
+var_dump($message->countMissedMessages('user5'));
+// 发送透传消息
+$msg = array(
+    'event' => 'notification',  // 自定义键值
+);
+var_dump($message->cmd('users', array('user4', 'user5'), $msg, 'user3'));
+// array(2) {
+//     ["user5"]=>
+//     string(18) "992038709236860040"
+//     ["user4"]=>
+//     string(18) "992038709236864136"
+// }
+var_dump($message->countMissedMessages('user4'));
+var_dump($message->countMissedMessages('user5'));
+ */
+
+
+/* 
+var_dump($message->countMissedMessages('user4'));
+var_dump($message->countMissedMessages('user5'));
+// 发送自定义消息
+$msg = array(
+    'customEvent' => 'xxx',  // 用户自定义的事件类型,必须是string,值必须满足正则表达式 [a-zA-Z0-9-_/\.]{1,32},最短1个字符 最长32个字符
+    'customExts' => array(   // 用户自定义的事件属性,类型必须是Map<String,String>,最多可以包含16个元素。customExts 是可选的,不需要可以不传
+        'asd' => '123',
+    ),
+    'ext' => array(
+        'test' => 'test111',
+    ),
+);
+var_dump($message->custom('users', array('user4', 'user5'), $msg, 'user3'));
+// array(2) {
+//     ["user5"]=>
+//     string(18) "992038835590266892"
+//     ["user4"]=>
+//     string(18) "992038835590270988"
+// }
+var_dump($message->countMissedMessages('user4'));
+var_dump($message->countMissedMessages('user5'));
+ */
+
+
+/* 
+// 获取某条离线消息状态
+var_dump($message->isMessageDeliveredToUser('user4', '992810797698650148'));
+var_dump($message->isMessageDeliveredToUser('user5', '992810797698646052'));
+ */
+
+
+/* 
+date_default_timezone_set('PRC');
+// 获取历史消息文件
+var_dump($message->getHistoryAsUri(2022032916));
+ */
+
+
+/* 
+// 下载消息历史文件到本地
+$message->getHistoryAsLocalFile(2022032916, '111.gz');
+ */
+
+
+/* 
+$msg = array(
+    'msg' => 'testmessage',
+    // 扩展,可以没有这个字段,但是如果有,值不能是 “ext:null” 这种形式,否则出错。
+    'ext' => array(
+        'ext1' => 'val1',
+        'ext2' => 'val2',
+    ),
+);
+var_dump($message->text('users', array('user4', 'user5'), $msg, 'user3'));
+// array(2) {
+//     ["user5"]=>
+//     string(18) "992813372330214544"
+//     ["user4"]=>
+//     string(18) "992813372330218640"
+// }
+ */
+
+
+/* 
+// 撤回消息
+$msg = array(
+    'msg_id' => '992813372330218640',
+    'chat_type' => 'chat',
+    'force' => true
+);
+var_dump($message->withdraw($msg));
+ */
+
+
+/* 
+// 服务端单向删除会话
+var_dump($message->deleteSession('user3', '992813372330218640', 'chat'));
+ */

+ 55 - 0
vendor/maniac/easemob-php/examples/push.php

@@ -0,0 +1,55 @@
+<?php
+/*
+ * 推送 API
+ */
+require_once __DIR__ . '/../autoload.php';
+$config = require_once 'config.php';
+
+use Easemob\Auth;
+use Easemob\User;
+use Easemob\Push;
+
+// 初始化授权对象,环信 token 初始化
+$easemob = $config['easemob'];
+$auth = new Auth($easemob['app_key'], $easemob['client_id'], $easemob['client_secret']);
+
+// 设置 REST 域名,沙箱环境使用,不是沙箱环境会自动获取
+if (isset($easemob['api_uri']) && $easemob['api_uri']) {
+    $auth->setApiUri($easemob['api_uri']);
+}
+
+// 实例化对象
+$user = new User($auth);
+$push = new Push($auth);
+
+echo '<pre>';
+
+
+/* 
+var_dump($user->get('user3'));
+// 设置用户推送昵称
+var_dump($push->updateUserNickname('user3', 'userthree'));
+var_dump($user->get('user3'));
+ */
+
+
+/* 
+// 设置推送消息展示方式
+var_dump($user->get('user3'));
+var_dump($push->setNotificationDisplayStyle('user3', 0));
+var_dump($user->get('user3'));
+var_dump($push->setNotificationDisplayStyle('user3'));
+var_dump($user->get('user3'));
+ */
+
+
+/* 
+// 设置免打扰
+var_dump($user->get('user3'));
+// 开启免打扰,设置免打扰时间
+var_dump($push->openNotificationNoDisturbing('user3', 10, 19));
+var_dump($user->get('user3'));
+// 取消免打扰
+var_dump($push->closeNotificationNoDisturbing('user3'));
+var_dump($user->get('user3'));
+ */

+ 217 - 0
vendor/maniac/easemob-php/examples/room.php

@@ -0,0 +1,217 @@
+<?php
+/*
+ * 聊天室管理示例
+ */
+require_once __DIR__ . '/../autoload.php';
+$config = require_once 'config.php';
+
+use Easemob\Auth;
+use Easemob\Room;
+
+// 初始化授权对象,环信 token 初始化
+$easemob = $config['easemob'];
+$auth = new Auth($easemob['app_key'], $easemob['client_id'], $easemob['client_secret']);
+
+// 设置 REST 域名,沙箱环境使用,不是沙箱环境会自动获取
+if (isset($easemob['api_uri']) && $easemob['api_uri']) {
+    $auth->setApiUri($easemob['api_uri']);
+}
+
+// 实例化对象
+$room = new Room($auth);
+
+echo '<pre>';
+
+
+/* 
+// 获取 APP 中所有的聊天室(分页)
+var_dump($room->listRooms());
+var_dump($room->listRooms(10, 'ZGNiMjRmNGY1YjczYjlhYTNkYjk1MDY2YmEyNzFmODQ6aW06Y2hhdHJvb206MTExNTIxMDkxNTE5MzI3NyNkZW1vOjI'));
+ */
+
+
+/* 
+// 获取 APP 中所有的聊天室
+var_dump($room->listAllRooms());
+ */
+
+
+/*  
+// 获取用户加入的聊天室(分页)
+var_dump($room->listRoomsUserJoined('user1'));
+var_dump($room->listRoomsUserJoined('user1', 10, 2));
+ */
+
+
+/* 
+// 获取用户加入的聊天室
+var_dump($room->listAllRoomsUserJoined('user1'));
+ */
+
+
+/* 
+// 创建聊天室
+var_dump($room->createRoom('测试聊天室1', '测试聊天室描述1', 'user1', array('user2', 'user3')));
+// 177630783537155
+ */
+
+
+/* 
+for ($i = 0; $i < 20; $i++) {
+    var_dump($room->createRoom('测试聊天室' . $i, '测试聊天室描述' . $i, 'user1', array('user2', 'user3')));
+}
+// string(15) "177630845403137"
+// string(15) "177630845403138"
+// string(15) "177630846451713"
+// string(15) "177630846451714"
+// string(15) "177630846451716"
+// string(15) "177630846451717"
+// string(15) "177630846451719"
+// string(15) "177630846451720"
+// string(15) "177630847500289"
+// string(15) "177630847500290"
+// string(15) "177630847500292"
+// string(15) "177630847500293"
+// string(15) "177630847500294"
+// string(15) "177630848548865"
+// string(15) "177630848548866"
+// string(15) "177630848548867"
+// string(15) "177630848548868"
+// string(15) "177630849597441"
+// string(15) "177630849597442"
+// string(15) "177630849597443"
+ */
+
+
+/* 
+// 获取聊天室详情
+var_dump($room->getRoom('177630845403137'));
+var_dump($room->getRoom('177630783537155,177630845403137'));
+ */
+
+
+/* 
+var_dump($room->getRoom('177630783537155'));
+// 修改聊天室信息
+$data = array(
+    'room_id' => '177630783537155',
+    'name' => '测试聊天室1x',
+    'description' => '测试聊天室描述1x',
+    'maxusers' => 500,
+);
+var_dump($room->updateRoom($data));
+var_dump($room->getRoom('177630783537155'));
+ */
+
+
+/* 
+// 删除聊天室
+var_dump($room->getRoom('177630849597443'));
+var_dump($room->destroyRoom('177630849597443'));
+var_dump($room->getRoom('177630849597443'));
+ */
+
+
+/* 
+// 获取聊天室公告
+var_dump($room->getRoomAnnouncement('177630783537155'));
+ */
+
+
+/* 
+var_dump($room->getRoomAnnouncement('177630783537155'));
+// 修改聊天室公告
+var_dump($room->updateRoomAnnouncement('177630783537155', '聊天室测试公告'));
+var_dump($room->getRoomAnnouncement('177630783537155'));
+ */
+
+
+/* 
+// 分页获取聊天室成员
+var_dump($room->listRoomMembers('177630783537155', 1));    // 默认第一页 10 条
+var_dump($room->listRoomMembers('177630783537155', 1, 2)); // 第二页 10 条
+ */
+
+
+/* 
+// 获取聊天室所有成员
+var_dump($room->listRoomMembersAll('177630783537155'));
+ */
+
+
+/* 
+var_dump($room->listRoomMembersAll('177630783537155'));
+// 添加单个聊天室成员
+var_dump($room->addRoomMember('177630783537155', 'user10'));
+var_dump($room->listRoomMembersAll('177630783537155'));
+ */
+
+
+/* 
+var_dump($room->listRoomMembersAll('177630783537155'));
+// 批量添加聊天室成员
+var_dump($room->addRoomMembers('177630783537155', array('user5', 'user6', 'user7')));
+var_dump($room->listRoomMembersAll('177630783537155'));
+ */
+
+
+/* 
+var_dump($room->listRoomMembersAll('177630783537155'));
+// 删除单个聊天室成员
+var_dump($room->removeRoomMember('177630783537155', 'user10'));
+var_dump($room->listRoomMembersAll('177630783537155'));
+ */
+
+
+/* 
+// 批量删除聊天室成员
+var_dump($room->listRoomMembersAll('177630783537155'));
+var_dump($room->removeRoomMembers('177630783537155', array('user11', 'user12', 'user13')));
+var_dump($room->listRoomMembersAll('177630783537155'));
+ */
+
+
+/* 
+// 获取聊天室管理员列表
+var_dump($room->listRoomAdminsAll('174712753815556'));
+ */
+
+
+/* 
+var_dump($room->listRoomAdminsAll('177630783537155'));
+// 添加聊天室管理员
+var_dump($room->promoteRoomAdmin('177630783537155', 'user4'));
+var_dump($room->listRoomAdminsAll('177630783537155'));
+ */
+
+
+/* 
+var_dump($room->listRoomAdminsAll('177630783537155'));
+// 移除聊天室管理员
+var_dump($room->demoteRoomAdmin('177630783537155', 'user4'));
+var_dump($room->listRoomAdminsAll('177630783537155'));
+ */
+
+
+/* 
+// 分页获取聊天室超级管理员列表
+var_dump($room->listRoomSuperAdmins(2));
+var_dump($room->listRoomSuperAdmins(2, 2));
+var_dump($room->listRoomSuperAdmins(2, 3));
+ */
+
+
+/* 
+var_dump($room->listRoomSuperAdminsAll());
+// 添加超级管理员
+var_dump($room->promoteRoomSuperAdmin('user3'));
+var_dump($room->listRoomSuperAdminsAll());
+ */
+
+
+/* 
+// 移除超级管理员
+var_dump($room->listRoomSuperAdmins());
+var_dump($room->demoteRoomSuperAdmin('user3'));
+var_dump($room->listRoomSuperAdmins());
+ */

+ 98 - 0
vendor/maniac/easemob-php/examples/user.php

@@ -0,0 +1,98 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+$config = require_once 'config.php';
+
+use Easemob\Auth;
+use Easemob\User;
+
+// 初始化授权对象
+$easemob = $config['easemob'];
+$auth = new Auth($easemob['app_key'], $easemob['client_id'], $easemob['client_secret']);
+
+// 设置 REST 域名,沙箱环境使用,不是沙箱环境会自动获取
+if (isset($easemob['api_uri']) && $easemob['api_uri']) {
+    $auth->setApiUri($easemob['api_uri']);
+}
+
+$user = new User($auth);
+
+echo '<pre>';
+
+
+/* 
+// 注册单个用户
+$data = array(
+    'username' => 'user1',
+    'password' => 'user1',
+);
+var_dump($user->create($data));
+ */
+
+
+/* 
+// 批量注册用户
+$data = array();
+for ($i = 2; $i< 21; $i++) {
+    $data[] = array(
+        'username' => 'user' . $i,
+        'password' => 'user' . $i
+    );
+}
+var_dump($user->create($data));
+ */
+
+
+/* 
+// 获取单个用户
+var_dump($user->get('user1'));
+ */
+
+
+/* 
+// 批量获取用户
+$data = $user->listUsers(2);
+var_dump($data);
+$data = $user->listUsers(2, 'ZGNiMjRmNGY1YjczYjlhYTNkYjk1MDY2YmEyNzFmODQ6aW06dXNlcjoxMTE1MjEwOTE1MTkzMjc3I2RlbW86OTU5MDkzMjc2ODMxNTc0MjMz');
+var_dump($data);
+// 批量获取被封禁的用户
+$data = $user->listUsers(2, '', false);
+var_dump($data);
+ */
+
+
+/* 
+// 删除单个用户
+var_dump($user->delete('user18'));
+ */
+
+
+/* 
+// 批量删除用户
+var_dump($user->batchDelete(2));
+ */
+
+
+/* 
+// 修改用户密码
+var_dump($user->updateUserPassword('user1', 'userOne'));
+ */
+
+
+/* 
+// 获取用户在线状态
+// true: 在线;false: 离线;
+var_dump($user->isUserOnline('user1'));
+ */
+
+
+/* 
+// 批量获取用户在线状态
+// true: 在线;false: 离线;
+var_dump($user->isUsersOnline(array('user1', 'user2')));
+ */
+
+
+/* 
+// 强制下线
+var_dump($user->forceLogoutAllDevices('user1'));
+ */

+ 58 - 0
vendor/maniac/easemob-php/examples/user_metadata.php

@@ -0,0 +1,58 @@
+<?php
+/*
+ * 用户属性示例
+ */
+require_once __DIR__ . '/../autoload.php';
+$config = require_once 'config.php';
+
+use Easemob\Auth;
+use Easemob\UserMetadata;
+
+// 初始化授权对象,环信 token 初始化
+$easemob = $config['easemob'];
+$auth = new Auth($easemob['app_key'], $easemob['client_id'], $easemob['client_secret']);
+
+// 设置 REST 域名,沙箱环境使用,不是沙箱环境会自动获取
+if (isset($easemob['api_uri']) && $easemob['api_uri']) {
+    $auth->setApiUri($easemob['api_uri']);
+}
+
+// 实例化对象
+$metadata = new UserMetadata($auth);
+
+echo '<pre>';
+
+
+/* 
+// 获取用户属性
+var_dump($metadata->getMetadataFromUser('user3'));
+ */
+
+
+/* 
+// 设置用户属性
+var_dump($metadata->setMetadataToUser('user3', array('avatar' => 'http://www.easemob.com/avatar2.png', 'nickname' => 'userthree')));
+var_dump($metadata->setMetadataToUser('user3', array('userasd' => 'sdf', 'userdfg' => 'fgh')));
+var_dump($metadata->setMetadataToUser('user4', array('avatar' => 'http://www.easemob.com/avatar4.png', 'nickname' => 'userfour')));
+var_dump($metadata->setMetadataToUser('user4', array('nickname' => 'userfive', 'age' => 20)));
+ */
+
+
+/* 
+// 批量获取用户属性
+var_dump($metadata->batchGetMetadataFromUser(array('user3', 'user4'), array('avatar', 'nickname', 'age', 'sex', 'asd')));
+ */
+
+
+/* 
+// 获取用户属性总量大小
+var_dump($metadata->getUsage());
+ */
+
+ 
+/* 
+// 删除用户属性
+var_dump($metadata->getMetadataFromUser('user3'));
+var_dump($metadata->deleteMetadataFromUser('user3'));
+var_dump($metadata->getMetadataFromUser('user3'));
+ */

+ 85 - 0
vendor/maniac/easemob-php/examples/whiteList.php

@@ -0,0 +1,85 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+$config = require_once 'config.php';
+
+use Easemob\Auth;
+use Easemob\WhiteList;
+
+// 初始化授权对象
+$easemob = $config['easemob'];
+$auth = new Auth($easemob['app_key'], $easemob['client_id'], $easemob['client_secret']);
+
+// 设置 REST 域名,沙箱环境使用,不是沙箱环境会自动获取
+if (isset($easemob['api_uri']) && $easemob['api_uri']) {
+    $auth->setApiUri($easemob['api_uri']);
+}
+
+$whiteList = new WhiteList($auth);
+
+echo '<pre>';
+
+
+/* 
+// 查询群组白名单
+var_dump($whiteList->getGroupWhiteList('177627101986819'));
+ */
+
+
+/* 
+var_dump($whiteList->getGroupWhiteList('177627101986819'));
+// 添加单个用户至群组白名单
+var_dump($whiteList->addUserToGroupWhiteList('177627101986819', 'user3'));
+var_dump($whiteList->getGroupWhiteList('177627101986819'));
+ */
+
+
+/* 
+var_dump($whiteList->getGroupWhiteList('177627101986819'));
+// 批量添加用户至群组白名单
+var_dump($whiteList->addUsersToGroupWhiteList('177627101986819', array('user4', 'user5')));
+var_dump($whiteList->getGroupWhiteList('177627101986819'));
+ */
+
+
+/* 
+var_dump($whiteList->getGroupWhiteList('177627101986819'));
+// 将用户移除群组白名单
+var_dump($whiteList->removeUsersFromGroupWhiteList('177627101986819', 'user3'));
+var_dump($whiteList->getGroupWhiteList('177627101986819'));
+// 将用户移除群组白名单
+var_dump($whiteList->removeUsersFromGroupWhiteList('177627101986819', 'user4,user5'));
+var_dump($whiteList->getGroupWhiteList('177627101986819'));
+ */
+
+
+/* 
+// 查询聊天室白名单
+var_dump($whiteList->getRoomWhiteList('177630783537155'));
+ */
+
+
+/* 
+var_dump($whiteList->getRoomWhiteList('177630783537155'));
+// 添加单个用户至聊天室白名单
+var_dump($whiteList->addUserToRoomWhiteList('177630783537155', 'user2'));
+var_dump($whiteList->getRoomWhiteList('177630783537155'));
+ */
+
+
+/* 
+var_dump($whiteList->getRoomWhiteList('177630783537155'));
+// 批量添加用户至聊天室白名单
+var_dump($whiteList->addUsersToRoomWhiteList('177630783537155', array('user3', 'user4')));
+var_dump($whiteList->getRoomWhiteList('177630783537155'));
+ */
+
+
+/* 
+var_dump($whiteList->getRoomWhiteList('177630783537155'));
+// 将用户移除聊天室白名单
+var_dump($whiteList->removeUsersFromRoomWhiteList('177630783537155', 'user4'));
+var_dump($whiteList->getRoomWhiteList('177630783537155'));
+// 将用户移除聊天室白名单
+var_dump($whiteList->removeUsersFromRoomWhiteList('177630783537155', 'user2,user3'));
+var_dump($whiteList->getRoomWhiteList('177630783537155'));
+ */

+ 12 - 0
vendor/maniac/easemob-php/phpunit.xml

@@ -0,0 +1,12 @@
+<phpunit bootstrap="./tests/bootstrap.php">
+  <testsuites>
+    <testsuite name="money">
+      <directory>tests</directory>
+    </testsuite>
+  </testsuites>
+  <logging>
+    <junit outputFile="tests/logs/junit.xml"/>
+    <testdoxHtml outputFile="tests/logs/testdox.html"/>
+    <testdoxXml outputFile="tests/logs/testdox.xml"/>
+  </logging>
+</phpunit>

+ 2 - 0
vendor/maniac/easemob-php/runtime/.gitignore

@@ -0,0 +1,2 @@
+*
+!.gitignore

+ 102 - 0
vendor/maniac/easemob-php/src/Agora/AccessToken2.php

@@ -0,0 +1,102 @@
+<?php
+namespace Easemob\Agora;
+
+/**
+ * @ignore
+ * @final
+ */
+final class AccessToken2
+{
+    const VERSION = "007";
+    const VERSION_LENGTH = 3;
+    public $appCert;
+    public $appId;
+    public $expire;
+    public $issueTs;
+    public $salt;
+    public $services = [];
+
+    public function __construct($appId = "", $appCert = "", $expire = 900)
+    {
+        $this->appId = $appId;
+        $this->appCert = $appCert;
+        $this->expire = $expire;
+        $this->issueTs = time();
+        $this->salt = rand(1, 99999999);
+    }
+
+    public function addService($service)
+    {
+        $this->services[$service->getServiceType()] = $service;
+    }
+
+    public function build()
+    {
+        if (!self::isUUid($this->appId) || !self::isUUid($this->appCert)) {
+            return "";
+        }
+
+        $signing = $this->getSign();
+        $data = Util::packString($this->appId) . Util::packUint32($this->issueTs) . Util::packUint32($this->expire)
+            . Util::packUint32($this->salt) . Util::packUint16(count($this->services));
+
+        foreach ($this->services as $key => $service) {
+            $data .= $service->pack();
+        }
+
+        $signature = hash_hmac("sha256", $data, $signing, true);
+
+        return self::getVersion() . base64_encode(zlib_encode(Util::packString($signature) . $data, ZLIB_ENCODING_DEFLATE));
+    }
+
+    public function getSign()
+    {
+        $hh = hash_hmac("sha256", $this->appCert, Util::packUint32($this->issueTs), true);
+        return hash_hmac("sha256", $hh, Util::packUint32($this->salt), true);
+    }
+
+    public static function getVersion()
+    {
+        return self::VERSION;
+    }
+
+    public static function isUUid($str)
+    {
+        if (strlen($str) != 32) {
+            return false;
+        }
+        return ctype_xdigit($str);
+    }
+
+    public function parse($token)
+    {
+        if (substr($token, 0, self::VERSION_LENGTH) != self::getVersion()) {
+            return false;
+        }
+
+        $data = zlib_decode(base64_decode(substr($token, self::VERSION_LENGTH)));
+        $signature = Util::unpackString($data);
+        $this->appId = Util::unpackString($data);
+        $this->issueTs = Util::unpackUint32($data);
+        $this->expire = Util::unpackUint32($data);
+        $this->salt = Util::unpackUint32($data);
+        $serviceNum = Util::unpackUint16($data);
+
+        $servicesObj = [
+            ServiceRtc::SERVICE_TYPE => new ServiceRtc(),
+            ServiceRtm::SERVICE_TYPE => new ServiceRtm(),
+            ServiceStreaming::SERVICE_TYPE => new ServiceStreaming(),
+            ServiceChat::SERVICE_TYPE => new ServiceChat()
+        ];
+        for ($i = 0; $i < $serviceNum; $i++) {
+            $serviceTye = Util::unpackUint16($data);
+            $service = $servicesObj[$serviceTye];
+            if ($service == null) {
+                return false;
+            }
+            $service->unpack($data);
+            $this->services[$serviceTye] = $service;
+        }
+        return true;
+    }
+}

+ 53 - 0
vendor/maniac/easemob-php/src/Agora/ChatTokenBuilder2.php

@@ -0,0 +1,53 @@
+<?php
+namespace Easemob\Agora;
+
+/**
+ * @ignore
+ */
+class ChatTokenBuilder2
+{
+    /**
+     * Build the CHAT token.
+     *
+     * @param $appId :          The App ID issued to you by Agora. Apply for a new App ID from
+     *                          Agora Dashboard if it is missing from your kit. See Get an App ID.
+     * @param $appCertificate : Certificate of the application that you registered in
+     *                          the Agora Dashboard. See Get an App Certificate.
+     * @param $userId :         The user's unique id.
+     * @param $expire :         represented by the number of seconds elapsed since now. If, for example, you want to access the
+     *                          Agora Service within 10 minutes after the token is generated, set expireTimestamp as 600(seconds).
+     * @return The CHAT token.
+     */
+    public static function buildUserToken($appId, $appCertificate, $userId, $expire)
+    {
+        $accessToken = new AccessToken2($appId, $appCertificate, $expire);
+        $serviceChat = new ServiceChat($userId);
+
+        $serviceChat->addPrivilege($serviceChat::PRIVILEGE_USER, $expire);
+        $accessToken->addService($serviceChat);
+
+        return $accessToken->build();
+    }
+
+    /**
+     * Build the CHAT token.
+     *
+     * @param $appId :          The App ID issued to you by Agora. Apply for a new App ID from
+     *                          Agora Dashboard if it is missing from your kit. See Get an App ID.
+     * @param $appCertificate : Certificate of the application that you registered in
+     *                          the Agora Dashboard. See Get an App Certificate.
+     * @param $expire :         represented by the number of seconds elapsed since now. If, for example, you want to access the
+     *                          Agora Service within 10 minutes after the token is generated, set expireTimestamp as 600(seconds).
+     * @return The CHAT token.
+     */
+    public static function buildAppToken($appId, $appCertificate, $expire)
+    {
+        $accessToken = new AccessToken2($appId, $appCertificate, $expire);
+        $serviceChat = new ServiceChat();
+
+        $serviceChat->addPrivilege($serviceChat::PRIVILEGE_APP, $expire);
+        $accessToken->addService($serviceChat);
+
+        return $accessToken->build();
+    }
+}

+ 36 - 0
vendor/maniac/easemob-php/src/Agora/Service.php

@@ -0,0 +1,36 @@
+<?php
+namespace Easemob\Agora;
+
+/**
+ * @ignore
+ */
+class Service
+{
+    public $type;
+    public $privileges;
+
+    public function __construct($serviceType)
+    {
+        $this->type = $serviceType;
+    }
+
+    public function addPrivilege($privilege, $expire)
+    {
+        $this->privileges[$privilege] = $expire;
+    }
+
+    public function getServiceType()
+    {
+        return $this->type;
+    }
+
+    public function pack()
+    {
+        return Util::packUint16($this->type) . Util::packMapUint32($this->privileges);
+    }
+
+    public function unpack(&$data)
+    {
+        $this->privileges = Util::unpackMapUint32($data);
+    }
+}

+ 30 - 0
vendor/maniac/easemob-php/src/Agora/ServiceChat.php

@@ -0,0 +1,30 @@
+<?php
+namespace Easemob\Agora;
+
+/**
+ * @ignore
+ */
+class ServiceChat extends Service
+{
+    const SERVICE_TYPE = 5;
+    const PRIVILEGE_USER = 1;
+    const PRIVILEGE_APP = 2;
+    public $userId;
+
+    public function __construct($userId = "")
+    {
+        parent::__construct(self::SERVICE_TYPE);
+        $this->userId = $userId;
+    }
+
+    public function pack()
+    {
+        return parent::pack() . Util::packString($this->userId);
+    }
+
+    public function unpack(&$data)
+    {
+        parent::unpack($data);
+        $this->userId = Util::unpackString($data);
+    }
+}

+ 26 - 0
vendor/maniac/easemob-php/src/Agora/ServiceFpa.php

@@ -0,0 +1,26 @@
+<?php
+namespace Easemob\Agora;
+
+/**
+ * @ignore
+ */
+class ServiceFpa extends Service
+{
+    const SERVICE_TYPE = 4;
+    const PRIVILEGE_LOGIN = 1;
+
+    public function __construct()
+    {
+        parent::__construct(self::SERVICE_TYPE);
+    }
+
+    public function pack()
+    {
+        return parent::pack();
+    }
+
+    public function unpack(&$data)
+    {
+        parent::unpack($data);
+    }
+}

+ 35 - 0
vendor/maniac/easemob-php/src/Agora/ServiceRtc.php

@@ -0,0 +1,35 @@
+<?php
+namespace Easemob\Agora;
+
+/**
+ * @ignore
+ */
+class ServiceRtc extends Service
+{
+    const SERVICE_TYPE = 1;
+    const PRIVILEGE_JOIN_CHANNEL = 1;
+    const PRIVILEGE_PUBLISH_AUDIO_STREAM = 2;
+    const PRIVILEGE_PUBLISH_VIDEO_STREAM = 3;
+    const PRIVILEGE_PUBLISH_DATA_STREAM = 4;
+    public $channelName;
+    public $uid;
+
+    public function __construct($channelName = "", $uid = "")
+    {
+        parent::__construct(self::SERVICE_TYPE);
+        $this->channelName = $channelName;
+        $this->uid = $uid;
+    }
+
+    public function pack()
+    {
+        return parent::pack() . Util::packString($this->channelName) . Util::packString($this->uid);
+    }
+
+    public function unpack(&$data)
+    {
+        parent::unpack($data);
+        $this->channelName = Util::unpackString($data);
+        $this->uid = Util::unpackString($data);
+    }
+}

+ 29 - 0
vendor/maniac/easemob-php/src/Agora/ServiceRtm.php

@@ -0,0 +1,29 @@
+<?php
+namespace Easemob\Agora;
+
+/**
+ * @ignore
+ */
+class ServiceRtm extends Service
+{
+    const SERVICE_TYPE = 2;
+    const PRIVILEGE_LOGIN = 1;
+    public $userId;
+
+    public function __construct($userId = "")
+    {
+        parent::__construct(self::SERVICE_TYPE);
+        $this->userId = $userId;
+    }
+
+    public function pack()
+    {
+        return parent::pack() . Util::packString($this->userId);
+    }
+
+    public function unpack(&$data)
+    {
+        parent::unpack($data);
+        $this->userId = Util::unpackString($data);
+    }
+}

+ 33 - 0
vendor/maniac/easemob-php/src/Agora/ServiceStreaming.php

@@ -0,0 +1,33 @@
+<?php
+namespace Easemob\Agora;
+
+/**
+ * @ignore
+ */
+class ServiceStreaming extends Service
+{
+    const SERVICE_TYPE = 3;
+    const PRIVILEGE_PUBLISH_MIX_STREAM = 1;
+    const PRIVILEGE_PUBLISH_RAW_STREAM = 2;
+    public $channelName;
+    public $uid;
+
+    public function __construct($channelName = "", $uid = "")
+    {
+        parent::__construct(self::SERVICE_TYPE);
+        $this->channelName = $channelName;
+        $this->uid = $uid;
+    }
+
+    public function pack()
+    {
+        return parent::pack() . Util::packString($this->channelName) . Util::packString($this->uid);
+    }
+
+    public function unpack(&$data)
+    {
+        parent::unpack($data);
+        $this->channelName = Util::unpackString($data);
+        $this->uid = Util::unpackString($data);
+    }
+}

+ 77 - 0
vendor/maniac/easemob-php/src/Agora/Util.php

@@ -0,0 +1,77 @@
+<?php
+namespace Easemob\Agora;
+
+/**
+ * @ignore
+ * @final
+ */
+final class Util
+{
+    public static function assertEqual($expected, $actual)
+    {
+        $debug = debug_backtrace();
+        $info = "\n- File:" . basename($debug[1]["file"]) . ", Func:" . $debug[1]["function"] . ", Line:" . $debug[1]["line"];
+        if ($expected != $actual) {
+            echo $info . "\n  Assert failed" . "\n    Expected :" . $expected . "\n    Actual   :" . $actual;
+        } else {
+            echo $info . "\n  Assert ok";
+        }
+    }
+
+    public static function packUint16($x)
+    {
+        return pack("v", $x);
+    }
+
+    public static function unpackUint16(&$data)
+    {
+        $up = unpack("v", substr($data, 0, 2));
+        $data = substr($data, 2);
+        return $up[1];
+    }
+
+    public static function packUint32($x)
+    {
+        return pack("V", $x);
+    }
+
+    public static function unpackUint32(&$data)
+    {
+        $up = unpack("V", substr($data, 0, 4));
+        $data = substr($data, 4);
+        return $up[1];
+    }
+
+    public static function packString($str)
+    {
+        return self::packUint16(strlen($str)) . $str;
+    }
+
+    public static function unpackString(&$data)
+    {
+        $len = self::unpackUint16($data);
+        $up = unpack("C*", substr($data, 0, $len));
+        $data = substr($data, $len);
+        return implode(array_map("chr", $up));
+    }
+
+    public static function packMapUint32($arr)
+    {
+        ksort($arr);
+        $kv = "";
+        foreach ($arr as $key => $val) {
+            $kv .= self::packUint16($key) . self::packUint32($val);
+        }
+        return self::packUint16(count($arr)) . $kv;
+    }
+
+    public static function unpackMapUint32(&$data)
+    {
+        $len = self::unpackUint16($data);
+        $arr = [];
+        for ($i = 0; $i < $len; $i++) {
+            $arr[self::unpackUint16($data)] = self::unpackUint32($data);
+        }
+        return $arr;
+    }
+}

+ 176 - 0
vendor/maniac/easemob-php/src/Attachment.php

@@ -0,0 +1,176 @@
+<?php
+namespace Easemob;
+
+use Easemob\Http\Http;
+
+/**
+ * \~chinese
+ * Attachment 用来上传下载附件
+ * 
+ * \~english
+ * The `Attachment` is used to upload and download attachments
+ */
+final class Attachment
+{
+    /**
+     * @ignore
+     * @var Auth $auth 授权对象
+     */    
+    private $auth;
+
+    /// @cond
+    public function __construct($auth)
+    {
+        $this->auth = $auth;
+    }
+    /// @endcond
+
+    /**
+     * \~chinese
+     * \brief
+     * 文件上传
+     * 
+     * \details 
+     * - 上传文件的大小不能超过 10 M,超过会上传失败。
+     * - 在上传文件的时候可以选择是否限制访问权限,如果选择限制的话,会在上传请求完成后返回一个 secret,只有知道这个 secret,并且是 app 的注册用户,才能够下载文件。如果选择不限制的话,则只要是 app 的注册用户就能够下载。
+     * - 如选择加 secret 限制的话,消息回调(包含发送前回调和发送后回调)、历史消息这些功能中涉及下载文件时,都需要在下载 url 中拼接 secret,才能正常下载文件;
+     * - 拼接规则如下:url?share-secret=secret
+     * 
+     * @param  string  $fileName       上传的附件
+     * @param  boolean $restrictAccess 控制文件是否可以被任何人获取,这个值为 true,返回结果中会添加一个 share-secret 值。再次访问文件需要用到这个值。默认值:false
+     * @return array                   上传文件信息或者错误
+     * 
+     * \~english
+     * \brief
+     * File upload
+     * 
+     * \details
+     * - The size of the uploaded file cannot exceed 10m. If it exceeds 10m, the upload will fail.
+     * - When uploading files, you can choose whether to restrict access rights. If you choose to restrict, a secret will be returned after the upload request is completed. Only those who know the secret and are registered users of the app can download files. If you choose not to limit, you can download as long as you are a registered user of the app.
+     * - If you choose to add the secret limit, the message callback (including the callback before sending and the callback after sending) and the historical message, which involve downloading files, need to splice the secret in the download URL to download files normally;
+     * - The splicing rules are as follows: url?share-secret=secret
+     * 
+     * @param  string  $fileName       Uploaded attachments
+     * @param  boolean $restrictAccess Controls whether the file can be obtained by anyone. This value is true. A share secret value will be added to the returned result. This value is required to access the file again. Default: false
+     * @return array                   Upload file information or error
+     */
+    public function uploadFile($fileName, $restrictAccess = false)
+    {
+        if (!trim($fileName)) {
+            \Easemob\exception('Please pass in the attachment name');
+        }
+        
+        $file = fopen($fileName, 'rb');
+        if ($file === false) {
+            \Easemob\exception('The attachment cannot be read');
+        }
+
+        $restrictAccess = (bool)$restrictAccess;
+        $headers = $this->auth->headers();
+        if ($restrictAccess) {
+            $headers['restrict-access'] = $restrictAccess;
+        }
+        
+        $stat = fstat($file);
+        $size = $stat['size'];
+        $data = fread($file, $size);
+        fclose($file);
+        $mimeType = mime_content_type($fileName) ? mime_content_type($fileName) : null;
+        $uri = $this->auth->getBaseUri() . '/chatfiles';
+        $resp = Http::multipartPost($uri, $fileName, $data, $mimeType, $headers);
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['entities'][0];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 下载附件
+     * 
+     * \details
+     * 这里需要注意的就是,如果上传文件时候选择了文件不共享,需要在请求头中带上上面返回的 share-secret 和当前登录用户的 token 才能够下载。
+     * 
+     * @param  string  $fileName    下载的文件名
+     * @param  string  $uuid        文件唯一 ID,文件上传成功后会返回
+     * @param  string  $shareSecret share-secret,文件上传成功后会返回
+     * @return int|array            下载文件的大小或者错误
+     * 
+     * \~english
+     * \brief
+     * Download attachments
+     * 
+     * \details
+     * It should be noted here that if you choose not to share the file when uploading the file, you need to bring the share secret returned above and the token of the currently logged in user in the request header to download it.
+     * 
+     * @param  string  $fileName    Downloaded file name
+     * @param  string  $uuid        Unique ID of the file, which will be returned after the file is uploaded successfully
+     * @param  string  $shareSecret share-secret,it will return after the file is uploaded successfully
+     * @return int|array            Download file size or error
+     */
+    public function downloadFile($fileName, $uuid, $shareSecret = '')
+    {
+        return $this->download($fileName, $uuid, $shareSecret);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 下载缩略图
+     * 
+     * \details
+     * 在服务器端支持自动的创建图片的缩略图。可以先下载缩略图,当用户有需求的时候,再下载大图。 这里和下载大图唯一不同的就是 header 中多了一个“thumbnail: true”,当服务器看到过来的请求的 header 中包括这个的时候,就会返回缩略图,否则返回原始大图。
+     * 
+     * @param  string $fileName    下载缩略图的文件名
+     * @param  string $uuid        文件唯一 ID,文件上传成功后会返回
+     * @param  string $shareSecret share-secret,文件上传成功后会返回
+     * @return int|array           下载缩略图的大小或者错误
+     * 
+     * \~english
+     * \brief
+     * Download thumbnails
+     * 
+     * \details
+     * The server side supports the automatic creation of thumbnails of pictures. You can download thumbnails first, and then download large images when users need them. The only difference between this and downloading the big picture is that there is an "thumbnail: true" in the header. When the server sees that this is included in the header of the request, it will return the thumbnail, otherwise it will return the original big picture.
+     * 
+     * @param  string $fileName    Download thumbnail file name
+     * @param  string $uuid        Unique ID of the file, which will be returned after the file is uploaded successfully
+     * @param  string $shareSecret share-secret,it will return after the file is uploaded successfully
+     * @return int|array           Download file size or error
+     */
+    public function downloadThumb($fileName, $uuid, $shareSecret = '')
+    {
+        return $this->download($fileName, $uuid, $shareSecret, true);
+    }
+
+    /**
+     * @ignore 下载附件
+     * @param  string  $fileName    下载的文件名
+     * @param  string  $uuid        文件唯一 ID,文件上传成功后会返回
+     * @param  string  $shareSecret share-secret,文件上传成功后会返回
+     * @param  boolean $thumb       下载缩略图标识
+     * @return int|array            下载文件的大小或者错误
+     */
+    private function download($fileName, $uuid, $shareSecret = '', $thumb = false)
+    {
+        $uri = $this->auth->getBaseUri() . '/chatfiles/' . $uuid;
+        $headers = $this->auth->headers();
+        if ($shareSecret) {
+            $headers['share-secret'] = $shareSecret;
+        }
+        if ($thumb) {
+            $headers['thumbnail'] = true;
+        }
+        $resp = Http::get($uri, $headers);
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $dir = dirname($fileName);
+        if (!file_exists($dir)) {
+            mkdir($dir, 0755, true);
+        }
+        return file_put_contents($fileName, $resp->body);
+    }
+}

+ 429 - 0
vendor/maniac/easemob-php/src/Auth.php

@@ -0,0 +1,429 @@
+<?php
+namespace Easemob;
+
+use Easemob\Http\Http;
+use Easemob\Cache\Cache;
+use Easemob\Agora\ChatTokenBuilder2;
+use Easemob\Agora\AccessToken2;
+
+/**
+ * 授权类
+ * @final
+ */
+final class Auth
+{
+    private static $DNS_URL = 'https://rs.easemob.com';
+
+    /**
+     * @ignore
+     * @var string $apiUri rest 域名
+     */
+    private $apiUri;
+
+    /**
+     * @ignore
+     * @var string $orgName 企业唯一标识 org_name
+     */
+    private $orgName;
+
+    /**
+     * @ignore
+     * @var string $appName 企业下 APP 唯一标识 app_name
+     */
+    private $appName;
+
+    /**
+     * @ignore
+     * @var string $appKey APP 的唯一标识,规则是 ${org_name}#${app_name}
+     */
+    private $appKey;
+
+    /**
+     * @ignore
+     * @var string $clientId App 的 client_id
+     */
+    private $clientId;
+
+    /**
+     * @ignore
+     * @var string $clientSecret App 的 client_secret
+     */
+    private $clientSecret;
+
+    /**
+     * @ignore
+     * @var string $easemobToken 环信 token
+     */
+    private $easemobToken;
+
+    /**
+     * @ignore
+     * @var boolean $isAgora 是否以声网 token 初始化标识
+     */
+    private $isAgora;
+
+    /**
+     * @ignore
+     * @var string $appID 声网 App ID
+     */
+    private $appID;
+
+    /**
+     * @ignore
+     * @var string $appCertificate 声网 App 证书
+     */
+    private $appCertificate;
+
+    /**
+     * @ignore
+     * @var int $expireTimeInSeconds 声网 token 有效期,单位(秒)
+     */
+    private $expireTimeInSeconds;
+
+    /**
+     * @ignore
+     * @var string $uuid 环信用户 uuid
+     */
+    private $uuid;
+
+    /**
+     * @ignore
+     * @var string $agoraToken2easemobToken 声网 token 置换的环信 token
+     */
+    private $agoraToken2easemobToken;
+
+    /**
+     * \~chinese
+     * \brief 构造方法
+     * 
+     * @param string  $appKey                       APP 的唯一标识,规则是 ${org_name}#${app_name}
+     * @param string  $clientIdOrAppID              环信 App 的 client_id 或者声网 App ID,由 boolean $isAgora 决定
+     * @param string  $clientSecretOrAppCertificate 环信 App 的 client_secret 或者 声网 App 证书,由 boolean $isAgora 决定
+     * @param int     $expireTimeInSeconds          token 有效期,单位(秒)
+     * @param mixed   $proxy                        代理信息
+     * @param boolean $isAgora                      是否使用声网 token 初始化
+     * @param string  $uuid                         环信用户 uuid
+     * 
+     * \~english
+     * \brief 构造方法
+     * 
+     * @param string  $appKey                       The unique identification of app. The rule is ${org_name}#${app_name}
+     * @param string  $clientIdOrAppID              easemob client_id or Agora App ID,Determined by $isagora
+     * @param string  $clientSecretOrAppCertificate easemob client_secret or Agora AppCertificate,Determined by $isagora
+     * @param int     $expireTimeInSeconds          Token validity, in seconds
+     * @param mixed   $proxy                        Agent information
+     * @param boolean $isAgora                      Whether to use Agora token initialization
+     * @param string  $uuid                         easemob username
+     */
+    public function __construct(
+        $appKey,
+        $clientIdOrAppID,
+        $clientSecretOrAppCertificate,
+        $expireTimeInSeconds = 2592000,
+        $isAgora = false,
+        $uuid = ''
+        )
+    {
+        if (strpos($appKey, '#') === false) {
+            throw new \Exception('appKey 数据结构有误');
+        }
+
+        $temp = explode('#', $appKey);
+        $this->orgName = $temp[0];
+        $this->appName = $temp[1];
+        $this->appKey = $appKey;
+        $this->expireTimeInSeconds = (int)$expireTimeInSeconds > 0 ? (int)$expireTimeInSeconds : 2592000;
+        $this->isAgora = $isAgora;
+        $this->uuid = $uuid;
+
+        // $this->getApiUri();
+        
+        if ($isAgora) {
+            $this->appID = $clientIdOrAppID;
+            $this->appCertificate = $clientSecretOrAppCertificate;
+            // $this->getAgoraToken2easemobToken();
+        } else {
+            $this->clientId = $clientIdOrAppID;
+            $this->clientSecret = $clientSecretOrAppCertificate;
+            // $this->getEasemobToken();
+        }
+    }
+
+    /// @cond
+    /**
+     * @ignore getter & setter
+     */
+    public function getBaseUri()
+    {
+        return $this->getApiUri() . '/' . $this->orgName . '/' . $this->appName;
+    }
+
+    /**
+     * @ignore getter & setter
+     */
+    public function getOrgName()
+    {
+        return $this->orgName;
+    }
+
+    /**
+     * @ignore getter & setter
+     */
+    public function getAppName()
+    {
+        return $this->appName;
+    }
+
+    /**
+     * @ignore getter & setter
+     */
+    public function getAppKey()
+    {
+        return $this->appKey;
+    }
+
+    /**
+     * @ignore getter & setter
+     */
+    public function getClientId()
+    {
+        return $this->clientId;
+    }
+
+    /**
+     * @ignore getter & setter
+     */
+    public function getClientSecret()
+    {
+        return $this->clientSecret;
+    }
+
+    /**
+     * @ignore getter & setter
+     */
+    public function getApiUri()
+    {
+        return $this->apiUri ? $this->apiUri : $this->getRemoteApiUri();
+    }
+
+    /**
+     * @ignore getter & setter
+     */
+    public function setApiUri($apiUri)
+    {
+        $this->apiUri = $apiUri;
+    }
+
+    /**
+     * @ignore 获取环信 token
+     * @return string 环信 token
+     * @example
+     * <pre>
+     * $auth->getEasemobToken();
+     * </pre>
+     */
+    public function getEasemobToken()
+    {
+        $this->easemobToken = Cache::get($this->appKey . '_easemob_token');
+        return $this->easemobToken ? $this->easemobToken : $this->getEasemobAccessToken();
+    }
+
+    /**
+     * @ignore 获取声网 token
+     * @param  string 环信用户 uuid
+     * @param  int    token 有效期
+     * @return string 声网 token
+     * @example
+     * <pre>
+     * $auth->getAgoraToken();
+     * </pre>
+     */
+    public function getAgoraToken()
+    {
+        if ($this->isAgora) {
+            $this->agoraToken = Cache::get($this->appKey . '_agora_token');
+            if (!$this->agoraToken) {
+                if ($this->uuid) {
+                    $this->agoraToken = ChatTokenBuilder2::buildUserToken($this->appID, $this->appCertificate, $this->uuid, $this->expireTimeInSeconds);
+                } else {
+                    $this->agoraToken = ChatTokenBuilder2::buildAppToken($this->appID, $this->appCertificate, $this->expireTimeInSeconds);
+                }
+                Cache::set($this->appKey . '_agora_token', $this->agoraToken, $this->expireTimeInSeconds);
+            }
+            return $this->agoraToken;
+        }
+        return '';
+    }
+
+    /**
+     * @ignore 获取声网 token 置换的环信 token
+     * @return string 声网 token 置换的环信 token
+     * @example
+     * <pre>
+     * $auth->getAgoraToken2easemobToken();
+     * </pre>
+     */
+    public function getAgoraToken2easemobToken()
+    {
+        if ($this->isAgora) {
+            $this->agoraToken2easemobToken = Cache::get($this->appKey . '_agora_2_easemob_token');
+            if (!$this->agoraToken2easemobToken) {
+                $agoraToken = $this->getAgoraToken();
+                $this->agoraToken2easemobToken();
+            }
+            return $this->agoraToken2easemobToken;
+        }
+        return '';
+    }
+    /// @endcond
+
+    /**
+     * \~chinese
+     * \brief
+     * 获取用户 token
+     * 
+     * @param  string $username        环信 IM 用户名,或者 uuid
+     * @param  string $password        环信用户密码,传递时获取 Easemob userToken 否则获取 Agora userToken
+     * @param  int    $expireInSeconds token 过期时间,单位:s
+     * @param  array  $configuration   privileges
+     * @return array                   用户 token 信息或者错误
+     * 
+     * \~english
+     * \brief
+     * Get user token
+     * 
+     * @param  string $username        easemob user name or uuid
+     * @param  string $password        easemob user login password, obtain easemob usertoken when passing, otherwise obtain Agora usertoken
+     * @param  int    $expireInSeconds Expiration time of token, unit: S
+     * @param  array  $configuration   privileges
+     * @return array                   User token information or error
+     */
+    public function getUserToken($username, $password = null, $expireInSeconds = 3600, $configuration = null)
+    {
+        if ($password) {
+            $body = array(
+                'grant_type' => 'password',
+                'username' => $username,
+                'password' => $password,
+            );
+            if ($expireInSeconds) {
+                $expireInSeconds = (int)$expireInSeconds;
+                if ($expireInSeconds) {
+                    $body['ttl'] = $expireInSeconds;
+                }
+            }
+            $uri = $this->getBaseUri() . '/token';
+            $resp = Http::post($uri, $body);
+            if (!$resp->ok()) {
+                return \Easemob\error($resp);
+            }
+            $data = $resp->data();
+            return array(
+                'access_token' => $data['access_token'],
+                'expires_in' => $data['expires_in'],
+            );
+        } elseif ($this->isAgora) {
+            if ($configuration) {
+                $accessToken = new AccessToken2($this->appID, $this->appCertificate, $expireInSeconds);
+                foreach ($configuration as $item) {
+                    $accessToken->addService($item);
+                }
+                $userToken = $accessToken->build();
+            } else {
+                $userToken = ChatTokenBuilder2::buildUserToken($this->appID, $this->appCertificate, $username, $expireInSeconds);
+            }
+            return array(
+                'access_token' => $userToken,
+                'expires_in' => $expireInSeconds ? $expireInSeconds : $this->expireTimeInSeconds,
+            );
+        }
+    }
+
+    /// @cond
+    /**
+     * @ignore 获取请求头
+     * @return array 请求头
+     */
+    public function headers()
+    {
+        $res = $this->isAgora ? $this->getAgoraToken2easemobToken() : $this->getEasemobToken();
+        if (!isset($res['code']) && is_string($res)) {
+            return array(
+                'Authorization' => 'Bearer ' . $res,
+            );
+        }
+    }
+    /// @endcond
+
+    /**
+     * @ignore 获取 REST API 域名
+     * @return string REST API 域名
+     */
+    private function getRemoteApiUri()
+    {
+        $uri = self::$DNS_URL . '/easemob/server.json?app_key=' . $this->appKey;
+        $resp = Http::get($uri);
+        if ($resp->ok()) {
+            $data = $resp->data();
+            $restDomains = array_values(array_filter($data['rest']['hosts'], function($item) {
+                return $item['protocol'] === 'https';
+            }));
+            $this->apiUri = $restDomains[0]['protocol'] . '://' . $restDomains[0]['domain'];
+            return $this->apiUri;
+        }
+        return '';
+    }
+
+    /**
+     * @ignore 获取环信 token
+     * @return string 环信 token
+     */
+    private function getEasemobAccessToken()
+    {
+        // 环信 token
+        $uri = $this->getBaseUri() . '/token';
+        $body = array(
+            'grant_type' => 'client_credentials',
+            'client_id' => $this->clientId,
+            'client_secret' => $this->clientSecret,
+            'ttl' => $this->expireTimeInSeconds,
+        );
+        $resp = Http::post($uri, $body);
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        if ($data['access_token']) {
+            Cache::set($this->appKey . '_easemob_token', $data['access_token'], $data['expires_in']/2);
+            return $data['access_token'];
+        }
+        return '';
+    }
+
+    /**
+     * @ignore 声网 token 置换环信 token
+     * @param string 环信用户 uuid
+     * @param int    token 有效期
+     */
+    private function agoraToken2easemobToken()
+    {
+        $uri = 'http://a41.easemob.com/' . $this->orgName . '/' . $this->appName . '/token';
+
+        $body = array(
+            'grant_type' => 'agora',
+        );
+        $headers = array(
+            'Authorization' => 'Bearer ' . $this->agoraToken,
+        );
+        $resp = Http::post($uri, $body, $headers);
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        
+        if ($data['access_token']) {
+            $this->agoraToken2easemobToken = $data['access_token'];
+            Cache::set($this->appKey . '_agora_2_easemob_token', $data['access_token'], 2592000);
+        }
+    }
+}

+ 1172 - 0
vendor/maniac/easemob-php/src/Block.php

@@ -0,0 +1,1172 @@
+<?php
+namespace Easemob;
+
+use Easemob\Http\Http;
+
+/**
+ * \~chinese
+ * Block 用于限制访问(将用户加入黑名单、群组/聊天室禁言等)
+ * 
+ * \~english
+ * The `Block` is used to restrict access (add users to blacklists, group / chat room prohibitions, etc.)
+ */
+final class Block
+{
+    /**
+     * @ignore
+     * @var Auth $auth 授权对象
+     */
+    private $auth;
+
+    /// @cond
+    public function __construct($auth)
+    {
+        $this->auth = $auth;
+    }
+    /// @endcond
+
+    /**
+     * \~chinese
+     * \brief
+     * 获取用户黑名单
+     * 
+     * @param  string $username 要获取黑名单的用户
+     * @return array            黑名单用户名列表或者错误
+     * 
+     * \~english
+     * \brief
+     * Get user blacklist
+     * 
+     * @param  string $username User name
+     * @return array            Blacklist user name list or error
+     */
+    public function getUsersBlockedFromSendMsgToUser($username)
+    {
+        if (!trim($username)) {
+            \Easemob\exception('Please enter username');
+        }
+        $uri = $this->auth->getBaseUri() . '/users/' . $username . '/blocks/users';
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data'];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 添加用户黑名单
+     * 
+     * \details
+     * 向用户的黑名单列表中添加一个或者多个用户,黑名单中的用户无法给该用户发送消息,每个用户的黑名单人数上限为 500。
+     * 
+     * @param  string  $username  要添加黑名单的用户名
+     * @param  array   $usernames 需要加入到黑名单中的用户名,以数组方式提交
+     * @return boolean|array      成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Add user blacklist
+     * 
+     * \details
+     * Add one or more users to the user's blacklist. Users in the blacklist cannot send messages to the user. The maximum number of blacklists for each user is 500.
+     * 
+     * @param  string  $username  User name
+     * @param  array   $usernames The user names that need to be added to the blacklist shall be submitted in the form of array
+     * @return boolean|array      Success or error
+     */
+    public function blockUserSendMsgToUser($username, $usernames = array())
+    {
+        if (!trim($username)) {
+            \Easemob\exception('Please enter username');
+        }
+        if (!is_array($usernames)) {
+            \Easemob\exception('Please pass in the user array to be blacklisted');
+        }
+        $uri = $this->auth->getBaseUri() . '/users/' . $username . '/blocks/users';
+        $body = compact('usernames');
+        $resp = Http::post($uri, $body, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 移除用户黑名单
+     * 
+     * \details
+     * 从用户的黑名单中移除用户。将用户从黑名单移除后,恢复到好友,或者未添加好友的用户关系。可以正常的进行消息收发。
+     * 
+     * @param  string  $username        要移除黑名单的用户名
+     * @param  string  $friend_username 好友用户名
+     * @return boolean|array            成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Remove user blacklist
+     * 
+     * \details
+     * Remove the user from the user's blacklist. After the user is removed from the blacklist, it can be restored to friends, or the user relationship without adding friends. Can send and receive messages normally.
+     * 
+     * @param  string  $username        User name
+     * @param  string  $friend_username Friends user name
+     * @return boolean|array            Success or error
+     */
+    public function unblockUserSendMsgToUser($username, $blocked_username)
+    {
+        if (!trim($username) || !trim($blocked_username)) {
+            \Easemob\exception('Please enter the user name and the user name to remove the blacklist');
+        }
+        $uri = $this->auth->getBaseUri() . '/users/' . $username . '/blocks/users/' . $blocked_username;
+        $resp = Http::delete($uri, null, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 账号封禁
+     * 
+     * \details
+     * 用户若被禁用将立即下线并无法登录,直到被解禁后才能恢复登录。常用在对异常用户的即时处理场景使用。
+     * 
+     * @param  string  $username 要封禁的用户名
+     * @return boolean|array     成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Account number ban
+     * 
+     * \details
+     * If the user is disabled, he will be offline immediately and cannot log in until he is lifted. It is often used in the immediate processing of abnormal users.
+     * 
+     * @param  string  $username User name
+     * @return boolean|array     Success or error
+     */
+    public function blockUserLogin($username)
+    {
+        return $this->activate($username, 0);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 账号解禁
+     * 
+     * \details
+     * 用户若被禁用将立即下线并无法登录,直到被解禁后才能恢复登录。常用在对异常用户的即时处理场景使用。
+     * 
+     * @param  string  $username 要解禁的用户名
+     * @return boolean|array     成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Account lifting
+     * 
+     * \details
+     * If the user is disabled, he will be offline immediately and cannot log in until he is lifted. It is often used in the immediate processing of abnormal users.
+     * 
+     * @param  string  $username User name
+     * @return boolean|array     Success or error
+     */
+    public function unblockUserLogin($username)
+    {
+        return $this->activate($username);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 设置用户全局禁言
+     * 
+     * \details
+     * 设置单个用户 ID 的单聊、群组、聊天室消息全局禁言。
+     * 
+     * @param  string $username              用户名
+     * @param  int    $chatMuteDuration      单聊消息禁言时间,单位为秒,非负整数,最大值为 2147483647,`0` 表示取消该帐号的单聊消息禁言,`-1` 表示该帐号被设置永久禁言,其它值表示该帐号的具体禁言时间,负值为非法值。
+     * @param  int    $groupchatMuteDuration 群组消息禁言时间,单位为秒,规则同上。
+     * @param  int    $chatroomMuteDuration  聊天室消息禁言时间,单位为秒,规则同上。
+     * @return boolean|array                 成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Set user global prohibitions
+     * 
+     * \details
+     * Set the global prohibition of single chat, group and chat room messages of a single user ID.
+     * 
+     * @param  string $username              User name
+     * @param  int    $chatMuteDuration      Single chat message forbidden time, in seconds, is a non negative integer. The maximum value is 2147483647, ` 0 'means to cancel the forbidden time of single chat messages of the account, ` - 1' means that the account is set with permanent forbidden time, other values mean the specific forbidden time of the account, and negative values are illegal values.
+     * @param  int    $groupchatMuteDuration Group message forbidden time, in seconds, and the rules are the same as above.
+     * @param  int    $chatroomMuteDuration  Chat room message forbidden time, in seconds, and the rules are the same as above.
+     * @return boolean|array                 Success or error
+     */
+    public function blockUserSendMsg($username, $chatMuteDuration = -1, $groupchatMuteDuration = -1, $chatroomMuteDuration = -1)
+    {
+        return $this->blockUserSendMsgGlobal($username, $chatMuteDuration, $groupchatMuteDuration, $chatroomMuteDuration);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 解除用户全局禁言
+     * 
+     * @param  string $username 解除禁言的用户名
+     * @return boolean|array    成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Lifting the user's global prohibition
+     * 
+     * @param  string $username User name
+     * @return boolean|array    Success or error
+     */
+    public function unblockUserSendMsg($username)
+    {
+        return $this->blockUserSendMsgGlobal($username, 0, 0, 0);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 查询单个用户 ID 全局禁言
+     * 
+     * \details
+     * 查询单个用户的单聊/群聊/聊天室消息禁言。
+     * 
+     * @param  string $username 查询禁言信息的用户名
+     * @return array            用户全局禁言信息或者错误
+     * 
+     * \~english
+     * \brief
+     * Query single user ID global prohibitions
+     * 
+     * \details
+     * Query the forbidden words of single chat / group chat / chat room messages of a single user.
+     * 
+     * @param  string $username User name
+     * @return array            User global forbidden information or error
+     */
+    public function getUserBlocked($username)
+    {
+        if (!trim($username)) {
+            \Easemob\exception('Please enter username');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/mutes/' . $username;
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data'];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 查询APPKEY的用户禁言
+     * 
+     * \details
+     * 查询 App Key 下的用户禁言剩余时间的集合。
+     * 
+     * @param  int   $pageSize 请求查询每页显示的禁言用户的数量,默认取 10 条
+     * @param  int   $pageNum  请求查询的页码,默认取第 1 页
+     * @return array           用户禁言信息或者错误
+     * 
+     * \~english
+     * \brief
+     * User prohibitions for querying appkey
+     * 
+     * \details
+     * Query the collection of the remaining time of the user's forbidden words under the app key.
+     * 
+     * @param  int   $pageSize Request to query the number of forbidden users displayed on each page. The default is 10
+     * @param  int   $pageNum  The page number requested for query is page 1 by default
+     * @return array           User forbidden information or error
+     */
+    public function getAppBlocked($pageSize = 10, $pageNum = 1)
+    {
+        $pageSize = (int)$pageSize > 0 ? (int)$pageSize : 10;
+        $pageNum = (int)$pageNum > 0 ? (int)$pageNum : 1;
+        
+        $uri = $this->auth->getBaseUri() . '/mutes';
+        $uri .= $pageSize ? ('?pagesize=' . $pageSize . '&pagenum=' . $pageNum) : '';
+
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return isset($data['data']) ? $data['data'] : $data;
+    }
+
+    // Group
+    /**
+     * \~chinese
+     * \brief
+     * 查询群组黑名单
+     * 
+     * \details
+     * 查询一个群组黑名单中的用户列表。位于黑名单中的用户查看不到该群组的信息,也无法收到该群组的消息。
+     * 
+     * @param  string $groupId 群组 ID
+     * @return array           群组黑名单信息或者错误
+     * 
+     * 
+     * \~english
+     * \brief
+     * Query group blacklist
+     * 
+     * \details
+     * Query the list of users in a group blacklist. Users in the blacklist cannot view the information of the group or receive the message of the group.
+     * 
+     * @param  string $groupId Group ID
+     * @return array           Group blacklist information or error
+     */
+    public function getUsersBlockedJoinGroup($groupId)
+    {
+        if (!trim($groupId)) {
+            \Easemob\exception('Please pass the group ID');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatgroups/' . $groupId . '/blocks/users';
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data'];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 添加单个用户至群组黑名单
+     * 
+     * \details
+     * 添加一个用户进入一个群组的黑名单。群主无法被加入群组的黑名单。
+     * 
+     * 用户进入群组黑名单后,会收到消息:You are kicked out of the group xxx。之后,该用户查看不到该群组的信息,也收不到该群组的消息。
+     * 
+     * @param  string $groupId  群组 ID
+     * @param  string $username 要添加的 IM 用户名
+     * @return boolean|array    成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Add a single user to the group blacklist
+     * 
+     * \details
+     * Add a user to the blacklist of a group. The group leader cannot be added to the blacklist of the group.
+     * 
+     * After entering the group blacklist, the user will receive a message: you are kicked out of the group XXX. After that, the user cannot view the information of the group or receive the message of the group.
+     * 
+     * @param  string $groupId  Group ID
+     * @param  string $username User name
+     * @return boolean|array    Success or error
+     */
+    public function blockUserJoinGroup($groupId, $username)
+    {
+        return $this->addGroupBlocks($groupId, $username);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 批量添加用户至群组黑名单
+     * 
+     * \details
+     * 将多个用户添加一个群组的黑名单。你一次最多可以添加 60 个用户至群组黑名单。群主无法被加入群组的黑名单。
+     * 
+     * 用户进入群组黑名单后,会收到消息:You are kicked out of the group xxx。之后,该用户查看不到该群组的信息,也收不到该群组的消息。
+     *
+     * @param  string $groupId   群组 ID
+     * @param  array  $usernames 要添加的 IM 用户名数组
+     * @return boolean|array     成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Batch add users to group blacklist
+     * 
+     * \details
+     * Add multiple users to the blacklist of a group. You can add up to 60 users to the group blacklist at a time. The group leader cannot be added to the blacklist of the group.
+     * 
+     * After entering the group blacklist, the user will receive a message: you are kicked out of the group XXX. After that, the user cannot view the information of the group or receive the message of the group.
+     * 
+     * @param  string $groupId   Group ID
+     * @param  array  $usernames User name array
+     * @return boolean|array     Success or error
+     */
+    public function blockUsersJoinGroup($groupId, $usernames)
+    {
+        return $this->addGroupBlocks($groupId, $usernames);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 从群组黑名单移除单个用户
+     * 
+     * \details
+     * 将指定用户移出群组黑名单。对于群组黑名单中的用户,如果需要将其再次加入群组,需要先将其从群组黑名单中移除。
+     * 
+     * @param  string $groupId  群组 ID
+     * @param  string $username 要移除的用户名
+     * @return boolean|array    成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Remove a single user from the group blacklist
+     * 
+     * \details
+     * Remove the specified user from the group blacklist. For users in the group blacklist, if they need to join the group again, they need to be removed from the group blacklist first.
+     * 
+     * @param  string $groupId  Group ID
+     * @param  string $username User name
+     * @return boolean|array    Success or error
+     */
+    public function unblockUserJoinGroup($groupId, $username)
+    {
+        return $this->removeGroupBlocks($groupId, $username);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 从群组黑名单批量移除用户
+     * 
+     * \details
+     * 将多名指定用户从群组黑名单中移除。对于群组黑名单中的用户,如果需要将其再次加入群组,需要先将其从群组黑名单中移除。
+     * 
+     * @param  string $groupId   群组 ID
+     * @param  array  $usernames 要添加的 IM 用户名数组
+     * @return boolean|array     成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Batch remove users from group blacklist
+     * 
+     * \details
+     * Remove multiple designated users from the group blacklist. For users in the group blacklist, if they need to join the group again, they need to be removed from the group blacklist first.
+     * 
+     * @param  string $groupId   Group ID
+     * @param  array  $usernames User name array
+     * @return boolean|array     Success or error
+     */
+    public function unblockUsersJoinGroup($groupId, $usernames)
+    {
+        return $this->removeGroupBlocks($groupId, $usernames);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 添加群组禁言
+     * 
+     * \details
+     * 对指定群成员禁言。群成员被禁言后,将无法在群中发送消息。
+     * 
+     * @param  string  $groupId       群组 ID
+     * @param  array   $usernames     要被添加禁言的用户 ID 数组
+     * @param  int     $mute_duration 禁言时长,单位为毫秒。
+     * @return boolean|array          成功或者错误
+     * 
+     * 
+     * \~english
+     * \brief
+     * Add group forbidden words
+     * 
+     * \details
+     * No speaking to designated group members. After group members are banned, they will not be able to send messages in the group.
+     * 
+     * @param  string  $groupId       Group ID
+     * @param  array   $usernames     User name array
+     * @param  int     $mute_duration Forbidden speech duration, in milliseconds.
+     * @return boolean|array          Success or error
+     */
+    public function blockUserSendMsgToGroup($groupId, $usernames, $mute_duration = -1)
+    {
+        if (!trim($groupId)) {
+            \Easemob\exception('Please pass the group ID');
+        }
+
+        if (!is_array($usernames) || empty($usernames)) {
+            \Easemob\exception('Please pass the user name');
+        }
+
+        $mute_duration = (int)$mute_duration > 0 ? (int)$mute_duration : -1;
+        $uri = $this->auth->getBaseUri() . '/chatgroups/' . $groupId . '/mute';
+        $resp = Http::post($uri, compact('usernames', 'mute_duration'), $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 解除群组成员禁言
+     * 
+     * \details
+     * 将一个或多个群成员移除禁言列表。移除后,群成员可以在群组中正常发送消息。
+     * 
+     * @param  string  $groupId   群组 ID
+     * @param  array   $usernames 要移除禁言的用户 ID 数组
+     * @return boolean|array      成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Release group members from prohibition
+     * 
+     * \details
+     * Remove one or more group members from the forbidden list. After removal, group members can send messages normally in the group.
+     * 
+     * @param  string  $groupId   Group ID
+     * @param  array   $usernames User name array
+     * @return boolean|array      Success or error
+     */
+    public function unblockUserSendMsgToGroup($groupId, $usernames)
+    {
+        if (!trim($groupId)) {
+            \Easemob\exception('Please pass the group ID');
+        }
+
+        if (!is_array($usernames) || empty($usernames)) {
+            \Easemob\exception('Please pass the user name');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatgroups/' . $groupId . '/mute/' . implode(',', $usernames);
+        $resp = Http::delete($uri, null, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 获取禁言列表
+     * 
+     * \details
+     * 获取当前群组的禁言用户列表。
+     * 
+     * @param  string $groupId 群组 ID
+     * @return array           禁言列表信息或者错误
+     * 
+     * \~english
+     * \brief
+     * Get forbidden list
+     * 
+     * \details
+     * Get the list of forbidden users in the current group.
+     * 
+     * @param  string $groupId Group ID
+     * @return array           Forbidden list information or error
+     */
+    public function getUsersBlockedSendMsgToGroup($groupId)
+    {
+        if (!trim($groupId)) {
+            \Easemob\exception('Please pass the group ID');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatgroups/' . $groupId . '/mute';
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data'];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 禁言群组全体成员
+     * 
+     * \details
+     * 对所有群组成员一键禁言,即将群组的所有成员均加入禁言列表。设置群组全员禁言后,仅群组白名单中的用户可在群组内发消息
+     * 
+     * @param  string  $groupId       群组 ID
+     * @param  int     $mute_duration 禁言时长,单位为毫秒。
+     * @return boolean|array          成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Forbidden all members of the group
+     * 
+     * \details
+     * One click prohibition for all group members, that is, all members of the group will be added to the prohibition list. After setting the prohibition of all members of the group, only users in the group white list can send messages in the group
+     * 
+     * @param  string  $groupId       Group ID
+     * @param  int     $mute_duration Forbidden speech duration, in milliseconds.
+     * @return boolean|array          Success or error
+     */
+    public function blockAllUserSendMsgToGroup($groupId, $mute_duration = -1)
+    {
+
+        if (!trim($groupId)) {
+            \Easemob\exception('Please pass the group ID');
+        }
+
+        $mute_duration = (int)$mute_duration > 0 ? (int)$mute_duration : -1;
+        $uri = $this->auth->getBaseUri() . '/chatgroups/' . $groupId . '/ban';
+        $resp = Http::post($uri, compact('mute_duration'), $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 解除群组全员禁言
+     * 
+     * \details
+     * 一键取消对群组全体成员的禁言。移除后,群成员可以在群组中正常发送消息。
+     * 
+     * @param  string  $groupId 群组 ID
+     * @return boolean|array    成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Lifting the ban on all members of the group
+     * 
+     * \details
+     * One click to cancel the ban on all members of the group. After removal, group members can send messages normally in the group.
+     * 
+     * @param  string  $groupId Group ID
+     * @return boolean|array    Success or error
+     */
+    public function unblockAllUserSendMsgToGroup($groupId)
+    {
+        if (!trim($groupId)) {
+            \Easemob\exception('Please pass the group ID');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatgroups/' . $groupId . '/ban';
+        $resp = Http::delete($uri, null, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+
+    // Room
+    /**
+     * \~chinese
+     * \brief
+     * 查询聊天室黑名单
+     * 
+     * \details
+     * 查询一个聊天室黑名单中的用户列表。黑名单中的用户无法查看或收到该聊天室的信息。
+     * 
+     * @param  string $roomId 聊天室 ID
+     * @return array          聊天室黑名单信息或者错误
+     * 
+     * \~english
+     * \brief
+     * Query chat room blacklist
+     * 
+     * \details
+     * Query the list of users in a chat room blacklist. Users in the blacklist cannot view or receive information from this chat room.
+     * 
+     * @param  string $roomId Chat room ID
+     * @return array          Chat room blacklist information or error
+     */
+    public function getUsersBlockedJoinRoom($roomId)
+    {
+        if (!trim($roomId)) {
+            \Easemob\exception('Please pass the chat room ID');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatrooms/' . $roomId . '/blocks/users';
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data'];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 添加单个用户至聊天室黑名单
+     * 
+     * \details
+     * 添加一个用户进入一个聊天室的黑名单。聊天室所有者无法被加入聊天室的黑名单。
+     * 
+     * 用户进入聊天室黑名单后,会收到消息:“You are kicked out of the chatroom xxx”。之后,该用户无法查看和收发该聊天室的信息。
+     * 
+     * @param  string $roomId   聊天室 ID
+     * @param  string $username 要添加的 IM 用户名
+     * @return boolean|array    成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Add a single user to the chat room blacklist
+     * 
+     * \details
+     * Add a user to the blacklist of a chat room. Chat room owners cannot be blacklisted.
+     * 
+     * After entering the chat room blacklist, users will receive a message: "you are kicked out of the chat room XXX". After that, the user cannot view and send the information of the chat room.
+     * 
+     * @param  string $roomId   Chat room ID
+     * @param  string $username User name
+     * @return boolean|array    Success or error
+     */
+    public function blockUserJoinRoom($roomId, $username)
+    {
+        return $this->addRoomBlocks($roomId, $username);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 批量添加用户至聊天室黑名单
+     * 
+     * \details
+     * 将多个用户加入一个聊天室的黑名单。你一次最多可以添加 60 个用户至聊天室黑名单。聊天室所有者无法被加入聊天室的黑名单。
+     * 
+     * 用户进入聊天室黑名单后,会收到消息:“You are kicked out of the chatroom xxx”。之后,这些用户无法查看和收发该聊天室的信息。
+     * 
+     * @param  string $roomId    聊天室 ID
+     * @param  array  $usernames 要添加的 IM 用户名数组
+     * @return boolean|array     成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Batch add users to chat room blacklist
+     * 
+     * \details
+     * Add multiple users to the blacklist of a chat room. You can add up to 60 users to the chat room blacklist at a time. Chat room owners cannot be blacklisted.
+     * 
+     * After entering the chat room blacklist, users will receive a message: "you are kicked out of the chat room XXX". After that, these users cannot view and send the information of the chat room.
+     * 
+     * @param  string $roomId    Chat room ID
+     * @param  array  $usernames User name array
+     * @return boolean|array     Success or error
+     */
+    public function blockUsersJoinRoom($roomId, $usernames)
+    {
+        return $this->addRoomBlocks($roomId, $usernames);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 从聊天室黑名单移除单个用户
+     * 
+     * \details
+     * 将指定用户移出聊天室黑名单。对于聊天室黑名单中的用户,如果需要将其再次加入聊天室,需要先将其从聊天室黑名单中移除。
+     * 
+     * @param  string $roomId   聊天室 ID
+     * @param  string $username 要添加的 IM 用户名
+     * @return boolean|array    成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Remove a single user from the chat room blacklist
+     * 
+     * \details
+     * Remove the specified user from the chat room blacklist. For users in the chat room blacklist, if they need to join the chat room again, they need to be removed from the chat room blacklist first.
+     * 
+     * @param  string $roomId   Chat room ID
+     * @param  string $username User name
+     * @return boolean|array    Success or error
+     */
+    public function unblockUserJoinRoom($roomId, $username)
+    {
+        return $this->removeRoomBlocks($roomId, $username);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 从聊天室黑名单批量移除用户
+     * 
+     * \details
+     * 将多名指定用户从聊天室黑名单中移除。你每次最多可移除 60 个用户。对于聊天室黑名单中的用户,如果需要将其再次加入聊天室,需要先将其从聊天室黑名单中移除。
+     * 
+     * @param  string $roomId    聊天室 ID
+     * @param  array  $usernames 要添加的 IM 用户名数组
+     * @return boolean|array     成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Batch remove users from chat room blacklist
+     * 
+     * \details
+     * Remove multiple designated users from the chat room blacklist. You can remove up to 60 users at a time. For users in the chat room blacklist, if they need to join the chat room again, they need to be removed from the chat room blacklist first.
+     * 
+     * @param  string $roomId    Chat room ID
+     * @param  array  $usernames User name array
+     * @return boolean|array     Success or error
+     */
+    public function unblockUsersJoinRoom($roomId, $usernames)
+    {
+        return $this->removeRoomBlocks($roomId, $usernames);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 禁言聊天室成员
+     * 
+     * \details
+     * 将用户禁言。用户被禁言后,将无法在聊天室中发送消息。
+     * 
+     * @param  string  $roomId        聊天室 ID
+     * @param  array   $usernames     要被添加禁言的用户 ID 数组
+     * @param  int     $mute_duration 禁言的时间,单位毫秒,如果是“-1”代表永久(实际的到期时间为固定时间戳4638873600000,即2117-01-01 00:00:00)
+     * @return boolean|array          成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Forbidden chat room members
+     * 
+     * \details
+     * Forbid users from speaking. After being banned, users will not be able to send messages in the chat room.
+     * 
+     * @param  string  $roomId        Chat room ID
+     * @param  array   $usernames     User name array
+     * @param  int     $mute_duration Forbidden time, in milliseconds. If "- 1" means permanent (the actual expiration time is the fixed timestamp 4638873600000, i.e. 2117-01-01 00:00:00)
+     * @return boolean|array          Success or error
+     */
+    public function blockUserSendMsgToRoom($roomId, $usernames, $mute_duration = -1)
+    {
+        if (!trim($roomId)) {
+            \Easemob\exception('Please pass the chat room ID');
+        }
+
+        if (!is_array($usernames) || empty($usernames)) {
+            \Easemob\exception('Please pass the user name');
+        }
+
+        $mute_duration = (int)$mute_duration > 0 ? (int)$mute_duration : -1;
+        $uri = $this->auth->getBaseUri() . '/chatrooms/' . $roomId . '/mute';
+        $resp = Http::post($uri, compact('usernames', 'mute_duration'), $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 解除聊天室禁言成员
+     * 
+     * \details
+     * 将用户从禁言列表中移除,可以移除多个 member。移除后,用户可以正常在聊天室中发送消息。
+     * 
+     * @param  string  $roomId    聊天室 ID
+     * @param  array   $usernames 要移除禁言的用户 ID 数组
+     * @return boolean|array      成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Unblock chat room members
+     * 
+     * \details
+     * You can remove multiple members by removing users from the forbidden list. After removal, users can send messages in the chat room normally.
+     * 
+     * @param  string  $roomId    Chat room ID
+     * @param  array   $usernames User name array
+     * @return boolean|array      Success or error
+     */
+    public function unblockUserSendMsgToRoom($roomId, $usernames)
+    {
+        if (!trim($roomId)) {
+            \Easemob\exception('Please pass the chat room ID');
+        }
+
+        if (!is_array($usernames) || empty($usernames)) {
+            \Easemob\exception('Please pass the user name');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatrooms/' . $roomId . '/mute/' . implode(',', $usernames);
+        $resp = Http::delete($uri, null, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 获取聊天室禁言列表
+     * 
+     * @param  string $roomId  聊天室 ID
+     * @return array           禁言列表信息或者错误
+     * 
+     * \~english
+     * \brief
+     * Get chat room forbidden list
+     * 
+     * @param  string $roomId  Chat room ID
+     * @return array           Forbidden list information or error
+     */
+    public function getUsersBlockedSendMsgToRoom($roomId)
+    {
+        if (!trim($roomId)) {
+            \Easemob\exception('Please pass the chat room ID');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatrooms/' . $roomId . '/mute';
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return isset($data['data']) ? $data['data'] : $data;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 禁言聊天室全体成员
+     * 
+     * \details
+     * 对所有聊天室成员一键禁言,即将聊天室的所有成员均加入禁言列表。设置聊天室全员禁言后,仅聊天室白名单中的用户可在聊天室内发消息。
+     * @param  string  $roomId        聊天室 ID
+     * @param  int     $mute_duration 禁言时长,单位为毫秒。
+     * @return boolean|array          成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Forbidden all members of the room
+     * 
+     * \details
+     * One click prohibition for all members of the chat room, that is, all members of the chat room will be added to the prohibition list. After setting the prohibition of all members in the chat room, only users in the chat room white list can send messages in the chat room.
+     * 
+     * @param  string  $roomId        Chat room ID
+     * @param  int     $mute_duration Forbidden speech duration, in milliseconds.
+     * @return boolean|array          Success or error
+     */
+    public function blockAllUserSendMsgToRoom($roomId, $mute_duration = -1)
+    {
+
+        if (!trim($roomId)) {
+            \Easemob\exception('Please pass the chat room ID');
+        }
+
+        $mute_duration = (int)$mute_duration > 0 ? (int)$mute_duration : -1;
+        $uri = $this->auth->getBaseUri() . '/chatrooms/' . $roomId . '/ban';
+        $resp = Http::post($uri, compact('mute_duration'), $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 解除聊天室全员禁言
+     * 
+     * \details
+     * 一键取消对聊天室全体成员的禁言。移除后,聊天室成员可以在聊天室中正常发送消息。
+     * 
+     * @param  string  $roomId  聊天室 ID
+     * @return boolean|array    成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Lifting the ban on all members of the chat room
+     * 
+     * \details
+     * One click to cancel the ban on all members of the chat room. After removal, chat room members can send messages normally in the chat room.
+     * 
+     * @param  string  $roomId  Chat room ID
+     * @return boolean|array    Success or error
+     */
+    public function unblockAllUserSendMsgToRoom($roomId)
+    {
+        if (!trim($roomId)) {
+            \Easemob\exception('Please pass the chat room ID');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatrooms/' . $roomId . '/ban';
+        $resp = Http::delete($uri, null, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * @ignore 设置用户全局禁言
+     * @param  string $username              用户名
+     * @param  int    $chatMuteDuration      单聊消息禁言时间,单位为秒,非负整数,最大值为 2147483647,`0` 表示取消该帐号的单聊消息禁言,`-1` 表示该帐号被设置永久禁言,其它值表示该帐号的具体禁言时间,负值为非法值。
+     * @param  int    $groupchatMuteDuration 群组消息禁言时间,单位为秒,规则同上。
+     * @param  int    $chatroomMuteDuration  聊天室消息禁言时间,单位为秒,规则同上。
+     * @return boolean|array                 成功或者错误
+     */
+    private function blockUserSendMsgGlobal($username, $chatMuteDuration = -1, $groupchatMuteDuration = -1, $chatroomMuteDuration = -1)
+    {
+        if (!trim($username)) {
+            \Easemob\exception('Please enter username');
+        }
+
+        $chatMuteDuration = (int)$chatMuteDuration >= 0 ? (int)$chatMuteDuration : -1;
+        $groupchatMuteDuration = (int)$groupchatMuteDuration >= 0 ? (int)$groupchatMuteDuration : -1;
+        $chatroomMuteDuration = (int)$chatroomMuteDuration >= 0 ? (int)$chatroomMuteDuration : -1;
+
+        $data = array(
+            'username' => $username,
+            'chat' => $chatMuteDuration,
+            'groupchat' => $groupchatMuteDuration,
+            'chatroom' => $chatroomMuteDuration,
+        );
+
+        $uri = $this->auth->getBaseUri() . '/mutes';
+        $resp = Http::post($uri, $data, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        $data = $data['data'];
+        if (isset($data['result']) && $data['result'] == 'ok') {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * @ignore 账号封禁与解禁
+     * @param  string  $username 要操作用户的用户名
+     * @param  int     $status   0:禁用;1:解禁
+     * @return boolean|array     成功或者错误
+     */
+    private function activate($username, $status = 1)
+    {
+        if (!trim($username)) {
+            \Easemob\exception('Please enter your username');
+        }
+        $status = (int)$status ? 1 : 0;
+        $uri = $this->auth->getBaseUri() . '/users/' . $username;
+        $uri .= $status ? '/activate' : '/deactivate';
+        $resp = Http::post($uri, array(), $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * @ignore 添加用户至群组黑名单
+     * @param  string       $groupId   群组 ID
+     * @param  array|string $usernames 要添加的 IM 用户名,string: 添加单个用户;array: 批量添加
+     * @return boolean|array           成功或者错误
+     */
+    private function addGroupBlocks($groupId, $usernames)
+    {
+        if (!trim($groupId)) {
+            \Easemob\exception('Please pass the group ID');
+        }
+
+        if ((is_array($usernames) && empty($usernames)) || (is_string($usernames) && !trim($usernames))) {
+            \Easemob\exception('Please pass the user name');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatgroups/' . $groupId . '/blocks/users';
+        $uri .= is_string($usernames) ? ('/' . $usernames) : '';
+        $body = is_array($usernames) ? compact('usernames') : null;
+        $resp = Http::post($uri, $body, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * @ignore 从群组黑名单移除用户
+     * @param  string       $groupId   群组 ID
+     * @param  array|string $usernames 要移除的 IM 用户名,string: 移除单个用户;array: 批量移除
+     * @return boolean|array           成功或者错误
+     */
+    private function removeGroupBlocks($groupId, $usernames)
+    {
+        if (!trim($groupId)) {
+            \Easemob\exception('Please pass the group ID');
+        }
+
+        if ((is_array($usernames) && empty($usernames)) || (is_string($usernames) && !trim($usernames))) {
+            \Easemob\exception('Please pass the user name');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatgroups/' . $groupId . '/blocks/users/';
+        $uri .= is_array($usernames) ? implode(',', $usernames) : $usernames;
+        $resp = Http::delete($uri, null, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * @ignore 添加用户至聊天室黑名单
+     * @param  string       $roomId   聊天室 ID
+     * @param  array|string $usernames 要添加的 IM 用户名,string: 添加单个用户;array: 批量添加
+     * @return boolean|array           成功或者错误
+     */
+    private function addRoomBlocks($roomId, $usernames)
+    {
+        if (!trim($roomId)) {
+            \Easemob\exception('Please pass the chat room ID');
+        }
+
+        if ((is_array($usernames) && empty($usernames)) || (is_string($usernames) && !trim($usernames))) {
+            \Easemob\exception('Please pass the user name');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatrooms/' . $roomId . '/blocks/users';
+        $uri .= is_string($usernames) ? ('/' . $usernames) : '';
+        $body = is_array($usernames) ? compact('usernames') : null;
+        $resp = Http::post($uri, $body, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * @ignore 从聊天室黑名单移除用户
+     * @param  string       $roomId   聊天室 ID
+     * @param  array|string $usernames 要移除的 IM 用户名,string: 移除单个用户;array: 批量移除
+     * @return boolean|array           成功或者错误
+     */
+    private function removeRoomBlocks($roomId, $usernames)
+    {
+        if (!trim($roomId)) {
+            \Easemob\exception('Please pass the chat room ID');
+        }
+
+        if ((is_array($usernames) && empty($usernames)) || (is_string($usernames) && !trim($usernames))) {
+            \Easemob\exception('Please pass the user name');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatrooms/' . $roomId . '/blocks/users/';
+        $uri .= is_array($usernames) ? implode(',', $usernames) : $usernames;
+        $resp = Http::delete($uri, null, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+}

+ 64 - 0
vendor/maniac/easemob-php/src/Cache/Cache.php

@@ -0,0 +1,64 @@
+<?php
+namespace Easemob\Cache;
+
+/**
+ * @ignore 文件缓存类
+ * @final
+ */
+final class Cache
+{
+    /**
+     * 获取缓存 key
+     * @param  string $name 缓存名称
+     * @return string       缓存 key
+     */
+    public static function getCacheKey($name)
+    {
+        return md5($name) . '.php';
+    }
+
+    /**
+     * 获取缓存值
+     * @param  string $name 缓存名称
+     * @return mixed        缓存值
+     */
+    public static function get($name)
+    {
+        $path = __DIR__ . '/../../runtime/cache';
+        $filename = $path . '/' . self::getCacheKey($name);
+        if (!file_exists($filename)) {
+            return null;
+        }
+
+        $content = file_get_contents($filename);
+        $data = json_decode($content, true);
+        return $data['expire'] <= time() ? null : $data['value'];
+    }
+
+    /**
+     * 设置缓存值
+     * @param  string $name   缓存名称
+     * @param  mixed  $value  缓存值
+     * @param  int    $expire 过期时间
+     * @return boolean        是否设置成功
+     */
+    public static function set($name, $value, $expire = 3600)
+    {
+        $path = __DIR__ . '/../../runtime/cache';
+        if (!is_dir($path)) {
+            mkdir($path, 0755, true);
+        }
+
+        $filename = self::getCacheKey($name);
+        $data = array(
+            'value'  => $value,
+            'expire' => time() + $expire,
+        );
+        
+        $result = file_put_contents($path . '/' . $filename, json_encode($data));
+        if ($result === false) {
+            \Easemob\exception($path . " 目录无写入权限");
+        }
+        return true;
+    }
+}

+ 128 - 0
vendor/maniac/easemob-php/src/Contact.php

@@ -0,0 +1,128 @@
+<?php
+namespace Easemob;
+
+use Easemob\Http\Http;
+
+/**
+ * \~chinese
+ * Contact 用来管理联系人(添加好友等)
+ * 
+ * \~english
+ * The `Contact` is used to manage contacts (add friends, etc.)
+ */
+final class Contact
+{
+    /**
+     * @ignore
+     * @var Auth $auth 授权对象
+     */
+    private $auth;
+
+    /// @cond
+    public function __construct($auth)
+    {
+        $this->auth = $auth;
+    }
+    /// @endcond
+
+    /**
+     * \~chinese
+     * \brief
+     * 添加好友
+     * 
+     * \details
+     * 好友必须是和自己在一个 App Key 下的用户。免费版 App Key 下的每个用户的好友数量上限为 1000,不同版本 App Key 上限不同,具体可参考:<a href="https://www.easemob.com/pricing/im" target="_blank">版本功能介绍</a>。
+     * 
+     * @param  string  $username 要添加好友的用户名
+     * @param  string  $contact  好友用户名
+     * @return boolean|array     成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Add friends
+     * 
+     * \details
+     * Friends must be users under the same app key with themselves. The maximum number of friends of each user under the free version of APP key is 1000. The maximum number of friends of different versions of APP key is different. For details, please refer to:<a href="https://www.easemob.com/pricing/im" target="_blank">version function introduction</a>.
+     * 
+     * @param  string  $username User name
+     * @param  string  $contact  Friend user name
+     * @return boolean|array     Success or error
+     */
+    public function add($username, $contact)
+    {
+        if (!trim($username) || !trim($contact)) {
+            \Easemob\exception('Please enter username and friend username');
+        }
+        $uri = $this->auth->getBaseUri() . '/users/' . $username . '/contacts/users/' . $contact;
+        $resp = Http::post($uri, array(), $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 移除好友
+     * 
+     * \details
+     * 从用户的好友列表中移除一个用户。
+     * 
+     * @param  string  $username 要移除好友的用户名
+     * @param  string  $contact  好友用户名
+     * @return boolean|array     成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Remove friends
+     * 
+     * \details
+     * Removes a user from the user's friends list.
+     * 
+     * @param  string  $username User name
+     * @param  string  $contact  Friend user name
+     * @return boolean|array     Success or error
+     */
+    public function remove($username, $contact)
+    {
+        if (!trim($username) || !trim($contact)) {
+            \Easemob\exception('Please enter username and friend username');
+        }
+        $uri = $this->auth->getBaseUri() . '/users/' . $username . '/contacts/users/' . $contact;
+        $resp = Http::delete($uri, null, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 获取好友列表
+     * 
+     * @param  string $username 要获取好友列表的用户名
+     * @return array            好友列表或者错误
+     * 
+     * \~english
+     * \brief
+     * Get friends list
+     * 
+     * @param  string $username User name
+     * @return array            Friends list or error
+     */
+    public function get($username)
+    {
+        if (!trim($username)) {
+            \Easemob\exception('Please enter username');
+        }
+        $uri = $this->auth->getBaseUri() . '/users/' . $username . '/contacts/users';
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data'];
+    }
+}

+ 1082 - 0
vendor/maniac/easemob-php/src/Group.php

@@ -0,0 +1,1082 @@
+<?php
+namespace Easemob;
+
+use Easemob\Http\Http;
+
+/**
+ * \~chinese
+ * Group 用来管理群组
+ * 
+ * \~english
+ * The `Group` is used to manage group
+ */
+final class Group
+{
+    /**
+     * @ignore
+     * @var Auth $auth 授权对象
+     */
+    private $auth;
+
+    /**
+     * @ignore
+     * @var array $modifiedAllowedField 修改群组信息时,请求体中允许的属性,5.6 版本前不能声明类常量数组,改为静态变量代替
+     */
+    private static $modifiedAllowedField = array(
+        'groupname',
+        'description',
+        'maxusers',
+        'membersonly',
+        'allowinvites',
+        'custom',
+    );
+
+    /// @cond
+    public function __construct($auth)
+    {
+        $this->auth = $auth;
+    }
+    /// @endcond
+
+    /**
+     * \~chinese
+     * \brief
+     * 获取 App 中所有的群组(可分页)
+     * 
+     * @param  int    $limit  一次获取的群组数量,默认获取 10 条。
+     * @param  string $cursor 分页使用,传入游标后便从游标起始的地方进行查询,类似于数据库 limit 1,5 中 1 的作用,可以理解为页码。
+     * @return array          群组列表信息或者错误
+     * 
+     * \~english
+     * \brief
+     * Get all groups in the app (pagable)
+     * 
+     * @param  int    $limit  Number of groups obtained at one time, get 10 by default.
+     * @param  string $cursor Paging is used. After the cursor is passed in, it will be queried from the beginning of the cursor, which is similar to the function of 1 in database limit 1,5. It can be understood as page number.
+     * @return array          Group list information or error
+     */
+    public function listGroups($limit = 10, $cursor = '')
+    {
+        $limit = (int)$limit < 0 ? 1 : (int)$limit;
+        $uri = $this->auth->getBaseUri() . '/chatgroups';
+        $uri .= $limit ? '?limit=' . $limit : '';
+        $uri .= ($limit && $cursor) ? '&cursor='.$cursor : '';
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        
+        $data = $resp->data();
+        return array(
+            'cursor' => isset($data['cursor']) ? $data['cursor'] : '',
+            'data' => $data['data'],
+        );
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 获取 App 中所有的群组
+     * 
+     * @return array 群组列表信息或者错误
+     * 
+     * \~english
+     * \brief
+     * Get all groups in the app
+     * 
+     * @return array Group list information or error
+     */
+    public function listAllGroups()
+    {
+        $data = $this->listGroups(0);
+        return $data['data'];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 分页获取单个用户加入的所有群组
+     * 
+     * @param  string $username 用户名
+     * @param  int    $pageSize 每页获取的群组数量。该参数仅适用于分页获取方法。默认取 10 条。
+     * @param  int    $pageNum  当前页码。该参数仅适用于分页获取方法。
+     * @return array            群组信息或者错误
+     * 
+     * \~english
+     * \brief
+     * Paged access to all groups joined by a single user
+     * 
+     * @param  string $username User name
+     * @param  int    $pageSize Number of groups obtained per page. This parameter is only applicable to the paging get method. 10 by default.
+     * @param  int    $pageNum  Current page number. This parameter is only applicable to the paging get method.
+     * @return array            Group information or error
+     */
+    public function listGroupsUserJoined($username, $pageSize = 10, $pageNum = 1)
+    {
+        if (!trim($username)) {
+            \Easemob\exception('Please pass the user name');
+        }
+        $pageSize = (int)$pageSize > 0 ? (int)$pageSize : 0;
+        $pageNum = (int)$pageNum > 0 ? (int)$pageNum : 1;
+        $uri = $this->auth->getBaseUri() . '/users/' . $username . '/joined_chatgroups';
+        $uri .= $pageSize ? ('?pagesize='.$pageSize.'&pagenum='.$pageNum) : '';
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data'];
+    }
+
+    /**
+     * \~chinese
+     * brief
+     * 获取单个用户加入的所有群组
+     * 
+     * @param  string $username 用户名
+     * @return array            群组信息或者错误
+     * 
+     * \~english
+     * brief
+     * Get all groups joined by a single user
+     * 
+     * @param  string $username User name
+     * @return array            Group information or error
+     */
+    public function listAllGroupsUserJoined($username)
+    {
+        return $this->listGroupsUserJoined($username, 0);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 获取群组详情
+     * 
+     * \details
+     * 可以获取一个或多个群组的详情。当获取多个群组的详情时,返回所有存在的群组的详情;对于不存在的群组,返回 “group id doesn’t exist”。
+     * 
+     * @param  string $groupId 群组 ID,可以以逗号分割,同时传递多个群组 ID
+     * @return array           群组信息或者错误
+     * 
+     * \~english
+     * \brief
+     * Get group details
+     * 
+     * \details
+     * You can get the details of one or more groups. When the details of multiple groups are obtained, the details of all existing groups are returned; For group ID 'exist', return 'EST'.
+     * 
+     * @param  string $groupId Group ID, which can be separated by commas, and multiple group IDs can be passed at the same time
+     * @return array           Group information or error
+     */
+    public function getGroup($groupId)
+    {
+        if (!trim($groupId)) {
+            \Easemob\exception('Please pass the group ID');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatgroups/'. $groupId;
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return count($data['data']) > 1 ? $data['data'] : $data['data'][0];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 创建公开群
+     * 
+     * \details
+     * 创建一个公开的群组,并设置群主、群组名称、群组描述、群成员、群成员最大人数(包括群主)、加入群是否需要批准、群组扩展信息。
+     * 
+     * @param  string  $owner        群组管理员的用户名
+     * @param  string  $groupname    群组名称,最大长度为 128 字符。
+     * @param  string  $desc         群组描述,最大长度为 512 字符。
+     * @param  array   $members      群组成员,此属性为非必需,但是如果加此项,数组元素至少一个,不能超过 100 个。(注:群主 user1 不需要写入到 members 里面)
+     * @param  int     $maxusers     群组最大成员数(包括群主),值为数值类型,默认值 200,具体上限请参考 <a href="https://console.easemob.com/user/login" target="_blank">环信即时通讯云控制台</a>。
+     * @param  boolean $members_only 用户申请入群是否需要群主或者群管理员审批,默认是 false。true:是;false:否。
+     * @param  string  $custom       群组扩展信息,例如可以给群组添加业务相关的标记,不要超过 1024 字符。
+     * @return string|array          群组 id 或者错误
+     * 
+     * \~english
+     * \brief
+     * Create public group
+     * 
+     * \details
+     * Create a public group, and set the group owner, group name, group description, group members, the maximum number of group members (including group owners), whether to join the group requires approval, and group extension information.
+     * 
+     * @param  string  $owner        User name of the group administrator
+     * @param  string  $groupname    Group name, with a maximum length of 128 characters.
+     * @param  string  $desc         Group description, with a maximum length of 512 characters.
+     * @param  array   $members      This attribute is not required for group members, but if this item is added, there must be at least one array element, not more than 100. (Note: group leader user1 does not need to be written into members)
+     * @param  int     $maxusers     The maximum number of group members (including group owners), the value is numerical type, the default value is 200, please refer to the specific upper limit <a href="https://console.easemob.com/user/login" target="_blank">easemob console</a>。
+     * @param  boolean $members_only Whether the user's application to join the group needs the approval of the group owner or group administrator. The default is false. True: Yes; False: No.
+     * @param  string  $custom       Group extension information, for example, you can add business-related tags to the group, which should not exceed 1024 characters.
+     * @return string|array          Group ID or error
+     */
+    public function createPublicGroup($owner, $groupname, $desc, $members = array(), $maxusers = 200, $members_only = true, $custom = '')
+    {
+        // 公开群
+        $public = true;
+        // 公开群(public 为 true),则不允许群成员邀请别人加入此群
+        $allowinvites = false;
+        $data = compact('owner', 'groupname', 'desc', 'public', 'maxusers', 'allowinvites', 'members_only', 'members', 'custom');
+        return $this->create($data);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 创建私有群
+     * 
+     * \details
+     * 创建一个私有的群组,并设置群主、群组名称、群组描述、群成员、群成员最大人数(包括群主)、是否允许群成员邀请别人加入此群、群组扩展信息。
+     * 
+     * @param  string  $owner        群组管理员的用户名
+     * @param  string  $groupname    群组名称,最大长度为 128 字符。
+     * @param  string  $desc         群组描述,最大长度为 512 字符。
+     * @param  array   $members      群组成员,此属性为非必需,但是如果加此项,数组元素至少一个,不能超过 100 个。(注:群主 user1 不需要写入到 members 里面)
+     * @param  int     $maxusers     群组最大成员数(包括群主),值为数值类型,默认值 200,具体上限请参考 <a href="https://console.easemob.com/user/login" target="_blank">环信即时通讯云控制台</a>。
+     * @param  boolean $allowinvites 是否允许群成员邀请别人加入此群:true:允许群成员邀请人加入此群;false:只有群主或者管理员才可以往群里加人。
+     * @param  string  $custom       群组扩展信息,例如可以给群组添加业务相关的标记,不要超过 1024 字符。
+     * @return string|array          群组 id 或者错误
+     * 
+     * \~english
+     * \brief
+     * Create private group
+     * 
+     * \details
+     * Create a private group, and set the group owner, group name, group description, group members, maximum number of group members (including group owner), whether to allow group members to invite others to join the group, and group extension information.
+     * 
+     * @param  string  $owner        User name of the group administrator
+     * @param  string  $groupname    Group name, with a maximum length of 128 characters.
+     * @param  string  $desc         Group description, with a maximum length of 512 characters.
+     * @param  array   $members      This attribute is not required for group members, but if this item is added, there must be at least one array element, not more than 100. (Note: group leader user1 does not need to be written into members)
+     * @param  int     $maxusers     The maximum number of group members (including group owners), the value is numerical type, the default value is 200, please refer to the specific upper limit <a href="https://console.easemob.com/user/login" target="_blank">easemob console</a>。
+     * @param  boolean $allowinvites Allow group members to invite others to join the group: true: allow group members to invite people to join the group; False: only the group leader or administrator can add people to the group.
+     * @param  string  $custom       Group extension information, for example, you can add business-related tags to the group, which should not exceed 1024 characters.
+     * @return string|array          Group ID or error
+     */
+    public function createPrivateGroup($owner, $groupname, $desc, $members = array(), $maxusers = 200, $allowinvites = false, $custom = '')
+    {
+        // 私有群
+        $public = false;
+        // 如果允许了群成员邀请用户进群(allowinvites 为 true),那么就不需要群主或群管理员审批了
+        $members_only = $allowinvites ? false : true;
+        $data = compact('owner', 'groupname', 'desc', 'public', 'maxusers', 'allowinvites', 'members_only', 'members', 'custom');
+        return $this->create($data);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 修改群组信息
+     * 
+     * \details
+     * 修改成功的数据行会返回 true,失败为 false。请求 body 只接收 groupname、description、maxusers、membersonly、allowinvites、custom 六个属性,传不存在的字段,或者不能修改的字段会抛异常。
+     * 
+     * @param  array   $data 群组信息
+     *     - `groupname` string 类型,群组名称,修改时值不能包含斜杠(“/”),最大长度为 128 字符。
+     *     - `description` string 类型,群组描述,修改时值不能包含斜杠(“/”),最大长度为 512 字符。
+     *     - `maxusers` int 类型,群组成员最大数(包括群主),值为数值类型。
+     *     - `membersonly` string 类型,加入群组是否需要群主或者群管理员审批:true:是;false:否。
+     *     - `allowinvites` string 类型,是否允许群成员邀请别人加入此群:true:允许群成员邀请人加入此群;false:只有群主才可以往群里加人。
+     *     - `custom` string 类型,群组扩展信息,例如可以给群组添加业务相关的标记,不要超过 1,024 字符。
+     * @return boolean|array 成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Modify group information
+     * 
+     * \details
+     * The modified data row will return true, and the failure will be false. The request body only receives six attributes: groupname, description, maxusers, members only, allowinvites and custom. If it passes non-existent fields or fields that cannot be modified, exceptions will be thrown.
+     * 
+     * @param  array   $data Group information
+     *     - `groupname` string type. Group name. When modified, the value cannot contain slash ("/"), and the maximum length is 128 characters.
+     *     - `description` string type. Group description. When modifying, the value cannot contain slash ("/"), and the maximum length is 512 characters.
+     *     - `maxusers` int type. The maximum number of group members (including group owners) is the numerical value type.
+     *     - `membersonly` string type. Whether to join the group requires the approval of the group owner or group administrator: true: Yes; False: No.
+     *     - `allowinvites` string type. Allow group members to invite others to join the group: true: allow group members to invite people to join the group; False: only the group leader can add people to the group.
+     *     - `custom` string type. Group extension information, for example, you can add business-related tags to the group, which should not exceed 1024 characters.
+     * @return boolean|array Success or error
+     */
+    public function updateGroup($data)
+    {
+        $data['group_id'] = isset($data['group_id']) ? trim($data['group_id']) : '';
+        $groupId = $data['group_id'];
+        if (!$groupId) {
+            \Easemob\exception('Please pass the group ID');
+        }
+        unset($data['group_id']);
+        foreach ($data as $field => $value) {
+            if (!in_array($field, self::$modifiedAllowedField)) {
+                \Easemob\exception('Only groupname, description, maxusers, members only, allowinvites and custom are allowed at most');
+            }
+        }
+
+        if (isset($data['groupname'])) {
+            $data['groupname'] = trim($data['groupname']);
+            if (!$data['groupname']) {
+                \Easemob\exception('Group name cannot be empty');
+            } elseif (preg_match('/\//', $data['groupname'])) {
+                \Easemob\exception('The group name cannot contain a diagonal bar ("/)');
+            } elseif (mb_strlen($data['groupname']) > 128) {
+                \Easemob\exception('The maximum length of the group name is 128 characters');
+            }
+        }
+
+        if (isset($data['description'])) {
+            $data['description'] = trim($data['description']);
+            if (!$data['description']) {
+                \Easemob\exception('Group description cannot be empty');
+            } elseif (preg_match('/\//', $data['description'])) {
+                \Easemob\exception('The group description cannot contain diagonal bars ("/")');
+            } elseif (mb_strlen($data['description']) > 512) {
+                \Easemob\exception('The maximum length of the group description is 512 characters');
+            }
+        }
+
+        // 获取原始群组信息
+        $info = $this->getGroup($groupId);
+
+        if (isset($data['maxusers'])) {
+            $data['maxusers'] = (int)$data['maxusers'];
+        }
+
+        // 如果是公开群(public为true),则不允许群成员邀请别人加入此群
+        if ($info['public'] && isset($data['allowinvites'])) {
+            $data['allowinvites'] = false;
+        }
+
+        if (isset($data['membersonly'])) {
+            // 如果允许了群成员邀请用户进群(allowinvites为true),那么就不需要群主或群管理员审批了
+            $data['membersonly'] = $info['allowinvites'] ? false : (boolean)$data['membersonly'];
+        }
+        
+        if (isset($data['allowinvites'])) {
+            $data['allowinvites'] = (boolean)$data['allowinvites'];
+
+            if (isset($data['membersonly'])) {
+                // 如果允许了群成员邀请用户进群(allowinvites为true),那么就不需要群主或群管理员审批了
+                $data['membersonly'] = $data['allowinvites'] ? false : (boolean)$data['membersonly'];
+            }
+        }
+
+        if (isset($data['custom'])) {
+            $data['custom'] = trim($data['custom']);
+            if ($data['custom'] && mb_strlen($data['custom']) > 1024) {
+                \Easemob\exception('The maximum length of group extension information is 1024 characters');
+            }
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatgroups/' . $groupId;
+        $resp = Http::put($uri, $data, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 删除群组
+     * 
+     * @param  string  $groupId 群组 id
+     * @return boolean|array    成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Delete Group
+     * 
+     * @param  string  $groupId Group ID
+     * @return boolean|array    Success or error
+     */
+    public function destroyGroup($groupId)
+    {
+        if (!trim($groupId)) {
+            \Easemob\exception('Please pass the group ID');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatgroups/' . $groupId;
+        $resp = Http::delete($uri, null, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 获取群组公告
+     * 
+     * \details
+     * 获取指定群组 ID 的群组公告。
+     * 
+     * @param  string $groupId 群组 id
+     * @return array           公告信息或者错误
+     * 
+     * \~english
+     * \brief
+     * Get group announcements
+     * 
+     * \details
+     * Gets the group announcement of the specified group ID.
+     * 
+     * @param  string $groupId Group ID
+     * @return array           Announcement information or error
+     */
+    public function getGroupAnnouncement($groupId)
+    {
+        if (!trim($groupId)) {
+            \Easemob\exception('Please enter group ID');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatgroups/'. $groupId . '/announcement';
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data'];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 修改群组公告
+     * 
+     * \details
+     * 修改指定群组 ID 的群组公告,注意群组公告的内容不能超过 512 个字符。
+     * 
+     * @param  string  $groupId      群组 ID
+     * @param  string  $announcement 群组公告内容
+     * @return boolean|array         成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Modify group announcement
+     * 
+     * \details
+     * Modify the group announcement of the specified group ID. note that the content of the group announcement cannot exceed 512 characters.
+     * 
+     * @param  string  $groupId      Group ID
+     * @param  string  $announcement Group announcement content
+     * @return boolean|array         Success or error
+     */
+    public function updateGroupAnnouncement($groupId, $announcement)
+    {
+        if (!trim($groupId) || !trim($announcement)) {
+            \Easemob\exception('Please enter the group ID and announcement content');
+        }
+
+        if (mb_strlen($announcement) > 512) {
+            \Easemob\exception('The content of the group announcement cannot exceed 512 characters');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatgroups/' . $groupId . '/announcement';
+        $body = compact('announcement');
+        $resp = Http::post($uri, $body, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data']['result'];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 获取群组共享文件
+     * 
+     * \details
+     * 分页获取指定群组 ID 的群组共享文件,之后可以根据 response 中返回的 file_id,file_id 是群组共享文件的唯一标识,调用 {@link #downloadGroupShareFile($fileName, $groupId, $fileId)} 下载文件,或调用 {@link #deleteGroupShareFile($groupId, $fileId)} 删除文件。
+     * 
+     * @param  string $groupId  群组 ID
+     * @param  int    $pageSize 每页获取的群组数量。该参数仅适用于分页获取方法。默认取 10 条。
+     * @param  int    $pageNum  当前页码。该参数仅适用于分页获取方法。
+     * @return array            群组文件信息或者错误
+     * 
+     * \~english
+     * \brief
+     * Get group shared files
+     * 
+     * \details
+     * Get the group shared file of the specified group ID by paging, and then according to the file returned in the response_ id,file_ ID is the unique identification of the group shared file, call {@link #downloadGroupShareFile($fileName, $groupId, $fileId)} download files, or call {@link #deleteGroupShareFile($groupId, $fileId)} delete file.
+     * 
+     * @param  string $groupId  Group ID
+     * @param  int    $pageSize Number of groups obtained per page. This parameter is only applicable to the paging get method. 10 by default.
+     * @param  int    $pageNum  Current page number. This parameter is only applicable to the paging get method.
+     * @return array            Group file information or error
+     */
+    public function getGroupShareFiles($groupId, $pageSize = 10, $pageNum = 1)
+    {
+        if (!trim($groupId)) {
+            \Easemob\exception('Please enter the group ID');
+        }
+
+        $pageSize = (int)$pageSize > 0 ? (int)$pageSize : 0;
+        $pageNum = (int)$pageNum > 0 ? (int)$pageNum : 1;
+        $uri = $this->auth->getBaseUri() . '/chatgroups/' . $groupId . '/share_files';
+        $uri .= $pageSize ? ('?pagesize='.$pageSize.'&pagenum='.$pageNum) : '';
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data'];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 上传群组共享文件
+     * 
+     * \details
+     * 上传指定群组 ID 的群组共享文件。注意上传的文件大小不能超过 10 MB。
+     * 
+     * @param  string $groupId  群组 ID
+     * @param  string $fileName 上传的文件路径
+     * @return array            上传的文件信息或者错误
+     * 
+     * \~english
+     * \brief
+     * Upload group shared files
+     * 
+     * \details
+     * Upload the group shared file with the specified group ID. Note that the uploaded file size cannot exceed 10 Mb.
+     * 
+     * @param  string $groupId  Group ID
+     * @param  string $fileName Uploaded file path
+     * @return array            Uploaded file information or error
+     */
+    public function uploadGroupShareFile($groupId, $fileName)
+    {
+        if (!trim($groupId)) {
+            \Easemob\exception('Please enter the group ID');
+        }
+
+        if (!trim($fileName)) {
+            \Easemob\exception('Please pass in the attachment name');
+        }
+        
+        $file = fopen($fileName, 'rb');
+        if ($file === false) {
+            \Easemob\exception('The attachment cannot be read');
+        }
+
+        $stat = fstat($file);
+        $size = $stat['size'];
+        $data = fread($file, $size);
+        fclose($file);
+        $mimeType = mime_content_type($fileName) ? mime_content_type($fileName) : null;
+        $uri = $this->auth->getBaseUri() . '/chatgroups/' . $groupId . '/share_files';
+        $resp = Http::multipartPost($uri, $fileName, $data, $mimeType, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data'];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 下载群组共享文件
+     * 
+     * \details
+     * 根据指定的群组 ID 与 file_id 下载群组共享文件,file_id 通过 {@link #getGroupShareFiles($groupId, $pageSize = 10, $pageNum = 1)} 获取。
+     * 
+     * @param  string  $fileName 要下载的文件路径
+     * @param  string  $groupId  群组 ID
+     * @param  string  $fileId   群组共享文件 id
+     * @return int|array         文件大小或者错误
+     * 
+     * \~english
+     * \brief
+     * Download group shared files
+     * 
+     * \details
+     * According to the specified group ID and file_id download group shared file, file_id is obtained through {@link #getGroupShareFiles($groupId, $pageSize = 10, $pageNum = 1)}.
+     * 
+     * @param  string  $fileName File path to download
+     * @param  string  $groupId  Group ID
+     * @param  string  $fileId   file_id
+     * @return int|array         File size or error
+     */
+    public function downloadGroupShareFile($fileName, $groupId, $fileId)
+    {
+        if (!trim($fileName) || !trim($fileId) || !trim($fileId)) {
+            \Easemob\exception('Please enter the file path, group ID and group shared file ID to download');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatgroups/' . $groupId . '/share_files/' . $fileId;
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $dir = dirname($fileName);
+        if (!file_exists($dir)) {
+            mkdir($dir, 0755, true);
+        }
+        return file_put_contents($fileName, $resp->body);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 删除群组共享文件
+     * 
+     * \details
+     * 根据指定的群组 ID 与 file_id 删除群组共享文件,file_id 通过 {@link #getGroupShareFiles($groupId, $pageSize = 10, $pageNum = 1)} 获取。
+     * 
+     * @param  string  $groupId 群组 ID
+     * @param  string  $fileId  群组共享文件 id
+     * @return boolean|array    成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Delete group shared files
+     * 
+     * \details
+     * According to the specified group ID and file_id delete group shared file, file_id is obtained through {@link #getGroupShareFiles($groupId, $pageSize = 10, $pageNum = 1)}。
+     * 
+     * @param  string  $groupId Group ID
+     * @param  string  $fileId  file_id
+     * @return boolean|array    Success or error
+     */
+    public function deleteGroupShareFile($groupId, $fileId)
+    {
+        if (!trim($groupId) || !trim($fileId)) {
+            \Easemob\exception('Please pass in the group ID and the group shared file ID');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatgroups/' . $groupId . '/share_files/' . $fileId;
+        $resp = Http::delete($uri, null, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data']['result'];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 分页获取群组成员
+     * 
+     * @param  string $groupId  群组 ID
+     * @param  int    $pageSize 每页成员数量,默认值为 10,最大为 100。
+     * @param  int    $pageNum  当前页码。默认值为 1。
+     * @return array            群组成员信息或者错误
+     * 
+     * \~english
+     * \brief
+     * Paging get group members
+     * 
+     * @param  string $groupId  Group ID
+     * @param  int    $pageSize Number of members per page. The default value is 10 and the maximum value is 100.
+     * @param  int    $pageNum  Current page number. The default value is 1.
+     * @return array            Group member information or error
+     */
+    public function listGroupMembers($groupId, $pageSize = 10, $pageNum = 1)
+    {
+        if (!trim($groupId)) {
+            \Easemob\exception('Please enter the group ID');
+        }
+
+        $pageSize = (int)$pageSize > 0 ? (int)$pageSize : 0;
+        $pageNum = (int)$pageNum > 0 ? (int)$pageNum : 1;
+
+        $uri = $this->auth->getBaseUri() . '/chatgroups/' . $groupId . '/users';
+        $uri .= $pageSize ? ('?pagesize='.$pageSize.'&pagenum='.$pageNum) : '';
+
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data'];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 获取群组所有成员
+     * 
+     * @param  string $groupId 群组 ID
+     * @return array           群组成员信息或者错误
+     * 
+     * \~english
+     * \brief
+     * Get all members of the group
+     * 
+     * @param  string $groupId Group ID
+     * @return array           Group member information or error
+     */
+    public function listAllGroupMembers($groupId)
+    {
+        return $this->listGroupMembers($groupId, 0);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 添加单个群组成员
+     * 
+     * \details
+     * 一次给群添加一个成员,不能重复添加同一个成员。如果用户已经是群成员,将添加失败,并返回错误。
+     * 
+     * @param  string  $groupId  群组 ID
+     * @param  string  $username 环信用户 ID
+     * @return boolean|array     成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Add individual group members
+     * 
+     * \details
+     * Add one member to the group at a time. You cannot add the same member repeatedly. If the user is already a member of the group, the addition fails with an error.
+     * 
+     * @param  string  $groupId  Group ID
+     * @param  string  $username User name
+     * @return boolean|array     Success or error
+     */
+    public function addGroupMember($groupId, $username)
+    {
+        return $this->addUsers($groupId, $username);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 批量添加群组成员
+     * 
+     * \details
+     * 为群组添加多个成员,一次最多可以添加 60 位成员。如果所有用户均已是群成员,将添加失败,并返回错误。
+     * 
+     * @param  string  $groupId   群组 ID
+     * @param  array   $usernames 环信用户 ID 数组
+     * @return boolean|array      成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Batch add group members
+     * 
+     * \details
+     * Add multiple members to the group. You can add up to 60 members at a time. If all users are already members of the group, the addition fails with an error.
+     * 
+     * @param  string  $groupId   Group ID
+     * @param  array   $usernames User name
+     * @return boolean|array      Success or error
+     */
+    public function addGroupMembers($groupId, $usernames)
+    {
+        return $this->addUsers($groupId, $usernames);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 移除单个群组成员
+     * 
+     * \details
+     * 从群中移除某个成员。如果被移除用户不是群成员,将移除失败,并返回错误。
+     * 
+     * @param  string  $groupId   群组 ID
+     * @param  string  $username  环信用户 ID
+     * @return boolean|array      成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Remove individual group members
+     * 
+     * \details
+     * Remove a member from the group. If the removed user is not a member of the group, the removal fails with an error.
+     * 
+     * @param  string  $groupId   Group ID
+     * @param  string  $username  User name
+     * @return boolean|array      Success or error
+     */
+    public function removeGroupMember($groupId, $username)
+    {
+        return $this->removeUsers($groupId, $username);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 批量移除群组成员
+     * 
+     * \details
+     * 移除群成员,用户名之间用英文逗号分隔。建议一次最多移除 60 个群成员。如果所有被移除用户均不是群成员,将移除失败,并返回错误。
+     * 
+     * @param  string  $groupId   群组 ID
+     * @param  array   $username  环信用户 ID 数组
+     * @return boolean|array      成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Batch remove group members
+     * 
+     * \details
+     * Remove group members and separate user names with English commas. It is recommended to remove up to 60 group members at a time. If all the removed users are not members of the group, the removal fails with an error.
+     * 
+     * @param  string  $groupId   Group ID
+     * @param  array   $username  User name array
+     * @return boolean|array      Success or error
+     */
+    public function removeGroupMembers($groupId, $usernames)
+    {
+        return $this->removeUsers($groupId, $usernames);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 获取群管理员列表
+     * 
+     * @param  string $groupId 群组 ID
+     * @return array           群管理员信息或者错误
+     * 
+     * \~english
+     * \brief
+     * Get the list of group administrators
+     * 
+     * @param  string $groupId Group ID
+     * @return array           Group administrator information or error
+     */
+    public function listGroupAdmins($groupId)
+    {
+        if (!trim($groupId)) {
+            \Easemob\exception('Please enter the group ID');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatgroups/' . $groupId . '/admin';
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data'];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 添加群管理员
+     * 
+     * \details
+     * 将一个群成员角色权限提升为群管理员。
+     * 
+     * @param  string  $groupId  群组 ID
+     * @param  string  $newadmin 添加的新管理员用户 ID
+     * @return boolean|array     成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Add group administrator
+     * 
+     * \details
+     * Promote the role permission of a group member to group administrator.
+     * 
+     * @param  string  $groupId  Group ID
+     * @param  string  $newadmin User name
+     * @return boolean|array     Success or error
+     */
+    public function addGroupAdmin($groupId, $newadmin)
+    {
+        if (!trim($groupId) || !trim($newadmin)) {
+            \Easemob\exception('Please pass in the group ID and the new administrator user ID to be added');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatgroups/' . $groupId . '/admin';
+        $body = compact('newadmin');
+        $resp = Http::post($uri, $body, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 移除群管理员
+     * 
+     * \details
+     * 将用户的角色从群管理员降为群普通成员。
+     * 
+     * @param  string  $groupId  群组 ID
+     * @param  string  $oldadmin 移除的管理员用户 ID
+     * @return boolean|array     成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Remove group administrator
+     * 
+     * \details
+     * Reduce the user's role from group administrator to ordinary member of the group.
+     * 
+     * @param  string  $groupId  Group ID
+     * @param  string  $oldadmin User name
+     * @return boolean|array     Success or error
+     */
+    public function removeGroupAdmin($groupId, $oldadmin)
+    {
+        if (!trim($groupId) || !trim($oldadmin)) {
+            \Easemob\exception('Please pass in the group ID and the administrator user ID to be removed');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatgroups/' . $groupId . '/admin/' . $oldadmin;
+        $resp = Http::delete($uri, null, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data']['result'] === 'success' ? true : false;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 转让群组
+     * 
+     * \details
+     * 修改群主为同一群组中的其他成员。
+     * 
+     * @param  string  $groupId  群组 ID
+     * @param  string  $newowner 群组的新管理员用户 ID
+     * @return boolean|array     成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Transfer group
+     * 
+     * \details
+     * Modify the group owner to other members in the same group.
+     * 
+     * @param  string  $groupId  Group ID
+     * @param  string  $newowner User name
+     * @return boolean|array     Success or error
+     */
+    public function updateGroupOwner($groupId, $newowner)
+    {
+        if (!trim($groupId) || !trim($newowner)) {
+            \Easemob\exception('Please pass in the group ID and the new administrator user ID of the group');
+        }
+
+        $uri = $this->auth->getBaseUri(). '/chatgroups/' . $groupId;
+        $body = compact('newowner');
+        $resp = Http::put($uri, $body, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * @ignore 创建群组
+     * @param  array $data  群组信息
+     * @return string|array 群组 id 或者错误
+     */
+    private function create($data)
+    {
+        $data['groupname'] = trim($data['groupname']);
+        $data['desc'] = trim($data['desc']);
+        $data['owner'] = trim($data['owner']);
+
+        if (!isset($data['groupname']) || !$data['groupname']) {
+            \Easemob\exception('Please pass the group name');
+        }
+
+        if (!isset($data['desc']) || !$data['desc']) {
+            \Easemob\exception('Please pass the group description');
+        }
+
+        if (!isset($data['owner']) || !$data['owner']) {
+            \Easemob\exception('Please pass the administrator of the group');
+        }
+
+        if (isset($data['members'])) {
+            if (!is_array($data['members']) || empty($data['members'])) {
+                \Easemob\exception('Please pass in group members. Group members must be arrays');
+            } elseif (count($data['members']) > 100) {
+                \Easemob\exception('Group members cannot exceed 100');
+            } elseif (in_array($data['owner'], $data['members'])) {
+                \Easemob\exception('The group leader does not need to be included in the group members');
+            }
+        }
+
+        $data['public'] = (boolean)$data['public'];
+        $data['maxusers'] = isset($data['maxusers']) && (int)$data['maxusers'] ? (int)$data['maxusers'] : 200;
+        // 如果是公开群(public 为 true),则不允许群成员邀请别人加入此群
+        $data['allowinvites'] = (boolean)$data['allowinvites'];
+        // 如果允许了群成员邀请用户进群(allowinvites为true),那么就不需要群主或群管理员审批了
+        $data['members_only'] = (boolean)$data['members_only'];
+        $data['custom'] = trim($data['custom']);
+
+        $uri = $this->auth->getBaseUri() . '/chatgroups';
+        $resp = Http::post($uri, $data, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return isset($data['data']['groupid']) ? $data['data']['groupid'] : $data['data'];
+    }
+
+    /**
+     * @ignore (批量)添加群组成员
+     * @param  string       $groupId   群组 ID
+     * @param  string|array $usernames 环信用户 ID,string: 添加单个群组成员;array: 批量添加群组成员
+     * @return boolean|array           成功或者错误
+     */
+    private function addUsers($groupId, $usernames)
+    {
+        if (!trim($groupId)) {
+            \Easemob\exception('Please pass the group ID');
+        }
+
+        if ((is_array($usernames) && empty($usernames)) || (is_string($usernames) && !trim($usernames))) {
+            \Easemob\exception('Please pass the user name');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatgroups/' . $groupId . '/users';
+        $uri .= is_array($usernames) ? '' : ('/' . $usernames);
+        $body = is_array($usernames) ? compact('usernames') : null;
+        $resp = Http::post($uri, $body, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * @ignore(批量)移除群组成员
+     * @param  string       $groupId   群组 ID
+     * @param  string|array $usernames 环信用户 ID,string: 移除单个群组成员;array: 批量移除群组成员
+     * @return boolean|array           成功或者错误
+     */
+    private function removeUsers($groupId, $usernames)
+    {
+        if (!trim($groupId)) {
+            \Easemob\exception('Please pass the group ID');
+        }
+
+        if ((is_array($usernames) && empty($usernames)) || (is_string($usernames) && !trim($usernames))) {
+            \Easemob\exception('Please pass the user name');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatgroups/' . $groupId . '/users/';
+        $uri .= is_array($usernames) ? implode(',', $usernames) : $usernames;
+        $resp = Http::delete($uri, null, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+}

+ 234 - 0
vendor/maniac/easemob-php/src/Http/Http.php

@@ -0,0 +1,234 @@
+<?php
+namespace Easemob\Http;
+
+/**
+ * Http 请求类
+ * @final
+ */
+final class Http
+{
+    /**
+     * @var string $proxyIp 代理 ip
+     */
+    public static $proxyIp;
+
+    /**
+     * @var string $proxyPort 代理端口
+     */
+    public static $proxyPort;
+
+    /**
+     * @var string $proxyUser 代理用户名
+     */
+    public static $proxyUser;
+
+    /**
+     * @var string $proxyUser 代理密码
+     */
+    public static $proxyPass;
+
+    /**
+     * 设置代理信息
+     * @param string $proxyIp   代理 ip
+     * @param int    $proxyPort 代理端口
+     * @param string $proxyUser 代理用户名
+     * @param string $proxyPass 代理密码
+     * @example
+     * <pre>
+     * \Easemob\Http\Http::setProxy("ip", 8080);
+     * </pre>
+     */
+    public static function setProxy($proxyIp, $proxyPort = 80, $proxyUser = '', $proxyPass = '')
+    {
+        self::$proxyIp = $proxyIp;
+        self::$proxyPort = $proxyPort;
+        self::$proxyUser = $proxyUser;
+        self::$proxyPass = $proxyPass;
+    }
+
+    /**
+     * @ignore 发送 get 请求
+     * @param  string   $uri     请求 uri
+     * @param  mixed    $headers 请求头
+     * @return Response          响应对象
+     */
+    public static function get($uri, $headers = null)
+    {
+        return self::send(new Request('GET', $uri, $headers));
+    }
+
+    /**
+     * @ignore 发送 post 请求
+     * @param  string   $uri     请求 uri
+     * @param  mixed    $body    请求体
+     * @param  mixed    $headers 请求头
+     * @return Response          响应对象
+     */
+    public static function post($uri, $body, $headers = null)
+    {
+        return self::send(new Request('POST', $uri, $headers, $body));
+    }
+
+    /**
+     * @ignore 发送 put 请求
+     * @param  string   $uri     请求 uri
+     * @param  mixed    $body    请求体
+     * @param  mixed    $headers 请求头
+     * @return Response          响应对象
+     */
+    public static function put($uri, $body, $headers = null)
+    {
+        return self::send(new Request('PUT', $uri, $headers, $body));
+    }
+
+    /**
+     * @ignore 发送 delete 请求
+     * @param  string   $uri     请求 uri
+     * @param  mixed    $headers 请求头
+     * @return Response          响应对象
+     */
+    public static function delete($uri, $body, $headers = null)
+    {
+        return self::send(new Request('DELETE', $uri, $headers, $body));
+    }
+
+    /**
+     * @ignore 发送 http 请求
+     * @param  Request $request 请求信息
+     * @return Response         响应对象
+     */
+    public static function send($request)
+    {
+        $startTime = microtime(true);
+        $ch = curl_init();
+        $options = array(
+            CURLOPT_RETURNTRANSFER => true,     // 将 curl_exec() 获取的信息以字符串返回,而不是直接输出。
+            CURLOPT_SSL_VERIFYPEER => false,    // 禁止 cURL 验证证书
+            CURLOPT_SSL_VERIFYHOST => false,    // 不检查服务器SSL证书名称
+            CURLOPT_HEADER => true,             // 将头文件的信息作为数据流输出
+            CURLOPT_NOBODY => false,            // true 时将不输出 BODY 部分。同时 Mehtod 变成了 HEAD。修改为 false 时不会变成 GET。
+            CURLOPT_CUSTOMREQUEST => $request->method,  // 请求方法
+            CURLOPT_URL => $request->uri,   // 请求地址
+            CURLOPT_USERAGENT => 'EasemobServerSDK-PHP/' . PHP_VERSION,
+        );
+
+        if (!empty($request->headers)) {
+            if (!isset($request->headers['Content-Type']) || !$request->headers['Content-Type']) {
+                $request->headers['Content-Type'] = (!$request->body || is_array($request->body)) ? 'application/json' : 'application/x-www-form-urlencoded';
+            }
+            $options[CURLOPT_HTTPHEADER] = self::buildHeaders($request->headers);
+        }
+
+        if (!empty($request->body)) {
+            $request->body = is_array($request->body) ? json_encode($request->body) : $request->body;
+            $options[CURLOPT_POSTFIELDS] = $request->body;
+        }
+
+        if (self::$proxyIp && self::$proxyPort) {
+            $options[CURLOPT_PROXY] = self::$proxyIp;
+            $options[CURLOPT_PROXYPORT] = self::$proxyPort;
+            if (self::$proxyUser && self::$proxyPass) {
+                $options[CURLOPT_PROXYUSERPWD] = self::$proxyUser .':'. self::$proxyPass;
+            }
+        }
+
+        curl_setopt_array($ch, $options);
+        $result = curl_exec($ch);
+        $endTime = microtime(true);
+        $duration = $endTime - $startTime;
+        $code = curl_errno($ch);
+        if ($code !== 0) {
+            $response = new Response(-1, $duration, null, null, curl_error($ch));
+            curl_close($ch);
+            return $response;
+        }
+
+        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+        $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
+        $headers = self::parseHeaders(substr($result, 0, $headerSize));
+        $body = substr($result, $headerSize);
+        curl_close($ch);
+        return new Response($code, $duration, $headers, $body, null);
+    }
+
+    /**
+     * @ignore 发送上传附件请求
+     * @param  string   $uri      请求 uri
+     * @param  string   $fileName 附件名
+     * @param  array    $fileBody 附件信息
+     * @param  mixed    $mimeType 附件 mime 类型
+     * @param  array    $headers  请求头
+     * @return Response           响应对象
+     */
+    public static function multipartPost(
+        $uri,
+        $fileName,
+        $fileBody,
+        $mimeType = null,
+        $headers = array()
+    ) {
+        $data = array();
+        $mimeBoundary = md5(microtime());
+        array_push($data, '--' . $mimeBoundary);
+        $finalMimeType = empty($mimeType) ? 'application/octet-stream' : $mimeType;
+        $finalFileName = self::escapeQuotes($fileName);
+        array_push($data, "Content-Disposition: form-data; name=\"file\"; filename=\"$finalFileName\"");
+        array_push($data, "Content-Type: $finalMimeType");
+        array_push($data, '');
+        array_push($data, $fileBody);
+
+        array_push($data, '--' . $mimeBoundary . '--');
+        array_push($data, '');
+
+        $body = implode("\r\n", $data);
+        $contentType = 'multipart/form-data; boundary=' . $mimeBoundary;
+        $headers['Content-Type'] = $contentType;
+        $headers['restrict-access'] = true;
+
+        return self::send(new Request('POST', $uri, $headers, $body));
+    }
+
+    /**
+     * @ignore 构造请求头信息
+     * @param  array $headers 请求头信息
+     * @return array          请求头信息
+     */
+    private static function buildHeaders($headers)
+    {
+        $headersArr = array();
+        foreach ($headers as $key => $value) {
+            array_push($headersArr, "{$key}: {$value}");
+        }
+        return $headersArr;
+    }
+
+    /**
+     * @ignore 解析请求头信息
+     * @param  string $headersRaw 请求头原始字符串
+     * @return array              请求头信息
+     */
+    private static function parseHeaders($headersRaw)
+    {
+        $headers = array();
+        $lines = explode("\r\n", $headersRaw);
+        foreach ($lines as $line) {
+            $item = explode(':', $line);
+            if (trim($item[0])) {
+                $headers[$item[0]] = isset($item[1]) ? trim($item[1]) : '';
+            }
+        }
+        return $headers;
+    }
+
+    /**
+     * @ignore 替换引号
+     * @param  string $str 原始字符串
+     * @return string      替换之后的字符串
+     */
+    private static function escapeQuotes($str)
+    {
+        $find = array("\\", "\"");
+        $replace = array("\\\\", "\\\"");
+        return str_replace($find, $replace, $str);
+    }
+}

+ 44 - 0
vendor/maniac/easemob-php/src/Http/Request.php

@@ -0,0 +1,44 @@
+<?php
+namespace Easemob\Http;
+
+/**
+ * @ignore 请求体类
+ * @final
+ */
+final class Request
+{
+    /**
+     * @var string $uri 请求 uri
+     */    
+    public $uri;
+    
+    /**
+     * @var string $method 请求方法
+     */  
+    public $method;
+    
+    /**
+     * @var mixed $headers 请求头
+     */  
+    public $headers;
+    
+    /**
+     * @var mixed $headers 请求体
+     */  
+    public $body;
+
+    /**
+     * 构造方法
+     * @param string $method  请求方法
+     * @param string $uri     请求 uri
+     * @param mixed  $headers 请求头
+     * @param mixed  $body    请求体
+     */
+    public function __construct($method, $uri, $headers = null, $body = null)
+    {
+        $this->method = strtoupper($method);
+        $this->uri = $uri;
+        $this->headers = $headers;
+        $this->body = $body;
+    }
+}

+ 108 - 0
vendor/maniac/easemob-php/src/Http/Response.php

@@ -0,0 +1,108 @@
+<?php
+namespace Easemob\Http;
+
+/**
+ * @ignore 响应体类
+ * @final
+ */
+final class Response
+{
+    /**
+     * @var int $httpCode http 状态码
+     */
+    public $httpCode;
+    
+    /**
+     * @var number $duration 响应时长
+     */
+    public $duration;
+    
+    /**
+     * @var mixed $headers 响应头
+     */
+    public $headers;
+    
+    /**
+     * @var mixed $body 响应体
+     */
+    public $body;
+    
+    /**
+     * @var mixed $error 错误信息
+     */
+    public $error;
+    
+    /**
+     * @var mixed $data 响应数据
+     */
+    private $data;
+    
+    /**
+     * @var array $statusText 状态码对应信息
+     */
+    private static $statusText = array(
+        400 => 'Bad Request',
+        401 => 'Unauthorized',
+        403 => 'Forbidden',
+        404 => 'Not Found',
+        405 => 'Method Not Allowed',
+        408 => 'Request Timeout',
+        413 => 'Request Entity Too Large',
+        415 => 'Unsupported Media Type',
+        429 => 'Too Many Requests',
+        500 => 'Internal Server Error',
+        501 => 'Not Implemented',
+        502 => 'Bad Gateway',
+        503 => 'Service Unavailable',
+        504 => 'Gateway Timeout',
+    );
+
+    /**
+     * 构造方法
+     * @param int    $httpCode 状态码
+     * @param double $duration 执行时间
+     * @param mixed  $headers  响应头
+     * @param mixed  $body     响应体
+     * @param mixed  $error    错误信息
+     */
+    public function __construct($httpCode, $duration, $headers = null, $body = null, $error = null)
+    {
+        $this->httpCode = $httpCode;
+        $this->duration = $duration;
+        $this->headers = $headers;
+        $this->body = $body;
+
+        if ($error !== null) {
+            return;
+        }
+
+        if ($body !== null) {
+            $this->data = json_decode($body, true);
+            $error = isset($this->data['error']) && $this->data['error'] ? $this->data['error'] : $error;
+        }
+
+        if ($error === null) {
+            $error = isset(self::$statusText[$httpCode]) ? self::$statusText[$httpCode] : $error;
+        }
+        
+        $this->error = $error;
+    }
+
+    /**
+     * 查看请求是否成功
+     * @return boolean 请求是否成功
+     */
+    public function ok()
+    {
+        return $this->httpCode >= 200 && $this->httpCode < 300 && $this->error == null;
+    }
+
+    /**
+     * 获取响应数据
+     * @return array 响应数据
+     */
+    public function data()
+    {
+        return $this->data;
+    }
+}

+ 630 - 0
vendor/maniac/easemob-php/src/Message.php

@@ -0,0 +1,630 @@
+<?php
+namespace Easemob;
+
+use Easemob\Http\Http;
+
+/**
+ * \~chinese
+ * Message 用来发送消息
+ * 
+ * \~english
+ * The `Message` is used to send message
+ */
+final class Message
+{
+    /**
+     * @ignore
+     * @var Auth $auth 授权对象
+     */
+    private $auth;
+
+    /// @cond
+    public function __construct($auth)
+    {
+        $this->auth = $auth;
+    }
+    /// @endcond
+
+    /**
+     * \~chinese
+     * \brief
+     * 发送文本消息
+     * 
+     * @param  string  $target_type 发送的目标类型;users:给用户发消息,chatgroups:给群发消息,chatrooms:给聊天室发消息
+     * @param  array   $target      发送的目标;注意这里需要用数组,数组内添加的最大用户数默认 600 个,即使只有一个用户,也要用数组 [‘u1’];给用户发送时数组元素是用户名,给群组发送时,数组元素是 groupid。
+     * @param  array   $message     消息内容
+     * @param  string  $from        表示消息发送者;无此字段 Server 会默认设置为 "from": "admin",有 from 字段但值为空串 ("") 时请求失败
+     * @param  string  $sync_device 消息发送成功后,是否将消息同步给发送方。true:是;false(默认):否。
+     * @param  boolean $isOnline    该参数值为 true 时,代表 routetype 的值为 “ROUTE_ONLINE”,表示发送消息时只有接收方在线时,才进行消息投递。若接收方离线,将不会收到此条消息。
+     * @return array                发送给的目标和对应消息 id 的数组或者错误
+     * 
+     * \~english
+     * \brief
+     * Send text message
+     * 
+     * @param  string  $target_type Type of target to send; Users: send messages to users, chatgroups: send messages to groups, chatrooms: send messages to chat rooms
+     * @param  array   $target      Target of transmission; Note that you need to use the array here. The maximum number of users added in the array is 600 by default. Even if there is only one user, you should also use the array ['u1 ']; When sending to the user, the array element is the user name. When sending to the group, the array element is the groupid.
+     * @param  array   $message     Message content
+     * @param  string  $from        Indicates the sender of the message; Without this field, the server will default to "from": "admin". When there is a from field but the value is an empty string (""), the request fails
+     * @param  string  $sync_device Whether to synchronize the message to the sender after the message is sent successfully. True: Yes; False (default): No.
+     * @param  boolean $isOnline    When the parameter value is true, the value representing routetype is "route_online", which means that the message is delivered only when the receiver is online. If the receiver is offline, it will not receive this message.
+     * @return array                The array or error of the target and corresponding message ID sent to
+     */
+    public function text($target_type, $target, $message, $from = 'admin', $sync_device = false, $isOnline = false)
+    {
+        return $this->send('txt', $target_type, $target, $message, $from, $sync_device, $isOnline);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 发送图片消息
+     * 
+     * @param  string  $target_type 发送的目标类型;users:给用户发消息,chatgroups:给群发消息,chatrooms:给聊天室发消息
+     * @param  array   $target      发送的目标;注意这里需要用数组,数组内添加的最大用户数默认 600 个,即使只有一个用户,也要用数组 [‘u1’];给用户发送时数组元素是用户名,给群组发送时,数组元素是 groupid。
+     * @param  array   $message     消息内容
+     * @param  string  $from        表示消息发送者;无此字段 Server 会默认设置为 "from": "admin",有 from 字段但值为空串 ("") 时请求失败
+     * @param  string  $sync_device 消息发送成功后,是否将消息同步给发送方。true:是;false(默认):否。
+     * @param  boolean $isOnline    该参数值为 true 时,代表 routetype 的值为 “ROUTE_ONLINE”,表示发送消息时只有接收方在线时,才进行消息投递。若接收方离线,将不会收到此条消息。
+     * @return array                发送给的目标和对应消息 id 的数组或者错误
+     * 
+     * \~english
+     * \brief
+     * Send picture message
+     * 
+     * @param  string  $target_type Type of target to send; Users: send messages to users, chatgroups: send messages to groups, chatrooms: send messages to chat rooms
+     * @param  array   $target      Target of transmission; Note that you need to use the array here. The maximum number of users added in the array is 600 by default. Even if there is only one user, you should also use the array ['u1 ']; When sending to the user, the array element is the user name. When sending to the group, the array element is the groupid.
+     * @param  array   $message     Message content
+     * @param  string  $from        Indicates the sender of the message; Without this field, the server will default to "from": "admin". When there is a from field but the value is an empty string (""), the request fails
+     * @param  string  $sync_device Whether to synchronize the message to the sender after the message is sent successfully. True: Yes; False (default): No.
+     * @param  boolean $isOnline    When the parameter value is true, the value representing routetype is "route_online", which means that the message is delivered only when the receiver is online. If the receiver is offline, it will not receive this message.
+     * @return array                The array or error of the target and corresponding message ID sent to
+     */
+    public function image($target_type, $target, $message, $from = 'admin', $sync_device = false, $isOnline = false)
+    {
+        return $this->send('img', $target_type, $target, $message, $from, $sync_device, $isOnline);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 发送语音消息
+     * 
+     * @param  string  $target_type 发送的目标类型;users:给用户发消息,chatgroups:给群发消息,chatrooms:给聊天室发消息
+     * @param  array   $target      发送的目标;注意这里需要用数组,数组内添加的最大用户数默认 600 个,即使只有一个用户,也要用数组 [‘u1’];给用户发送时数组元素是用户名,给群组发送时,数组元素是 groupid。
+     * @param  array   $message     消息内容
+     * @param  string  $from        表示消息发送者;无此字段 Server 会默认设置为 "from": "admin",有 from 字段但值为空串 ("") 时请求失败
+     * @param  string  $sync_device 消息发送成功后,是否将消息同步给发送方。true:是;false(默认):否。
+     * @param  boolean $isOnline    该参数值为 true 时,代表 routetype 的值为 “ROUTE_ONLINE”,表示发送消息时只有接收方在线时,才进行消息投递。若接收方离线,将不会收到此条消息。
+     * @return array                发送给的目标和对应消息 id 的数组或者错误
+     * 
+     * \~english
+     * \brief
+     * Send voice message
+     * 
+     * @param  string  $target_type Type of target to send; Users: send messages to users, chatgroups: send messages to groups, chatrooms: send messages to chat rooms
+     * @param  array   $target      Target of transmission; Note that you need to use the array here. The maximum number of users added in the array is 600 by default. Even if there is only one user, you should also use the array ['u1 ']; When sending to the user, the array element is the user name. When sending to the group, the array element is the groupid.
+     * @param  array   $message     Message content
+     * @param  string  $from        Indicates the sender of the message; Without this field, the server will default to "from": "admin". When there is a from field but the value is an empty string (""), the request fails
+     * @param  string  $sync_device Whether to synchronize the message to the sender after the message is sent successfully. True: Yes; False (default): No.
+     * @param  boolean $isOnline    When the parameter value is true, the value representing routetype is "route_online", which means that the message is delivered only when the receiver is online. If the receiver is offline, it will not receive this message.
+     * @return array                The array or error of the target and corresponding message ID sent to
+     */
+    public function audio($target_type, $target, $message, $from = 'admin', $sync_device = false, $isOnline = false)
+    {
+        return $this->send('audio', $target_type, $target, $message, $from, $sync_device, $isOnline);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 发送视频消息
+     * 
+     * @param  string  $target_type 发送的目标类型;users:给用户发消息,chatgroups:给群发消息,chatrooms:给聊天室发消息
+     * @param  array   $target      发送的目标;注意这里需要用数组,数组内添加的最大用户数默认 600 个,即使只有一个用户,也要用数组 [‘u1’];给用户发送时数组元素是用户名,给群组发送时,数组元素是 groupid。
+     * @param  array   $message     消息内容
+     * @param  string  $from        表示消息发送者;无此字段 Server 会默认设置为 "from": "admin",有 from 字段但值为空串 ("") 时请求失败
+     * @param  string  $sync_device 消息发送成功后,是否将消息同步给发送方。true:是;false(默认):否。
+     * @param  boolean $isOnline    该参数值为 true 时,代表 routetype 的值为 “ROUTE_ONLINE”,表示发送消息时只有接收方在线时,才进行消息投递。若接收方离线,将不会收到此条消息。
+     * @return array                发送给的目标和对应消息 id 的数组或者错误
+     * 
+     * \~english
+     * \brief
+     * Send video message
+     * 
+     * @param  string  $target_type Type of target to send; Users: send messages to users, chatgroups: send messages to groups, chatrooms: send messages to chat rooms
+     * @param  array   $target      Target of transmission; Note that you need to use the array here. The maximum number of users added in the array is 600 by default. Even if there is only one user, you should also use the array ['u1 ']; When sending to the user, the array element is the user name. When sending to the group, the array element is the groupid.
+     * @param  array   $message     Message content
+     * @param  string  $from        Indicates the sender of the message; Without this field, the server will default to "from": "admin". When there is a from field but the value is an empty string (""), the request fails
+     * @param  string  $sync_device Whether to synchronize the message to the sender after the message is sent successfully. True: Yes; False (default): No.
+     * @param  boolean $isOnline    When the parameter value is true, the value representing routetype is "route_online", which means that the message is delivered only when the receiver is online. If the receiver is offline, it will not receive this message.
+     * @return array                The array or error of the target and corresponding message ID sent to
+     */
+    public function video($target_type, $target, $message, $from = 'admin', $sync_device = false, $isOnline = false)
+    {
+        return $this->send('video', $target_type, $target, $message, $from, $sync_device, $isOnline);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 发送文件消息
+     * 
+     * @param  string  $target_type 发送的目标类型;users:给用户发消息,chatgroups:给群发消息,chatrooms:给聊天室发消息
+     * @param  array   $target      发送的目标;注意这里需要用数组,数组内添加的最大用户数默认 600 个,即使只有一个用户,也要用数组 [‘u1’];给用户发送时数组元素是用户名,给群组发送时,数组元素是 groupid。
+     * @param  array   $message     消息内容
+     * @param  string  $from        表示消息发送者;无此字段 Server 会默认设置为 "from": "admin",有 from 字段但值为空串 ("") 时请求失败
+     * @param  string  $sync_device 消息发送成功后,是否将消息同步给发送方。true:是;false(默认):否。
+     * @param  boolean $isOnline    该参数值为 true 时,代表 routetype 的值为 “ROUTE_ONLINE”,表示发送消息时只有接收方在线时,才进行消息投递。若接收方离线,将不会收到此条消息。
+     * @return array                发送给的目标和对应消息 id 的数组或者错误
+     * 
+     * \~english
+     * \brief
+     * Send file message
+     * 
+     * @param  string  $target_type Type of target to send; Users: send messages to users, chatgroups: send messages to groups, chatrooms: send messages to chat rooms
+     * @param  array   $target      Target of transmission; Note that you need to use the array here. The maximum number of users added in the array is 600 by default. Even if there is only one user, you should also use the array ['u1 ']; When sending to the user, the array element is the user name. When sending to the group, the array element is the groupid.
+     * @param  array   $message     Message content
+     * @param  string  $from        Indicates the sender of the message; Without this field, the server will default to "from": "admin". When there is a from field but the value is an empty string (""), the request fails
+     * @param  string  $sync_device Whether to synchronize the message to the sender after the message is sent successfully. True: Yes; False (default): No.
+     * @param  boolean $isOnline    When the parameter value is true, the value representing routetype is "route_online", which means that the message is delivered only when the receiver is online. If the receiver is offline, it will not receive this message.
+     * @return array                The array or error of the target and corresponding message ID sent to
+     */
+    public function file($target_type, $target, $message, $from = 'admin', $sync_device = false, $isOnline = false)
+    {
+        return $this->send('file', $target_type, $target, $message, $from, $sync_device, $isOnline);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 发送位置消息
+     * 
+     * @param  string  $target_type 发送的目标类型;users:给用户发消息,chatgroups:给群发消息,chatrooms:给聊天室发消息
+     * @param  array   $target      发送的目标;注意这里需要用数组,数组内添加的最大用户数默认 600 个,即使只有一个用户,也要用数组 [‘u1’];给用户发送时数组元素是用户名,给群组发送时,数组元素是 groupid。
+     * @param  array   $message     消息内容
+     * @param  string  $from        表示消息发送者;无此字段 Server 会默认设置为 "from": "admin",有 from 字段但值为空串 ("") 时请求失败
+     * @param  string  $sync_device 消息发送成功后,是否将消息同步给发送方。true:是;false(默认):否。
+     * @param  boolean $isOnline    该参数值为 true 时,代表 routetype 的值为 “ROUTE_ONLINE”,表示发送消息时只有接收方在线时,才进行消息投递。若接收方离线,将不会收到此条消息。
+     * @return array                发送给的目标和对应消息 id 的数组或者错误
+     * 
+     * \~english
+     * \brief
+     * Send location message
+     * 
+     * @param  string  $target_type Type of target to send; Users: send messages to users, chatgroups: send messages to groups, chatrooms: send messages to chat rooms
+     * @param  array   $target      Target of transmission; Note that you need to use the array here. The maximum number of users added in the array is 600 by default. Even if there is only one user, you should also use the array ['u1 ']; When sending to the user, the array element is the user name. When sending to the group, the array element is the groupid.
+     * @param  array   $message     Message content
+     * @param  string  $from        Indicates the sender of the message; Without this field, the server will default to "from": "admin". When there is a from field but the value is an empty string (""), the request fails
+     * @param  string  $sync_device Whether to synchronize the message to the sender after the message is sent successfully. True: Yes; False (default): No.
+     * @param  boolean $isOnline    When the parameter value is true, the value representing routetype is "route_online", which means that the message is delivered only when the receiver is online. If the receiver is offline, it will not receive this message.
+     * @return array                The array or error of the target and corresponding message ID sent to
+     */
+    public function location($target_type, $target, $message, $from = 'admin', $sync_device = false, $isOnline = false)
+    {
+        return $this->send('loc', $target_type, $target, $message, $from, $sync_device, $isOnline);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 发送透传消息
+     * 
+     * @param  string  $target_type 发送的目标类型;users:给用户发消息,chatgroups:给群发消息,chatrooms:给聊天室发消息
+     * @param  array   $target      发送的目标;注意这里需要用数组,数组内添加的最大用户数默认 600 个,即使只有一个用户,也要用数组 [‘u1’];给用户发送时数组元素是用户名,给群组发送时,数组元素是 groupid。
+     * @param  array   $message     消息内容
+     * @param  string  $from        表示消息发送者;无此字段 Server 会默认设置为 "from": "admin",有 from 字段但值为空串 ("") 时请求失败
+     * @param  string  $sync_device 消息发送成功后,是否将消息同步给发送方。true:是;false(默认):否。
+     * @param  boolean $isOnline    该参数值为 true 时,代表 routetype 的值为 “ROUTE_ONLINE”,表示发送消息时只有接收方在线时,才进行消息投递。若接收方离线,将不会收到此条消息。
+     * @return array                发送给的目标和对应消息 id 的数组或者错误
+     * 
+     * \~english
+     * \brief
+     * Send transparent message
+     * 
+     * @param  string  $target_type Type of target to send; Users: send messages to users, chatgroups: send messages to groups, chatrooms: send messages to chat rooms
+     * @param  array   $target      Target of transmission; Note that you need to use the array here. The maximum number of users added in the array is 600 by default. Even if there is only one user, you should also use the array ['u1 ']; When sending to the user, the array element is the user name. When sending to the group, the array element is the groupid.
+     * @param  array   $message     Message content
+     * @param  string  $from        Indicates the sender of the message; Without this field, the server will default to "from": "admin". When there is a from field but the value is an empty string (""), the request fails
+     * @param  string  $sync_device Whether to synchronize the message to the sender after the message is sent successfully. True: Yes; False (default): No.
+     * @param  boolean $isOnline    When the parameter value is true, the value representing routetype is "route_online", which means that the message is delivered only when the receiver is online. If the receiver is offline, it will not receive this message.
+     * @return array                The array or error of the target and corresponding message ID sent to
+     */
+    public function cmd($target_type, $target, $message, $from = 'admin', $sync_device = false, $isOnline = false)
+    {
+        return $this->send('cmd', $target_type, $target, $message, $from, $sync_device, $isOnline);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 发送自定义消息
+     * 
+     * @param  string  $target_type 发送的目标类型;users:给用户发消息,chatgroups:给群发消息,chatrooms:给聊天室发消息
+     * @param  array   $target      发送的目标;注意这里需要用数组,数组内添加的最大用户数默认 600 个,即使只有一个用户,也要用数组 [‘u1’];给用户发送时数组元素是用户名,给群组发送时,数组元素是 groupid。
+     * @param  array   $message     消息内容
+     * @param  string  $from        表示消息发送者;无此字段 Server 会默认设置为 "from": "admin",有 from 字段但值为空串 ("") 时请求失败
+     * @param  string  $sync_device 消息发送成功后,是否将消息同步给发送方。true:是;false(默认):否。
+     * @param  boolean $isOnline    该参数值为 true 时,代表 routetype 的值为 “ROUTE_ONLINE”,表示发送消息时只有接收方在线时,才进行消息投递。若接收方离线,将不会收到此条消息。
+     * @return array                发送给的目标和对应消息 id 的数组或者错误
+     * 
+     * \~english
+     * \brief
+     * Send custom message
+     * 
+     * @param  string  $target_type Type of target to send; Users: send messages to users, chatgroups: send messages to groups, chatrooms: send messages to chat rooms
+     * @param  array   $target      Target of transmission; Note that you need to use the array here. The maximum number of users added in the array is 600 by default. Even if there is only one user, you should also use the array ['u1 ']; When sending to the user, the array element is the user name. When sending to the group, the array element is the groupid.
+     * @param  array   $message     Message content
+     * @param  string  $from        Indicates the sender of the message; Without this field, the server will default to "from": "admin". When there is a from field but the value is an empty string (""), the request fails
+     * @param  string  $sync_device Whether to synchronize the message to the sender after the message is sent successfully. True: Yes; False (default): No.
+     * @param  boolean $isOnline    When the parameter value is true, the value representing routetype is "route_online", which means that the message is delivered only when the receiver is online. If the receiver is offline, it will not receive this message.
+     * @return array                The array or error of the target and corresponding message ID sent to
+     */
+    public function custom($target_type, $target, $message, $from = 'admin', $sync_device = false, $isOnline = false)
+    {
+        return $this->send('custom', $target_type, $target, $message, $from, $sync_device, $isOnline);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 获取用户离线消息数
+     * 
+     * @param  string $username 用户名
+     * @return int|array        用户离线消息数量或者错误
+     * 
+     * \~english
+     * \brief
+     * Get the number of user offline messages
+     * 
+     * @param  string $username User name
+     * @return int|array        Number of offline messages or errors
+     */
+    public function countMissedMessages($username)
+    {
+        if (!trim($username)) {
+            \Easemob\exception('Please enter username');
+        }
+        $uri = $this->auth->getBaseUri() . '/users/' . $username . '/offline_msg_count';
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data'][$username];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 获取某条离线消息状态
+     * 
+     * @param  string $username 用户名
+     * @param  string $msgId    消息 ID 编号
+     * @return string|array     离线消息状态(delivered:表示状态为消息已投递;undelivered:表示消息未投递;msg_not_found:消息不存在)或者错误
+     * 
+     * \~english
+     * \brief
+     * Get the status of an offline message
+     * 
+     * @param  string $username User name
+     * @param  string $msgId    Message ID number
+     * @return string|array     Offline message status (delivered: indicates that the message has been delivered; undelivered: indicates that the message has not been delivered; msg_not_found: the message does not exist) or error
+     */
+    public function isMessageDeliveredToUser($username, $msgId)
+    {
+        if (!trim($username)) {
+            \Easemob\exception('Please enter username');
+        }
+        
+        if (!trim($msgId)) {
+            \Easemob\exception('Please enter message ID');
+        }
+        $uri = $this->auth->getBaseUri() . '/users/' . $username . '/offline_msg_status/' . $msgId;
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data'][$msgId];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 获取历史消息文件下载地址
+     * 
+     * \details
+     * 导出聊天记录接口不是实时接口,获取成功存在一定的延时,不能够作为实时拉取消息的接口使用。以下 API 均需要企业管理员权限才能访问。此接口一次只能获取一个小时的历史消息。
+     * 
+     * @param  int    $datetime 时间,每次只能获取一小时的消息,格式为 yyyyMMddHH 如 2018112717。
+     * @return string|array     聊天记录文件下载地址或者错误
+     * 
+     * \~english
+     * \brief
+     * Get the download address of historical message file
+     * 
+     * \details
+     * The export chat record interface is not a real-time interface. There is a certain delay in obtaining success, so it can not be used as an interface for real-time pulling messages. The following APIs require enterprise administrator privileges to access. This interface can only get historical messages for one hour at a time.
+     * 
+     * @param  int    $datetime Time, only one hour of messages can be obtained at a time. The format is yyyymmddhh, such as 2018112717.
+     * @return string|array     Chat record file download address or error
+     */
+    public function getHistoryAsUri($dateTime)
+    {
+        $dateTime = (int)$dateTime;
+        if (!$dateTime) {
+            \Easemob\exception('Please enter the time period to get');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatmessages/' . $dateTime;
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return isset($data['data'][0]['url']) ? $data['data'][0]['url'] : $data;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 下载消息历史文件到本地
+     * 
+     * @param  int    $datetime 时间,每次只能获取一小时的消息,格式为 yyyyMMddHH 如 2018112717。
+     * @param  string $filename 下载后的文件名,消息历史文件是 gz 压缩的。
+     * @return boolean|array    下载成功或错误
+     * 
+     * \~english
+     * \brief
+     * Download message history file to local
+     * 
+     * @param  int    $datetime Time, only one hour of messages can be obtained at a time. The format is yyyymmddhh, such as 2018112717.
+     * @param  string $filename The downloaded file name and message history file are GZ compressed.
+     * @return boolean|array    Download success or error
+     */
+    public function getHistoryAsLocalFile($dateTime, $filename)
+    {
+        $fileurl = $this->getHistoryAsUri($dateTime);
+        return copy($fileurl, $filename);
+        // if (is_string($fileurl)) {
+        //     header("Content-Description: File Transfer");
+        //     header("Content-Type: application/octet-stream");
+        //     header("Content-Disposition: attachment;filename=".$filename);
+        //     header("Content-Transfer-Encoding: binary");
+        //     header("Expires: 0");
+        //     header("Cache-Control: must-revalidate");
+        //     header("Pragma: public");
+        //     header("Content-Length: ". filesize($fileurl));
+        //     ob_clean();
+        //     flush();
+        //     readfile($fileurl);
+        //     exit();
+        // }
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 服务端消息撤回
+     * 
+     * \details
+     * 应用管理员可调用接口撤回发送的消息,默认时限为 2 分钟,如需调整请联系环信商务经理。
+     * 
+     * @param  array $msg 要撤回的消息,一维数组代表撤回一条消息,二维数组代表撤回多条消息
+     *     - `msg_id` String 类型,撤回消息的消息 ID。
+     *     - `to` 可选,String 类型,撤回消息的接收方。如果不提供则消息体找不到就撤回不了。单聊为接收方用户名称,群组为群 ID,聊天室为聊天室 ID。
+     *     - `chat_type` String 类型,撤回消息的三种消息类型:单聊:chat;群聊:group_chat;聊天室:chatroom。
+     *     - `from` 可选,String 类型,消息撤回方,不传默认使用的是 admin,默认消息撤回方为原消息发送者。你可以通过用户 ID 指定消息撤回方。
+     *     - `force` boolean 类型,是否为强制撤回:
+     *         - true:是,即超过服务器保存消息时间消息也可以被撤回,具体见服务器消息保存时长;
+     *         - false:否,若设置的消息撤回时限超过服务端的消息保存时间,请求消息撤回时消息可能由于过期已在服务端删除,消息撤回请求会失败,即无法从收到该消息的客户端撤回该消息。
+     * @return array 撤回的消息或者错误
+     * 
+     * \~english
+     * \brief
+     * Server message withdrawal
+     * 
+     * \details
+     * The application administrator can call the interface to withdraw the sent message. The default time limit is 2 minutes. If you need to adjust, please contact the business manager of Huanxin.
+     * 
+     * @param  array $msgs For the message to be withdrawn, one-dimensional array represents withdrawing one message, and two-dimensional array represents withdrawing multiple messages
+     *     - `msg_id` String type, the message ID of the withdrawal message.
+     *     - `to` Optional, String type, The recipient of the recall message. If it is not provided, the message body cannot be found and cannot be withdrawn. The single chat is the user name of the receiver, the group is the group ID, and the chat room is the chat room ID.
+     *     - `chat_type` String type,Three message types of recall messages: single chat: chat; Group chat: Group_ chat; Chat room: chatroom.
+     *     - `from` Optional, String type,The message withdrawing party is not transmitted. By default, admin is used. By default, the message withdrawing party is the original message sender. You can specify the message withdrawing party through the user ID.
+     *     - `force` boolean type, Forced withdrawal:
+     *         - true: Yes, that is, the message can also be withdrawn after the server saves the message. See the server message saving time for details;
+     *         - false: No, if the set message withdrawal time limit exceeds the message saving time of the server, the message may have been deleted at the server due to expiration when requesting message withdrawal, and the message withdrawal request will fail, that is, the message cannot be withdrawn from the client receiving the message.
+     * @return array Withdrawn message or error
+     */
+    public function withdraw($msgs)
+    {
+        // 一维数组标识
+        $OneFlag = false;
+        if (count($msgs) == count($msgs, 1)) {
+            // 一维数组
+            $OneFlag = true;
+            $this->authMsg($msgs);
+        } else {
+            // 多维数组
+            foreach ($msgs as $msg) {
+                $this->authMsg($msg);
+            }
+        }
+
+        $body = compact('msgs');
+        $uri = $this->auth->getBaseUri() . '/messages/recall';
+        $resp = Http::post($uri, $body, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return isset($data['data']['msgs']) ? $data['data']['msgs'] : $data['data'];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 服务端单向删除会话
+     * 
+     * @param  string $username    用户名
+     * @param  string $channel     要删除的会话 ID。
+     * @param  string $type        会话类型。chat:单聊会话;groupchat:群聊会话。
+     * @param  string $delete_roam 是否删除服务端消息,不允许为空。true:是;false:否。
+     * @return boolean             成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Server side one-way deletion session
+     * 
+     * @param  string $username    User name
+     * @param  string $channel     Session ID to delete.
+     * @param  string $type        Session type. Chat: single chat session; Group chat: group chat conversation.
+     * @param  string $delete_roam Whether to delete the server message. It cannot be empty. True: Yes; False: No.
+     * @return boolean             Success or error
+     */
+    public function deleteSession($username, $channel, $type, $delete_roam = true)
+    {
+        if (!trim($username)) {
+            return \Easemob\exception('Please enter username');
+        }
+        $uri = $this->auth->getBaseUri() . '/users/'.$username.'/user_channel';
+        $delete_roam = (bool)$delete_roam;
+        $body = compact('channel', 'type', 'delete_roam');
+        $resp = Http::delete($uri, $body, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return isset($data['data']['result']) && $data['data']['result'] === 'ok' ? true : false;
+    }
+
+    /**
+     * @ignore 验证消息参数
+     * @param array $msg 消息参数
+     */
+    private function authMsg($msg)
+    {
+        if (!is_array($msg) || empty($msg)) {
+            return \Easemob\exception('Please enter a message to recall');
+        }
+
+        if (!isset($msg['msg_id']) || !trim($msg['msg_id']) || !isset($msg['chat_type']) || !trim($msg['chat_type']) || !isset($msg['force'])) {
+            return \Easemob\exception('Please enter msg_id, chat_type, force');
+        }
+    }
+
+    /**
+     * @ignore 发送消息
+     * @param  string  $type        消息类型;txt:文本消息,img:图片消息,loc:位置消息,audio:语音消息,video:视频消息,file:文件消息,cmd:透传消息,custom:自定义消息
+     * @param  string  $target_type 发送的目标类型;users:给用户发消息,chatgroups:给群发消息,chatrooms:给聊天室发消息
+     * @param  array   $target      发送的目标;注意这里需要用数组,数组内添加的最大用户数默认 600 个,即使只有一个用户,也要用数组 ['u1'];给用户发送时数组元素是用户名,给群组发送时,数组元素是 groupid。
+     * @param  mixed   $message     消息内容
+     * @param  string  $from        表示消息发送者;无此字段 Server 会默认设置为 "from": "admin",有 from 字段但值为空串 ("") 时请求失败
+     * @param  string  $sync_device 消息发送成功后,是否将消息同步给发送方。true:是;false(默认):否。
+     * @param  boolean $isOnline    该参数值为 true 时,代表 routetype 的值为 “ROUTE_ONLINE”,表示发送消息时只有接收方在线时,才进行消息投递。若接收方离线,将不会收到此条消息。
+     * @return array                发送给的目标和对应消息 id 的数组或者错误
+     */
+    private function send($type, $target_type, $target, $message, $from = 'admin', $sync_device = false, $isOnline = false)
+    {
+        if (!trim($type)) {
+            \Easemob\exception('Please enter type');
+        }
+
+        if (!trim($target_type)) {
+            \Easemob\exception('Please enter target_type');
+        }
+
+        if (!is_array($target) || empty($target)) {
+            \Easemob\exception('Please enter target');
+        }
+
+        if (!is_array($message)) {
+            \Easemob\exception('Please enter message');
+        }
+
+        if (!trim($from)) {
+            \Easemob\exception('If the message sender is delivered, it cannot be empty');
+        }
+
+        if (isset($message['ext'])) {
+            if (!$message['ext']) {
+                \Easemob\exception('If there is no extended attribute, please remove the EXT field');
+            } elseif (!is_array($message['ext'])) {
+                \Easemob\exception('The extended attribute, if any, must be an array');
+            }
+            $ext = $message['ext'];
+            unset($message['ext']);
+        }
+        
+        if ($type == 'txt') {
+            $msg = array('msg' => $message['msg']);
+        } else {
+            $msg = $message;
+        }
+        switch ($type) {
+            case 'txt':
+                // 文本消息
+                // $msg = array(
+                //     'msg' => $message['msg'],
+                // );
+                break;
+            case 'img': case 'audio':
+                // 图片消息 | 语音消息
+                $msg['url'] = $this->auth->getBaseUri() . '/chatfiles/' . $msg['uuid'];
+                unset($msg['uuid']);
+                break;
+            case 'video':
+                // 视频消息
+                $msg['url'] = $this->auth->getBaseUri() . '/chatfiles/' . $msg['uuid'];
+                $msg['thumb'] = $this->auth->getBaseUri() . '/chatfiles/' . $msg['thumb_uuid'];
+                unset($msg['uuid'], $msg['thumb_uuid']);
+                break;
+            case 'loc': case 'cmd':
+                // 位置消息 | 透传消息
+                break;
+            case 'custom':
+                // 自定义消息
+                if (!isset($message['customEvent']) || !preg_match('/^[a-zA-Z0-9-_\/\.]{1,32}$/', $message['customEvent'])) {
+                    \Easemob\exception('User defined event type format error');
+                }
+
+                if (isset($message['customExts']) && !is_array($message['customExts'])) {
+                    \Easemob\exception('User defined event attribute format error');
+                } elseif (isset($message['customExts'])) {
+                    if (count($message['customExts']) > 16) {
+                        \Easemob\exception('User defined event attributes can contain at most 16 elements');
+                    } else {
+                        foreach ($message['customExts'] as $key => $val) {
+                            if (!is_string($key) || !is_string($val)) {
+                                \Easemob\exception('User defined event attribute element key values can only be strings');
+                            }
+                        }
+                    }
+                }
+                break;
+        }
+
+        $msg['type'] = $type;
+        $uri = $this->auth->getBaseUri() . '/messages?useMsgId=true';
+        $body = compact('target_type', 'target', 'msg', 'from');
+        if (isset($ext)) {
+            $body['ext'] = $ext;
+        }
+
+        if ((bool)$sync_device) {
+            $body['sync_device'] = true;
+        }
+
+        if ((bool)$isOnline) {
+            $body['routetype'] = 'ROUTE_ONLINE';
+        }
+        $resp = Http::post($uri, $body, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data'];
+    }
+}

+ 179 - 0
vendor/maniac/easemob-php/src/Push.php

@@ -0,0 +1,179 @@
+<?php
+namespace Easemob;
+
+use Easemob\Http\Http;
+
+/**
+ * \~chinese
+ * Push 用来管理用户推送(设置推送免打扰等)
+ * 
+ * \~english
+ * The `Push` is used to manage user push (set push free, etc.)
+ */
+final class Push
+{
+    /**
+     * @ignore
+     * @var Auth $auth 授权对象
+     */
+    private $auth;
+
+    /// @cond
+    public function __construct($auth)
+    {
+        $this->auth = $auth;
+    }
+    /// @endcond
+
+    /**
+     * \~chinese
+     * \brief
+     * 设置推送昵称
+     * 
+     * \details
+     * 设置用户的推送昵称,在离线推送时使用。
+     * 
+     * @param  string  $username 用户名
+     * @param  string  $nickname 要设置的推送昵称
+     * @return boolean|array     成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Set push nickname
+     * 
+     * \details
+     * Set the user's push nickname and use it when pushing offline.
+     * 
+     * @param  string  $username User name
+     * @param  string  $nickname Nickname
+     * @return boolean|array     Success or error
+     */
+    public function updateUserNickname($username, $nickname)
+    {
+        if (!trim($username) || !trim($nickname)) {
+            \Easemob\exception('Please enter your username and nickname');
+        }
+        $uri = $this->auth->getBaseUri() . '/users/' . $username;
+        $body = compact('nickname');
+        $resp = Http::put($uri, $body, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 设置推送消息展示方式
+     * 
+     * \details
+     * 设置推送消息至客户端的方式,修改后及时有效。服务端对应不同的设置,向用户发送不同展示方式的消息。
+     * 
+     * @param  string  $username                   用户名
+     * @param  int     $notification_display_style 消息提醒方式,0:仅通知;1:通知以及消息详情;
+     * @return boolean|array                       成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Set push message display method
+     * 
+     * \details
+     * Set the method of pushing messages to the client, which is timely and effective after modification. The server sends messages with different display methods to users according to different settings.
+     * 
+     * @param  string  $username                   User name
+     * @param  int     $notification_display_style Message reminder method, 0: notification only; 1: Notice and message details;
+     * @return boolean|array                       Success or error
+     */
+    public function setNotificationDisplayStyle($username, $notification_display_style = 1)
+    {
+        if (!trim($username)) {
+            \Easemob\exception('Please enter your username');
+        }
+        $notification_display_style = (int)$notification_display_style ? 1 : 0;
+        $uri = $this->auth->getBaseUri() . '/users/' . $username;
+        $body = compact('notification_display_style');
+        $resp = Http::put($uri, $body, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 设置推送免打扰
+     * 
+     * \details
+     * 设置用户免打扰,在免打扰期间,用户将不会收到离线消息推送。
+     * 
+     * @param  string   $username  用户名
+     * @param  int      $startTime 免打扰起始时间,单位是小时,例如 8 代表每日 8:00 开启免打扰
+     * @param  int      $endTime   免打扰结束时间,单位是小时,例如 18 代表每日 18:00 关闭免打扰
+     * @return boolean|array       成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Set no disturb
+     * 
+     * \details
+     * Set the user to be undisturbed. During the undisturbed period, the user will not receive offline message push.
+     * 
+     * @param  string   $username  User name
+     * @param  int      $startTime The starting time of no disturbance, in hours, for example, 8 represents 8:00 every day
+     * @param  int      $endTime   The end time of no disturbance, in hours, for example, 18 means that no disturbance is closed at 18:00 every day
+     * @return boolean|array       Success or error
+     */
+    public function openNotificationNoDisturbing($username, $startTime, $endTime)
+    {
+        return $this->disturb($username, 1, $startTime, $endTime);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 取消推送免打扰
+     * 
+     * @param  string   $username  用户名
+     * @return boolean|array       成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Cancel push without interruption
+     * 
+     * @param  string   $username  User name
+     * @return boolean|array       Success or error
+     */
+    public function closeNotificationNoDisturbing($username)
+    {
+        return $this->disturb($username, 0);
+    }
+
+    /**
+     * @ignore 设置免打扰
+     * @param  string   $username                         用户名
+     * @param  int      $notification_no_disturbing       是否免打扰,0:代表免打扰关闭,1:免打扰开启
+     * @param  int      $notification_no_disturbing_start 免打扰起始时间,单位是小时,例如 8 代表每日 8:00 开启免打扰
+     * @param  int      $notification_no_disturbing_end   免打扰结束时间,单位是小时,例如 18 代表每日 18:00 关闭免打扰
+     * @return boolean|array                              成功或者错误
+     */    
+    private function disturb(
+        $username,
+        $notification_no_disturbing,
+        $notification_no_disturbing_start = 0,
+        $notification_no_disturbing_end = 0
+    ) {
+        if (!trim($username)) {
+            \Easemob\exception('Please enter your username');
+        }
+        $notification_no_disturbing = (int)$notification_no_disturbing ? 1 : 0;
+        $uri = $this->auth->getBaseUri() . '/users/' . $username;
+        $body = compact('notification_no_disturbing', 'notification_no_disturbing_start', 'notification_no_disturbing_end');
+        $resp = Http::put($uri, $body, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+}

+ 855 - 0
vendor/maniac/easemob-php/src/Room.php

@@ -0,0 +1,855 @@
+<?php
+namespace Easemob;
+
+use Easemob\Http\Http;
+
+/**
+ * \~chinese
+ * Room 用于管理聊天室
+ * 
+ * \~english
+ * The `Room` is used to manage chat rooms
+ */
+final class Room
+{
+    /**
+     * @ignore
+     * @var Auth $auth 授权对象
+     */
+    private $auth;
+
+    /// @cond
+    public function __construct($auth)
+    {
+        $this->auth = $auth;
+    }
+    /// @endcond
+
+    /**
+     * \~chinese
+     * \brief
+     * 获取 app 中所有的聊天室(分页)
+     * 
+     * @param  int    $limit  每页显示的数量,默认取 10 条
+     * @param  string $cursor 分页游标
+     * @return array          聊天室列表信息或者错误
+     * 
+     * \~english
+     * \brief
+     * Get all chat rooms in the app (paging)
+     * 
+     * @param  int    $limit  The number displayed on each page is 10 by default
+     * @param  string $cursor Paging cursor
+     * @return array          Chat room list information or error
+     */
+    public function listRooms($limit = 10, $cursor = '')
+    {
+        $limit = (int)$limit >= 0 ? (int)$limit : 10;
+        $uri = $this->auth->getBaseUri() . '/chatrooms';
+        $uri .= $limit ? '?limit=' . $limit : '';
+        $uri .= ($limit && $cursor) ? '&cursor='.$cursor : '';
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return array(
+            'cursor' => isset($data['cursor']) ? $data['cursor'] : '',
+            'data' => $data['data'],
+        );
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 获取 app 中所有的聊天室
+     * 
+     * @return array 聊天室列表信息或者错误
+     * 
+     * \~english
+     * \brief
+     * Get all chat rooms in the app
+     * 
+     * @return array Chat room list information or error
+     */
+    public function listAllRooms()
+    {
+        $result = $this->listRooms(0);
+        return $result['data'];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 获取用户加入的聊天室(分页)
+     * 
+     * \details
+     * 根据用户名称获取该用户加入的全部聊天室
+     * 
+     * @param  string $username 用户名
+     * @param  int    $pageSize 每页获取的群组数量,默认取 10 条
+     * @param  int    $pageNum  当前页码,默认取第 1 页
+     * @return array            用户加入的聊天室列表或者错误
+     * 
+     * \~english
+     * \brief
+     * Get the chat room that the user joined (paging)
+     * 
+     * \details
+     * Get all chat rooms joined by the user according to the user name
+     * 
+     * @param  string $username User name
+     * @param  int    $pageSize The number of groups obtained per page is 10 by default
+     * @param  int    $pageNum  The current page number is page 1 by default
+     * @return array            The chat room list added by the user is incorrect
+     */
+    public function listRoomsUserJoined($username, $pageSize = 10, $pageNum = 1)
+    {
+        if (!trim($username)) {
+            \Easemob\exception('Please pass the user name');
+        }
+        $pageSize = (int)$pageSize >= 0 ? (int)$pageSize : 10;
+        $pageNum = (int)$pageNum > 0 ? (int)$pageNum : 1;
+        $uri = $this->auth->getBaseUri() . '/users/' . trim($username) . '/joined_chatrooms';
+        $uri .= $pageSize ? ('?pagesize=' . $pageSize . '&pagenum=' . $pageNum) : '';
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data'];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 获取用户加入的聊天室
+     * 
+     * \details
+     * 根据用户名称获取该用户加入的全部聊天室
+     * 
+     * @param  string $username 用户名
+     * @return array            用户加入的聊天室列表或者错误
+     * 
+     * \~english
+     * \brief
+     * Get the chat room that the user joined
+     * 
+     * \details
+     * Get all chat rooms joined by the user according to the user name
+     * 
+     * @param  string $username User name
+     * @return array            The chat room list added by the user is incorrect
+     */
+    public function listAllRoomsUserJoined($username)
+    {
+        return $this->listRoomsUserJoined($username, 0);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 获取聊天室详情
+     * 
+     * \details
+     * 可以获取一个或多个聊天室的详情。当获取多个聊天室的详情时,可以直接填写多个 chatroom_id 并用 “,” 隔开,一次调用最多输入 100 个聊天室 ID,会返回所有存在的聊天室的详情,对于不存在的聊天室,response body 内返回 “chatroom id doesn’t exist”。
+     * 
+     * @param  string $roomId 聊天室 ID,多个之间用 “,” 分隔
+     * @return array          聊天室详情或者错误
+     * 
+     * \~english
+     * \brief
+     * Get chat room details
+     * 
+     * \details
+     * You can get details of one or more chat rooms. When obtaining the details of multiple chat rooms, you can directly fill in multiple chatrooms_ For all chat rooms that do not exist, enter "response ID" and return "response ID" at most once.
+     * 
+     * @param  string $roomId Chat room ID, separated by ","
+     * @return array          Chat room details or errors
+     */
+    public function getRoom($roomId)
+    {
+        if (!trim($roomId)) {
+            \Easemob\exception('Please pass the chat room ID');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatrooms/' . $roomId;
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return strpos($roomId, ',') !== false ? $data['data'] : $data['data'][0];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 创建聊天室
+     * 
+     * \details
+     * 创建一个聊天室,并设置聊天室名称、聊天室描述、公开聊天室/私有聊天室属性、聊天室成员最大人数(包括管理员)、加入公开聊天室是否需要批准、管理员、以及聊天室成员。
+     * 
+     * @param  array  $name        聊天聊天室名称
+     * @param  string $description 聊天室描述
+     * @param  string $owner       聊天室的管理员
+     * @param  array  $members     聊天室成员,此属性为可选的,但是如果加了此项,数组元素至少一个
+     * @param  int    $maxusers    聊天室成员最大数(包括聊天室所有者),值为数值类型。
+     * @return string|array        创建的聊天室 id 或者错误
+     * 
+     * \~english
+     * \brief
+     * Create a chat room
+     * 
+     * \details
+     * Create a chat room, and set the chat room name, chat room description, public chat room / private chat room properties, the maximum number of chat room members (including administrators), whether approval is required to join the public chat room, administrators, and chat room members.
+     * 
+     * @param  array  $name        Chat room name
+     * @param  string $description Chat room description
+     * @param  string $owner       Chat room administrator
+     * @param  array  $members     This attribute is optional for chat room members, but if this item is added, there must be at least one array element
+     * @param  int    $maxusers    Maximum number of chat room members (including chat room owners). The value is numeric.
+     * @return string|array        Created chat room ID or error
+     */
+    public function createRoom($name, $description, $owner, $members = array(), $maxusers = 0)
+    {
+        if (!trim($name)) {
+            \Easemob\exception('Please pass the chat room name');
+        }
+
+        if (!trim($description)) {
+            \Easemob\exception('Please pass the chat room description');
+        }
+
+        if (!trim($owner)) {
+            \Easemob\exception('Please pass the chat room administrator');
+        }
+
+        if ($members && (!is_array($members) || empty($members))) {
+            \Easemob\exception('Please pass chat room members');
+        }
+
+        $data = compact('name', 'description', 'owner', 'members');
+
+        $maxusers = (int)$maxusers > 0 ? (int)$maxusers : 0;
+        if ($maxusers) {
+            $data['maxusers'] = $maxusers;
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatrooms';
+        $resp = Http::post($uri, $data, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data']['id'];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 修改聊天室信息
+     * 
+     * \details
+     * 修改成功的数据行会返回 true,失败为 false。请求 body 只接收 name、description、maxusers 三个属性。传其他字段,或者不能修改的字段会抛异常。
+     * 
+     * @param  array   $data 聊天室信息
+     *     - `name` string 类型,聊天室名称,修改时值不能包含斜杠(“/”)。
+     *     - `description` string 类型,聊天室描述,修改时值不能包含斜杠(“/”)。
+     *     - `maxusers` int 类型,聊天室最大成员数(包括聊天室所有者),值为数值类型。
+     * @return boolean|array 成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Modify chat room information
+     * 
+     * \details
+     * The modified data row will return true, and the failure will be false. The request body only receives three attributes: name, description and maxusers. Exceptions will be thrown if other fields are passed or fields that cannot be modified.
+     * 
+     * @param  array   $data Chat room information
+     *     - `name` String type, chat room name. The value cannot contain slash ("/") when modified.
+     *     - `description` String type, chat room description. When modifying, the value cannot contain slash ("/").
+     *     - `maxusers` The type of chat room is int, and the value is the maximum number of chat room owners.
+     * @return boolean|array Success or error
+     */
+    public function updateRoom($data)
+    {
+        if (!is_array($data) || empty($data)) {
+            \Easemob\exception('Please pass the chat room information');
+        }
+
+        if (!isset($data['room_id']) || !trim($data['room_id'])) {
+            \Easemob\exception('Please pass the chat room ID');
+        }
+
+        if (isset($data['name']) && preg_match('/\//', $data['name'])) {
+            \Easemob\exception('Chat room names cannot contain slashes ("/")');
+        }
+
+        if (isset($data['description']) && preg_match('/\//', $data['description'])) {
+            \Easemob\exception('Chat room description cannot contain slashes ("/")');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatrooms/' . $data['room_id'];
+        unset($data['room_id']);
+        if (isset($data['maxusers'])) {
+            $data['maxusers'] = (int)$data['maxusers'];
+        }
+        $resp = Http::put($uri, $data, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 删除聊天室
+     * 
+     * \details
+     * 删除单个聊天室。如果被删除的聊天室不存在,会返回错误。
+     * 
+     * @param  string  $roomId 聊天室 ID
+     * @return boolean|array   成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Delete chat room
+     * 
+     * \details
+     * Delete a single chat room. If the deleted chat room does not exist, an error will be returned.
+     * 
+     * @param  string  $roomId Chat room ID
+     * @return boolean|array   Success or error
+     */
+    public function destroyRoom($roomId)
+    {
+        if (!trim($roomId)) {
+            \Easemob\exception('Please pass the chat room ID');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatrooms/' . $roomId;
+        $resp = Http::delete($uri, null, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 获取聊天室公告
+     * 
+     * \details
+     * 获取指定聊天室 ID 的聊天室公告。
+     * 
+     * @param  string $roomId 聊天室 id
+     * @return array          公告信息或者错误
+     * 
+     * \~english
+     * \brief
+     * Get chat announcements
+     * 
+     * \details
+     * Gets the chat announcement of the specified chat room ID.
+     * 
+     * @param  string $roomId Chat room ID
+     * @return array           Announcement information or error
+     */
+    public function getRoomAnnouncement($roomId)
+    {
+        if (!trim($roomId)) {
+            \Easemob\exception('Please pass the chat room ID');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatrooms/'. $roomId . '/announcement';
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data'];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 修改聊天室公告
+     * 
+     * \details
+     * 修改指定聊天室 ID 的聊天室公告。聊天室公告内容不能超过 512 个字符。
+     * @param  string  $roomId       聊天室 ID
+     * @param  string  $announcement 聊天室公告内容
+     * @return boolean|array         成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Modify chat announcement
+     * 
+     * \details
+     * Modify the chat announcement of the specified chat ID. The content of chat room announcement cannot exceed 512 characters.
+     * @param  string  $roomId       Chat room ID
+     * @param  string  $announcement Chat room announcement content
+     * @return boolean|array         Success or error
+     */
+    public function updateRoomAnnouncement($roomId, $announcement)
+    {
+        if (!trim($roomId) || !trim($announcement)) {
+            \Easemob\exception('Please pass the chat room ID and announcement content');
+        }
+
+        if (mb_strlen($announcement) > 512) {
+            \Easemob\exception('The content of the announcement room cannot exceed 512 characters');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatrooms/' . $roomId . '/announcement';
+        $body = compact('announcement');
+        $resp = Http::post($uri, $body, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data']['result'];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 分页获取聊天室成员
+     * 
+     * @param  string $roomId   聊天室 ID
+     * @param  int    $pageSize 每页获取的群组数量,默认取 10 条
+     * @param  int    $pageNum  当前页码,默认取第 1 页
+     * @return array            聊天室成员信息或者错误
+     * 
+     * \~english
+     * \brief
+     * Paging to get chat room members
+     * 
+     * @param  string $roomId   Chat room ID
+     * @param  int    $pageSize The number of groups obtained per page is 10 by default
+     * @param  int    $pageNum  The current page number is page 1 by default
+     * @return array            Chat room member information or error
+     */
+    public function listRoomMembers($roomId, $pageSize = 10, $pageNum = 1)
+    {
+        if (!trim($roomId)) {
+            \Easemob\exception('Please pass the chat room ID');
+        }
+
+        $pageSize = (int)$pageSize >= 0 ? (int)$pageSize : 10;
+        $pageNum = (int)$pageNum > 0 ? (int)$pageNum : 1;
+
+        $uri = $this->auth->getBaseUri() . '/chatrooms/' . $roomId . '/users';
+        $uri .= $pageSize ? ('?pagesize=' . $pageSize . '&pagenum=' . $pageNum) : '';
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data'];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 获取聊天室所有成员
+     * 
+     * @param  string $roomId 聊天室 ID
+     * @return array          聊天室成员信息或者错误
+     * 
+     * \~english
+     * \brief
+     * Get all members of the chat room
+     * 
+     * @param  string $roomId Chat room ID
+     * @return array          Chat room member information or error
+     */
+    public function listRoomMembersAll($roomId)
+    {
+        return $this->listRoomMembers($roomId, 0);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 添加单个聊天室成员
+     * 
+     * \details
+     * 一次给聊天室添加一个成员,不能重复添加同一个成员。如果用户已经是聊天室成员,将添加失败,并返回错误。
+     * 
+     * @param  string $roomId   聊天室 ID
+     * @param  string $username 环信用户 ID
+     * @return boolean|array    成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Add individual chat room members
+     * 
+     * \details
+     * Add one member to the chat room at a time. You cannot add the same member repeatedly. If the user is already a member of the chat room, the addition will fail with an error.
+     * 
+     * @param  string $roomId   Chat room ID
+     * @param  string $username User name
+     * @return boolean|array    Success or error
+     */
+    public function addRoomMember($roomId, $username)
+    {
+        return $this->addUsers($roomId, $username);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 批量添加聊天室成员
+     * 
+     * \details
+     * 向聊天室添加多位用户,一次性最多可添加 60 位用户。
+     * 
+     * @param  string $roomId    聊天室 ID
+     * @param  array  $usernames 环信用户 ID 数组
+     * @return boolean|array     成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Batch add chat members
+     * 
+     * \details
+     * Add more than 60 users to the chat room at one time.
+     * 
+     * @param  string $roomId    Chat room ID
+     * @param  array  $usernames User name array
+     * @return boolean|array     Success or error
+     */
+    public function addRoomMembers($roomId, $usernames)
+    {
+        return $this->addUsers($roomId, $usernames);  
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 删除单个聊天室成员
+     * 
+     * \details
+     * 从聊天室删除一个成员。如果被删除用户不在聊天室中,或者聊天室不存在,将返回错误。
+     * 
+     * @param  string $roomId   聊天室 ID
+     * @param  string $username 环信用户 ID
+     * @return boolean|array    成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Delete individual chat members
+     * 
+     * \details
+     * Delete a member from the chat room. If the deleted user is not in the chat room, or the chat room does not exist, an error will be returned.
+     * 
+     * @param  string $roomId   Chat room ID
+     * @param  string $username User name
+     * @return boolean|array    Success or error
+     */
+    public function removeRoomMember($roomId, $username)
+    {
+        return $this->removeUsers($roomId, $username);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 批量删除聊天室成员
+     * 
+     * \details
+     * 从聊天室删除多个成员。如果被删除用户不在聊天室中,或者聊天室不存在,将返回错误。
+     * 
+     * 一次最多传 100 个用户 ID。
+     * 
+     * @param  string $roomId    聊天室 ID
+     * @param  array  $usernames 环信用户 ID 数组
+     * @return boolean|array     成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Batch delete chat room members
+     * 
+     * \details
+     * Delete multiple members from the chat room. If the deleted user is not in the chat room, or the chat room does not exist, an error will be returned.
+     * 
+     * Up to 100 user IDs can be transmitted at a time.
+     * 
+     * @param  string $roomId    Chat room ID
+     * @param  array  $usernames User name array
+     * @return boolean|array     Success or error
+     */
+    public function removeRoomMembers($roomId, $usernames)
+    {
+        return $this->removeUsers($roomId, $usernames);
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 获取聊天室管理员列表
+     * 
+     * @param  string $roomId 聊天室 ID
+     * @return array          聊天室管理员列表信息或者错误
+     * 
+     * \~english
+     * \brief
+     * Get the list of chat room administrators
+     * 
+     * @param  string $roomId Chat room ID
+     * @return array          Chat room administrator list information or error
+     */
+    public function listRoomAdminsAll($roomId)
+    {
+        if (!trim($roomId)) {
+            \Easemob\exception('Please pass the chat room ID');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatrooms/' . $roomId . '/admin';
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data'];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 添加聊天室管理员
+     * 
+     * @param  string  $roomId   聊天室 ID
+     * @param  string  $newadmin 添加的新管理员用户 ID
+     * @return boolean|array     成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Add chat administrator
+     * 
+     * @param  string  $roomId   Chat room ID
+     * @param  string  $newadmin New administrator user ID added
+     * @return boolean|array     Success or error
+     */
+    public function promoteRoomAdmin($roomId, $newadmin)
+    {
+        if (!trim($roomId) || !trim($newadmin)) {
+            \Easemob\exception('Please pass the chat room ID and the new administrator user ID to be added');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatrooms/' . $roomId . '/admin';
+        $body = compact('newadmin');
+        $resp = Http::post($uri, $body, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 移除聊天室管理员
+     * 
+     * \details
+     * 将用户的角色从聊天室管理员降为普通聊天室成员。
+     * 
+     * @param  string  $roomId   聊天室 ID
+     * @param  string  $oldadmin 移除的管理员用户 ID
+     * @return boolean|array     成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Remove chat admin
+     * 
+     * \details
+     * Reduce the user's role from chat room administrator to ordinary chat room member.
+     * 
+     * @param  string  $roomId   Chat room ID
+     * @param  string  $oldadmin Removed administrator user ID
+     * @return boolean|array     Success or error
+     */
+    public function demoteRoomAdmin($roomId, $oldadmin)
+    {
+        if (!trim($roomId) || !trim($oldadmin)) {
+            \Easemob\exception('Please pass the chat room ID and the administrator user ID to be removed');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatrooms/' . $roomId . '/admin/' . $oldadmin;
+        $resp = Http::delete($uri, null, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data']['result'] === 'success' ? true : false;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 分页获取聊天室超级管理员列表
+     * 
+     * @param  int   $pageSize 每页获取的数量,默认取 10 条
+     * @param  int   $pageNum  当前页码,默认取第 1 页
+     * @return array           超级管理员列表信息或者错误
+     * 
+     * \~english
+     * \brief
+     * Paging to get the list of chat room super administrators
+     * 
+     * @param  int   $pageSize The quantity obtained per page is 10 by default
+     * @param  int   $pageNum  The current page number is page 1 by default
+     * @return array           Super administrator list information or error
+     */
+    public function listRoomSuperAdmins($pageSize = 10, $pageNum = 1)
+    {
+        return $this->superAdmins($pageSize, $pageNum);
+    }
+
+    /// @cond
+    public function listRoomSuperAdminsAll()
+    {
+        return $this->superAdmins(0);
+    }
+    /// @endcond
+
+    /**
+     * \~chinese
+     * \brief
+     * 添加超级管理员
+     * 
+     * \details
+     * 给用户添加聊天室超级管理员身份,一次只能添加一个。
+     * 
+     * @param  string  $superadmin 添加的用户名称
+     * @return boolean|array       成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Add super administrator
+     * 
+     * \details
+     * Add the chat room super administrator identity to users. You can only add one at a time.
+     * 
+     * @param  string  $superadmin User name
+     * @return boolean|array       Success or error
+     */
+    public function promoteRoomSuperAdmin($superadmin)
+    {
+        if (!is_string($superadmin) || !trim($superadmin)) {
+            \Easemob\exception('Please pass the user name');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatrooms/super_admin';
+        $resp = Http::post($uri, compact('superadmin'), $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 撤销超级管理员
+     * 
+     * @param  string  $superadmin 需要移除的 IM 用户名
+     * @return boolean|array       成功或者错误
+     * 
+     * \~chinese
+     * \brief
+     * 撤销超级管理员
+     * 
+     * @param  string  $superadmin User name
+     * @return boolean|array       Success or error
+     */
+    public function demoteRoomSuperAdmin($superadmin)
+    {
+        if (!trim($superadmin)) {
+            \Easemob\exception('Please pass the user name');
+        }
+        $uri = $this->auth->getBaseUri() . '/chatrooms/super_admin/' . $superadmin;
+        $resp = Http::delete($uri, null, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * @ignore (批量)添加聊天室成员
+     * @param  string       $roomId    聊天室 ID
+     * @param  string|array $usernames 环信用户 ID
+     * @return boolean|array           成功或者错误
+     */
+    private function addUsers($roomId, $usernames)
+    {
+        if (!trim($roomId)) {
+            \Easemob\exception('Please pass the chat room ID');
+        }
+
+        if ((is_array($usernames) && empty($usernames)) || (is_string($usernames) && !trim($usernames))) {
+            \Easemob\exception('Please pass the user name');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatrooms/' . $roomId . '/users';
+        $uri .= is_array($usernames) ? '' : ('/' . $usernames);
+        $body = is_array($usernames) ? compact('usernames') : null;
+        $resp = Http::post($uri, $body, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * @ignore (批量)移除聊天室成员
+     * @param  string       $roomId    聊天室 ID
+     * @param  string|array $usernames 环信用户 ID,string: 移除单个成员;array: 批量移除成员
+     * @return boolean|array           成功或者错误
+     */
+    private function removeUsers($roomId, $usernames)
+    {
+        if (!trim($roomId)) {
+            \Easemob\exception('Please pass the chat room ID');
+        }
+
+        if ((is_array($usernames) && empty($usernames)) || (is_string($usernames) && !trim($usernames))) {
+            \Easemob\exception('Please pass the user name');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatrooms/' . $roomId . '/users/';
+        $uri .= is_array($usernames) ? implode(',', $usernames) : $usernames;
+        $resp = Http::delete($uri, null, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * @ignore 分页获取聊天室超级管理员列表
+     * @param  int    $pageSize 每页获取的群组数量,默认取 10 条
+     * @param  int    $pageNum  当前页码,默认取第 1 页
+     * @return array            超级管理员列表信息或者错误
+     */
+    private function superAdmins($pageSize = 10, $pageNum = 1)
+    {
+        $pageSize = (int)$pageSize >= 0 ? (int)$pageSize : 10;
+        $pageNum = (int)$pageNum > 0 ? (int)$pageNum : 1;
+
+        $uri = $this->auth->getBaseUri() . '/chatrooms/super_admin';
+        $uri .= $pageSize ? ('?pagesize=' . $pageSize . '&pagenum=' . $pageNum) : '';
+
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data'];
+    }
+}

+ 422 - 0
vendor/maniac/easemob-php/src/User.php

@@ -0,0 +1,422 @@
+<?php
+namespace Easemob;
+
+use Easemob\Http\Http;
+
+/**
+ * \~chinese
+ * User 用来实现用户体系建立和管理
+ * 
+ * \~english
+ * The `User` is used to realize the establishment and management of user system
+ */
+final class User
+{
+    /**
+     * @ignore
+     * @var Auth $auth 授权对象
+     */
+    private $auth;
+
+    /// @cond
+    public function __construct($auth)
+    {
+        $this->auth = $auth;
+    }
+    /// @endcond
+
+    /**
+     * \~chinese
+     * \brief
+     * 注册单个用户 | 批量注册用户
+     * 
+     * @param array $users 要注册的用户信息,注册单个用户时传入一维数组,批量注册用户时传入二维数组。
+     *     - `username` String 类型,用户名,长度不可超过 64 个字节长度。支持以下字符集:
+     *         - 26 个小写英文字母 a-z;
+     *         - 26 个大写英文字母 A-Z;
+     *         - 10 个数字 0-9;
+     *         - “_”, “-”, “.”。
+     *         <pre><b style="color: red">注意:不区分大小写。同一个 app 下,用户名唯一。</b></pre>
+     *     - `password` String 类型,登录密码,长度不可超过 64 个字符长度。
+     *     - `nickname` String 类型,昵称(可选),仅用在客户端推送通知栏显示的昵称,并不是用户个人信息的昵称,开发者可自定义该内容。长度不可超过 100 个字符。支持以下字符集:
+     *         - 26 个小写英文字母 a-z;
+     *         - 26 个大写英文字母 A-Z;
+     *         - 10 个数字 0-9;
+     *         - 中文;
+     *         - 特殊字符。
+     * @return array 注册的用户信息或者错误
+     * 
+     * \~english
+     * \brief
+     * Register individual users | Batch registered users
+     * 
+     * @param array $users For the user information to be registered, a one-dimensional array is passed in when registering a single user, and a two-dimensional array is passed in when registering users in batch.
+     *     - `username` String type, user name, length cannot exceed 64 bytes. The following character sets are supported:
+     *         - 26 lowercase English letters a-z;
+     *         - 26 uppercase English letters A-Z;
+     *         - 10 digits 0-9;
+     *         - "_", "-" ".".
+     *         <pre><b style="color: red">Note: case insensitive. Under the same app, the user name is unique.</b></pre>
+     *     - `password` String type, login password, the length cannot exceed 64 characters.
+     *     - `nickname` String type, nickname (optional). It is only used for the nickname displayed in the client push notification bar, not the nickname of the user's personal information. The developer can customize this content. The length cannot exceed 100 characters. The following character sets are supported:
+     *         - 26 lowercase English letters a-z;
+     *         - 26 uppercase English letters A-Z;
+     *         - 10 digits 0-9;
+     *         - Chinese;
+     *         - Special characters;
+     * @return array Registered user information or error
+     */
+    public function create($users)
+    {
+        // 一维数组标识
+        $usersOneFlag = false;
+        if (count($users) == count($users, 1)) {
+            // 一维数组
+            $usersOneFlag = true;
+            $this->authUser($users);
+        } else {
+            // 多维数组
+            foreach ($users as $user) {
+                $this->authUser($user);
+            }
+        }
+        
+        $uri = $this->auth->getBaseUri() . '/users';
+        $resp = Http::post($uri, $users, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        
+        $data = $resp->data();
+        return $usersOneFlag ? $data['entities'][0] : $data['entities'];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 获取单个用户的详细信息
+     * 
+     * @param  string $username 用户名
+     * @return array            用户信息或者错误
+     * 
+     * \~english
+     * \brief
+     * Get the details of a single user
+     * 
+     * @param  string $username User name
+     * @return array            User information or error
+     */
+    public function get($username)
+    {
+        if (!trim($username)) {
+            \Easemob\exception('Please enter your username');
+        }
+        $uri = $this->auth->getBaseUri() . '/users/' . $username;
+
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        
+        $data = $resp->data();
+        return $data['entities'][0];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 批量获取用户
+     * 
+     * @param  int     $limit     获取用户的数量。默认值 10,最大值 100。超过 100 按照 100 返回。
+     * @param  string  $cursor    游标,用于分页显示用户列表。第一次发起批量查询用户请求时无需设置 cursor,请求成功后会获得第一页用户列表。从响应 body 中获取 cursor,并在下一次请求 中传入该 cursor,直到响应 body 中不再有 cursor 字段,则表示已查询到 app 中所有用户。
+     * @param  boolean $activated 用户是否激活。true:已激活;false:封禁,封禁需要通过解禁接口进行解禁,才能正常登录。
+     * @return array              分页用户信息或者错误
+     * 
+     * \~english
+     * \brief Get users in batch
+     * 
+     * @param  int     $limit     Gets the number of users. The default value is 10 and the maximum value is 100. If it exceeds 100, it will be returned as 100.
+     * @param  string  $cursor    Cursor, used to display the list of users in pages. When initiating a batch query user request for the first time, there is no need to set cursor. After the request is successful, the user list on the first page will be obtained. If there is no response from cursor to the next user in the URL field of cursor, it indicates that there is no response from cursor to the next user in the URL field of cursor.
+     * @param  boolean $activated Whether the user is activated. True: activated; False: blocking. The blocking needs to be lifted through the unblocking interface to log in normally.
+     * @return array              Paging user information or error
+     */
+    public function listUsers($limit = 10, $cursor = '', $activated = true)
+    {
+        $limit = (int)$limit <= 0 ? 10 : (int)$limit;
+        $limit = $limit > 100 ? 100 : $limit;
+        $activated = (boolean)$activated;
+
+        $uri = $this->auth->getBaseUri() . '/users';
+        $uri .= '?limit='.$limit;
+        $uri .= $cursor ? '&cursor='.$cursor : '';
+        $uri .= '&activated='.($activated ? 1 : 0);
+
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        
+        $data = $resp->data();
+        return array(
+            'data' => $data['entities'],
+            'cursor' => isset($data['cursor']) && $data['cursor'] ? $data['cursor'] : '', 
+        );
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 删除单个用户
+     * 
+     * \details
+     * 删除一个用户,如果此用户是群组或者聊天室的群主,系统会同时删除这些群组和聊天室。请在操作时进行确认。
+     * 
+     * @param  string  $username 用户名
+     * @return boolean|array     成功或失败或者错误
+     * 
+     * \~english
+     * \brief
+     * Delete single user
+     * 
+     * \details
+     * Delete a user. If the user is the group owner of a group or chat room, the system will delete these groups and chat rooms at the same time. Please confirm during operation.
+     * 
+     * @param  string  $username User name
+     * @return boolean|array     Success or failure or error
+     */
+    public function delete($username)
+    {
+        if (!trim($username)) {
+            \Easemob\exception('Please enter your username');
+        }
+        $uri = $this->auth->getBaseUri() . '/users/' . $username;
+        $resp = Http::delete($uri, null, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return isset($data['entities'][0]) && $data['entities'][0]['username'] === $username ? true : false;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 批量删除用户
+     * 
+     * \details
+     * 删除某个 APP 下指定数量的用户账号。
+     * 
+     * @param  int   $limit 要删除的用户数量,建议这个数值在 100-500 之间,不要过大。需要注意的是,这里只是批量的一次性删除掉 N 个用户,具体删除哪些并没有指定,可以在返回值中查看到哪些用户被删除掉了。如果 $limit 的值小于等于 0,值会按 1 处理
+     * @return array        被删除的用户信息或者错误
+     * 
+     * \~english 
+     * \brief
+     * Batch delete user
+     * 
+     * \details
+     * Delete a specified number of user accounts under an app.
+     * 
+     * @param  int   $limit The number of users to be deleted is recommended to be between 100-500, not too large. It should be noted that only n users are deleted in batches at one time. The specific deleted users are not specified. You can see which users have been deleted in the return value. If the value of $limit is less than or equal to 0, the value will be treated as 1
+     * @return array        Deleted user information or error
+     */
+    public function batchDelete($limit = 0)
+    {
+        $limit = (int)$limit <= 0 ? 1 : (int)$limit;
+        $uri = $this->auth->getBaseUri() . '/users?limit='.$limit;
+        $resp = Http::delete($uri, null, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['entities'];
+    }
+
+    /// @cond
+    public function deleteAll()
+    {
+        return $this->batchDelete(0);
+    }
+    /// @endcond
+
+    /**
+     * \~chinese
+     * \brief
+     * 修改用户密码
+     * 
+     * \details
+     * 可以修改用户的登录密码,不需要提供原密码。
+     * 
+     * @param  string  $username    用户名
+     * @param  string  $newpassword 新密码
+     * @return boolean|array        成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Modify user password
+     * 
+     * \details
+     * You can change the user's login password without providing the original password.
+     * 
+     * @param  string  $username    User name
+     * @param  string  $newpassword New password
+     * @return boolean|array        Success or error
+     */
+    public function updateUserPassword($username, $newpassword)
+    {
+        if (!trim($username) || !trim($newpassword)) {
+            \Easemob\exception('Please enter your username and password');
+        }
+        $uri = $this->auth->getBaseUri() . '/users/' . $username . '/password';
+        $body = compact('newpassword');
+        $resp = Http::put($uri, $body, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 获取用户在线状态
+     * 
+     * @param  string  $username 要获取在线状态的用户名
+     * @return boolean|array     是否在线(true:在线,false:离线)或者错误
+     * 
+     * \~english
+     * \brief
+     * Get user online status
+     * 
+     * @param  string  $username User name
+     * @return boolean|array     Online status (true: online, false: offline) or error
+     */
+    public function isUserOnline($username)
+    {
+        if (!is_string($username) || !trim($username)) {
+            \Easemob\exception('Please enter your username');
+        }
+        $result = $this->status($username);
+        return isset($result[$username]) && $result[$username] === 'online' ? true : false;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 批量获取用户在线状态
+     * 
+     * \details
+     * 批量查看用户的在线状态,最大同时查看100个用户。
+     * 
+     * @param  array $usernames 要获取在线状态的用户名数组,最多不能超过100个
+     * @return array            用户在线状态数组(数组键为用户名,数组值为用户对应的在线状态,true:在线,false:离线)或者错误
+     * 
+     * \~english
+     * \brief
+     * Get online status of users in batch
+     * 
+     * \details
+     * View the online status of users in batches, with a maximum of 100 users at the same time.
+     * 
+     * @param  array $usernames User name array, no more than 100
+     * @return array            User online status array (array key is user name, array value is user's corresponding online status, true: online, false: offline) or error
+     */
+    public function isUsersOnline($usernames)
+    {
+        if (!is_array($usernames) || empty($usernames)) {
+            \Easemob\exception('Please enter user name array');
+        }
+        $result = $this->status($usernames);
+        $data = array();
+        foreach ($result as $user => $status) {
+            $data[$user] = $status === 'online' ? true : false;
+        }
+        return $data;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 强制下线
+     * 
+     * \details
+     * 强制用户即把用户状态改为离线,用户需要重新登录才能正常使用。
+     * 
+     * @param  string  $username 要强制下线用户的用户名
+     * @return boolean|array     成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Force user offline
+     * 
+     * \details
+     * Force the user to change the user status to offline, and the user needs to log in again to use it normally.
+     * 
+     * @param  string  $username User name
+     * @return boolean|array     Success or error
+     */
+    public function forceLogoutAllDevices($username)
+    {
+        if (!trim($username)) {
+            \Easemob\exception('Please enter your username');
+        }
+        $uri = $this->auth->getBaseUri() . '/users/' . $username . '/disconnect';
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data']['result'];
+    }
+
+    /// @cond
+    public function forceLogoutOneDevice($username, $resource)
+    {
+
+    }
+    /// @endcond
+
+    /**
+     * @ignore 获取用户在线状态 | 批量获取用户在线状态
+     * @param  string|array $username 要获取在线状态的用户名,string:获取用户在线状态;array:批量获取用户在线状态
+     * @return boolean|array 是否在线(true:在线,false:离线)或者用户在线状态数组(数组键为用户名,数组值为用户对应的在线状态,true:在线,false:离线)或者错误
+     */
+    private function status($username)
+    {
+        if (is_array($username)) {
+            // 批量获取用户在线状态
+            $uri = $this->auth->getBaseUri() . '/users/batch/status';
+            $body = array('usernames' => $username);
+            $resp = Http::post($uri, $body, $this->auth->headers());
+        } else {
+            // 获取用户在线状态
+            $uri = $this->auth->getBaseUri() . '/users/' . $username . '/status';
+            $resp = Http::get($uri, $this->auth->headers());
+        }
+        
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        if (is_array($username)) {
+            $result = array();
+            foreach ($data['data'] as $val) {
+                foreach ($val as $key => $item) {
+                    $result[$key] = $item;
+                }
+            }
+            return $result;
+        }
+        return $data['data'];
+    }
+
+    /**
+     * @ignore 验证用户信息
+     * @param array $user 用户信息
+     */
+    private function authUser($user)
+    {
+        if (!isset($user['username']) || !trim($user['username']) || !isset($user['password']) || !trim($user['password'])) {
+            return \Easemob\exception('Please enter your username and password');
+        }
+    }
+}

+ 211 - 0
vendor/maniac/easemob-php/src/UserMetadata.php

@@ -0,0 +1,211 @@
+<?php
+namespace Easemob;
+
+use Easemob\Http\Http;
+
+/**
+ * \~chinese
+ * UserMetadata 用来管理用户属性
+ * 
+ * \~english
+ * The `UserMetadata` is used to manage user attribute
+ */
+final class UserMetadata
+{
+    /**
+     * @ignore
+     * @var Auth $auth 授权对象
+     */
+    private $auth;
+
+    /// @cond
+    public function __construct($auth)
+    {
+        $this->auth = $auth;
+    }
+    /// @endcond
+
+    /**
+     * \~chinese
+     * \brief
+     * 设置用户属性
+     * 
+     * \details
+     * 用户属性的内容为一个或多个纯文本键值对,默认单一用户的属性总长不得超过 2 KB,默认一个 app 下所有用户的所有属性总长不得超过 10 GB。
+     * 
+     * @param  string  $username 要设置属性的用户名
+     * @param  array   $metadata 要设置的属性(键:属性名;值:属性值)
+     * @return boolean|array     成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Set user properties
+     * 
+     * \details
+     * The content of user attributes is one or more plain text key value pairs. By default, the total length of attributes of a single user shall not exceed 2 kb. By default, the total length of all attributes of all users under an app shall not exceed 10 GB.
+     * 
+     * @param  string  $username User name
+     * @param  array   $metadata Properties(key: attribute name; value: attribute value)
+     * @return boolean|array     Success or error
+     */
+    public function setMetadataToUser($username, $metadata)
+    {
+        if (!trim($username)) {
+            \Easemob\exception('Please enter username');
+        }
+        if (!is_array($metadata) || empty($metadata)) {
+            \Easemob\exception('Please enter metadata');
+        }
+        $uri = $this->auth->getBaseUri() . '/metadata/user/' . $username;
+        $resp = Http::put($uri, http_build_query($metadata), $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 获取用户属性
+     * 
+     * \details
+     * 获取指定用户的所有用户属性键值对。如果指定的用户或用户属性不存在,返回空数据 {}。
+     * 
+     * @param  string $username 要获取属性的用户名
+     * @return array            用户属性或者错误
+     * 
+     * \~english
+     * \brief
+     * Get user properties
+     * 
+     * \details
+     * Gets all user attribute key value pairs for the specified user. If the specified user or user attribute does not exist, null data {} is returned.
+     * 
+     * @param  string $username User name
+     * @return array            User properties or error
+     */
+    public function getMetadataFromUser($username)
+    {
+        if (!trim($username)) {
+            \Easemob\exception('Please enter username');
+        }
+        $uri = $this->auth->getBaseUri() . '/metadata/user/' . $username;
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data'];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 批量获取用户属性
+     * 
+     * \details
+     * 根据指定的用户名列表和属性列表,查询用户属性。如果指定的用户或用户属性不存在,返回空数据 {}。 每次最多指定 100 个用户。
+     * 
+     * @param  array $targets    用户名列表,最多 100 个用户名。
+     * @param  array $properties 属性名列表,查询结果只返回该列表中包含的属性,不在该列表中的属性将被忽略。
+     * @return array             用户属性(数组键是用户名,数组值是用户对应的属性)或者错误
+     * 
+     * \~english
+     * \brief
+     * Get user attributes in batch
+     * 
+     * \details
+     * Query user attributes according to the specified user name list and attribute list. If the specified user or user attribute does not exist, null data {} is returned. Specify up to 100 users at a time.
+     * 
+     * @param  array $targets    User name list, up to 100 user names.
+     * @param  array $properties Attribute name list. The query result only returns the attributes contained in the list, and the attributes not in the list will be ignored.
+     * @return array             User attribute (the array key is the user name, and the array value is the attribute corresponding to the user) or error
+     */
+    public function batchGetMetadataFromUser($targets, $properties)
+    {
+        if (!is_array($targets) || empty($targets) || !is_array($properties) || empty($properties)) {
+            \Easemob\exception('Parameters error');
+        }
+
+        // 最多 100 个用户
+        $limitNums = 100;
+        if (count($targets) > $limitNums) {
+            // 截取前 100 个用户
+            $targets = array_slice($targets, 0, $limitNums);
+        }
+        $uri = $this->auth->getBaseUri() . '/metadata/user/get';
+        $body = compact('targets', 'properties');
+        $resp = Http::post($uri, $body, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data'];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 获取用户属性总量大小
+     * 
+     * \details
+     * 获取该 app 下所有用户的属性数据大小,单位为 byte。
+     * 
+     * @return float|array 用户属性总量大小(单位:byte)或者错误
+     * 
+     * \~english
+     * \brief
+     * Get the total size of user attributes
+     * 
+     * \details
+     * Get the attribute data size of all users under the app, in bytes.
+     * 
+     * @return float|array Total size of user attributes (unit: byte) or error
+     */
+    public function getUsage()
+    {
+        $uri = $this->auth->getBaseUri() . '/metadata/user/capacity';
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data'];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 删除用户属性
+     * 
+     * \details
+     * 删除指定用户的所有属性。如果指定的用户或用户属性不存在(可能已删除),也视为删除成功。
+     * 
+     * @param  string  $username 用户名
+     * @return boolean|array     成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Delete user attributes
+     * 
+     * \details
+     * Deletes all properties of the specified user. If the specified user or user attribute does not exist (may have been deleted), the deletion is also regarded as successful.
+     * 
+     * @param  string  $username User name
+     * @return boolean|array     Success or error
+     */
+    public function deleteMetadataFromUser($username)
+    {
+        if (!trim($username)) {
+            \Easemob\exception('Please enter username');
+        }
+        $uri = $this->auth->getBaseUri() . '/metadata/user/' . $username;
+        $resp = Http::delete($uri, null, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data'];
+    }
+}

+ 349 - 0
vendor/maniac/easemob-php/src/WhiteList.php

@@ -0,0 +1,349 @@
+<?php
+namespace Easemob;
+
+use Easemob\Http\Http;
+
+/**
+ * \~chinese
+ * WhiteList 用于管理群组、聊天室白名单
+ * 
+ * \~english
+ * The `WhiteList` used to manage groups, chat rooms white lists
+ */
+final class WhiteList
+{
+    /**
+     * @ignore
+     * @var Auth $auth 授权对象
+     */
+    private $auth;
+
+    /// @cond
+    public function __construct($auth)
+    {
+        $this->auth = $auth;
+    }
+    /// @endcond
+
+    // Group
+    /**
+     * \~chinese
+     * \brief
+     * 查询群组白名单
+     * 
+     * \details
+     * 查询一个群组白名单中的用户列表。
+     * 
+     * @param  string $groupId 群组 id
+     * @return array           白名单列表或者错误
+     * 
+     * \~english
+     * \brief
+     * Query group whitelist
+     * 
+     * \details
+     * Query the list of users in a group white list.
+     * 
+     * @param  string $groupId group ID
+     * @return array           Whitelist or error
+     */
+    public function getGroupWhiteList($groupId)
+    {
+        if (!trim($groupId)) {
+            \Easemob\exception('Please pass the group ID');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatgroups/' . $groupId . '/white/users';
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data'];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 添加单个用户至群组白名单
+     * 
+     * \details
+     * 查询一个群组将指定的单个用户添加至群组白名单。用户在添加至群组白名单后,当群组全员被禁言时,仍可以在群组中发送消息。白名单中的用户列表。
+     * 
+     * @param  string $groupId  群组 id
+     * @param  string $username 用户名
+     * @return boolean|array    成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Add a single user to the group whitelist
+     * 
+     * \details
+     * Query a group and add the specified individual user to the group white list. After being added to the group white list, users can still send messages in the group when all members of the group are banned. List of users in the whitelist.
+     * 
+     * @param  string $groupId  Group id
+     * @param  string $username user name
+     * @return boolean|array    Success or error
+     */
+    public function addUserToGroupWhiteList($groupId, $username)
+    {
+        if (!trim($groupId)) {
+            \Easemob\exception('Please pass the group ID');
+        }
+
+        if (!trim($username)) {
+            \Easemob\exception('Please pass the user name');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatgroups/' . $groupId . '/white/users/' . $username;
+        $resp = Http::post($uri, null, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 批量添加用户至群组白名单
+     * 
+     * \details
+     * 添加多个用户至群组白名单。你一次最多可添加 60 个用户。用户添加至白名单后在群组全员禁言时仍可以在群组中发送消息。
+     * 
+     * @param  string $groupId   群组 id
+     * @param  array  $usernames 用户名数组
+     * @return boolean|array     成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Batch add users to the group whitelist
+     * 
+     * \details
+     * Add multiple users to the group whitelist. You can add up to 60 users at a time. After users are added to the white list, they can still send messages in the group when all members of the group are forbidden.
+     * 
+     * @param  string $groupId   Group id
+     * @param  array  $usernames User name array
+     * @return boolean|array     Success or error
+     */
+    public function addUsersToGroupWhiteList($groupId, $usernames)
+    {
+        if (!trim($groupId)) {
+            \Easemob\exception('Please pass the group ID');
+        }
+
+        if (!is_array($usernames) || empty($usernames)) {
+            \Easemob\exception('Please pass the user name');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatgroups/' . $groupId . '/white/users';
+        $body = compact('usernames');
+        $resp = Http::post($uri, $body, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 将用户移除群组白名单
+     * 
+     * \details
+     * 将指定用户从群组白名单中移除。你每次最多可移除 60 个用户。
+     * 
+     * @param  string $groupId  群组 id
+     * @param  string $username 用户名,可以以逗号分隔传递多个用户名
+     * @return boolean|array     成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Remove user from group whitelist
+     * 
+     * \details
+     * Removes the specified user from the group whitelist. You can remove up to 60 users at a time.
+     * 
+     * @param  string $groupId   Group ID
+     * @param  string $username  User name. Multiple user names can be passed in comma separated form
+     * @return boolean|array     Success or error
+     */
+    public function removeUsersFromGroupWhiteList($groupId, $username)
+    {
+        if (!trim($groupId)) {
+            \Easemob\exception('Please pass the group ID');
+        }
+
+        if (!trim($username)) {
+            \Easemob\exception('Please pass the user name');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatgroups/' . $groupId . '/white/users/' . $username;
+        $resp = Http::delete($uri, null, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    // Room
+    /**
+     * \~chinese
+     * \brief
+     * 查询聊天室白名单
+     * 
+     * \details
+     * 查询一个聊天室白名单中的用户列表。
+     * 
+     * @param  string $roomId 聊天室 id
+     * @return array          白名单列表或者错误
+     * 
+     * \~english
+     * \brief
+     * Query chat room white list
+     * 
+     * \details
+     * Query the list of users in a chat room white list.
+     * 
+     * @param  string $roomId Chat room ID
+     * @return array          Whitelist or error
+     */
+    public function getRoomWhiteList($roomId)
+    {
+        if (!trim($roomId)) {
+            \Easemob\exception('Please pass the chat room ID');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatrooms/' . $roomId . '/white/users';
+        $resp = Http::get($uri, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        $data = $resp->data();
+        return $data['data'];
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 添加单个用户至聊天室白名单
+     * 
+     * \details
+     * 将指定的单个用户添加至聊天室白名单。用户添加至聊天室白名单后,当聊天室全员禁言时,仍可以在聊天室中发送消息。
+     * 
+     * @param  string $roomId   聊天室 id
+     * @param  string $username 用户名
+     * @return boolean|array    成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Add a single user to the chat room whitelist
+     * 
+     * \details
+     * Adds the specified individual user to the chat room whitelist. After the user is added to the chat room white list, when the chat room is forbidden, he can still send messages in the chat room.
+     * 
+     * @param  string $roomId   Chat room id
+     * @param  string $username user name
+     * @return boolean|array    Success or error
+     */
+    public function addUserToRoomWhiteList($roomId, $username)
+    {
+        if (!trim($roomId)) {
+            \Easemob\exception('Please pass the chat room ID');
+        }
+
+        if (!trim($username)) {
+            \Easemob\exception('Please pass the user name');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatrooms/' . $roomId . '/white/users/' . $username;
+        $resp = Http::post($uri, null, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 批量添加用户至聊天室白名单
+     * 
+     * \details
+     * 添加多个用户至聊天室白名单。你一次最多可添加 60 个用户。用户添加至聊天室白名单后,在聊天室全员禁言时,仍可以在聊天室中发送消息。
+     * 
+     * @param  string $roomId    聊天室 id
+     * @param  array  $usernames 用户名数组
+     * @return boolean|array     成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Batch add users to chat room white list
+     * 
+     * \details
+     * Add multiple users to the chat room whitelist. You can add up to 60 users at a time. After users are added to the chat room white list, they can still send messages in the chat room when all members of the chat room are forbidden to speak.
+     * 
+     * @param  string $roomId    Chat room id
+     * @param  array  $usernames User name array
+     * @return boolean|array     Success or error
+     */
+    public function addUsersToRoomWhiteList($roomId, $usernames)
+    {
+        if (!trim($roomId)) {
+            \Easemob\exception('Please pass the chat room ID');
+        }
+
+        if (!is_array($usernames) || empty($usernames)) {
+            \Easemob\exception('Please pass the user name');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatrooms/' . $roomId . '/white/users';
+        $body = compact('usernames');
+        $resp = Http::post($uri, $body, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+
+    /**
+     * \~chinese
+     * \brief
+     * 将用户移除聊天室白名单
+     * 
+     * \details
+     * 将指定用户从聊天室白名单移除。你每次最多可移除 60 个用户。
+     * 
+     * @param  string $roomId   聊天室 id
+     * @param  string $username 用户名,可以以逗号分隔传递多个用户名
+     * @return boolean|array    成功或者错误
+     * 
+     * \~english
+     * \brief
+     * Remove user from chat room whitelist
+     * 
+     * \details
+     * Remove the specified user from the chat room whitelist. You can remove up to 60 users at a time.
+     * 
+     * @param  string $roomId   Chat room ID
+     * @param  string $username User name. Multiple user names can be passed in comma separated form
+     * @return boolean|array    Success or error
+     */
+    public function removeUsersFromRoomWhiteList($roomId, $username)
+    {
+        if (!trim($roomId)) {
+            \Easemob\exception('Please pass the chat room ID');
+        }
+
+        if (!trim($username)) {
+            \Easemob\exception('Please pass the user name');
+        }
+
+        $uri = $this->auth->getBaseUri() . '/chatrooms/' . $roomId . '/white/users/' . $username;
+        $resp = Http::delete($uri, null, $this->auth->headers());
+        if (!$resp->ok()) {
+            return \Easemob\error($resp);
+        }
+        return true;
+    }
+}

+ 39 - 0
vendor/maniac/easemob-php/src/functions.php

@@ -0,0 +1,39 @@
+<?php
+/**
+ * @ignore 公共函数库
+ */
+namespace Easemob;
+
+/**
+ * @ignore 返回错误信息
+ * @param  Response $response 响应对象
+ * @param  string   $message  错误信息
+ * @param  int      $code     错误码
+ * @return array              返回信息
+ */
+function error($response, $message = null, $code = 1)
+{
+    if ($response !== null) {
+        $data = $response->data();
+        return array(
+            'code' => $response->httpCode,
+            'error' => isset($data['error']) ? $data['error'] : $response->error,
+            'error_description' => isset($data['error_description']) ? $data['error_description'] : '',
+        );
+    }
+    return array(
+        'code' => $code,
+        'error' => $message,
+    );
+    // throw new \Exception($message, $code);
+}
+
+/**
+ * @ignore 输出异常信息
+ * @param string $message 异常信息
+ * @param int    $code    错误码
+ */
+function exception($message, $code = 1)
+{
+    throw new \Exception($message, $code);
+}

+ 19 - 0
vendor/maniac/easemob-php/tests/AttachmentTest.php

@@ -0,0 +1,19 @@
+<?php
+namespace tests;
+
+class AttachmentTest extends Base
+{
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    public function testAttachmentUploadDownload()
+    {
+        $data = $this->attachment->uploadFile(dirname(__FILE__).'/assets/1.png');
+        $this->assertArrayHasKey('uuid', $data);
+
+        $this->assertIsInt($this->attachment->downloadFile(dirname(__FILE__).'/assets/11.png', $data['uuid'], $data['share-secret']));
+        $this->assertIsInt($this->attachment->downloadThumb(dirname(__FILE__).'/assets/11_thumb.png', $data['uuid'], $data['share-secret']));
+    }
+}

+ 45 - 0
vendor/maniac/easemob-php/tests/Base.php

@@ -0,0 +1,45 @@
+<?php
+namespace tests;
+
+use PHPUnit\Framework\TestCase;
+use Easemob\Auth;
+use Easemob\User;
+use Easemob\Contact;
+use Easemob\Block;
+use Easemob\Message;
+use Easemob\UserMetadata;
+use Easemob\Push;
+use Easemob\Attachment;
+use Easemob\Group;
+use Easemob\Room;
+
+class Base extends TestCase
+{
+    public $auth;
+    public $user;
+    public $contact;
+    public $block;
+    public $message;
+    public $metadata;
+    public $push;
+    public $attachment;
+    public $group;
+    public $room;
+
+    public function __construct()
+    {
+        parent::__construct();
+        $this->auth = new Auth("appKey", "Client ID", "ClientSecret");
+        // 设置 Rest Api 域名
+        // $this->auth->setApiUri('http://a1-hsb.easemob.com');
+        $this->user = new User($this->auth);
+        $this->contact = new Contact($this->auth);
+        $this->block = new Block($this->auth);
+        $this->message = new Message($this->auth);
+        $this->metadata = new UserMetadata($this->auth);
+        $this->push = new Push($this->auth);
+        $this->attachment = new Attachment($this->auth);
+        $this->group = new Group($this->auth);
+        $this->room = new Room($this->auth);
+    }
+}

+ 508 - 0
vendor/maniac/easemob-php/tests/GroupTest.php

@@ -0,0 +1,508 @@
+<?php
+namespace tests;
+
+class GroupTest extends Base
+{
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    public function testGroupCreatePublic()
+    {
+        $randomOwnerUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+
+        $randomMemberUsername = Utils::randomUserName();
+
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomOwnerUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername, 'password' => $randomPassword))));
+
+        $members = array($randomMemberUsername);
+        $groupId = $this->group->createPublicGroup($randomOwnerUsername, "group", "group description", $members, 200, true);
+        $this->assertIsString($groupId);
+
+        $data = $this->group->listAllGroupMembers($groupId);
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals(count($data) - 1, count($members));
+
+        $this->assertTrue($this->group->destroyGroup($groupId));
+
+        $this->assertTrue($this->user->delete($randomOwnerUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername));
+    }
+
+    public function testGroupCreatePublicWithCustom()
+    {
+        $randomOwnerUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+
+        $randomMemberUsername = Utils::randomUserName();
+
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomOwnerUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername, 'password' => $randomPassword))));
+
+        $members = array($randomMemberUsername);
+        $groupId = $this->group->createPublicGroup($randomOwnerUsername, "group", "group description", $members, 200, true, 'custom');
+        $this->assertIsString($groupId);
+
+        $data = $this->group->listAllGroupMembers($groupId);
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals(count($data) - 1, count($members));
+
+        $result = $this->group->getGroup($groupId);
+
+        $this->assertArrayNotHasKey('code', $result);
+        $this->assertEquals($result['custom'], 'custom');
+
+        $this->assertTrue($this->group->destroyGroup($groupId));
+
+        $this->assertTrue($this->user->delete($randomOwnerUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername));
+    }
+
+    public function testGroupCreatePrivate()
+    {
+        $randomOwnerUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+
+        $randomMemberUsername = Utils::randomUserName();
+
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomOwnerUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername, 'password' => $randomPassword))));
+
+        $members = array($randomMemberUsername);
+        $groupId = $this->group->createPrivateGroup($randomOwnerUsername, "group", "group description", $members, 200, true);
+        $this->assertIsString($groupId);
+
+        $data = $this->group->listAllGroupMembers($groupId);
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals(count($data) - 1, count($members));
+
+        $this->assertTrue($this->group->destroyGroup($groupId));
+
+        $this->assertTrue($this->user->delete($randomOwnerUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername));
+    }
+
+    public function testGroupCreatePrivateWithCustom()
+    {
+        $randomOwnerUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+
+        $randomMemberUsername = Utils::randomUserName();
+
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomOwnerUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername, 'password' => $randomPassword))));
+
+        $members = array($randomMemberUsername);
+        $groupId = $this->group->createPrivateGroup($randomOwnerUsername, "group", "group description", $members, 200, true, 'custom');
+        $this->assertIsString($groupId);
+
+        $data = $this->group->listAllGroupMembers($groupId);
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals(count($data) - 1, count($members));
+
+        $group = $this->group->getGroup($groupId);
+        $this->assertArrayNotHasKey('code', $group);
+        $this->assertEquals($group['custom'], 'custom');
+
+        $this->assertTrue($this->group->destroyGroup($groupId));
+
+        $this->assertTrue($this->user->delete($randomOwnerUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername));
+    }
+
+    public function testGroupDestroy()
+    {
+        $randomOwnerUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomMemberUsername = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomOwnerUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername, 'password' => $randomPassword))));
+
+        $members = array($randomMemberUsername);
+        $groupId = $this->group->createPrivateGroup($randomOwnerUsername, "group", "group description", $members, 200, true);
+        $this->assertIsString($groupId);
+
+        $this->assertTrue($this->group->destroyGroup($groupId));
+
+        $this->assertArrayHasKey('code', $this->group->getGroup($groupId));
+
+        $this->assertTrue($this->user->delete($randomOwnerUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername));
+    }
+
+    public function testGroupListAllGroups()
+    {
+        $randomOwnerUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomMemberUsername = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomOwnerUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername, 'password' => $randomPassword))));
+
+        $members = array($randomMemberUsername);
+        $groupId = $this->group->createPrivateGroup($randomOwnerUsername, "group", "group description", $members, 200, true);
+        $this->assertIsString($groupId);
+
+        $this->assertArrayNotHasKey('code', $this->group->listAllGroups());
+
+        $this->assertTrue($this->group->destroyGroup($groupId));
+
+        $this->assertTrue($this->user->delete($randomOwnerUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername));
+    }
+
+    public function testGroupListGroups()
+    {
+        $randomOwnerUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomMemberUsername = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomOwnerUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername, 'password' => $randomPassword))));
+
+        $members = array($randomMemberUsername);
+        $groupId = $this->group->createPrivateGroup($randomOwnerUsername, "group", "group description", $members, 200, true);
+        $this->assertIsString($groupId);
+
+        $this->assertArrayNotHasKey('code', $this->group->listGroups(1));
+
+        $this->assertTrue($this->group->destroyGroup($groupId));
+
+        $this->assertTrue($this->user->delete($randomOwnerUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername));
+    }
+
+    public function testGroupGet()
+    {
+        $randomOwnerUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomMemberUsername = Utils::randomUserName();
+        $randomAdminUsername = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomOwnerUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername, 'password' => $randomPassword), array('username' => $randomAdminUsername, 'password' => $randomPassword))));
+
+        $members = array($randomMemberUsername, $randomAdminUsername);
+        $groupId = $this->group->createPrivateGroup($randomOwnerUsername, "group", "group description", $members, 200, true);
+        $this->assertIsString($groupId);
+
+        $this->assertTrue($this->group->addGroupAdmin($groupId, $randomAdminUsername));
+        $group = $this->group->getGroup($groupId);
+        $this->assertArrayNotHasKey('code', $group);
+        $this->assertEquals($group['affiliations_count'], 3);
+        $this->assertNotNull($group['affiliations']);
+
+        $this->assertTrue($this->group->destroyGroup($groupId));
+
+        $this->assertTrue($this->user->delete($randomOwnerUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername));
+        $this->assertTrue($this->user->delete($randomAdminUsername));
+    }
+
+    public function testGroupUpdate()
+    {
+        $randomOwnerUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomMemberUsername = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomOwnerUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername, 'password' => $randomPassword))));
+
+        $members = array($randomMemberUsername);
+        $groupId = $this->group->createPrivateGroup($randomOwnerUsername, "group", "group description", $members, 200, true);
+        $this->assertIsString($groupId);
+
+        $maxUsers = 400;
+        $this->assertTrue($this->group->updateGroup(array('group_id' => $groupId, 'maxusers' => $maxUsers)));
+
+        $group = $this->group->getGroup($groupId);
+        $this->assertArrayNotHasKey('code', $group);
+        $this->assertEquals($group['maxusers'], $maxUsers);
+
+        $this->assertTrue($this->group->destroyGroup($groupId));
+
+        $this->assertTrue($this->user->delete($randomOwnerUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername));
+    }
+
+    public function testGroupUpdateOwner()
+    {
+        $randomOwnerUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomMemberUsername = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomOwnerUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername, 'password' => $randomPassword))));
+
+        $members = array($randomMemberUsername);
+        $groupId = $this->group->createPrivateGroup($randomOwnerUsername, "group", "group description", $members, 200, true);
+        $this->assertIsString($groupId);
+
+        $this->assertTrue($this->group->updateGroupOwner($groupId, $randomMemberUsername));
+
+        $group = $this->group->getGroup($groupId);
+        $this->assertArrayNotHasKey('code', $group);
+        $this->assertEquals($group['owner'], $randomMemberUsername);
+
+        $this->assertTrue($this->group->destroyGroup($groupId));
+
+        $this->assertTrue($this->user->delete($randomOwnerUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername));
+    }
+
+    public function testGroupGetAnnouncement()
+    {
+        $randomOwnerUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomMemberUsername = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomOwnerUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername, 'password' => $randomPassword))));
+
+        $members = array($randomMemberUsername);
+        $groupId = $this->group->createPrivateGroup($randomOwnerUsername, "group", "group description", $members, 200, true);
+        $this->assertIsString($groupId);
+
+        $this->assertArrayNotHasKey('code', $this->group->getGroupAnnouncement($groupId));
+
+        $this->assertTrue($this->group->destroyGroup($groupId));
+
+        $this->assertTrue($this->user->delete($randomOwnerUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername));
+    }
+
+    public function testGroupUpdateAnnouncement()
+    {
+        $randomOwnerUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomMemberUsername = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomOwnerUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername, 'password' => $randomPassword))));
+
+        $members = array($randomMemberUsername);
+        $groupId = $this->group->createPrivateGroup($randomOwnerUsername, "group", "group description", $members, 200, true);
+        $this->assertIsString($groupId);
+
+        $announcement = "update announcement";
+        $this->assertTrue($this->group->updateGroupAnnouncement($groupId, $announcement));
+
+        $group = $this->group->getGroupAnnouncement($groupId);
+        $this->assertArrayNotHasKey('code', $group);
+        $this->assertEquals($announcement, $group['announcement']);
+
+        $this->assertTrue($this->group->destroyGroup($groupId));
+
+        $this->assertTrue($this->user->delete($randomOwnerUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername));
+    }
+
+    public function testGroupListAllGroupMembers()
+    {
+        $randomOwnerUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomMemberUsername = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomOwnerUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername, 'password' => $randomPassword))));
+
+        $members = array($randomMemberUsername);
+        $groupId = $this->group->createPrivateGroup($randomOwnerUsername, "group", "group description", $members, 200, true);
+        $this->assertIsString($groupId);
+
+        $this->assertArrayNotHasKey('code', $this->group->listAllGroupMembers($groupId));
+
+        $this->assertTrue($this->group->destroyGroup($groupId));
+
+        $this->assertTrue($this->user->delete($randomOwnerUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername));
+    }
+
+    public function testGroupListMembers()
+    {
+        $randomOwnerUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomMemberUsername = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomOwnerUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername, 'password' => $randomPassword))));
+
+        $members = array($randomMemberUsername);
+        $groupId = $this->group->createPrivateGroup($randomOwnerUsername, "group", "group description", $members, 200, true);
+        $this->assertIsString($groupId);
+
+        $this->assertArrayNotHasKey('code', $this->group->listGroupMembers($groupId, 2));
+
+        $this->assertTrue($this->group->destroyGroup($groupId));
+
+        $this->assertTrue($this->user->delete($randomOwnerUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername));
+    }
+
+    public function testGroupAddMemberSingle()
+    {
+        $randomOwnerUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomMemberUsername = Utils::randomUserName();
+        $randomMemberUsername1 = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomOwnerUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername1, 'password' => $randomPassword))));
+
+        $members = array($randomMemberUsername);
+        $groupId = $this->group->createPrivateGroup($randomOwnerUsername, "group", "group description", $members, 200, true);
+        $this->assertIsString($groupId);
+
+        $this->assertTrue($this->group->addGroupMember($groupId, $randomMemberUsername1));
+
+        $this->assertTrue($this->group->destroyGroup($groupId));
+
+        $this->assertTrue($this->user->delete($randomOwnerUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername1));
+    }
+
+    public function testGroupAddMemberBatch()
+    {
+        $randomOwnerUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomMemberUsername = Utils::randomUserName();
+        $randomMemberUsername1 = Utils::randomUserName();
+        $randomMemberUsername2 = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomOwnerUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername1, 'password' => $randomPassword), array('username' => $randomMemberUsername2, 'password' => $randomPassword))));
+
+        $members = array($randomMemberUsername);
+        $groupId = $this->group->createPrivateGroup($randomOwnerUsername, "group", "group description", $members, 200, true);
+        $this->assertIsString($groupId);
+
+        $addMembers = array($randomMemberUsername1, $randomMemberUsername2);
+        $this->assertTrue($this->group->addGroupMembers($groupId, $addMembers));
+        
+        $members = $this->group->listAllGroupMembers($groupId);
+        $this->assertArrayNotHasKey('code', $members);
+        $this->assertEquals(4, count($members));
+
+        $this->assertTrue($this->group->destroyGroup($groupId));
+
+        $this->assertTrue($this->user->delete($randomOwnerUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername1));
+        $this->assertTrue($this->user->delete($randomMemberUsername2));
+    }
+
+    public function testGroupRemoveMemberSingle()
+    {
+        $randomOwnerUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomMemberUsername = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomOwnerUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername, 'password' => $randomPassword))));
+
+        $members = array($randomMemberUsername);
+        $groupId = $this->group->createPrivateGroup($randomOwnerUsername, "group", "group description", $members, 200, true);
+        $this->assertIsString($groupId);
+
+        $this->assertTrue($this->group->removeGroupMember($groupId, $randomMemberUsername));
+
+        $members = $this->group->listAllGroupMembers($groupId);
+        $this->assertArrayNotHasKey('code', $members);
+        $this->assertEquals(1, count($members));
+
+        $this->assertTrue($this->group->destroyGroup($groupId));
+
+        $this->assertTrue($this->user->delete($randomOwnerUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername));
+    }
+
+    public function testGroupRemoveMemberBatch()
+    {
+        $randomOwnerUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomMemberUsername = Utils::randomUserName();
+        $randomMemberUsername1 = Utils::randomUserName();
+        $randomMemberUsername2 = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomOwnerUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername1, 'password' => $randomPassword), array('username' => $randomMemberUsername2, 'password' => $randomPassword))));
+
+        $members = array($randomMemberUsername, $randomMemberUsername1, $randomMemberUsername2);
+        $groupId = $this->group->createPrivateGroup($randomOwnerUsername, "group", "group description", $members, 200, true);
+        $this->assertIsString($groupId);
+
+        $this->assertTrue($this->group->removeGroupMembers($groupId, $members));
+        
+        $members = $this->group->listAllGroupMembers($groupId);
+        $this->assertArrayNotHasKey('code', $members);
+        $this->assertEquals(1, count($members));
+
+        $this->assertTrue($this->group->destroyGroup($groupId));
+
+        $this->assertTrue($this->user->delete($randomOwnerUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername1));
+        $this->assertTrue($this->user->delete($randomMemberUsername2));
+    }
+
+    public function testGroupAdmin()
+    {
+        $randomOwnerUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomMemberUsername = Utils::randomUserName();
+        $randomAdminUsername = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomOwnerUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername, 'password' => $randomPassword), array('username' => $randomAdminUsername, 'password' => $randomPassword))));
+
+        $members = array($randomMemberUsername, $randomAdminUsername);
+        $groupId = $this->group->createPrivateGroup($randomOwnerUsername, "group", "group description", $members, 200, true);
+        $this->assertIsString($groupId);
+
+        $this->assertTrue($this->group->addGroupAdmin($groupId, $randomAdminUsername));
+
+        $admins = $this->group->listGroupAdmins($groupId);
+        $this->assertArrayNotHasKey('code', $admins);
+        $this->assertEquals(1, count($admins));
+
+        $this->assertIsBool($this->group->removeGroupAdmin($groupId, $randomAdminUsername));
+
+        $admins = $this->group->listGroupAdmins($groupId);
+        $this->assertArrayNotHasKey('code', $admins);
+        $this->assertEquals(0, count($admins));
+
+        $this->assertTrue($this->group->destroyGroup($groupId));
+
+        $this->assertTrue($this->user->delete($randomOwnerUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername));
+        $this->assertTrue($this->user->delete($randomAdminUsername));
+    }
+
+    public function testGroupUsersBlockedJoin()
+    {
+        $randomOwnerUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomMemberUsername = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomOwnerUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername, 'password' => $randomPassword))));
+
+        $members = array($randomMemberUsername);
+        $groupId = $this->group->createPrivateGroup($randomOwnerUsername, "group", "group description", $members, 200, true);
+        $this->assertIsString($groupId);
+
+        $this->assertTrue($this->block->blockUserJoinGroup($groupId, $randomMemberUsername));
+
+        $data = $this->block->getUsersBlockedJoinGroup($groupId);
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals($data[0], $randomMemberUsername);
+
+        $this->assertTrue($this->block->unblockUserJoinGroup($groupId, $randomMemberUsername));
+        $data = $this->block->getUsersBlockedJoinGroup($groupId);
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals(count($data), 0);
+
+        $this->assertTrue($this->group->destroyGroup($groupId));
+
+        $this->assertTrue($this->user->delete($randomOwnerUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername));
+    }
+
+    public function testGroupBlockUserSendMsg()
+    {
+        $randomOwnerUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomMemberUsername = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomOwnerUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername, 'password' => $randomPassword))));
+
+        $members = array($randomMemberUsername);
+        $groupId = $this->group->createPrivateGroup($randomOwnerUsername, "group", "group description", $members, 200, true);
+        $this->assertIsString($groupId);
+
+        $this->assertTrue($this->block->blockUserSendMsgToGroup($groupId, $members, 30000));
+
+        $data = $this->block->getUsersBlockedSendMsgToGroup($groupId);
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals(1, count($data));
+
+        $this->assertTrue($this->block->unblockUserSendMsgToGroup($groupId, $members));
+
+        $data = $this->block->getUsersBlockedSendMsgToGroup($groupId);
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals(0, count($data));
+
+        $this->assertTrue($this->group->destroyGroup($groupId));
+
+        $this->assertTrue($this->user->delete($randomOwnerUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername));
+    }
+}

+ 24 - 0
vendor/maniac/easemob-php/tests/HistoryMessageTest.php

@@ -0,0 +1,24 @@
+<?php
+namespace tests;
+
+class HistoryMessageTest extends Base
+{
+    public function __construct()
+    {
+        parent::__construct();
+        date_default_timezone_set('PRC');
+    }
+
+    /* 
+    // 发送消息一小时后再测试
+    public function testHistoryMsgGetAsUri()
+    {
+        $this->assertIsNotArray($this->message->getHistoryAsUri(date('YmdH', time() - 60*60)));
+    }
+
+    public function testHistoryMsgGetAsLocalFile()
+    {
+        $this->assertIsNotArray($this->assertIsString($this->message->getHistoryAsUri(date('YmdH', time() - 60*60))));
+    }
+     */
+}

+ 202 - 0
vendor/maniac/easemob-php/tests/MessageTest.php

@@ -0,0 +1,202 @@
+<?php
+namespace tests;
+
+class MessageTest extends Base
+{
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    public function testMessageSendText()
+    {
+        $randomFromUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomToUsername = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomFromUsername, 'password' => $randomPassword), array('username' => $randomToUsername, 'password' => $randomPassword))));
+
+        $this->assertArrayNotHasKey('code', $this->message->text('users', array($randomToUsername), array('msg' => 'hello', 'ext' => array('ext1' => 'val1')), $randomFromUsername));
+
+        $this->assertTrue($this->user->delete($randomFromUsername));
+        $this->assertTrue($this->user->delete($randomToUsername));
+    }
+
+    public function testMessageSendImage()
+    {
+        $randomFromUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomToUsername = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomFromUsername, 'password' => $randomPassword), array('username' => $randomToUsername, 'password' => $randomPassword))));
+
+        $data = $this->attachment->uploadFile(dirname(__FILE__).'/assets/1.png');
+        $this->assertArrayHasKey('uuid', $data);
+
+        $msg = array(
+            'filename' => '1.png',
+            'uuid' => $data['uuid'],
+            'secret' => $data['share-secret'],
+            'size' => array(
+                'width' => 36,
+                'height' => 36,
+            ),
+        );
+        $this->assertArrayNotHasKey('code', $this->message->image('users', array($randomToUsername), $msg, $randomFromUsername));
+
+        $this->assertTrue($this->user->delete($randomFromUsername));
+        $this->assertTrue($this->user->delete($randomToUsername));
+    }
+
+    public function testMessageSendVoice()
+    {
+        $randomFromUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomToUsername = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomFromUsername, 'password' => $randomPassword), array('username' => $randomToUsername, 'password' => $randomPassword))));
+
+        $data = $this->attachment->uploadFile(dirname(__FILE__).'/assets/mario.amr');
+        $this->assertArrayHasKey('uuid', $data);
+        
+        $msg = array(
+            'filename' => 'mario.amr',
+            'uuid' => $data['uuid'],
+            'secret' => $data['share-secret'],
+            'length' => 89,
+        );
+        $this->assertArrayNotHasKey('code', $this->message->audio('users', array($randomToUsername), $msg, $randomFromUsername));
+
+        $this->assertTrue($this->user->delete($randomFromUsername));
+        $this->assertTrue($this->user->delete($randomToUsername));
+    }
+
+    public function testMessageSendVideo()
+    {
+        $randomFromUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomToUsername = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomFromUsername, 'password' => $randomPassword), array('username' => $randomToUsername, 'password' => $randomPassword))));
+
+        $movie = dirname(__FILE__).'/assets/movie.ogg';
+        $data = $this->attachment->uploadFile($movie);
+        $this->assertArrayHasKey('uuid', $data);
+
+        $thumb = $this->attachment->uploadFile(dirname(__FILE__).'/assets/1.png');
+        $this->assertArrayHasKey('uuid', $thumb);
+
+        $msg = array(
+            'filename' => 'movie.ogg',  // 视频文件名称
+            'uuid' => $data['uuid'],   // 成功上传视频文件返回的UUID
+            'secret' => $data['share-secret'], // 成功上传视频文件后返回的secret
+            'thumb_uuid' => $thumb['uuid'],  // 成功上传视频缩略图返回的 UUID
+            'thumb_secret' => $thumb['share-secret'],   // 成功上传视频缩略图后返回的secret
+            'length' => 3, // 视频播放长度
+            'file_length' => filesize($movie),    // 视频文件大小(单位:字节)
+        );
+        $this->assertArrayNotHasKey('code', $this->message->video('users', array($randomToUsername), $msg, $randomFromUsername));
+
+        $this->assertTrue($this->user->delete($randomFromUsername));
+        $this->assertTrue($this->user->delete($randomToUsername));
+    }
+
+    public function testMessageSendFile()
+    {
+        $randomFromUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomToUsername = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomFromUsername, 'password' => $randomPassword), array('username' => $randomToUsername, 'password' => $randomPassword))));
+
+        $data = $this->attachment->uploadFile(dirname(__FILE__).'/assets/1.txt');
+        $this->assertArrayHasKey('uuid', $data);
+
+        $msg = array(
+            'filename' => '1.txt',  // 文件名称
+            'uuid' => $data['uuid'],   // 成功上传文件返回的UUID
+            'secret' => $data['share-secret'], // 成功上传文件后返回的secret
+        );
+        $this->assertArrayNotHasKey('code', $this->message->file('users', array($randomToUsername), $msg, $randomFromUsername));
+
+        $this->assertTrue($this->user->delete($randomFromUsername));
+        $this->assertTrue($this->user->delete($randomToUsername));
+    }
+
+    public function testMessageSendLocation()
+    {
+        $randomFromUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomToUsername = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomFromUsername, 'password' => $randomPassword), array('username' => $randomToUsername, 'password' => $randomPassword))));
+
+        $msg = array(
+            'lat' => '39.966',  // 纬度
+            'lng' => '116.322',   // 经度
+            'addr' => '中国北京市海淀区中关村', // 地址
+        );
+        $this->assertArrayNotHasKey('code', $this->message->location('users', array($randomToUsername), $msg, $randomFromUsername));
+
+        $this->assertTrue($this->user->delete($randomFromUsername));
+        $this->assertTrue($this->user->delete($randomToUsername));
+    }
+
+    public function testMessageSendCommand()
+    {
+        $randomFromUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomToUsername = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomFromUsername, 'password' => $randomPassword), array('username' => $randomToUsername, 'password' => $randomPassword))));
+
+        $msg = array(
+            'event' => 'notification',  // 自定义键值
+            'id' => '123',   // 自定义键值
+        );
+        $this->assertArrayNotHasKey('code', $this->message->cmd('users', array($randomToUsername), $msg, $randomFromUsername));
+
+        $this->assertTrue($this->user->delete($randomFromUsername));
+        $this->assertTrue($this->user->delete($randomToUsername));
+    }
+
+    public function testMessageSendCustom()
+    {
+        $randomFromUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomToUsername = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomFromUsername, 'password' => $randomPassword), array('username' => $randomToUsername, 'password' => $randomPassword))));
+
+        $msg = array(
+            // 用户自定义的事件类型,必须是string,值必须满足正则表达式 [a-zA-Z0-9-_/\.]{1,32},最短1个字符 最长32个字符
+            'customEvent' => 'xxx',
+            // 用户自定义的事件属性,类型必须是Map<String,String>,最多可以包含16个元素。customExts 是可选的,不需要可以不传
+            'customExts' => array(
+                'asd' => '123',
+            ),
+        );
+        $this->assertArrayNotHasKey('code', $this->message->custom('users', array($randomToUsername), $msg, $randomFromUsername));
+
+        $this->assertTrue($this->user->delete($randomFromUsername));
+        $this->assertTrue($this->user->delete($randomToUsername));
+    }
+
+    public function testMessageSendExtension()
+    {
+        $randomFromUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomToUsername = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomFromUsername, 'password' => $randomPassword), array('username' => $randomToUsername, 'password' => $randomPassword))));
+
+        $msg = array(
+            // 用户自定义的事件类型,必须是string,值必须满足正则表达式 [a-zA-Z0-9-_/\.]{1,32},最短1个字符 最长32个字符
+            'customEvent' => 'xxx',
+            // 用户自定义的事件属性,类型必须是Map<String,String>,最多可以包含16个元素。customExts 是可选的,不需要可以不传
+            'customExts' => array(
+                'asd' => '123',
+            ),
+            'ext' => array(
+                "em_apns_ext" => array(
+                    "em_push_content" => "自定义推送显示"
+                )
+            )
+        );
+        $this->assertArrayNotHasKey('code', $this->message->custom('users', array($randomToUsername), $msg, $randomFromUsername));
+
+        $this->assertTrue($this->user->delete($randomFromUsername));
+        $this->assertTrue($this->user->delete($randomToUsername));
+    }
+}

+ 56 - 0
vendor/maniac/easemob-php/tests/PushTest.php

@@ -0,0 +1,56 @@
+<?php
+namespace tests;
+
+class PushTest extends Base
+{
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    public function testUpdateUserNickname()
+    {
+        $username = Utils::randomUserName();
+        $password = Utils::randomPassword();
+        $this->assertArrayNotHasKey('code', $this->user->create(array('username' => $username, 'password' => $password)));
+        $this->assertTrue($this->push->updateUserNickname($username, sprintf("nickname-%s", $username)));
+        $data = $this->user->get($username);
+        $this->assertArrayHasKey('uuid', $data);
+        $this->assertEquals('nickname-' . $data['username'], $data['nickname']);
+        $this->assertTrue($this->user->delete($username));
+    }
+
+    public function testNotificationDisplayStyle()
+    {
+        $username = Utils::randomUserName();
+        $password = Utils::randomPassword();
+        $this->assertArrayNotHasKey('code', $this->user->create(array('username' => $username, 'password' => $password)));
+
+        $this->assertTrue($this->push->setNotificationDisplayStyle($username, 0));
+        $data = $this->user->get($username);
+        $this->assertEquals('0', $data['notification_display_style']);
+
+        $this->assertTrue($this->push->setNotificationDisplayStyle($username));
+        $data = $this->user->get($username);
+        $this->assertEquals('1', $data['notification_display_style']);
+
+        $this->assertTrue($this->user->delete($username));
+    }
+
+    public function testOpenCloseNotificationNoDisturbing()
+    {
+        $username = Utils::randomUserName();
+        $password = Utils::randomPassword();
+        $this->assertArrayNotHasKey('code', $this->user->create(array('username' => $username, 'password' => $password)));
+
+        $this->assertTrue($this->push->openNotificationNoDisturbing($username, 10, 19));
+        $data = $this->user->get($username);
+        $this->assertTrue($data['notification_no_disturbing']);
+
+        $this->assertTrue($this->push->closeNotificationNoDisturbing($username));
+        $data = $this->user->get($username);
+        $this->assertFalse($data['notification_no_disturbing']);
+
+        $this->assertTrue($this->user->delete($username));
+    }
+}

+ 216 - 0
vendor/maniac/easemob-php/tests/RoomTest.php

@@ -0,0 +1,216 @@
+<?php
+namespace tests;
+
+class RoomTest extends Base
+{
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    public function testRoomCycles()
+    {
+        $randomOwnerUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomMemberUsername = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomOwnerUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername, 'password' => $randomPassword))));
+
+        $members = array($randomMemberUsername);
+        $roomId = $this->room->createRoom("chat room", "room description", $randomOwnerUsername, $members, 200);
+        $this->assertIsString($roomId);
+
+        $data = $this->room->getRoom($roomId);
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals($data['name'], "chat room");
+
+        $this->assertTrue($this->room->updateRoom(array('room_id' => $roomId, 'name' => "room chat")));
+
+        $data = $this->room->getRoom($roomId);
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals($data['name'], "room chat");
+
+        $this->assertTrue($this->room->destroyRoom($roomId));
+
+        $this->assertTrue($this->user->delete($randomOwnerUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername));
+    }
+
+    public function testRoomListAll()
+    {
+        $randomOwnerUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomMemberUsername = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomOwnerUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername, 'password' => $randomPassword))));
+
+        $members = array($randomMemberUsername);
+        $roomId = $this->room->createRoom("chat room", "room description", $randomOwnerUsername, $members, 200);
+        $this->assertIsString($roomId);
+
+        $this->assertArrayNotHasKey('code', $this->room->listAllRooms());
+        $this->assertArrayNotHasKey('code', $this->room->listRooms(1));
+
+        $this->assertTrue($this->room->destroyRoom($roomId));
+
+        $this->assertTrue($this->user->delete($randomOwnerUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername));
+    }
+
+    public function testRoomUserJoinedList()
+    {
+        $randomOwnerUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomMemberUsername = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomOwnerUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername, 'password' => $randomPassword))));
+
+        $members = array($randomMemberUsername);
+        $roomId = $this->room->createRoom("chat room", "room description", $randomOwnerUsername, $members, 200);
+        $this->assertIsString($roomId);
+
+        $data = $this->room->listAllRoomsUserJoined($randomOwnerUsername);
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals(1, count($data));
+
+        $this->assertTrue($this->room->destroyRoom($roomId));
+
+        $this->assertTrue($this->user->delete($randomOwnerUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername));
+    }
+
+    public function testRoomMembers()
+    {
+        $randomOwnerUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomMemberUsername = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomOwnerUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername, 'password' => $randomPassword))));
+
+        $members = array();
+        $roomId = $this->room->createRoom("chat room", "room description", $randomOwnerUsername, $members, 200);
+        $this->assertIsString($roomId);
+
+        $this->assertTrue($this->room->addRoomMember($roomId, $randomMemberUsername));
+
+        $data = $this->room->listRoomMembersAll($roomId);
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals(2, count($data));
+
+        $this->assertTrue($this->room->removeRoomMember($roomId, $randomMemberUsername));
+
+        $data = $this->room->listRoomMembersAll($roomId);
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals(1, count($data));
+
+        $this->assertArrayNotHasKey('code', $this->room->listRoomMembers($roomId, 2));
+
+        $this->assertTrue($this->room->destroyRoom($roomId));
+
+        $this->assertTrue($this->user->delete($randomOwnerUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername));
+    }
+
+    public function testRoomAdmins()
+    {
+        $randomOwnerUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomMemberUsername = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomOwnerUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername, 'password' => $randomPassword))));
+
+        $members = array($randomMemberUsername);
+        $roomId = $this->room->createRoom("chat room", "room description", $randomOwnerUsername, $members, 200);
+        $this->assertIsString($roomId);
+
+        $this->assertTrue($this->room->promoteRoomAdmin($roomId, $randomMemberUsername));
+
+        $data = $this->room->listRoomAdminsAll($roomId);
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals(1, count($data));
+
+        $this->assertTrue($this->room->demoteRoomAdmin($roomId, $randomMemberUsername));
+
+        $data = $this->room->listRoomAdminsAll($roomId);
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals(0, count($data));
+
+        $this->assertTrue($this->room->destroyRoom($roomId));
+
+        $this->assertTrue($this->user->delete($randomOwnerUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername));
+    }
+
+    public function testRoomSuperAdmins()
+    {
+        $randomUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $this->assertArrayNotHasKey('code', $this->user->create(array('username' => $randomUsername, 'password' => $randomPassword)));
+
+        $this->assertTrue($this->room->promoteRoomSuperAdmin($randomUsername));
+
+        $data = $this->room->listRoomSuperAdminsAll();
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals(1, count($data));
+
+        $this->assertTrue($this->room->demoteRoomSuperAdmin($randomUsername));
+
+        $data = $this->room->listRoomSuperAdminsAll();
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals(0, count($data));
+
+        $this->assertTrue($this->user->delete($randomUsername));
+    }
+
+    public function testRoomUsersBlockedJoin()
+    {
+        $randomOwnerUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomMemberUsername = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomOwnerUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername, 'password' => $randomPassword))));
+
+        $members = array($randomMemberUsername);
+        $roomId = $this->room->createRoom("chat room", "room description", $randomOwnerUsername, $members, 200);
+        $this->assertIsString($roomId);
+
+        $this->assertTrue($this->block->blockUserJoinRoom($roomId, $randomMemberUsername));
+
+        $data = $this->block->getUsersBlockedJoinRoom($roomId);
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals($data[0], $randomMemberUsername);
+
+        $this->assertTrue($this->block->unblockUserJoinRoom($roomId, $randomMemberUsername));
+        $data = $this->block->getUsersBlockedJoinRoom($roomId);
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals(count($data), 0);
+
+        $this->assertTrue($this->room->destroyRoom($roomId));
+
+        $this->assertTrue($this->user->delete($randomOwnerUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername));
+    }
+
+    public function testRoomBlockUserSendMsg()
+    {
+        $randomOwnerUsername = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+        $randomMemberUsername = Utils::randomUserName();
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomOwnerUsername, 'password' => $randomPassword), array('username' => $randomMemberUsername, 'password' => $randomPassword))));
+
+        $members = array($randomMemberUsername);
+        $roomId = $this->room->createRoom("chat room", "room description", $randomOwnerUsername, $members, 200);
+        $this->assertIsString($roomId);
+
+        $this->assertTrue($this->block->blockUserSendMsgToRoom($roomId, $members, 30000));
+
+        $data = $this->block->getUsersBlockedSendMsgToRoom($roomId);
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals(1, count($data));
+
+        $this->assertTrue($this->block->unblockUserSendMsgToRoom($roomId, $members));
+
+        $data = $this->block->getUsersBlockedSendMsgToRoom($roomId);
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals(0, count($data));
+
+        $this->assertTrue($this->room->destroyRoom($roomId));
+
+        $this->assertTrue($this->user->delete($randomOwnerUsername));
+        $this->assertTrue($this->user->delete($randomMemberUsername));
+    }
+}

+ 32 - 0
vendor/maniac/easemob-php/tests/UserMetadataTest.php

@@ -0,0 +1,32 @@
+<?php
+namespace tests;
+
+class UserMetadataTest extends Base
+{
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    public function testMetadataCycles()
+    {
+        $username = Utils::randomUserName();
+        $password = Utils::randomPassword();
+        $this->assertArrayNotHasKey('code', $this->user->create(array('username' => $username, 'password' => $password)));
+
+        $data = array(
+            'nickname' => '昵称',
+            'avatar' => 'http://www.easemob.com/avatar.png',
+            'phone' => '159',
+        );
+        $this->assertTrue($this->metadata->setMetadataToUser($username, $data));
+
+        $this->assertArrayNotHasKey('code', $this->metadata->getMetadataFromUser($username));
+
+        $this->assertIsNumeric($this->metadata->getUsage());
+
+        $this->assertIsBool($this->metadata->deleteMetadataFromUser($username));
+
+        $this->assertTrue($this->user->delete($username));
+    }
+}

+ 262 - 0
vendor/maniac/easemob-php/tests/UserTest.php

@@ -0,0 +1,262 @@
+<?php
+namespace tests;
+
+class UserTest extends Base
+{
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    public function testCreate()
+    {
+        $username = Utils::randomUserName();
+        $password = Utils::randomPassword();
+        $this->assertArrayNotHasKey('code', $this->user->create(array('username' => $username, 'password' => $password)));
+        $this->assertArrayHasKey('uuid', $this->user->get($username));
+    }
+
+    public function testBatchCreate()
+    {
+        $randomUsername = Utils::randomUserName();
+        $randomUsername1 = Utils::randomUserName();
+        $randomPassword = Utils::randomPassword();
+
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $randomUsername, 'password' => $randomPassword), array('username' => $randomUsername1, 'password' => $randomPassword))));
+        $this->assertArrayHasKey('uuid', $this->user->get($randomUsername));
+        $this->assertArrayHasKey('uuid', $this->user->get($randomUsername1));
+    }
+
+    public function testUserLifeCycles()
+    {
+        $username = Utils::randomUserName();
+        $password = Utils::randomPassword();
+        $this->assertArrayNotHasKey('code', $this->user->create(array('username' => $username, 'password' => $password)));
+        $this->assertArrayHasKey('uuid', $this->user->get($username));
+        $this->assertTrue($this->user->delete($username));
+        $result = $this->user->get($username);
+        $this->assertEquals(404, $result['code']);
+    }
+
+    public function testUserForceLogout()
+    {
+        $username = Utils::randomUserName();
+        $password = Utils::randomPassword();
+        $this->assertArrayNotHasKey('code', $this->user->create(array('username' => $username, 'password' => $password)));
+        $this->assertIsBool($this->user->forceLogoutAllDevices($username));
+        $this->assertTrue($this->user->delete($username));
+    }
+
+    public function testUserUpdatePassword()
+    {
+        $username = Utils::randomUserName();
+        $password = Utils::randomPassword();
+        $this->assertArrayNotHasKey('code', $this->user->create(array('username' => $username, 'password' => $password)));
+        $this->assertIsBool($this->user->updateUserPassword($username, 'password'));
+        $this->assertTrue($this->user->delete($username));
+    }
+
+    public function testUserListUsers()
+    {
+        $username = Utils::randomUserName();
+        $password = Utils::randomPassword();
+        $this->assertArrayNotHasKey('code', $this->user->create(array('username' => $username, 'password' => $password)));
+        $data = $this->user->listUsers(1);
+        $this->assertArrayHasKey('data', $data);
+        $this->assertEquals(1, count($data['data']));
+        $this->assertArrayHasKey('uuid', $data['data'][0]);
+        $this->assertTrue($this->user->delete($username));
+    }
+
+    public function testUserContactLifeCycles()
+    {
+        $username = Utils::randomUserName();
+        $password = Utils::randomPassword();
+
+        $contactUsername = Utils::randomUserName();
+        $contactPassword = Utils::randomPassword();
+
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $username, 'password' => $password), array('username' => $contactUsername, 'password' => $contactPassword))));
+
+        $this->assertIsBool($this->contact->add($username, $contactUsername));
+
+        $data = $this->contact->get($username);
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals(1, count($data));
+        $this->assertEquals($data[0], $contactUsername);
+
+        $this->assertIsBool($this->contact->remove($username, $contactUsername));
+
+        $data = $this->contact->get($username);
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals(0, count($data));
+
+        $this->assertTrue($this->user->delete($username));
+        $this->assertTrue($this->user->delete($contactUsername));
+    }
+
+    public function testUserGetUsersBlockedFromSendMsg()
+    {
+        $username = Utils::randomUserName();
+        $password = Utils::randomPassword();
+
+        $randomUsernameCodeJack = Utils::randomUserName();
+
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $username, 'password' => $password), array('username' => $randomUsernameCodeJack, 'password' => $password))));
+
+        $this->assertTrue($this->block->blockUserSendMsgToUser($username, array($randomUsernameCodeJack)));
+
+        $data = $this->block->getUsersBlockedFromSendMsgToUser($username);
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals(1, count($data));
+        $this->assertEquals($data[0], $randomUsernameCodeJack);
+
+        $this->assertTrue($this->user->delete($username));
+        $this->assertTrue($this->user->delete($randomUsernameCodeJack));
+    }
+
+    public function testUserBlockUserSendMsg()
+    {
+        $username = Utils::randomUserName();
+        $password = Utils::randomPassword();
+
+        $randomUsernameCodeJack = Utils::randomUserName();
+
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $username, 'password' => $password), array('username' => $randomUsernameCodeJack, 'password' => $password))));
+
+        $this->assertIsBool($this->contact->add($username, $randomUsernameCodeJack));
+
+        $data = $this->contact->get($username);
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals(1, count($data));
+        $this->assertEquals($data[0], $randomUsernameCodeJack);
+
+        $this->assertTrue($this->block->blockUserSendMsgToUser($username, array($randomUsernameCodeJack)));
+
+        $data = $this->block->getUsersBlockedFromSendMsgToUser($username);
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals(1, count($data));
+        $this->assertEquals($data[0], $randomUsernameCodeJack);
+
+        $data = $this->contact->get($username);
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals(1, count($data));
+        $this->assertEquals($data[0], $randomUsernameCodeJack);
+
+        $this->assertTrue($this->user->delete($username));
+        $this->assertTrue($this->user->delete($randomUsernameCodeJack));
+    }
+
+    public function testUserUnblockUserSendMsg()
+    {
+        $username = Utils::randomUserName();
+        $password = Utils::randomPassword();
+
+        $randomUsernameCodeJack = Utils::randomUserName();
+
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $username, 'password' => $password), array('username' => $randomUsernameCodeJack, 'password' => $password))));
+
+        $this->assertIsBool($this->contact->add($username, $randomUsernameCodeJack));
+
+        $data = $this->contact->get($username);
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals(1, count($data));
+        $this->assertEquals($data[0], $randomUsernameCodeJack);
+
+        $this->assertTrue($this->block->blockUserSendMsgToUser($username, array($randomUsernameCodeJack)));
+
+        $data = $this->block->getUsersBlockedFromSendMsgToUser($username);
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals(1, count($data));
+        $this->assertEquals($data[0], $randomUsernameCodeJack);
+
+        $this->assertTrue($this->block->unblockUserSendMsgToUser($username, $randomUsernameCodeJack));
+
+        $data = $this->block->getUsersBlockedFromSendMsgToUser($username);
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals(0, count($data));
+
+        $data = $this->contact->get($username);
+        $this->assertArrayNotHasKey('code', $data);
+        $this->assertEquals(1, count($data));
+        $this->assertEquals($data[0], $randomUsernameCodeJack);
+
+        $this->assertTrue($this->user->delete($username));
+        $this->assertTrue($this->user->delete($randomUsernameCodeJack));
+    }
+
+    public function testUserCountMissedMessages()
+    {
+        $username = Utils::randomUserName();
+        $password = Utils::randomPassword();
+
+        $randomUsernameCodeJack = Utils::randomUserName();
+
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $username, 'password' => $password), array('username' => $randomUsernameCodeJack, 'password' => $password))));
+
+        $this->assertArrayNotHasKey('code', $this->message->text('users', array($randomUsernameCodeJack), array('msg' => 'CountMissedMessages'), $username));
+
+        $data = $this->message->countMissedMessages($randomUsernameCodeJack);
+        $this->assertIsInt($data);
+        $this->assertEquals(1, $data);
+
+        $this->assertTrue($this->user->delete($username));
+        $this->assertTrue($this->user->delete($randomUsernameCodeJack));
+    }
+
+    public function testUserBlockLogin()
+    {
+        $username = Utils::randomUserName();
+        $password = Utils::randomPassword();
+        $this->assertArrayNotHasKey('code', $this->user->create(array('username' => $username, 'password' => $password)));
+
+        $this->assertTrue($this->block->blockUserLogin($username));
+        $result = $this->user->get($username);
+        $this->assertFalse($result['activated']);
+
+        $this->assertTrue($this->block->unblockUserLogin($username));
+        $result = $this->user->get($username);
+        $this->assertTrue($result['activated']);
+
+        $this->assertTrue($this->user->delete($username));
+    }
+
+    public function testUserOnlineStatus()
+    {
+        $username = Utils::randomUserName();
+        $password = Utils::randomPassword();
+        
+        $this->assertArrayNotHasKey('code', $this->user->create(array('username' => $username, 'password' => $password)));
+
+        $this->assertIsBool($this->user->isUserOnline($username));
+
+        $this->assertTrue($this->user->delete($username));
+    }
+
+    public function testUsersOnlineStatus()
+    {
+        $username = Utils::randomUserName();
+        $password = Utils::randomPassword();
+
+        $randomUsernameCodeJack = Utils::randomUserName();
+
+        $this->assertArrayNotHasKey('code', $this->user->create(array(array('username' => $username, 'password' => $password), array('username' => $randomUsernameCodeJack, 'password' => $password))));
+
+        $this->assertArrayNotHasKey('code', $this->user->isUsersOnline(array($username, $randomUsernameCodeJack)));
+
+        $this->assertTrue($this->user->delete($username));
+        $this->assertTrue($this->user->delete($randomUsernameCodeJack));
+    }
+
+    public function testGetUserToken()
+    {
+        $username = Utils::randomUserName();
+        $password = Utils::randomPassword();
+        
+        $this->assertArrayNotHasKey('code', $this->user->create(array('username' => $username, 'password' => $password)));
+
+        $this->assertArrayHasKey('access_token', $this->auth->getUserToken($username, $password));
+
+        $this->assertTrue($this->user->delete($username));
+    }
+}

+ 15 - 0
vendor/maniac/easemob-php/tests/Utils.php

@@ -0,0 +1,15 @@
+<?php
+namespace tests;
+
+class Utils
+{
+    public static function randomUserName()
+    {
+        return sprintf("it-%d-%d", mt_rand(), microtime(true));
+    }
+
+    public static function randomPassword()
+    {
+        return sprintf("it-password-%d-%d", mt_rand(), microtime(true));
+    }
+}

BIN
vendor/maniac/easemob-php/tests/assets/1.png


+ 1 - 0
vendor/maniac/easemob-php/tests/assets/1.txt

@@ -0,0 +1 @@
+123123123

BIN
vendor/maniac/easemob-php/tests/assets/11.png


BIN
vendor/maniac/easemob-php/tests/assets/11_thumb.png


BIN
vendor/maniac/easemob-php/tests/assets/mario.amr


BIN
vendor/maniac/easemob-php/tests/assets/movie.ogg


+ 2 - 0
vendor/maniac/easemob-php/tests/bootstrap.php

@@ -0,0 +1,2 @@
+<?php
+require_once __DIR__ . '/../autoload.php';

+ 23 - 0
vendor/maniac/easemob-php/tests/logs/junit.xml

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<testsuites>
+  <testsuite name="tests\UserTest" file="/Users/maniac/docker/nginx/www/sdk.com/tests/UserTest.php" tests="13" assertions="84" errors="0" warnings="0" failures="1" skipped="0" time="14.720870">
+    <testcase name="testUserLifeCycles" class="tests\UserTest" classname="tests.UserTest" file="/Users/maniac/docker/nginx/www/sdk.com/tests/UserTest.php" line="11" assertions="4" time="0.945469"/>
+    <testcase name="testUserForceLogout" class="tests\UserTest" classname="tests.UserTest" file="/Users/maniac/docker/nginx/www/sdk.com/tests/UserTest.php" line="22" assertions="2" time="0.232179">
+      <failure type="PHPUnit\Framework\ExpectationFailedException">tests\UserTest::testUserForceLogout
+Failed asserting that 0 is of type "bool".
+
+/Users/maniac/docker/nginx/www/sdk.com/tests/UserTest.php:27</failure>
+    </testcase>
+    <testcase name="testUserUpdatePassword" class="tests\UserTest" classname="tests.UserTest" file="/Users/maniac/docker/nginx/www/sdk.com/tests/UserTest.php" line="31" assertions="3" time="1.726544"/>
+    <testcase name="testUserListUsers" class="tests\UserTest" classname="tests.UserTest" file="/Users/maniac/docker/nginx/www/sdk.com/tests/UserTest.php" line="40" assertions="5" time="0.768956"/>
+    <testcase name="testUserContactLifeCycles" class="tests\UserTest" classname="tests.UserTest" file="/Users/maniac/docker/nginx/www/sdk.com/tests/UserTest.php" line="52" assertions="10" time="1.571048"/>
+    <testcase name="testUserGetUsersBlockedFromSendMsg" class="tests\UserTest" classname="tests.UserTest" file="/Users/maniac/docker/nginx/www/sdk.com/tests/UserTest.php" line="79" assertions="7" time="1.231210"/>
+    <testcase name="testUserBlockUserSendMsg" class="tests\UserTest" classname="tests.UserTest" file="/Users/maniac/docker/nginx/www/sdk.com/tests/UserTest.php" line="99" assertions="14" time="1.546252"/>
+    <testcase name="testUserUnblockUserSendMsg" class="tests\UserTest" classname="tests.UserTest" file="/Users/maniac/docker/nginx/www/sdk.com/tests/UserTest.php" line="131" assertions="17" time="2.030985"/>
+    <testcase name="testUserCountMissedMessages" class="tests\UserTest" classname="tests.UserTest" file="/Users/maniac/docker/nginx/www/sdk.com/tests/UserTest.php" line="169" assertions="6" time="0.991811"/>
+    <testcase name="testUserBlockLogin" class="tests\UserTest" classname="tests.UserTest" file="/Users/maniac/docker/nginx/www/sdk.com/tests/UserTest.php" line="188" assertions="6" time="1.335960"/>
+    <testcase name="testUserOnlineStatus" class="tests\UserTest" classname="tests.UserTest" file="/Users/maniac/docker/nginx/www/sdk.com/tests/UserTest.php" line="205" assertions="3" time="0.695629"/>
+    <testcase name="testUsersOnlineStatus" class="tests\UserTest" classname="tests.UserTest" file="/Users/maniac/docker/nginx/www/sdk.com/tests/UserTest.php" line="217" assertions="4" time="0.982006"/>
+    <testcase name="testGetUserToken" class="tests\UserTest" classname="tests.UserTest" file="/Users/maniac/docker/nginx/www/sdk.com/tests/UserTest.php" line="232" assertions="3" time="0.662822"/>
+  </testsuite>
+</testsuites>

+ 48 - 0
vendor/maniac/easemob-php/tests/logs/testdox.html

@@ -0,0 +1,48 @@
+<!doctype html>
+<html lang="en">
+    <head>
+        <meta charset="utf-8"/>
+        <title>Test Documentation</title>
+        <style>
+            body {
+                text-rendering: optimizeLegibility;
+                font-variant-ligatures: common-ligatures;
+                font-kerning: normal;
+                margin-left: 2em;
+            }
+
+            body > ul > li {
+                font-family: Source Serif Pro, PT Sans, Trebuchet MS, Helvetica, Arial;
+                font-size: 2em;
+            }
+
+            h2 {
+                font-family: Tahoma, Helvetica, Arial;
+                font-size: 3em;
+            }
+
+            ul {
+                list-style: none;
+                margin-bottom: 1em;
+            }
+        </style>
+    </head>
+    <body>
+        <h2 id="tests\UserTest">User (tests\User)</h2>
+        <ul>
+            <li style="color: #555753;">✓ User life cycles</li>
+            <li style="color: #ef2929;">❌ User force logout</li>
+            <li style="color: #555753;">✓ User update password</li>
+            <li style="color: #555753;">✓ User list users</li>
+            <li style="color: #555753;">✓ User contact life cycles</li>
+            <li style="color: #555753;">✓ User get users blocked from send msg</li>
+            <li style="color: #555753;">✓ User block user send msg</li>
+            <li style="color: #555753;">✓ User unblock user send msg</li>
+            <li style="color: #555753;">✓ User count missed messages</li>
+            <li style="color: #555753;">✓ User block login</li>
+            <li style="color: #555753;">✓ User online status</li>
+            <li style="color: #555753;">✓ Users online status</li>
+            <li style="color: #555753;">✓ Get user token</li>
+        </ul>
+    </body>
+</html>

+ 42 - 0
vendor/maniac/easemob-php/tests/logs/testdox.xml

@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<tests>
+  <test className="tests\UserTest" methodName="testUserLifeCycles" prettifiedClassName="User (tests\User)" prettifiedMethodName="User life cycles" status="0" time="0.945468773" size="-1" groups="default">
+    <group name="default"/>
+  </test>
+  <test className="tests\UserTest" methodName="testUserForceLogout" prettifiedClassName="User (tests\User)" prettifiedMethodName="User force logout" status="3" time="0.232178852" size="-1" groups="default" exceptionLine="27" exceptionMessage="Failed asserting that 0 is of type &quot;bool&quot;.">
+    <group name="default"/>
+  </test>
+  <test className="tests\UserTest" methodName="testUserUpdatePassword" prettifiedClassName="User (tests\User)" prettifiedMethodName="User update password" status="0" time="1.726543796" size="-1" groups="default">
+    <group name="default"/>
+  </test>
+  <test className="tests\UserTest" methodName="testUserListUsers" prettifiedClassName="User (tests\User)" prettifiedMethodName="User list users" status="0" time="0.768956463" size="-1" groups="default">
+    <group name="default"/>
+  </test>
+  <test className="tests\UserTest" methodName="testUserContactLifeCycles" prettifiedClassName="User (tests\User)" prettifiedMethodName="User contact life cycles" status="0" time="1.571047531" size="-1" groups="default">
+    <group name="default"/>
+  </test>
+  <test className="tests\UserTest" methodName="testUserGetUsersBlockedFromSendMsg" prettifiedClassName="User (tests\User)" prettifiedMethodName="User get users blocked from send msg" status="0" time="1.231210003" size="-1" groups="default">
+    <group name="default"/>
+  </test>
+  <test className="tests\UserTest" methodName="testUserBlockUserSendMsg" prettifiedClassName="User (tests\User)" prettifiedMethodName="User block user send msg" status="0" time="1.546252359" size="-1" groups="default">
+    <group name="default"/>
+  </test>
+  <test className="tests\UserTest" methodName="testUserUnblockUserSendMsg" prettifiedClassName="User (tests\User)" prettifiedMethodName="User unblock user send msg" status="0" time="2.030984724" size="-1" groups="default">
+    <group name="default"/>
+  </test>
+  <test className="tests\UserTest" methodName="testUserCountMissedMessages" prettifiedClassName="User (tests\User)" prettifiedMethodName="User count missed messages" status="0" time="0.991811143" size="-1" groups="default">
+    <group name="default"/>
+  </test>
+  <test className="tests\UserTest" methodName="testUserBlockLogin" prettifiedClassName="User (tests\User)" prettifiedMethodName="User block login" status="0" time="1.33595969" size="-1" groups="default">
+    <group name="default"/>
+  </test>
+  <test className="tests\UserTest" methodName="testUserOnlineStatus" prettifiedClassName="User (tests\User)" prettifiedMethodName="User online status" status="0" time="0.695628743" size="-1" groups="default">
+    <group name="default"/>
+  </test>
+  <test className="tests\UserTest" methodName="testUsersOnlineStatus" prettifiedClassName="User (tests\User)" prettifiedMethodName="Users online status" status="0" time="0.982005793" size="-1" groups="default">
+    <group name="default"/>
+  </test>
+  <test className="tests\UserTest" methodName="testGetUserToken" prettifiedClassName="User (tests\User)" prettifiedMethodName="Get user token" status="0" time="0.662822307" size="-1" groups="default">
+    <group name="default"/>
+  </test>
+</tests>

+ 1 - 3
vendor/overtrue/wechat/src/Kernel/Providers/LogServiceProvider.php

@@ -48,9 +48,7 @@ class LogServiceProvider implements ServiceProviderInterface
     public function formatLogConfig($app)
     {
         if (!empty($app['config']->get('log.channels'))) {
-            return [
-                'log' => $app['config']->get('log'),
-            ];
+            return $app['config']->get('log');
         }
 
         if (empty($app['config']->get('log'))) {

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